Skip to content

Commit 63f1302

Browse files
committed
Added support for ECS Secrets
1 parent 857ebf6 commit 63f1302

File tree

5 files changed

+112
-10
lines changed

5 files changed

+112
-10
lines changed

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ task_definition:
458458
timeout: string
459459
retries: integer
460460
start_period: string
461+
secrets:
462+
- value_from: string
463+
name: string
461464
docker_volumes:
462465
- name: string
463466
scope: string // Valid values: "shared" | "task"
@@ -520,6 +523,9 @@ Fields listed under `task_definition` correspond to fields that will be included
520523
* `healthcheck` This parameter maps to `healthcheck` in the [Docker compose file reference](https://docs.docker.com/compose/compose-file/#healthcheck). This field can either be used here in the ECS Params file, or it can be used in Compose File version 3 with the ECS CLI.
521524
* `test` can also be specified as `command` and must be either a string or a list or strings. If `test` is specified as a list of strings, the first item must be either NONE, CMD, or CMD-SHELL. If test or command is specified as a string, CMD-SHELL will be prepended and ECS will run the command in the container's default shell.
522525
* `interval`, `timeout`, and `start_period` are specified as durations in a string format. For example: 2.5s, 10s, 1m30s, 2h23m, or 5h34m56s.
526+
* `secrets` allows you to specify secrets which will be retrieved from SSM Parameter Store. See the [ECS Docs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) for more information, including how reference AWS Secrets Managers secrets from SSM Parameter Store.
527+
* `value_from` is the SSM Parameter ARN or name (if the parameter is in the same region as your ECS Task).
528+
* `name` is the name of the environment variable in which the secret will be stored.
523529

524530
* `docker_volumes` allows you to create docker volumes. The name key is required, and `scope`, `autoprovision`, `driver`, `driver_opts` and `labels` correspond with the fields under [dockerVolumeConfiguration](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-volumes.html) in an ECS Task Definition. Volumes defined with the `docker_volumes` key can be referenced in your compose file by name, even if they were not also specified in the compose file.
525531

@@ -861,7 +867,7 @@ To get started, first create an input file that contains the name of your regist
861867
862868
version: '1'
863869
registry_credentials:
864-
my-registry.example.com:
870+
my-registry.example.com:
865871
secrets_manager_arn: # required when using (with no modification) or updating an existing secret
866872
username: myUserName # required when creating or updating a new secret
867873
password: ${MY_PASSWORD} # required when creating or updating a new secret
@@ -874,7 +880,7 @@ registry_credentials:
874880
In this example, we're storing credentials for a registry called `my-registry.example.com` and passing in the password with an environment variable. `container_names` is a list of the `service_names` in your Docker Compose project which need access to images in this registry. If you don't plan to use the output of `registry-creds up` to launch a task or service with `compose`, then you can leave this field empty.
875881

876882
Other options:
877-
* To store credentials for multiple private registries, add additional (up to 10 total) registry names and their required details as separate keys under `registry_credentials`.
883+
* To store credentials for multiple private registries, add additional (up to 10 total) registry names and their required details as separate keys under `registry_credentials`.
878884
* Existing registry secrets from other regions can be included by specifying their `secrets_manager_arn` and associated `kms_key_id`. Creating or updating secrets must be done from within that region.
879885
* If you want to encrypt the AWS Secrets Manager secret for your registry with a custom KMS Key, then add the ARN, ID or Alias of the Key in the `kms_key_id` field. Otherwise, AWS Secrets Manager will use the default key in your account.
880886
* If you don't want to create or update an IAM Task Execution Role for these secrets, use the `--no-role` flag instead of specifying a role name.
@@ -914,8 +920,8 @@ registry_credential_outputs:
914920
- log
915921
```
916922

917-
This file contains:
918-
* the name of the IAM Task Execution Role with permissions for the new secrets
923+
This file contains:
924+
* the name of the IAM Task Execution Role with permissions for the new secrets
919925
* the ARN of the new `credentials_parameter` created for the registry
920926
* the list of containers the new `credentials_parameter` should be used for when running a task or service
921927

@@ -933,7 +939,7 @@ services:
933939
web:
934940
environment:
935941
- SERVICE_NAME=web
936-
image: my-registry.example.com/httpd
942+
image: my-registry.example.com/httpd
937943
ports:
938944
- "80:80"
939945
log:

ecs-cli/modules/utils/compose/convert_task_definition.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ func convertToContainerDef(inputCfg *adapter.ContainerConfig, ecsContainerDef *C
228228
outputContDef.RepositoryCredentials.SetCredentialsParameter(credParam)
229229
}
230230

231+
if len(ecsContainerDef.Secrets) > 0 {
232+
outputContDef.SetSecrets(convertToECSSecrets(ecsContainerDef.Secrets))
233+
}
234+
231235
var err error
232236
healthCheck, err = resolveHealthCheck(inputCfg.Name, healthCheck, ecsContainerDef.HealthCheck)
233237
if err != nil {
@@ -355,6 +359,18 @@ func convertToECSVolumes(hostPaths *adapter.Volumes, ecsParams *ECSParams) ([]*e
355359
return output, nil
356360
}
357361

362+
func convertToECSSecrets(secrets []Secret) []*ecs.Secret {
363+
var ecsSecrets []*ecs.Secret
364+
for _, secret := range secrets {
365+
s := &ecs.Secret{
366+
ValueFrom: aws.String(secret.ValueFrom),
367+
Name: aws.String(secret.Name),
368+
}
369+
ecsSecrets = append(ecsSecrets, s)
370+
}
371+
return ecsSecrets
372+
}
373+
358374
func mergeVolumesWithoutHost(composeVolumes []string, ecsParams *ECSParams) ([]*ecs.Volume, error) {
359375
volumesWithoutHost := make(map[string]DockerVolume)
360376
output := []*ecs.Volume{}

ecs-cli/modules/utils/compose/convert_task_definition_test.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,10 +1519,6 @@ task_definition:
15191519
}
15201520
}
15211521

1522-
///////////////////////
1523-
// helper functions //
1524-
//////////////////////
1525-
15261522
func TestConvertToTaskDefinitionWithECSRegistryCreds(t *testing.T) {
15271523
containerConfigs := testContainerConfigs([]string{"mysql", "wordpress"})
15281524
credsFileString := `version: "1"
@@ -1686,6 +1682,65 @@ registry_credential_outputs:
16861682
assert.Error(t, err, "Expected error when converting task definition")
16871683
}
16881684

