diff --git a/lib/backend/dynamo/dynamodbbk.go b/lib/backend/dynamo/dynamodbbk.go index 0cb6636b384a1..dd2c445e1680c 100644 --- a/lib/backend/dynamo/dynamodbbk.go +++ b/lib/backend/dynamo/dynamodbbk.go @@ -47,9 +47,9 @@ import ( "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/modules" awsmetrics "github.com/gravitational/teleport/lib/observability/metrics/aws" dynamometrics "github.com/gravitational/teleport/lib/observability/metrics/dynamo" + awsutils "github.com/gravitational/teleport/lib/utils/aws" "github.com/gravitational/teleport/lib/utils/aws/endpoint" ) @@ -293,7 +293,7 @@ func New(ctx context.Context, params backend.Params) (*Backend, error) { // FIPS settings are applied on the individual service instead of the aws config, // as DynamoDB Streams and Application Auto Scaling do not yet have FIPS endpoints in non-GovCloud. // See also: https://aws.amazon.com/compliance/fips/#FIPS_Endpoints_by_Service - if modules.GetModules().IsBoringBinary() { + if awsutils.UseFIPSForDynamoDB() { dynamoOpts = append(dynamoOpts, func(o *dynamodb.Options) { o.EndpointOptions.UseFIPSEndpoint = aws.FIPSEndpointStateEnabled }) diff --git a/lib/events/dynamoevents/dynamoevents.go b/lib/events/dynamoevents/dynamoevents.go index 68d411f2796a3..077f029985ec3 100644 --- a/lib/events/dynamoevents/dynamoevents.go +++ b/lib/events/dynamoevents/dynamoevents.go @@ -59,10 +59,10 @@ import ( apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" - "github.com/gravitational/teleport/lib/modules" awsmetrics "github.com/gravitational/teleport/lib/observability/metrics/aws" dynamometrics "github.com/gravitational/teleport/lib/observability/metrics/dynamo" "github.com/gravitational/teleport/lib/utils" + awsutils "github.com/gravitational/teleport/lib/utils/aws" "github.com/gravitational/teleport/lib/utils/aws/endpoint" ) @@ -330,7 +330,7 @@ func New(ctx context.Context, cfg Config) (*Log, error) { // FIPS settings are applied on the individual service instead of the aws config, // as DynamoDB Streams and Application Auto Scaling do not yet have FIPS endpoints in non-GovCloud. // See also: https://aws.amazon.com/compliance/fips/#FIPS_Endpoints_by_Service - if modules.GetModules().IsBoringBinary() && cfg.UseFIPSEndpoint == types.ClusterAuditConfigSpecV2_FIPS_ENABLED { + if awsutils.UseFIPSForDynamoDB() && cfg.UseFIPSEndpoint == types.ClusterAuditConfigSpecV2_FIPS_ENABLED { dynamoOpts = append(dynamoOpts, func(o *dynamodb.Options) { o.EndpointOptions.UseFIPSEndpoint = aws.FIPSEndpointStateEnabled }) diff --git a/lib/events/dynamoevents/dynamoevents_test.go b/lib/events/dynamoevents/dynamoevents_test.go index 28eb81c1f4653..87c86819b41cb 100644 --- a/lib/events/dynamoevents/dynamoevents_test.go +++ b/lib/events/dynamoevents/dynamoevents_test.go @@ -48,6 +48,7 @@ import ( "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils" + awsutils "github.com/gravitational/teleport/lib/utils/aws" ) const dynamoDBLargeQueryRetries int = 10 @@ -608,12 +609,23 @@ func randStringAlpha(n int) string { func TestEndpoints(t *testing.T) { tests := []struct { - name string - fips bool + name string + fips bool + env map[string]string + wantFIPSError bool }{ { - name: "fips", + name: "fips", + fips: true, + wantFIPSError: true, + }, + { + name: "fips with env skip", fips: true, + env: map[string]string{ + awsutils.EnvVarDisableDynamoDBFIPS: "yes", + }, + wantFIPSError: false, }, { name: "without fips", @@ -631,6 +643,10 @@ func TestEndpoints(t *testing.T) { }) } + for k, v := range tt.env { + t.Setenv(k, v) + } + mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) @@ -655,15 +671,13 @@ func TestEndpoints(t *testing.T) { }) // FIPS mode should fail because it is a violation to enable FIPS // while also setting a custom endpoint. - if tt.fips { - assert.Error(t, err) - require.ErrorContains(t, err, "FIPS") + if tt.wantFIPSError { + assert.ErrorContains(t, err, "FIPS") return } - assert.Error(t, err) - assert.Nil(t, b) - require.ErrorContains(t, err, fmt.Sprintf("StatusCode: %d", http.StatusTeapot)) + assert.ErrorContains(t, err, fmt.Sprintf("StatusCode: %d", http.StatusTeapot)) + assert.Nil(t, b, "backend not nil") }) } } diff --git a/lib/utils/aws/dynamo_fips.go b/lib/utils/aws/dynamo_fips.go new file mode 100644 index 0000000000000..63909979a5b7b --- /dev/null +++ b/lib/utils/aws/dynamo_fips.go @@ -0,0 +1,49 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package aws + +import ( + "os" + "strconv" + + "github.com/gravitational/teleport/lib/modules" +) + +// EnvVarDisableDynamoDBFIPS holds the name of the environment variable that +// disables FIPS for DynamoDB access in builds where FIPS would otherwise be +// required. +const EnvVarDisableDynamoDBFIPS = "TELEPORT_UNSTABLE_DISABLE_DYNAMODB_FIPS" + +// UseFIPSForDynamoDB is a DynamoDB-specific check that builds on +// [modules.Modules.IsBoringBinary]. +// +// FIPS is enabled by default for boring/FIPS teleport binaries, unless the +// TELEPORT_UNSTABLE_DISABLE_DYNAMODB_FIPS env variable is set to "yes" (or an +// equivalent boolean). +func UseFIPSForDynamoDB() bool { + // If the skip toggle is set we don't use FIPS DynamoDB. + if val := os.Getenv(EnvVarDisableDynamoDBFIPS); val != "" { + if val == "yes" { + return false + } + if b, _ := strconv.ParseBool(val); b { + return false + } + } + + return modules.GetModules().IsBoringBinary() +} diff --git a/lib/utils/aws/dynamo_fips_test.go b/lib/utils/aws/dynamo_fips_test.go new file mode 100644 index 0000000000000..d3006c3ef47c8 --- /dev/null +++ b/lib/utils/aws/dynamo_fips_test.go @@ -0,0 +1,82 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package aws_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/gravitational/teleport/lib/modules" + awsutils "github.com/gravitational/teleport/lib/utils/aws" +) + +func TestUseFIPSForDynamoDB(t *testing.T) { + // Don't t.Parallel(), uses both modules.SetTestModules and t.Setenv. + + tests := []struct { + name string + fips bool + env map[string]string + want bool + }{ + { + name: "non-FIPS binary", + want: false, + }, + { + name: "non-FIPS binary with env skip", + env: map[string]string{ + awsutils.EnvVarDisableDynamoDBFIPS: "yes", + }, + want: false, + }, + { + name: "FIPS binary", + fips: true, + want: true, + }, + { + name: "FIPS binary with env skip", + fips: true, + env: map[string]string{ + awsutils.EnvVarDisableDynamoDBFIPS: "yes", + }, + want: false, + }, + { + name: "FIPS binary with env skip (strconv.ParseBool)", + fips: true, + env: map[string]string{ + awsutils.EnvVarDisableDynamoDBFIPS: "1", + }, + want: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{ + FIPS: test.fips, + }) + for k, v := range test.env { + t.Setenv(k, v) + } + + assert.Equal(t, test.want, awsutils.UseFIPSForDynamoDB(), "UseFIPSForDynamoDB mismatch") + }) + } +}