Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: automate AppSec enablement setup (e.g: AWS_LAMBDA_RUNTIME_API) #143

Merged
merged 6 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions awslambdanorpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build lambda.norpc
// +build lambda.norpc

/*
* Unless explicitly stated otherwise all files in this repository are licensed
* under the Apache License Version 2.0.
*
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2021 Datadog, Inc.
*/

package ddlambda

const awsLambdaRpcSupport = false
14 changes: 14 additions & 0 deletions awslambdawithrpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !lambda.norpc
// +build !lambda.norpc

/*
* Unless explicitly stated otherwise all files in this repository are licensed
* under the Apache License Version 2.0.
*
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2021 Datadog, Inc.
*/

package ddlambda

const awsLambdaRpcSupport = true
59 changes: 59 additions & 0 deletions ddlambda.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,35 @@ const (
DefaultSite = "datadoghq.com"
// DefaultEnhancedMetrics enables enhanced metrics by default.
DefaultEnhancedMetrics = true

// serverlessAppSecEnabledEnvVar is the environment variable used to activate Serverless ASM through the use of an
// AWS Lambda runtime API proxy.
serverlessAppSecEnabledEnvVar = "DD_SERVERLESS_APPSEC_ENABLED"
// awsLambdaRuntimeApiEnvVar is the environment variable used to redirect AWS Lambda runtime API calls to the proxy.
awsLambdaRuntimeApiEnvVar = "AWS_LAMBDA_RUNTIME_API"
// datadogAgentUrl is the URL of the agent and proxy started by the Datadog lambda extension.
datadogAgentUrl = "127.0.0.1:9000"
// ddExtensionFilePath is the path on disk of the datadog lambda extension.
ddExtensionFilePath = "/opt/extensions/datadog-agent"

// awsLambdaServerPortEnvVar is the environment variable set by the go1.x Lambda Runtime to indicate which port the
// RCP server should listen on. This is used as a sign that a warning should be printed if customers want to enable
// ASM support, but did not enable the lambda.norpc build taf.
awsLambdaServerPortEnvVar = "_LAMBDA_SERVER_PORT"
)

// WrapLambdaHandlerInterface is used to instrument your lambda functions.
// It returns a modified handler that can be passed directly to the lambda.StartHandler function from aws-lambda-go.
func WrapLambdaHandlerInterface(handler lambda.Handler, cfg *Config) lambda.Handler {
setupAppSec()
listeners := initializeListeners(cfg)
return wrapper.WrapHandlerInterfaceWithListeners(handler, listeners...)
}

// WrapFunction is used to instrument your lambda functions.
// It returns a modified handler that can be passed directly to the lambda.Start function from aws-lambda-go.
func WrapFunction(handler interface{}, cfg *Config) interface{} {
setupAppSec()
listeners := initializeListeners(cfg)
return wrapper.WrapHandlerWithListeners(handler, listeners...)
}
Expand Down Expand Up @@ -289,3 +306,45 @@ func (cfg *Config) toMetricsConfig(isExtensionRunning bool) metrics.Config {

return mc
}

// setupAppSec checks if DD_SERVERLESS_APPSEC_ENABLED is set (to true) and when that
// is the case, redirects `AWS_LAMBDA_RUNTIME_API` to the agent extension, and turns
// on universal instrumentation unless it was already configured by the customer, so
// that the HTTP context (invocation details span tags) is available on AppSec traces.
func setupAppSec() {
enabled := false
if env := os.Getenv(serverlessAppSecEnabledEnvVar); env != "" {
if on, err := strconv.ParseBool(env); err == nil {
enabled = on
}
}

if !enabled {
return
}

if _, err := os.Stat(ddExtensionFilePath); os.IsNotExist(err) {
logger.Debug(fmt.Sprintf("%s is enabled, but the Datadog extension was not found at %s", serverlessAppSecEnabledEnvVar, ddExtensionFilePath))
return
}

if awsLambdaRpcSupport {
if port := os.Getenv(awsLambdaServerPortEnvVar); port != "" {
logger.Warn(fmt.Sprintf("%s activation with the go1.x AWS Lambda runtime requires setting the `lambda.norpc` go build tag", serverlessAppSecEnabledEnvVar))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I'd hate to say that setting this build tag is required to use appsec, when in fact it is not. Alternatively, they can always set these environment variables manually.

}
}

if err := os.Setenv(awsLambdaRuntimeApiEnvVar, datadogAgentUrl); err != nil {
logger.Debug(fmt.Sprintf("failed to set %s=%s: %v", awsLambdaRuntimeApiEnvVar, datadogAgentUrl, err))
} else {
logger.Debug(fmt.Sprintf("successfully set %s=%s", awsLambdaRuntimeApiEnvVar, datadogAgentUrl))
}

if val := os.Getenv(UniversalInstrumentation); val == "" {
if err := os.Setenv(UniversalInstrumentation, "1"); err != nil {
logger.Debug(fmt.Sprintf("failed to set %s=%d: %v", UniversalInstrumentation, 1, err))
} else {
logger.Debug(fmt.Sprintf("successfully set %s=%d", UniversalInstrumentation, 1))
}
}
RomainMuller marked this conversation as resolved.
Show resolved Hide resolved
}
36 changes: 23 additions & 13 deletions internal/logger/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ type LogLevel int
const (
// LevelDebug logs all information
LevelDebug LogLevel = iota
// LevelError only logs errors
LevelError LogLevel = iota
// LevelWarn only logs warnings and errors
LevelWarn LogLevel = iota
)

var (
logLevel = LevelError
logLevel = LevelWarn
output io.Writer = os.Stdout
)

Expand All @@ -36,12 +36,6 @@ func SetOutput(w io.Writer) {

// Error logs a structured error message to stdout
func Error(err error) {

type logStructure struct {
Status string `json:"status"`
Message string `json:"message"`
}

finalMessage := logStructure{
Status: "error",
Message: fmt.Sprintf("datadog: %s", err.Error()),
Expand All @@ -56,10 +50,6 @@ func Debug(message string) {
if logLevel > LevelDebug {
return
}
type logStructure struct {
Status string `json:"status"`
Message string `json:"message"`
}
finalMessage := logStructure{
Status: "debug",
Message: fmt.Sprintf("datadog: %s", message),
Expand All @@ -70,7 +60,27 @@ func Debug(message string) {
log.Println(string(result))
}

// Warn logs a structured log message to stdout
func Warn(message string) {
if logLevel > LevelWarn {
return
}
finalMessage := logStructure{
Status: "warning",
Message: fmt.Sprintf("datadog: %s", message),
}

result, _ := json.Marshal(finalMessage)

log.Println(string(result))
}

// Raw prints a raw message to the logs.
func Raw(message string) {
fmt.Fprintln(output, message)
}

type logStructure struct {
Status string `json:"status"`
Message string `json:"message"`
}
Loading