Skip to content

Commit 7ec9261

Browse files
authored
feat: automate AppSec enablement setup (e.g: AWS_LAMBDA_RUNTIME_API) (#143)
* feat: honor AWS_LAMBDA_EXEC_WRAPPER when AWS Lambda does not In order to simplify onboarding & make it more uniform across languages, inspect the value of the `AWS_LAMBDA_EXEC_WRAPPER` environment variable and apply select environment variable changes it perofrms upon decorating a handler. This is necessary/useful because that environment variable is not honored by custom runtimes (`provided`, `provided.al2`) as well as the `go1.x` runtime (which is a glorified provided runtime). The datadog Lambda wrapper starts a proxy to inject ASM functionality directly on the Lambda runtime API instead of having to manually instrument each and every lambda handler/application, and modifies `AWS_LAMBDA_RUNTIME_API` to instruct Lambda language runtime client libraries to go through it instead of directly interacting with the Lambda control plane. APPSEC-11534 * pivot to a different, cheaper strategy * typo fix * PR feedback * minor fixups * add warning in go1.x runtime if lambda.norpc build tag was not enabled
1 parent 1ca51d5 commit 7ec9261

File tree

4 files changed

+110
-13
lines changed

4 files changed

+110
-13
lines changed

awslambdanorpc.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build lambda.norpc
2+
// +build lambda.norpc
3+
4+
/*
5+
* Unless explicitly stated otherwise all files in this repository are licensed
6+
* under the Apache License Version 2.0.
7+
*
8+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
9+
* Copyright 2021 Datadog, Inc.
10+
*/
11+
12+
package ddlambda
13+
14+
const awsLambdaRpcSupport = false

awslambdawithrpc.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build !lambda.norpc
2+
// +build !lambda.norpc
3+
4+
/*
5+
* Unless explicitly stated otherwise all files in this repository are licensed
6+
* under the Apache License Version 2.0.
7+
*
8+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
9+
* Copyright 2021 Datadog, Inc.
10+
*/
11+
12+
package ddlambda
13+
14+
const awsLambdaRpcSupport = true

ddlambda.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,35 @@ const (
9696
DefaultSite = "datadoghq.com"
9797
// DefaultEnhancedMetrics enables enhanced metrics by default.
9898
DefaultEnhancedMetrics = true
99+
100+
// serverlessAppSecEnabledEnvVar is the environment variable used to activate Serverless ASM through the use of an
101+
// AWS Lambda runtime API proxy.
102+
serverlessAppSecEnabledEnvVar = "DD_SERVERLESS_APPSEC_ENABLED"
103+
// awsLambdaRuntimeApiEnvVar is the environment variable used to redirect AWS Lambda runtime API calls to the proxy.
104+
awsLambdaRuntimeApiEnvVar = "AWS_LAMBDA_RUNTIME_API"
105+
// datadogAgentUrl is the URL of the agent and proxy started by the Datadog lambda extension.
106+
datadogAgentUrl = "127.0.0.1:9000"
107+
// ddExtensionFilePath is the path on disk of the datadog lambda extension.
108+
ddExtensionFilePath = "/opt/extensions/datadog-agent"
109+
110+
// awsLambdaServerPortEnvVar is the environment variable set by the go1.x Lambda Runtime to indicate which port the
111+
// RCP server should listen on. This is used as a sign that a warning should be printed if customers want to enable
112+
// ASM support, but did not enable the lambda.norpc build taf.
113+
awsLambdaServerPortEnvVar = "_LAMBDA_SERVER_PORT"
99114
)
100115

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

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

290307
return mc
291308
}
309+
310+
// setupAppSec checks if DD_SERVERLESS_APPSEC_ENABLED is set (to true) and when that
311+
// is the case, redirects `AWS_LAMBDA_RUNTIME_API` to the agent extension, and turns
312+
// on universal instrumentation unless it was already configured by the customer, so
313+
// that the HTTP context (invocation details span tags) is available on AppSec traces.
314+
func setupAppSec() {
315+
enabled := false
316+
if env := os.Getenv(serverlessAppSecEnabledEnvVar); env != "" {
317+
if on, err := strconv.ParseBool(env); err == nil {
318+
enabled = on
319+
}
320+
}
321+
322+
if !enabled {
323+
return
324+
}
325+
326+
if _, err := os.Stat(ddExtensionFilePath); os.IsNotExist(err) {
327+
logger.Debug(fmt.Sprintf("%s is enabled, but the Datadog extension was not found at %s", serverlessAppSecEnabledEnvVar, ddExtensionFilePath))
328+
return
329+
}
330+
331+
if awsLambdaRpcSupport {
332+
if port := os.Getenv(awsLambdaServerPortEnvVar); port != "" {
333+
logger.Warn(fmt.Sprintf("%s activation with the go1.x AWS Lambda runtime requires setting the `lambda.norpc` go build tag", serverlessAppSecEnabledEnvVar))
334+
}
335+
}
336+
337+
if err := os.Setenv(awsLambdaRuntimeApiEnvVar, datadogAgentUrl); err != nil {
338+
logger.Debug(fmt.Sprintf("failed to set %s=%s: %v", awsLambdaRuntimeApiEnvVar, datadogAgentUrl, err))
339+
} else {
340+
logger.Debug(fmt.Sprintf("successfully set %s=%s", awsLambdaRuntimeApiEnvVar, datadogAgentUrl))
341+
}
342+
343+
if val := os.Getenv(UniversalInstrumentation); val == "" {
344+
if err := os.Setenv(UniversalInstrumentation, "1"); err != nil {
345+
logger.Debug(fmt.Sprintf("failed to set %s=%d: %v", UniversalInstrumentation, 1, err))
346+
} else {
347+
logger.Debug(fmt.Sprintf("successfully set %s=%d", UniversalInstrumentation, 1))
348+
}
349+
}
350+
}

