Inject Secure Parameters

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.

Click here to expand feature comparison

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.

working-app

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/core';
import { Vpc } from "@aws-cdk/aws-ec2";
import { Cluster, ContainerImage, Secret as ECSSecret } from "@aws-cdk/aws-ecs";
import { ApplicationLoadBalancedFargateService } from '@aws-cdk/aws-ecs-patterns';
import { Secret } from '@aws-cdk/aws-secretsmanager';

//SSM Parameter imports
import { SecretValue } from '@aws-cdk/core';
import { StringParameter, ParameterTier, ParameterType } from "@aws-cdk/aws-ssm";

export interface ECSStackProps extends StackProps {
  vpc: 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 = Secret.fromSecretCompleteArn(this, 'postgresCreds', props.dbSecretArn);

    //fetch existing parameter from parameter store securely
    const DEMOPARAM = StringParameter.fromSecureStringParameterAttributes(this, 'demo_param', {
      parameterName: 'DEMO_PARAMETER',
      version: 1
    });

    const cluster = new Cluster(this, 'Cluster', {
      vpc: props.vpc,
      clusterName: 'fargateClusterDemo'
    });

    const fargateService = new ApplicationLoadBalancedFargateService(this, "fargateService", {
      cluster,
      taskImageOptions: {
        image: ContainerImage.fromRegistry(containerImage),
        containerPort: containerPort,
        enableLogging: true,
        secrets: {
          POSTGRES_DATA: ECSSecret.fromSecretsManager(creds),
          //Inject parameter value securely
          DEMO_PARAMETER: ECSSecret.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.

working-app