Skip to content

Commit a345715

Browse files
feat: DD_LAMBDA_FIPS_MODE handling for metrics
1 parent 77ce8c5 commit a345715

File tree

5 files changed

+210
-21
lines changed

5 files changed

+210
-21
lines changed

ddlambda.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ type (
7070
// the counter will get totally reset after CircuitBreakerInterval
7171
// default: 4
7272
CircuitBreakerTotalFailures uint32
73+
// FIPSMode enables FIPS mode. Defaults to true in GovCloud regions and false elsewhere.
74+
FIPSMode *bool
7375
// TraceContextExtractor is the function that extracts a root/parent trace context from the Lambda event body.
7476
// See trace.DefaultTraceExtractor for an example.
7577
TraceContextExtractor trace.ContextExtractor
@@ -97,6 +99,9 @@ const (
9799
UniversalInstrumentation = "DD_UNIVERSAL_INSTRUMENTATION"
98100
// Initialize otel tracer provider if enabled
99101
OtelTracerEnabled = "DD_TRACE_OTEL_ENABLED"
102+
// FIPSModeEnvVar is the environment variable that determines whether to enable FIPS mode.
103+
// Defaults to true in GovCloud regions and false otherwise.
104+
FIPSModeEnvVar = "DD_LAMBDA_FIPS_MODE"
100105

101106
// DefaultSite to send API messages to.
102107
DefaultSite = "datadoghq.com"
@@ -268,6 +273,7 @@ func (cfg *Config) toMetricsConfig(isExtensionRunning bool) metrics.Config {
268273

269274
mc := metrics.Config{
270275
ShouldRetryOnFailure: false,
276+
FIPSMode: cfg.calculateFipsMode(),
271277
}
272278

273279
if cfg != nil {
@@ -325,6 +331,34 @@ func (cfg *Config) toMetricsConfig(isExtensionRunning bool) metrics.Config {
325331
return mc
326332
}
327333

334+
func (cfg *Config) calculateFipsMode() bool {
335+
if cfg != nil && cfg.FIPSMode != nil {
336+
return *cfg.FIPSMode
337+
}
338+
339+
region := os.Getenv("AWS_REGION")
340+
isGovCloud := strings.HasPrefix(region, "us-gov-")
341+
342+
fipsMode := isGovCloud
343+
344+
fipsModeEnv := os.Getenv(FIPSModeEnvVar)
345+
if fipsModeEnv != "" {
346+
if parsedFipsMode, err := strconv.ParseBool(fipsModeEnv); err == nil {
347+
fipsMode = parsedFipsMode
348+
}
349+
}
350+
351+
if fipsMode || isGovCloud {
352+
if fipsMode {
353+
logger.Debug("Go Lambda Layer FIPS mode enabled")
354+
} else {
355+
logger.Debug("Go Lambda Layer FIPS mode disabled")
356+
}
357+
}
358+
359+
return fipsMode
360+
}
361+
328362
// setupAppSec checks if DD_SERVERLESS_APPSEC_ENABLED is set (to true) and when that
329363
// is the case, redirects `AWS_LAMBDA_RUNTIME_API` to the agent extension, and turns
330364
// on universal instrumentation unless it was already configured by the customer, so

ddlambda_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,102 @@ func TestToMetricConfigLocalTest(t *testing.T) {
104104
})
105105
}
106106
}
107+
108+
func TestCalculateFipsMode(t *testing.T) {
109+
// Save original environment to restore later
110+
originalRegion := os.Getenv("AWS_REGION")
111+
originalFipsMode := os.Getenv(FIPSModeEnvVar)
112+
defer func() {
113+
os.Setenv("AWS_REGION", originalRegion)
114+
os.Setenv(FIPSModeEnvVar, originalFipsMode)
115+
}()
116+
117+
testCases := []struct {
118+
name string
119+
configFIPSMode *bool
120+
region string
121+
fipsModeEnv string
122+
expected bool
123+
}{
124+
{
125+
name: "Config explicit true",
126+
configFIPSMode: boolPtr(true),
127+
region: "us-east-1",
128+
fipsModeEnv: "",
129+
expected: true,
130+
},
131+
{
132+
name: "Config explicit false",
133+
configFIPSMode: boolPtr(false),
134+
region: "us-gov-west-1",
135+
fipsModeEnv: "",
136+
expected: false,
137+
},
138+
{
139+
name: "GovCloud default true",
140+
configFIPSMode: nil,
141+
region: "us-gov-east-1",
142+
fipsModeEnv: "",
143+
expected: true,
144+
},
145+
{
146+
name: "Non-GovCloud default false",
147+
configFIPSMode: nil,
148+
region: "us-east-1",
149+
fipsModeEnv: "",
150+
expected: false,
151+
},
152+
{
153+
name: "Env var override to true",
154+
configFIPSMode: nil,
155+
region: "us-east-1",
156+
fipsModeEnv: "true",
157+
expected: true,
158+
},
159+
{
160+
name: "Env var override to false",
161+
configFIPSMode: nil,
162+
region: "us-gov-west-1",
163+
fipsModeEnv: "false",
164+
expected: false,
165+
},
166+
{
167+
name: "Invalid env var in GovCloud",
168+
configFIPSMode: nil,
169+
region: "us-gov-west-1",
170+
fipsModeEnv: "invalid",
171+
expected: true,
172+
},
173+
{
174+
name: "Invalid env var in non-GovCloud",
175+
configFIPSMode: nil,
176+
region: "us-east-1",
177+
fipsModeEnv: "invalid",
178+
expected: false,
179+
},
180+
{
181+
name: "Config takes precedence over env and region",
182+
configFIPSMode: boolPtr(true),
183+
region: "us-east-1",
184+
fipsModeEnv: "false",
185+
expected: true,
186+
},
187+
}
188+
189+
for _, tc := range testCases {
190+
t.Run(tc.name, func(t *testing.T) {
191+
os.Setenv("AWS_REGION", tc.region)
192+
os.Setenv(FIPSModeEnvVar, tc.fipsModeEnv)
193+
194+
cfg := &Config{FIPSMode: tc.configFIPSMode}
195+
result := cfg.calculateFipsMode()
196+
197+
assert.Equal(t, tc.expected, result, "calculateFipsMode returned incorrect value")
198+
})
199+
}
200+
}
201+
202+
// Helper function to create bool pointers
203+
func boolPtr(b bool) *bool {
204+
return &b
205+
}

internal/metrics/kms_decrypter.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"fmt"
1414
"github.com/aws/aws-sdk-go-v2/aws"
1515
"os"
16-
"strings"
1716

1817
"github.com/DataDog/datadog-lambda-go/internal/logger"
1918
"github.com/aws/aws-sdk-go-v2/config"
@@ -45,12 +44,11 @@ const regionEnvVar string = "AWS_REGION"
4544
const encryptionContextKey string = "LambdaFunctionName"
4645

4746
// MakeKMSDecrypter creates a new decrypter which uses the AWS KMS service to decrypt variables
48-
func MakeKMSDecrypter() Decrypter {
49-
region := os.Getenv(regionEnvVar)
47+
func MakeKMSDecrypter(fipsMode bool) Decrypter {
5048
fipsEndpoint := aws.FIPSEndpointStateUnset
51-
if strings.HasPrefix(region, "us-gov-") {
49+
if fipsMode {
5250
fipsEndpoint = aws.FIPSEndpointStateEnabled
53-
logger.Debug("GovCloud region detected. Using FIPS endpoint for KMS decryption.")
51+
logger.Debug("Using FIPS endpoint for KMS decryption.")
5452
}
5553

5654
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithUseFIPSEndpoint(fipsEndpoint))

internal/metrics/listener.go

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type (
5050
CircuitBreakerTimeout time.Duration
5151
CircuitBreakerTotalFailures uint32
5252
LocalTest bool
53+
FIPSMode bool
5354
}
5455

5556
logMetric struct {
@@ -63,13 +64,17 @@ type (
6364
// MakeListener initializes a new metrics lambda listener
6465
func MakeListener(config Config, extensionManager *extension.ExtensionManager) Listener {
6566

66-
apiClient := MakeAPIClient(context.Background(), APIClientOptions{
67-
baseAPIURL: config.Site,
68-
apiKey: config.APIKey,
69-
decrypter: MakeKMSDecrypter(),
70-
kmsAPIKey: config.KMSAPIKey,
71-
httpClientTimeout: config.HTTPClientTimeout,
72-
})
67+
var apiClient *APIClient
68+
if !config.FIPSMode {
69+
apiClient = MakeAPIClient(context.Background(), APIClientOptions{
70+
baseAPIURL: config.Site,
71+
apiKey: config.APIKey,
72+
decrypter: MakeKMSDecrypter(config.FIPSMode),
73+
kmsAPIKey: config.KMSAPIKey,
74+
httpClientTimeout: config.HTTPClientTimeout,
75+
})
76+
}
77+
7378
if config.HTTPClientTimeout <= 0 {
7479
config.HTTPClientTimeout = defaultHttpClientTimeout
7580
}
@@ -109,7 +114,7 @@ func MakeListener(config Config, extensionManager *extension.ExtensionManager) L
109114

110115
// canSendMetrics reports whether l can send metrics.
111116
func (l *Listener) canSendMetrics() bool {
112-
return l.isAgentRunning || l.apiClient.apiKey != "" || l.config.KMSAPIKey != "" || l.config.ShouldUseLogForwarder
117+
return l.isAgentRunning || l.config.ShouldUseLogForwarder || !l.config.FIPSMode || l.apiClient.apiKey != "" || l.config.KMSAPIKey != ""
113118
}
114119

115120
// HandlerStarted adds metrics service to the context
@@ -118,16 +123,20 @@ func (l *Listener) HandlerStarted(ctx context.Context, msg json.RawMessage) cont
118123
logger.Error(fmt.Errorf("datadog api key isn't set, won't be able to send metrics"))
119124
}
120125

121-
ts := MakeTimeService()
122-
pr := MakeProcessor(ctx, l.apiClient, ts, l.config.BatchInterval, l.config.ShouldRetryOnFailure, l.config.CircuitBreakerInterval, l.config.CircuitBreakerTimeout, l.config.CircuitBreakerTotalFailures)
123-
l.processor = pr
124-
125126
ctx = AddListener(ctx, l)
126-
// Setting the context on the client will mean that future requests will be cancelled correctly
127-
// if the lambda times out.
128-
l.apiClient.context = ctx
129127

130-
pr.StartProcessing()
128+
if !l.config.FIPSMode {
129+
ts := MakeTimeService()
130+
pr := MakeProcessor(ctx, l.apiClient, ts, l.config.BatchInterval, l.config.ShouldRetryOnFailure, l.config.CircuitBreakerInterval, l.config.CircuitBreakerTimeout, l.config.CircuitBreakerTotalFailures)
131+
l.processor = pr
132+
133+
// Setting the context on the client will mean that future requests will be cancelled correctly
134+
// if the lambda times out.
135+
l.apiClient.context = ctx
136+
137+
pr.StartProcessing()
138+
}
139+
131140
l.submitEnhancedMetrics("invocations", ctx)
132141

133142
return ctx
@@ -192,6 +201,12 @@ func (l *Listener) AddDistributionMetric(metric string, value float64, timestamp
192201
logger.Raw(payload)
193202
return
194203
}
204+
205+
if l.config.FIPSMode {
206+
logger.Debug(fmt.Sprintf("skipping metric %s due to FIPS mode - direct API calls are disabled", metric))
207+
return
208+
}
209+
195210
m := Distribution{
196211
Name: metric,
197212
Tags: tags,

internal/metrics/listener_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,49 @@ func TestAddDistributionMetricWithForceLogForwarder(t *testing.T) {
9999
assert.False(t, called)
100100
}
101101

102+
func TestAddDistributionMetricWithFIPSMode(t *testing.T) {
103+
// Setup a test server to detect if any API calls are made
104+
apiCallAttempted := false
105+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
106+
apiCallAttempted = true
107+
w.WriteHeader(http.StatusCreated)
108+
}))
109+
defer server.Close()
110+
111+
// Create a listener with FIPS mode enabled
112+
listener := MakeListener(Config{
113+
APIKey: "12345",
114+
Site: server.URL,
115+
FIPSMode: true,
116+
}, &extension.ExtensionManager{})
117+
118+
// Verify the API client wasn't created
119+
assert.Nil(t, listener.apiClient, "API client should be nil when FIPS mode is enabled")
120+
121+
// Initialize the listener
122+
ctx := listener.HandlerStarted(context.Background(), json.RawMessage{})
123+
124+
// Verify processor wasn't initialized
125+
assert.Nil(t, listener.processor, "Processor should be nil when FIPS mode is enabled")
126+
127+
// Log calls to validate we're getting the expected log message
128+
var logOutput string
129+
logger.SetLogLevel(logger.LevelDebug)
130+
logOutput = captureOutput(func() {
131+
listener.AddDistributionMetric("fips-test-metric", 42, time.Now(), false, "tag:fips")
132+
})
133+
134+
// Check that we logged the skipping message
135+
assert.Contains(t, logOutput, "skipping metric fips-test-metric due to FIPS mode", "Expected log about skipping metric")
136+
assert.Contains(t, logOutput, "direct API calls are disabled", "Expected log about disabled API calls")
137+
138+
// Finish the handler
139+
listener.HandlerFinished(ctx, nil)
140+
141+
// Check that no API call was attempted
142+
assert.False(t, apiCallAttempted, "No API call should be attempted when FIPS mode is enabled")
143+
}
144+
102145
func TestGetEnhancedMetricsTags(t *testing.T) {
103146
//nolint
104147
ctx := context.WithValue(context.Background(), "cold_start", false)

0 commit comments

Comments
 (0)