Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/docs/guides/parent-ecs-fargate.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ values:
MONGODB_ATLAS_PRIVATE_KEY: "private-key-456"
```

> **Ambient credentials (OIDC / instance profile):** `accessKey` and `secretAccessKey`
> are optional. Omit them (keep `region`) and `sc` falls back to the standard AWS
> credential chain — GitHub Actions OIDC web-identity, an EC2/ECS instance profile,
> or the `AWS_*` environment — so CI can assume a short-lived federated role instead
> of storing long-lived keys in `secrets.yaml`.

### **What This Does**

Stores **AWS credentials** for programmatic access.
Expand Down
41 changes: 29 additions & 12 deletions pkg/clouds/pulumi/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ func InitStateStore(ctx context.Context, stateStoreCfg api.StateStorageConfig, l
return errors.Wrapf(err, "failed to convert auth config to aws.AccountConfig")
}

// hackily set aws creds env variable, so that we can access AWS state storage
if err := os.Setenv("AWS_ACCESS_KEY", pcfg.AccessKey); err != nil {
fmt.Println("Failed to set AWS_ACCESS_KEY env variable: ", err.Error())
// Export static creds for AWS state-store access ONLY when configured. When
// empty (OIDC web-identity / instance profile / ambient credentials), leave the
// environment untouched — otherwise we would blank out the credentials the AWS
// default chain (e.g. the GitHub OIDC creds the runner already exported) relies on.
if pcfg.AccessKey != "" {
if err := os.Setenv("AWS_ACCESS_KEY", pcfg.AccessKey); err != nil {
fmt.Println("Failed to set AWS_ACCESS_KEY env variable: ", err.Error())
}
}
if err := os.Setenv("AWS_SECRET_ACCESS_KEY", pcfg.SecretAccessKey); err != nil {
fmt.Println("Failed to set AWS_SECRET_ACCESS_KEY env variable: ", err.Error())
if pcfg.SecretAccessKey != "" {
if err := os.Setenv("AWS_SECRET_ACCESS_KEY", pcfg.SecretAccessKey); err != nil {
fmt.Println("Failed to set AWS_SECRET_ACCESS_KEY env variable: ", err.Error())
}
}
if err := os.Setenv("AWS_DEFAULT_REGION", pcfg.Region); err != nil {
fmt.Println("Failed to set AWS_DEFAULT_REGION env variable: ", err.Error())
if pcfg.Region != "" {
if err := os.Setenv("AWS_DEFAULT_REGION", pcfg.Region); err != nil {
fmt.Println("Failed to set AWS_DEFAULT_REGION env variable: ", err.Error())
}
}
return nil
}
Expand All @@ -52,11 +61,19 @@ func Provider(ctx *sdk.Context, stack api.Stack, input api.ResourceInput, params
return nil, errors.Wrapf(err, "failed to convert auth config to aws.AccountConfig")
}

provider, err := sdkAws.NewProvider(ctx, input.ToResName(input.Descriptor.Name), &sdkAws.ProviderArgs{
AccessKey: sdk.String(pcfg.AccessKey),
SecretKey: sdk.String(pcfg.SecretAccessKey),
Region: sdk.String(pcfg.Region),
})
providerArgs := &sdkAws.ProviderArgs{
Region: sdk.String(pcfg.Region),
}
// Pin static creds only when configured; otherwise fall back to the AWS default
// credential chain (OIDC web-identity / instance profile / env). Mirrors the
// guarded handling already in cloudtrail_security_alerts.go.
if pcfg.AccessKey != "" {
providerArgs.AccessKey = sdk.String(pcfg.AccessKey)
}
if pcfg.SecretAccessKey != "" {
providerArgs.SecretKey = sdk.String(pcfg.SecretAccessKey)
}
provider, err := sdkAws.NewProvider(ctx, input.ToResName(input.Descriptor.Name), providerArgs)
return &api.ResourceOutput{
Ref: provider,
}, err
Expand Down
57 changes: 57 additions & 0 deletions pkg/clouds/pulumi/aws/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
// Copyright (c) Simple Container

package aws

import (
"context"
"os"
"testing"

. "github.com/onsi/gomega"

"github.com/simple-container-com/api/pkg/api/logger"
"github.com/simple-container-com/api/pkg/clouds/aws"
)

// TestInitStateStore_AmbientCredsPreserved is the regression guard for OIDC /
// instance-profile deploys: when the auth config carries NO static keys,
// InitStateStore must not touch the AWS_* env the runner already exported, so
// the AWS default credential chain (e.g. the GitHub OIDC web-identity creds)
// still works. The previous code always ran os.Setenv("AWS_SECRET_ACCESS_KEY",
// "") which blanked those ambient credentials.
func TestInitStateStore_AmbientCredsPreserved(t *testing.T) {
RegisterTestingT(t)
t.Setenv("AWS_ACCESS_KEY_ID", "ASIAAMBIENT")
t.Setenv("AWS_SECRET_ACCESS_KEY", "ambient-secret")
t.Setenv("AWS_SESSION_TOKEN", "ambient-session")

// Auth config with region only — no accessKey/secretAccessKey (OIDC).
cfg := &aws.StateStorageConfig{AccountConfig: aws.AccountConfig{Region: "eu-central-1"}}
Expect(InitStateStore(context.Background(), cfg, logger.New())).To(Succeed())

Expect(os.Getenv("AWS_SECRET_ACCESS_KEY")).To(Equal("ambient-secret"))
Expect(os.Getenv("AWS_ACCESS_KEY_ID")).To(Equal("ASIAAMBIENT"))
Expect(os.Getenv("AWS_SESSION_TOKEN")).To(Equal("ambient-session"))
Expect(os.Getenv("AWS_DEFAULT_REGION")).To(Equal("eu-central-1"))
}

// TestInitStateStore_StaticCredsExported confirms back-compat: a config WITH
// static keys still exports them unchanged.
func TestInitStateStore_StaticCredsExported(t *testing.T) {
RegisterTestingT(t)
t.Setenv("AWS_ACCESS_KEY", "")
t.Setenv("AWS_SECRET_ACCESS_KEY", "")
t.Setenv("AWS_DEFAULT_REGION", "")

cfg := &aws.StateStorageConfig{AccountConfig: aws.AccountConfig{
AccessKey: "AKIASTATIC",
SecretAccessKey: "static-secret",
Region: "us-east-1",
}}
Expect(InitStateStore(context.Background(), cfg, logger.New())).To(Succeed())

Expect(os.Getenv("AWS_ACCESS_KEY")).To(Equal("AKIASTATIC"))
Expect(os.Getenv("AWS_SECRET_ACCESS_KEY")).To(Equal("static-secret"))
Expect(os.Getenv("AWS_DEFAULT_REGION")).To(Equal("us-east-1"))
}
22 changes: 14 additions & 8 deletions pkg/clouds/pulumi/aws/static_website.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,21 @@ func provisionStaticSite(input *StaticSiteInput) (*StaticSiteOutput, error) {
checksum = time.Now().String()

// fixme: implement own s3 uploader instead of aws s3 sync
// Pass static creds only when configured; otherwise inherit the ambient AWS
// default chain (OIDC web-identity / instance profile) for the s3 sync.
syncEnv := map[string]string{}
if input.Account.Region != "" {
syncEnv["AWS_DEFAULT_REGION"] = input.Account.Region
}
if input.Account.AccessKey != "" {
syncEnv["AWS_ACCESS_KEY_ID"] = input.Account.AccessKey
syncEnv["AWS_SECRET_ACCESS_KEY"] = input.Account.SecretAccessKey
}
_, err = local.NewCommand(ctx, fmt.Sprintf("%s-sync", input.ServiceName), &local.CommandArgs{
Create: sdk.Sprintf("aws s3 sync %s s3://%s", input.BundleDir, mainBucket.Bucket),
Update: sdk.Sprintf("aws s3 sync %s s3://%s", input.BundleDir, mainBucket.Bucket),
Triggers: sdk.ArrayInput(sdk.Array{sdk.String(checksum)}),
Environment: sdk.ToStringMap(map[string]string{
"AWS_ACCESS_KEY_ID": input.Account.AccessKey,
"AWS_SECRET_ACCESS_KEY": input.Account.SecretAccessKey,
"AWS_DEFAULT_REGION": input.Account.Region,
}),
Create: sdk.Sprintf("aws s3 sync %s s3://%s", input.BundleDir, mainBucket.Bucket),
Update: sdk.Sprintf("aws s3 sync %s s3://%s", input.BundleDir, mainBucket.Bucket),
Triggers: sdk.ArrayInput(sdk.Array{sdk.String(checksum)}),
Environment: sdk.ToStringMap(syncEnv),
}, sdk.DependsOn([]sdk.Resource{mainBucket, publicAccessBlock, ownershipControls, mainBucketPolicy}))
if err != nil {
return nil, errors.Wrapf(err, "failed to invoke aws s3 sync")
Expand Down
Loading