AWS IAM – Safely Store User Keys (Access and Secret) Created by IaC

amazon-iamamazon-web-services

I've the following setup:

  1. Infrastructure is setup using AWS CDK;
  2. I've one Stack/Environment (Production, Staging…);
  3. Each Stack has a different S3 Bucket (used for website hosting);
  4. I've a Stack that creates an IAM User (used by CI/CD);
  5. CI/CD in this case is GitHub Actions (deploy every time a merge to main happens);
  6. The IAM User has only put rights to all the Buckets (deploy means put the assets in the Bucket);

What is the best way to store/handle the keys for that user?

I started printing it in the Outputs but it is not secure. Everyone can see it (if they've access to the logs of CI/CD for example).

I've been suggested to store them in SSM: it works but you can't create it as SecureString so it would be just a String.

I've also taken a look into Secrets Manager: it also works and seems to be more secure (not sure if my feeling here is valid though).

Any ideas/opinions here?

Thanks!


In the code it looks something like:

// Production Stack
const bucket = new Bucket(this, "Bucket", {
  bucketName: "production",
});

// Staging Stack
const bucket = new Bucket(this, "Bucket", {
  bucketName: "staging",
});

// IAM Stack
const user = new User(this, "User", {
  userName: "ci-cd-user",
});
const userAccessKey = new AccessKey(this, "UserAccessKey", { user });

// This is just an example, I go through all the available Buckets
bucketProduction.grantPut(user);
bucketStaging.grantPut(user);

Best Answer

It took me a while but I finally wrapped my thoughts around it here: https://dev.to/viniciuskneves/use-aws-through-github-actions-without-secret-keys-32eo

The ideia is to use OpenID Connect to connect with GitHub and then have a Role that can be assumed by GitHub Actions and perform the deployment (same policies I would give to GitHub Actions user).

Using AWS CDK the solution looks like the following:

const provider = new OpenIdConnectProvider(this, "GitHubProvider", {
  url: "https://token.actions.githubusercontent.com",
  clientIds: ["sts.amazonaws.com"], // Referred as "Audience" in the documentation
});
const role = new Role(this, "Role", {
  assumedBy: new OpenIdConnectPrincipal(provider, {
    StringEquals: {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", // "Audience" from above
    },
    StringLike: {
      "token.actions.githubusercontent.com:sub":
      "repo:<ORGANIZATION>/<REPOSITORY>:*", // Organization and repository that are allowed to assume this role
    },
  }),
});

bucket.grantPut(role); // Creates policy

After that we need to update GitHub Actions to use this instead of our user keys:

...
permissions:
  id-token: write
  contents: read # This is required for actions/checkout (GitHub documentation mentions that)
...
- name: Configure AWS Credentials 🔧
  uses: aws-actions/[email protected] # Version at the time of this writing
  with: # We are loading keys stored in secrets
    role-to-assume: arn:aws:iam::<ACCOUNT>:role/<ROLE-NAME> # You can specify the role name when creating it or AWS will automatically generate one for you
    aws-region: eu-central-1
- run: npm run deploy # Just an example
Related Topic