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
2 changes: 1 addition & 1 deletion src/pkg/cli/compose/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func Test_getRemoteBuildContext(t *testing.T) {
},
}

tmpDir := t.TempDir()
tmpDir := t.TempDir() // change this to "/tmp" or so to inspect the files

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
Expand Down
15 changes: 11 additions & 4 deletions src/pkg/clouds/aws/ecs/cfn/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import (

type Jwk struct {
Kty string `json:"kty"`
Kid string `json:"kid,omitempty"`
Alg string `json:"alg,omitempty"`
Use string `json:"use,omitempty"`
X5c []string `json:"x5c,omitempty"`
N string `json:"n,omitempty"` // RSA modulus, base64url-encoded
E string `json:"e,omitempty"` // RSA exponent, base64url-encoded
X5c [][]byte `json:"x5c,omitempty"` // DER-encoded cert(s)
X5t string `json:"x5t,omitempty"` // base64url-encoded
}

Expand Down Expand Up @@ -60,12 +63,16 @@ func FetchThumbprints(iss string) ([]string, error) {

var thumbprints []string
for _, key := range jwks.Keys {
if len(key.X5c) > 0 {
decoded, err := base64.RawURLEncoding.DecodeString(key.X5t)
if key.X5t != "" {
thumbprint, err := base64.RawURLEncoding.DecodeString(key.X5t)
if err != nil {
return nil, fmt.Errorf("invalid base64url encoding in x5t claim: %w", err)
}
thumbprints = append(thumbprints, hex.EncodeToString(decoded))
thumbprints = append(thumbprints, hex.EncodeToString(thumbprint))
} else if len(key.X5c) > 0 {
// Compute SHA-1 thumbprint of DER-encoded cert
// thumbprint := sha1.Sum(key.X5c[0]) not important; avoid importing sha1
// thumbprints = append(thumbprints, hex.EncodeToString(thumbprint[:]))
}
}
return thumbprints, nil
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/clouds/aws/ecs/cfn/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ const (
OutputsLogGroupARN = "logGroupArn"
OutputsSecurityGroupID = "securityGroupId"
OutputsSubnetID = "subnetId"
OutputsTaskDefArn = "taskDefArn"
OutputsTaskDefARN = "taskDefArn"
OutputsTemplateVersion = "templateVersion"
)
12 changes: 7 additions & 5 deletions src/pkg/clouds/aws/ecs/cfn/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,6 @@ func (a *AwsEcsCfn) SetUp(ctx context.Context, containers []clouds.Container) er
return err
}

return a.upsertStackAndWait(ctx, templateBody)
}

func (a *AwsEcsCfn) upsertStackAndWait(ctx context.Context, templateBody []byte) error {
// Set parameter values based on current configuration
parameters := []cfnTypes.Parameter{
// {
Expand Down Expand Up @@ -200,6 +196,10 @@ func (a *AwsEcsCfn) upsertStackAndWait(ctx context.Context, templateBody []byte)
}
// TODO: support DOCKER_AUTH_CONFIG

return a.upsertStackAndWait(ctx, templateBody, parameters...)
}

func (a *AwsEcsCfn) upsertStackAndWait(ctx context.Context, templateBody []byte, parameters ...cfnTypes.Parameter) error {
// Upsert with parameters
if err := a.updateStackAndWait(ctx, string(templateBody), parameters); err != nil {
// Check if the stack doesn't exist; if so, create it, otherwise return the error
Expand Down Expand Up @@ -249,7 +249,7 @@ func (a *AwsEcsCfn) fillWithOutputs(dso *cloudformation.DescribeStacksOutput) er
}
case OutputsDefaultSecurityGroupID:
a.DefaultSecurityGroupID = *output.OutputValue
case OutputsTaskDefArn:
case OutputsTaskDefARN:
a.TaskDefARN = *output.OutputValue
case OutputsClusterName:
a.ClusterName = *output.OutputValue
Expand All @@ -259,6 +259,8 @@ func (a *AwsEcsCfn) fillWithOutputs(dso *cloudformation.DescribeStacksOutput) er
a.SecurityGroupID = *output.OutputValue
case OutputsBucketName:
a.BucketName = *output.OutputValue
case OutputsCIRoleARN:
a.CIRoleARN = *output.OutputValue
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/pkg/clouds/aws/ecs/cfn/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ func TestCloudFormation(t *testing.T) {
aws.RetainBucket = false // delete bucket after test
aws.Spot = true

ctx := context.Background()
ctx := t.Context()

t.Run("SetUp", func(t *testing.T) {
template := createTestTemplate(t)
// Enable fancy features so we can test all conditional resources
t.Setenv("DEFANG_NO_CACHE", "0") // force cache usage
t.Setenv("DOCKERHUB_USERNAME", "defanglabs2")
t.Setenv("DOCKERHUB_ACCESS_TOKEN", "defanglabs")
err := aws.upsertStackAndWait(ctx, template)

err := aws.SetUp(ctx, testContainers)
if err != nil {
t.Fatal(err)
}
Expand Down
41 changes: 39 additions & 2 deletions src/pkg/clouds/aws/ecs/cfn/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,43 @@ func CreateTemplate(stack string, containers []clouds.Container) (*cloudformatio
Description: ptr.String(`Additional OIDC claim conditions as comma-separated JSON "key":"value" pairs (optional)`),
}

// Metadata - AWS::CloudFormation::Interface for parameter grouping and labels
template.Metadata = map[string]interface{}{
"AWS::CloudFormation::Interface": map[string]interface{}{
"ParameterGroups": []map[string]interface{}{
{
"Label": map[string]string{"default": "CI/CD Integration (OIDC)"},
"Parameters": []string{ParamsOidcProviderIssuer, ParamsOidcProviderSubjects, ParamsOidcProviderAudiences, ParamsCIRoleName, ParamsOidcProviderThumbprints, ParamsOidcProviderClaims},
},
{
"Label": map[string]string{"default": "Network Configuration"},
"Parameters": []string{ParamsExistingVpcId},
},
{
"Label": map[string]string{"default": "Container Registry (ECR Pull-Through Cache)"},
"Parameters": []string{ParamsEnablePullThroughCache, ParamsDockerHubUsername, ParamsDockerHubAccessToken},
},
{
"Label": map[string]string{"default": "Storage Configuration"},
"Parameters": []string{ParamsRetainBucket},
},
},
"ParameterLabels": map[string]interface{}{
ParamsExistingVpcId: map[string]string{"default": "Existing VPC ID"},
ParamsRetainBucket: map[string]string{"default": "Retain S3 Bucket on Delete"},
ParamsEnablePullThroughCache: map[string]string{"default": "Enable ECR Pull-Through Cache"},
ParamsDockerHubUsername: map[string]string{"default": "Docker Hub Username"},
ParamsDockerHubAccessToken: map[string]string{"default": "Docker Hub Access Token"},
ParamsOidcProviderIssuer: map[string]string{"default": "OIDC Provider Issuer URL"},
ParamsOidcProviderSubjects: map[string]string{"default": "OIDC Trusted Subject Patterns"},
ParamsOidcProviderAudiences: map[string]string{"default": "OIDC Trusted Audiences"},
ParamsOidcProviderThumbprints: map[string]string{"default": "OIDC Provider Thumbprints"},
ParamsOidcProviderClaims: map[string]string{"default": "Additional OIDC Claim Conditions"},
ParamsCIRoleName: map[string]string{"default": "CI Role Name"},
},
},
}

// Conditions
const _condCreateVpcResources = "CreateVpcResources"
template.Conditions[_condCreateVpcResources] = cloudformation.Equals(cloudformation.Ref(ParamsExistingVpcId), "")
Expand Down Expand Up @@ -672,9 +709,9 @@ func CreateTemplate(stack string, containers []clouds.Container) (*cloudformatio
template.Outputs[OutputsCIRoleARN] = cloudformation.Output{
Condition: ptr.String(_condOidcProvider),
Description: ptr.String("ARN of the CI role"),
Value: cloudformation.Ref(_CIRole),
Value: cloudformation.GetAtt(_CIRole, "Arn"),
}
template.Outputs[OutputsTaskDefArn] = cloudformation.Output{
template.Outputs[OutputsTaskDefARN] = cloudformation.Output{
Description: ptr.String("ARN of the ECS task definition"),
Value: cloudformation.Ref(_taskDefinition),
}
Expand Down
36 changes: 19 additions & 17 deletions src/pkg/clouds/aws/ecs/cfn/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,33 @@ func TestGetCacheRepoPrefix(t *testing.T) {
}
}

var testContainers = []clouds.Container{
{
Image: "alpine:latest",
},
{
Image: "docker.io/library/alpine:latest",
Name: "main2",
},
{
Name: "main3",
Image: "public.ecr.aws/docker/library/alpine:latest",
Memory: 512_000_000,
Platform: "linux/amd64",
},
}

func createTestTemplate(t *testing.T) []byte {
t.Helper()
template, err := CreateTemplate("test", []clouds.Container{
{
Image: "alpine:latest",
},
{
Image: "docker.io/library/alpine:latest",
Name: "main2",
},
{
Name: "main3",
Image: "public.ecr.aws/docker/library/alpine:latest",
Memory: 512_000_000,
Platform: "linux/amd64",
},
})
template, err := CreateTemplate("test", testContainers)
if err != nil {
t.Fatalf("Error creating template: %v", err)
}
actual, err := template.YAML()
templateBody, err := template.YAML()
if err != nil {
t.Fatalf("Error generating template YAML: %v", err)
}
return actual
return templateBody
}

func TestCreateTemplate(t *testing.T) {
Expand Down
53 changes: 52 additions & 1 deletion src/pkg/clouds/aws/ecs/cfn/testdata/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,55 @@ Conditions:
- Ref: RetainBucket
- "true"
Description: 'Defang AWS CloudFormation template for the CD task. Do not delete this stack in the AWS console: use the Defang CLI instead. To create this stack, scroll down to acknowledge the risks and press ''Create stack''.'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: CI/CD Integration (OIDC)
Parameters:
- OidcProviderIssuer
- OidcProviderSubjects
- OidcProviderAudiences
- CIRoleName
- OidcProviderThumbprints
- OidcProviderClaims
- Label:
default: Network Configuration
Parameters:
- ExistingVpcId
- Label:
default: Container Registry (ECR Pull-Through Cache)
Parameters:
- EnablePullThroughCache
- DockerHubUsername
- DockerHubAccessToken
- Label:
default: Storage Configuration
Parameters:
- RetainBucket
ParameterLabels:
CIRoleName:
default: CI Role Name
DockerHubAccessToken:
default: Docker Hub Access Token
DockerHubUsername:
default: Docker Hub Username
EnablePullThroughCache:
default: Enable ECR Pull-Through Cache
ExistingVpcId:
default: Existing VPC ID
OidcProviderAudiences:
default: OIDC Trusted Audiences
OidcProviderClaims:
default: Additional OIDC Claim Conditions
OidcProviderIssuer:
default: OIDC Provider Issuer URL
OidcProviderSubjects:
default: OIDC Trusted Subject Patterns
OidcProviderThumbprints:
default: OIDC Provider Thumbprints
RetainBucket:
default: Retain S3 Bucket on Delete
Outputs:
bucketName:
Description: Name of the S3 bucket
Expand All @@ -66,7 +115,9 @@ Outputs:
Condition: OidcProvider
Description: ARN of the CI role
Value:
Ref: CIRole
Fn::GetAtt:
- CIRole
- Arn
clusterName:
Description: Name of the ECS cluster
Value:
Expand Down
1 change: 1 addition & 0 deletions src/pkg/clouds/aws/ecs/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type TaskArn = clouds.TaskID
type AwsEcs struct {
aws.Aws
BucketName string
CIRoleARN string
ClusterName string
DefaultSecurityGroupID string
LogGroupARN string
Expand Down
Loading