AWS CDK - How to pass Access Key Secret and Secret Key Id as Env Param to Container

Viewed 3281

I'm using CDK to build our infra on AWS. I create my IAM User for my microservices to talk to AWS Services under the defined policies. My issue is I cannot get aws secret key and id and then, pass as Env variable to my container. See below:

First, I create my IAM user which will be used by my microservices.

const user = new User(this, "user", {
  userName: `${myAppEnv}-api-iam-user`,
});

Second, I'm trying to create Access Key.

const accessKey = new CfnAccessKey(this, "ApiUserAccessKey", {
      userName: user.userName,
});

const accessKeyId = new CfnOutput(this, "AccessKeyId", {
      value: accessKey.ref,
});

const accessKeySecret = new CfnOutput(this, "SecretAccessKeyId", {
      value: accessKey.attrSecretAccessKey,
});

Next, I want to pass it as an env variable.

const apiContainer = apiTaskDefinition.addContainer(containerId, {
      image: apiImage,
      environment: {
        APP_ENV: myAppEnv,
        AWS_ACCESS_KEY_ID: awsAccessKeyId.value || ":(",
        AWS_SECRET_ACCESS_KEY: awsSecretAccessKey.value || ":(",
        NOTIFICATIONS_TABLE_ARN: notificationsTableDynamoDBArn,
        NOTIFICATIONS_QUEUE_URL: notificationsQueueUrl,
      },
      cpu: 256,
      memoryLimitMiB: 512,
      logging: new AwsLogDriver({ streamPrefix: `${myAppEnv}-ec-api` }),
    });

When my CDK deploy finishes successfully, I see the below printed out :/

Outputs:
staging-ecstack.AccessKeyId = SOMETHING
staging-ecstack.SecretAccessKeyId = SOMETHINGsy12X21xSSOMETHING2X2

Do you have any idea how I can achieve this?

2 Answers

Generally speaking, creating an IAM user isn't the way to go here - you're better off using an IAM role. With the CDK it will create a taskRole for you automatically when you instantiate the taskDefinition construct. You can assign permissions to other constructs in your stack using various grant* methods as described here:

const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
const container = taskDefinition.addContainer('web', {
  image: ecs.ContainerImage.fromRegistry("apps/myapp"),
  memoryLimitMiB: 256,
});
// Grant this task role access to use other resources
myDynamodbTable.grantReadWriteData(taskDefinition.taskRole);
mySnsTopic.grantPublish(taskDefinition.taskRole);

In short, find the answer in my blogpost here: https://blog.michaelfecher.com/i-tell-you-a-secret-provide-database-credentials-to-an-ecs-fargate-task-in-aws-cdk

To explain a bit more detailled on your issue:

  1. Avoid custom created IAM Roles and use the generated ones by CDK. They are aligned and using the least-privilege principle. In my blog post, the manual IAM role creation isn't necessary. Yes, I know... I need to update that. ;)
  2. Use the grant* method of the corresponding resource, e.g. taskDefinition. This will create a policy statement for the generated role of 1)
  3. Don't use environment variables for secrets. There are a lot of resources on the web, which tell you why. One is this here: https://security.stackexchange.com/questions/197784/is-it-unsafe-to-use-environmental-variables-for-secret-data Especially, when working with ECS, make use of the secrets argument in the task definition (see the blog post).
  4. It seems, that you're passing the token instead of the actual secret value. That doesn't work. The token is resolved during synth / Cloud Formation generation.
  5. You won't need the CfnOutput. Use the direct Stack -> Stack passing of fields. The CfnOutput is really sth., which you should avoid, when having all Stacks under control in one CDK application. That only makes sense, if you want to share between CDK applications, which are separated deployments and repositories.

If sth. is unclear, don't hesitate asking questions.

Related