Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 4c645b6

Browse files
RomainMullerpeterdeme
authored andcommitted
feat: automate AppSec enablement setup (e.g: AWS_LAMBDA_RUNTIME_API) (DataDog#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 37f2ffb commit 4c645b6

File tree

4 files changed

+110
-13
lines changed

4 files changed

+110
-13
lines changed

awslambdanorpc.go

+14
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

+14
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

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

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

112128
// WrapFunction is used to instrument your lambda functions.
113129
// It returns a modified handler that can be passed directly to the lambda.Start function from aws-lambda-go.
114130
func WrapFunction(handler interface{}, cfg *Config) interface{} {
131+
setupAppSec()
115132
listeners := initializeListeners(cfg)
116133
return wrapper.WrapHandlerWithListeners(handler, listeners...)
117134
}
@@ -294,3 +311,45 @@ func (cfg *Config) toMetricsConfig(isExtensionRunning bool) metrics.Config {
294311

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

internal/logger/log.go

+23-13
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)