1685+
func TestConvertToTaskDefinitionWithECSParams_Secrets(t *testing.T) {
1686+
containerConfig := &adapter.ContainerConfig{
1687+
Name: "web",
1688+
Image: "wordpress",
1689+
}
1690+
1691+
ecsParamsString := `version: 1
1692+
task_definition:
1693+
services:
1694+
web:
1695+
secrets:
1696+
- value_from: /mysecrets/dbusername
1697+
name: DB_USERNAME
1698+
- value_from: arn:aws:ssm:eu-west-1:111111111111:parameter/mysecrets/dbpassword
1699+
name: DB_PASSWORD`
1700+
1701+
content := []byte(ecsParamsString)
1702+
1703+
tmpfile, err := ioutil.TempFile("", "ecs-params")
1704+
assert.NoError(t, err, "Could not create ecs params tempfile")
1705+
1706+
defer os.Remove(tmpfile.Name())
1707+
1708+
_, err = tmpfile.Write(content)
1709+
assert.NoError(t, err, "Could not write data to ecs params tempfile")
1710+
1711+
err = tmpfile.Close()
1712+
assert.NoError(t, err, "Could not close tempfile")
1713+
1714+
ecsParamsFileName := tmpfile.Name()
1715+
ecsParams, err := ReadECSParams(ecsParamsFileName)
1716+
assert.NoError(t, err, "Could not read ECS Params file")
1717+
1718+
containerConfigs := []adapter.ContainerConfig{*containerConfig}
1719+
taskDefinition, err := convertToTaskDefinitionForTest(t, containerConfigs, "", "", ecsParams, nil)
1720+
1721+
containerDefs := taskDefinition.ContainerDefinitions
1722+
web := findContainerByName("web", containerDefs)
1723+
1724+
expectedSecrets := []*ecs.Secret{
1725+
&ecs.Secret{
1726+
ValueFrom: aws.String("arn:aws:ssm:eu-west-1:111111111111:parameter/mysecrets/dbpassword"),
1727+
Name: aws.String("DB_PASSWORD"),
1728+
},
1729+
&ecs.Secret{
1730+
ValueFrom: aws.String("/mysecrets/dbusername"),
1731+
Name: aws.String("DB_USERNAME"),
1732+
},
1733+
}
1734+
1735+
if assert.NoError(t, err) {
1736+
assert.ElementsMatch(t, expectedSecrets, web.Secrets, "Expected secrets to match")
1737+
}
1738+
}
1739+
1740+
///////////////////////
1741+
// helper functions //
1742+
//////////////////////
1743+
16891744
func convertToTaskDefinitionForTest(t *testing.T, containerConfigs []adapter.ContainerConfig, taskRoleArn string, launchType string, ecsParams *ECSParams, ecsRegCreds *regcredio.ECSRegistryCredsOutput) (*ecs.TaskDefinition, error) {
16901745
volumeConfigs := &adapter.Volumes{
16911746
VolumeEmptyHost: []string{namedVolume},

ecs-cli/modules/utils/compose/ecs_params_reader.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type ContainerDef struct {
6363
Memory libYaml.MemStringorInt `yaml:"mem_limit"`
6464
MemoryReservation libYaml.MemStringorInt `yaml:"mem_reservation"`
6565
HealthCheck *HealthCheck `yaml:"healthcheck"`
66+
Secrets []Secret `yaml:"secrets"`
6667
}
6768

6869
type DockerVolume struct {
@@ -90,6 +91,12 @@ type RepositoryCredentials struct {
9091
CredentialsParameter string `yaml:"credentials_parameter"`
9192
}
9293

94+
// Secret supports the ECS Secrets integration with SSM Parameter Store
95+
type Secret struct {
96+
ValueFrom string `yaml:"value_from"`
97+
Name string `yaml:"name"`
98+
}
99+
93100
// TaskSize holds Cpu and Memory values needed for Fargate tasks
94101
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
95102
type TaskSize struct {

ecs-cli/modules/utils/compose/ecs_params_reader_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,12 @@ task_definition:
8585
wordpress:
8686
essential: true
8787
repository_credentials:
88-
credentials_parameter: arn:aws:secretsmanager:1234567890:secret:test-RT4iv`
88+
credentials_parameter: arn:aws:secretsmanager:1234567890:secret:test-RT4iv
89+
secrets:
90+
- value_from: arn:aws:ssm:eu-west-1:111111111111:parameter/mysecrets/dbpassword
91+
name: DB_PASSWORD
92+
- value_from: /mysecrets/dbusername
93+
name: DB_USERNAME`
8994

9095
content := []byte(ecsParamsString)
9196

@@ -120,6 +125,19 @@ task_definition:
120125
assert.Equal(t, yaml.MemStringorInt(524288000), mysql.MemoryReservation)
121126
assert.True(t, wordpress.Essential, "Expected container to be essential")
122127
assert.Equal(t, "arn:aws:secretsmanager:1234567890:secret:test-RT4iv", wordpress.RepositoryCredentials.CredentialsParameter, "Expected CredentialsParameter to match")
128+
129+
expectedSecrets := []Secret{
130+
Secret{
131+
ValueFrom: "arn:aws:ssm:eu-west-1:111111111111:parameter/mysecrets/dbpassword",
132+
Name: "DB_PASSWORD",
133+
},
134+
Secret{
135+
ValueFrom: "/mysecrets/dbusername",
136+
Name: "DB_USERNAME",
137+
},
138+
}
139+
140+
assert.ElementsMatch(t, expectedSecrets, wordpress.Secrets, "Expected secrets to match")
123141
}
124142
}
125143

0 commit comments

Comments
 (0)