diff --git a/internal/pullbox/s3/config.go b/internal/pullbox/s3/config.go index ff6fdac523a..c8a1004ebc9 100644 --- a/internal/pullbox/s3/config.go +++ b/internal/pullbox/s3/config.go @@ -22,23 +22,31 @@ const ( region = "us-east-2" ) -func assumeRole(ctx context.Context, c *devopt.Credentials) (*aws.Config, error) { - noPermsConfig, _ := config.LoadDefaultConfig(ctx) +func assumeRole(ctx context.Context, cred *devopt.Credentials) (*aws.Config, error) { + // STS needs an explicit region to resolve its endpoint. Without one, + // users who don't have an AWS region configured in their environment hit + // "Invalid Configuration: Missing Region" when running `devbox global + // push`. Pin it to the bucket's region, same as the config below. + noPermsConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) + if err != nil { + return nil, errors.WithStack(err) + } stsClient := sts.NewFromConfig(noPermsConfig) creds, err := stsClient.AssumeRoleWithWebIdentity( ctx, &sts.AssumeRoleWithWebIdentityInput{ RoleArn: aws.String(roleArn), - RoleSessionName: aws.String(c.Email), - WebIdentityToken: aws.String(c.IDToken), + RoleSessionName: aws.String(cred.Email), + WebIdentityToken: aws.String(cred.IDToken), }, ) if err != nil { return nil, err } - config, err := config.LoadDefaultConfig( + cfg, err := config.LoadDefaultConfig( ctx, + config.WithRegion(region), config.WithCredentialsProvider( credentials.NewStaticCredentialsProvider( *creds.Credentials.AccessKeyId, @@ -47,9 +55,8 @@ func assumeRole(ctx context.Context, c *devopt.Credentials) (*aws.Config, error) ), ), ) - config.Region = region if err != nil { return nil, errors.WithStack(err) } - return &config, err + return &cfg, nil } diff --git a/internal/pullbox/s3/config_test.go b/internal/pullbox/s3/config_test.go new file mode 100644 index 00000000000..28faa4c0ee6 --- /dev/null +++ b/internal/pullbox/s3/config_test.go @@ -0,0 +1,53 @@ +package s3 + +import ( + "testing" + + "github.com/aws/aws-sdk-go-v2/config" +) + +// TestConfigPinsRegion guards against a regression of jetify-com/devbox#2591, +// where `devbox global push` failed with "Invalid Configuration: Missing +// Region" for users who had no AWS region configured in their environment. +// +// assumeRole builds AWS configs (for both the STS and S3 clients) that must +// explicitly pin the region, since neither STS nor S3 can resolve an endpoint +// without one. This test reproduces the reporter's environment (no region +// configured anywhere) and verifies that pinning the region via +// config.WithRegion(region) still yields a config with the expected region. +func TestConfigPinsRegion(t *testing.T) { + if region == "" { + t.Fatal("region constant must not be empty; STS/S3 endpoint resolution requires it") + } + + // Simulate a user with no AWS region configured anywhere: clear the + // region environment variables and point the shared config/credentials + // files at nonexistent paths so no ambient region leaks in. + t.Setenv("AWS_REGION", "") + t.Setenv("AWS_DEFAULT_REGION", "") + t.Setenv("AWS_CONFIG_FILE", "/dev/null") + t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "/dev/null") + + ctx := t.Context() + + // Without an explicit region, the loaded config has no region. This is the + // condition that produced the "Missing Region" error before the fix. + noRegion, err := config.LoadDefaultConfig(ctx) + if err != nil { + t.Fatalf("loading default config: %v", err) + } + if noRegion.Region != "" { + t.Skipf("test environment provides a default AWS region (%q); "+ + "cannot exercise the missing-region case", noRegion.Region) + } + + // With the region pinned (as assumeRole now does for both configs), the + // region is set regardless of the environment. + pinned, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) + if err != nil { + t.Fatalf("loading config with pinned region: %v", err) + } + if pinned.Region != region { + t.Errorf("pinned region = %q, want %q", pinned.Region, region) + } +}