Skip to content

Commit c95006f

Browse files
committed
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
1 parent 1ca51d5 commit c95006f

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

ddlambda.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"fmt"
1515
"net/http"
1616
"os"
17+
"os/exec"
1718
"strconv"
1819
"strings"
1920
"time"
@@ -102,13 +103,15 @@ const (
102103
// It returns a modified handler that can be passed directly to the lambda.StartHandler function from aws-lambda-go.
103104
func WrapLambdaHandlerInterface(handler lambda.Handler, cfg *Config) lambda.Handler {
104105
listeners := initializeListeners(cfg)
106+
applyLambdaExecWrapperConfiguration()
105107
return wrapper.WrapHandlerInterfaceWithListeners(handler, listeners...)
106108
}
107109

108110
// WrapFunction is used to instrument your lambda functions.
109111
// It returns a modified handler that can be passed directly to the lambda.Start function from aws-lambda-go.
110112
func WrapFunction(handler interface{}, cfg *Config) interface{} {
111113
listeners := initializeListeners(cfg)
114+
applyLambdaExecWrapperConfiguration()
112115
return wrapper.WrapHandlerWithListeners(handler, listeners...)
113116
}
114117

@@ -289,3 +292,64 @@ func (cfg *Config) toMetricsConfig(isExtensionRunning bool) metrics.Config {
289292

290293
return mc
291294
}
295+
296+
// applyLambdaExecWrapperConfiguration applies environment variables set by the wrapper configured in the
297+
// `AWS_LAMBDA_EXEC_WRAPPER` environment variable if present. This is done here because the AWS Lambda runtimes used by
298+
// go applications (`go1.x` and `provided.al2`) do not honor this setting, while other runtimes do. This assumes the
299+
// wrapper script does nothing other than setting environment variables, and will only honor environment variables with
300+
// names starting with `DD_` or `AWS_LAMBDA_`. In particular, `AWS_LAMBDA_RUNTIME_API` is used to re-route the Lambda
301+
// control flow API through a proxy, which is used by ASM.
302+
//
303+
// See: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-modify.html#runtime-wrapper.
304+
// See: https://github.com/DataDog/datadog-lambda-extension/blob/main/scripts/datadog_wrapper.
305+
func applyLambdaExecWrapperConfiguration() {
306+
const AwsExecutionEnvEnvVar = "AWS_EXECUTION_ENV"
307+
const AwsLambdaExecWrapperEnvVar = "AWS_LAMBDA_EXEC_WRAPPER"
308+
309+
// Only perform this operation if the Lambda runtime doesn't do it by itself, meaning we only do something for
310+
// provided runtimes (in which `AWS_EXECUTION_ENV` is not set) and `go1.x`.
311+
if env := os.Getenv(AwsExecutionEnvEnvVar); env != "" && env != "AWS_Lambda_go1.x" {
312+
logger.Debug(fmt.Sprintf("Skipping applyLambdaExecWrapperConfiguration, runtime is %s", env))
313+
return
314+
}
315+
316+
script := os.Getenv(AwsLambdaExecWrapperEnvVar)
317+
if script == "" {
318+
// Nothing to do
319+
return
320+
}
321+
322+
cmd := exec.Command("env", "-u", AwsLambdaExecWrapperEnvVar, script, "sh", "-c", "env")
323+
logger.Debug(fmt.Sprintf("[%s] Command: %s", AwsLambdaExecWrapperEnvVar, cmd.String()))
324+
325+
if stdout, err := cmd.Output(); err != nil {
326+
logger.Debug(fmt.Sprintf("[%s] Failed to run: %s", AwsLambdaExecWrapperEnvVar, err))
327+
} else {
328+
for _, line := range strings.Split(string(stdout), "\n") {
329+
parts := strings.SplitN(line, "=", 2)
330+
if len(parts) != 2 {
331+
// Forward what the wrapper script prints to the standard output...
332+
fmt.Println(line)
333+
continue
334+
}
335+
name := parts[0]
336+
val := parts[1]
337+
338+
if os.Getenv(name) == val {
339+
// Not changed, nothing to do.
340+
continue
341+
}
342+
343+
if !strings.HasPrefix(name, "DD_") && !strings.HasPrefix(name, "AWS_LAMBDA_") {
344+
logger.Debug(fmt.Sprintf("[%s] Skip %s=<redacted>", AwsLambdaExecWrapperEnvVar, name))
345+
continue
346+
}
347+
348+
if err := os.Setenv(name, val); err != nil {
349+
logger.Debug(fmt.Sprintf("[%s] Failed %s=%s: %s", AwsLambdaExecWrapperEnvVar, name, val, err))
350+
} else {
351+
logger.Debug(fmt.Sprintf("[%s] Set %s=%s", AwsLambdaExecWrapperEnvVar, name, val))
352+
}
353+
}
354+
}
355+
}

0 commit comments

Comments
 (0)