When working with secrets in an AWS infrastructure workload, you have the option to use AWS System Manager Parameter Store.
AWS Systems Manager Parameter Store provides secure, hierarchical storage for configuration data management and secrets management. You can store data such as passwords, database strings, Amazon Machine Image (AMI) IDs, and license codes as parameter values.
You can store values as plain text or encrypted data. You can reference Systems Manager parameters in your scripts, commands, SSM documents, and configuration and automation workflows by using the unique name that you specified when you created the parameter.
As a best practice, you should generally opt to use Secrets Manager over Parameter store for secure credentials infrequently accessed.
copilot secret init
creates or updates secrets as SecureString parameters in SSM Parameter Store for your application. A secret can have different values in each of your existing environments, and is accessible by your services or or jobs from the same application and environment.
Lets create a secret in the application.
APP=$(copilot svc show --json | jq -r .application)
CENV=$(copilot svc show --json | jq -r .configurations[].environment)
copilot secret init --app $APP --name DEMO_PARAMETER --values $CENV=static/parameter-diagram.png
The parameter is created with the correct copilot application name and environment value as tags so copilot knows how to retrieve it.
Environment test is already on the latest version v1.4.0, skip upgrade.
...Put secret DEMO_PARAMETER to environment test
✔ Successfully put secret DEMO_PARAMETER in environment test as /copilot/ecsworkshop2322/test/secrets/DEMO_PARAMETER.
You can refer to these secrets from your manifest file by editing the `secrets` section.
test
secrets:
DEMO_PARAMETER: /copilot/ecsworkshop2322/test/secrets/DEMO_PARAMETER
Next, inside of our manifest.yml
file we add a section called called secrets
. Any secure parameter from SSM can be accessed via this area in the manifest. The key is the name of the environment variable, the value is the name of the SSM parameter. Paste the code below to append to the manifest.yml
file.
cat << EOF >> copilot/todo-app/manifest.yml
secrets:
DEMO_PARAMETER: /copilot/ecsworkshop2322/test/secrets/DEMO_PARAMETER
EOF
The value of the secure string inside SSM Parameter store will be available to your app via the DEMO_PARAMETER
environment variable.
Now that the secure parameter secret value has been added, the ECS service running task needs to pick up the newly defined parameter.
First we must commit the change to the local git repository - copilot only picks up committed changes in a git-enabled project.
git commit -am "Added Secrets to manifest"
Next, we trigger a copilot deployment for the ECS Task Definition to receive the new parameter. We pass the arbitrary tag of “update-credentials” to force the new deployment.
copilot svc deploy --tag update-credentials
Head back to the ECS Console to check on progress - this usually takes 1-2 minutes. Once the task is running, go back to the todo app and refresh, you should see a fully functional app once again with a diagram displayed.
Within a CDK application, you can pull both plaintext and secure secret parameters via the aws-cdk/aws-ssm
library. This library is included inside the repository’s package.json
file.
This part of the tutorial will demonstrate how to add a secure SSM parameter and use it in the application to display an image that is conditional on the value of the parameter passed via environment variables.
To create the secure SSM parameter for this tutorial (specifying the name of the image to display which is already present in the application):
aws ssm put-parameter --name DEMO_PARAMETER --value "static/parameter-diagram.png" --type SecureString
You should see the result:
{
"Tier": "Standard",
"Version": 1
}
Next, replace the contents of the file lib/ecs-fargate-stack.ts
with the below code. This code can also be found in lib/ecs-fargate-stack-ssm.ts
for reference.
cd ~/environment/secret-ecs-cdk-example
cat << EOF > lib/ecs-fargate-stack.ts
import { App, Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
//SSM Parameter imports
import * as ssm from 'aws-cdk-lib/aws-ssm';
export interface ECSStackProps extends StackProps {
vpc: ec2.Vpc
dbSecretArn: string
}
export class ECSStack extends Stack {
constructor(scope: App, id: string, props: ECSStackProps) {
super(scope, id, props);
const containerPort = this.node.tryGetContext("containerPort");
const containerImage = this.node.tryGetContext("containerImage");
const creds = secretsmanager.Secret.fromSecretCompleteArn(this, 'postgresCreds', props.dbSecretArn);
//fetch existing parameter from parameter store securely
const DEMOPARAM = ssm.StringParameter.fromSecureStringParameterAttributes(this, 'demo_param', {
parameterName: 'DEMO_PARAMETER',
version: 1
});
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc: props.vpc,
clusterName: 'fargateClusterDemo'
});
const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, "fargateService", {
cluster,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry(containerImage),
containerPort: containerPort,
enableLogging: true,
secrets: {
POSTGRES_DATA: ecs.Secret.fromSecretsManager(creds),
//Inject parameters value securely
DEMO_PARAMETER: ecs.Secret.fromSsmParameter(DEMOPARAM),
},
},
desiredCount: 1,
publicLoadBalancer: true,
serviceName: 'fargateServiceDemo'
});
new CfnOutput(this, 'LoadBalancerDNS', { value: fargateService.loadBalancer.loadBalancerDnsName });
}
}
EOF
After you make the changes, save the file and redeploy the app:
cdk deploy --all --require-approval never
This revision to the stack injects the parameter DEMO_PARAMETER
into the container via the secrets
property.
Once the deployment is complete, go back to the browser and you should see the app again with the new image displayed.