internal/logger/log.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ type LogLevel int
1414
const (
1515
// LevelDebug logs all information
1616
LevelDebug LogLevel = iota
17-
// LevelError only logs errors
18-
LevelError LogLevel = iota
17+
// LevelWarn only logs warnings and errors
18+
LevelWarn LogLevel = iota
1919
)
2020

2121
var (
22-
logLevel = LevelError
22+
logLevel = LevelWarn
2323
output io.Writer = os.Stdout
2424
)
2525

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

3737
// Error logs a structured error message to stdout
3838
func Error(err error) {
39-
40-
type logStructure struct {
41-
Status string `json:"status"`
42-
Message string `json:"message"`
43-
}
44-
4539
finalMessage := logStructure{
4640
Status: "error",
4741
Message: fmt.Sprintf("datadog: %s", err.Error()),
@@ -56,10 +50,6 @@ func Debug(message string) {
5650
if logLevel > LevelDebug {
5751
return
5852
}
59-
type logStructure struct {
60-
Status string `json:"status"`
61-
Message string `json:"message"`
62-
}
6353
finalMessage := logStructure{
6454
Status: "debug",
6555
Message: fmt.Sprintf("datadog: %s", message),
@@ -70,7 +60,27 @@ func Debug(message string) {
7060
log.Println(string(result))
7161
}
7262

63+
// Warn logs a structured log message to stdout
64+
func Warn(message string) {
65+
if logLevel > LevelWarn {
66+
return
67+
}
68+
finalMessage := logStructure{
69+
Status: "warning",
70+
Message: fmt.Sprintf("datadog: %s", message),
71+
}
72+
73+
result, _ := json.Marshal(finalMessage)
74+
75+
log.Println(string(result))
76+
}
77+
7378
// Raw prints a raw message to the logs.
7479
func Raw(message string) {
7580
fmt.Fprintln(output, message)
7681
}
82+
83+
type logStructure struct {
84+
Status string `json:"status"`
85+
Message string `json:"message"`
86+
}

0 commit comments

Comments
 (0)