Skip to content
Open
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
4 changes: 4 additions & 0 deletions provider/aws/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ func (p *Provider) appRepository2(app string) (*appRepository, error) {

// cleanup deletes AWS resources that aren't handled by the CloudFormation during stack deletion.
func (p *Provider) cleanup(app *structs.App) error {
if err := p.secretsManagerDelete(p.Rack, app.Name); err != nil {
fmt.Printf("fn=cleanup level=error msg=\"delete secrets manager secret: %s\"\n", err)
}

settings, err := p.appResource(app.Name, "Settings")
if err != nil {
return err
Expand Down
9 changes: 9 additions & 0 deletions provider/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/aws/aws-sdk-go/service/kms"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/aws/aws-sdk-go/service/sns"
"github.com/aws/aws-sdk-go/service/sqs"
"github.com/aws/aws-sdk-go/service/ssm"
Expand Down Expand Up @@ -366,6 +367,14 @@ func (p *Provider) dynamodb() *dynamodb.DynamoDB {
return dynamodb.New(s, p.config())
}

func (p *Provider) secretsmanager() *secretsmanager.SecretsManager {
s, err := helpers.NewSession()
if err != nil {
panic(errors.WithStack(err))
}
return secretsmanager.New(s, p.config())
}

func (p *Provider) ec2() *ec2.EC2 {
s, err := helpers.NewSession()
if err != nil {
Expand Down
22 changes: 22 additions & 0 deletions provider/aws/formation/app.json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"ServiceRole": {
"Export": { "Name": { "Fn::Sub": "${AWS::StackName}:ServiceRole" } },
"Value": { "Fn::GetAtt": [ "ServiceRole", "Arn" ] }
},
"ExecutionRole": {
"Value": { "Fn::GetAtt": [ "ExecutionRole", "Arn" ] }
}
},
"Parameters" : {
Expand Down Expand Up @@ -164,6 +167,11 @@
"Description": "Override the password set for embedded resources",
"NoEcho": true
},
"SecretsManagerEnv": {
"Type": "String",
"Default": "No",
"AllowedValues": [ "Yes", "No" ]
},
"SlowStartDuration": {
"AllowedPattern": "^(0|[3-8][0-9]|9[0-9]|[1-8][0-9]{2}|900)$",
"Default": "0",
Expand Down Expand Up @@ -207,6 +215,20 @@
{ "Fn::ImportValue": { "Fn::Sub": "${Rack}:CMKPolicy" } }
],
"Path": "/convox/"
{{ if $.SecretsManagerARN }}
,
"Policies": [{
"PolicyName": "SecretsManagerAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "{{ $.SecretsManagerARN }}"
}]
}
}]
{{ end }}
}
},
"LogGroup": {
Expand Down
29 changes: 29 additions & 0 deletions provider/aws/formation/rack.json
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,12 @@
"Description": "The recurring schedule for when the rack will start. This format consists of five fields separated by white spaces: [Minute] [Hour] [Day_of_Month] [Month_of_Year] [Day_of_Week]",
"Default": ""
},
"SecretsManagerEnv": {
"Type": "String",
"Default": "No",
"Description": "Use AWS Secrets Manager for env var injection into ECS tasks (gen2 apps only)",
"AllowedValues": [ "Yes", "No" ]
},
"SpotFleetAllowedInstanceTypes": {
"Type": "CommaDelimitedList",
"Description": "Comma-separated list of allowed instance types in the Spot Fleet. It can not be used with SpotFleetExcludedInstanceTypes, it takes precedent over it. The following are examples: m5.8xlarge, c5*.*, m5a.*, r*, *3*.",
Expand Down Expand Up @@ -4398,6 +4404,29 @@
}
]
}
},
{
"PolicyName": "SecretsManagerAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:GetSecretValue",
"secretsmanager:DeleteSecret",
"secretsmanager:DescribeSecret",
"secretsmanager:RestoreSecret",
"secretsmanager:TagResource"
],
"Resource": {
"Fn::Sub": "arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${AWS::StackName}/*"
}
}
]
}
}
]
}
Expand Down
24 changes: 24 additions & 0 deletions provider/aws/formation/service.json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,20 @@
},
"ManagedPolicyArns": [ { "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } ],
"Path": "/convox/"
{{ if $.SecretsManagerARN }}
,
"Policies": [{
"PolicyName": "SecretsManagerAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "{{ $.SecretsManagerARN }}"
}]
}
}]
{{ end }}
}
},
{{ if .Port.Port }}
Expand Down Expand Up @@ -915,9 +929,11 @@
{ "Name": "BUILD", "Value": "{{$.Build.Id}}" },
{ "Name": "BUILD_GIT_SHA", "Value": {{ safe $.Build.GitSha }} },
{ "Name": "BUILD_DESCRIPTION", "Value": {{ safe $.Build.Description }} },
{{ if not $.SecretsManagerARN }}
{ "Name": "CONVOX_ENV_KEY", "Value": { "Fn::ImportValue": { "Fn::Sub": "${Rack}:EncryptionKey" } } },
{ "Name": "CONVOX_ENV_URL", "Value": { "Fn::Sub": "s3://${Settings}/releases/{{$.Release.Id}}/env" } },
{ "Name": "CONVOX_ENV_VARS", "Value": "{{.EnvironmentKeys}}" },
{{ end }}
{ "Name": "RACK", "Value": { "Ref": "Rack" } },
{ "Fn::If": [ "RackUrl",
{ "Name": "RACK_URL", "Value": { "Fn::Sub": "https://convox:{{$.Password}}@rack.${Rack}.convox" } },
Expand All @@ -926,6 +942,14 @@
{ "Name": "RELEASE", "Value": "{{$.Release.Id}}" },
{ "Name": "SERVICE", "Value": "{{.Name}}" }
],
{{ if and $.SecretsManagerARN $.SecretsManagerKeys }}
"Secrets": [
{{ range $i, $key := $.SecretsManagerKeys }}
{{ if $i }},{{ end }}
{ "Name": "{{ $key }}", "ValueFrom": "{{ $.SecretsManagerARN }}:{{ $key }}::" }
{{ end }}
],
{{ end }}
"Image": { "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Registry}:{{.Name}}.{{$.Release.Build}}" },
"LinuxParameters": {
{{ if .Init }}
Expand Down
10 changes: 10 additions & 0 deletions provider/aws/formation/timer.json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,11 @@
{{ end }}
{ "Name": "AWS_REGION", "Value": { "Ref": "AWS::Region" } },
{ "Name": "APP", "Value": "{{$.App}}" },
{{ if not $.SecretsManagerARN }}
{ "Name": "CONVOX_ENV_KEY", "Value": { "Fn::ImportValue": { "Fn::Sub": "${Rack}:EncryptionKey" } } },
{ "Name": "CONVOX_ENV_URL", "Value": { "Fn::Sub": "s3://${Settings}/releases/{{$.Release.Id}}/env" } },
{ "Name": "CONVOX_ENV_VARS", "Value": "{{.EnvironmentKeys}}" },
{{ end }}
{ "Name": "RACK", "Value": { "Ref": "Rack" } },
{ "Fn::If": [ "RackUrl",
{ "Name": "RACK_URL", "Value": { "Fn::Sub": "https://convox:{{$.Password}}@rack.${Rack}.convox" } },
Expand All @@ -236,6 +238,14 @@
{ "Name": "BUILD_DESCRIPTION", "Value": {{ safe $.Build.Description }} },
{ "Name": "SERVICE", "Value": "{{.Name}}" }
],
{{ if and $.SecretsManagerARN $.SecretsManagerKeys }}
"Secrets": [
{{ range $i, $key := $.SecretsManagerKeys }}
{{ if $i }},{{ end }}
{ "Name": "{{ $key }}", "ValueFrom": "{{ $.SecretsManagerARN }}:{{ $key }}::" }
{{ end }}
],
{{ end }}
"Image": { "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Registry}:{{.Name}}.{{$.Release.Build}}" },
"LogConfiguration": {
"Fn::If": [
Expand Down
25 changes: 25 additions & 0 deletions provider/aws/processes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,26 @@ func (p *Provider) generateTaskDefinition2(app, service string, opts structs.Pro
senv["RACK_URL"] = fmt.Sprintf("https://convox:%s@rack.%s.convox", url.QueryEscape(p.Password), p.Rack)
}

var smSecrets []*ecs.Secret
if aps["SecretsManagerEnv"] == "Yes" {
smARN, descErr := p.secretsManagerGetARN(p.Rack, r.App)
if descErr == nil {
keys := sortedKeys(env)
smSecrets = make([]*ecs.Secret, 0, len(keys))
for _, k := range keys {
smSecrets = append(smSecrets, &ecs.Secret{
Name: aws.String(k),
ValueFrom: aws.String(fmt.Sprintf("%s:%s::", smARN, k)),
})
}
delete(senv, "CONVOX_ENV_KEY")
delete(senv, "CONVOX_ENV_URL")
delete(senv, "CONVOX_ENV_VARS")
} else {
fmt.Printf("fn=generateTaskDefinition2 level=warn msg=\"secrets manager lookup failed, falling back to S3: %s\"\n", descErr)
}
}

cenv := []*ecs.KeyValuePair{}

for k, v := range senv {
Expand Down Expand Up @@ -1182,6 +1202,7 @@ func (p *Provider) generateTaskDefinition2(app, service string, opts structs.Pro
MountPoints: mps,
Name: aws.String(service),
Privileged: aws.Bool(s.Privileged),
Secrets: smSecrets,
}

if len(s.Command) > 0 {
Expand Down Expand Up @@ -1227,6 +1248,10 @@ func (p *Provider) generateTaskDefinition2(app, service string, opts structs.Pro
Volumes: vs,
}

if executionRole := aos["ExecutionRole"]; executionRole != "" {
req.ExecutionRoleArn = aws.String(executionRole)
}

return req, nil
}

Expand Down
108 changes: 70 additions & 38 deletions provider/aws/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,65 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt
tp[fmt.Sprintf("ResourceTemplate%s", upperName(r.Name))] = ou.Url
}

private, err := p.stackParameter(p.Rack, "Private")
if err != nil {
return err
}

lambdaInVpc, err := p.stackParameter(p.Rack, "PlaceLambdaInVpc")
if err != nil {
return err
}

readonlyRootFilesystem, err := p.stackParameter(p.Rack, "EnableContainerReadonlyRootFilesystem")
if err != nil {
return err
}

rackSMParam, _ := p.stackParameter(p.Rack, "SecretsManagerEnv")
if rackSMParam == "" {
rackSMParam = "No"
}

appSMParam, appSMErr := p.stackParameter(p.rackStack(r.App), "SecretsManagerEnv")
smValue := rackSMParam
if appSMErr == nil && appSMParam == "Yes" {
smValue = "Yes"
}

updates := map[string]string{
"LogBucket": p.LogBucket,
"LogDriver": p.LogDriver,
"PlaceLambdaInVpc": lambdaInVpc,
"Private": private,
"SyslogDestination": p.SyslogDestination,
"SyslogFormat": p.SyslogFormat,
"EnableContainerReadonlyRootFilesystem": readonlyRootFilesystem,
"SecretsManagerEnv": smValue,
}

if m.Params != nil {
for k, v := range m.Params {
updates[k] = v
}
}

smEnabled := updates["SecretsManagerEnv"] == "Yes"
tp["SecretsManagerARN"] = ""

var smARN string
var smKeys []string
if smEnabled {
arn, keys, smErr := p.secretsManagerWrite(p.Rack, r.App, env)
if smErr != nil {
fmt.Printf("fn=ReleasePromote level=warn msg=\"secrets manager write failed, falling back to S3: %s\"\n", smErr)
} else {
smARN = arn
smKeys = keys
tp["SecretsManagerARN"] = smARN
}
}

for _, s := range m.Services {
min := s.Deployment.Minimum
max := s.Deployment.Maximum
Expand Down Expand Up @@ -388,6 +447,8 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt
"WildcardDomain": tp["WildcardDomain"],
"NLBPreserveClientIPDefault": yesNo(p.NLBPreserveClientIP),
"NLBInternalPreserveClientIPDefault": yesNo(p.NLBInternalPreserveClientIP),
"SecretsManagerARN": smARN,
"SecretsManagerKeys": smKeys,
}

data, err := formationTemplate("service", stp)
Expand All @@ -405,13 +466,15 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt

for _, t := range m.Timers {
ttp := map[string]interface{}{
"App": r.App,
"Build": tp["Build"],
"Manifest": tp["Manifest"],
"Password": p.Password,
"Release": tp["Release"],
"Timer": t,
"TimeState": "",
"App": r.App,
"Build": tp["Build"],
"Manifest": tp["Manifest"],
"Password": p.Password,
"Release": tp["Release"],
"Timer": t,
"TimeState": "",
"SecretsManagerARN": smARN,
"SecretsManagerKeys": smKeys,
}

if p.MaintainTimerState {
Expand Down Expand Up @@ -439,37 +502,6 @@ func (p *Provider) ReleasePromote(app, id string, opts structs.ReleasePromoteOpt
return err
}

private, err := p.stackParameter(p.Rack, "Private")
if err != nil {
return err
}

lambdaInVpc, err := p.stackParameter(p.Rack, "PlaceLambdaInVpc")
if err != nil {
return err
}

readonlyRootFilesystem, err := p.stackParameter(p.Rack, "EnableContainerReadonlyRootFilesystem")
if err != nil {
return err
}

updates := map[string]string{
"LogBucket": p.LogBucket,
"LogDriver": p.LogDriver,
"PlaceLambdaInVpc": lambdaInVpc,
"Private": private,
"SyslogDestination": p.SyslogDestination,
"SyslogFormat": p.SyslogFormat,
"EnableContainerReadonlyRootFilesystem": readonlyRootFilesystem,
}

if m.Params != nil {
for k, v := range m.Params {
updates[k] = v
}
}

tags := map[string]string{
"Version": p.Version,
}
Expand Down
Loading