Blog

Building an AWS CDK Credential Provider

30 Jan, 2020
Xebia Background Header Wave

When working in a multi-account environment with the AWS Cloud Development Kit (CDK), the CDK needs to be able to obtain the appropriate credentials. We are going to show you how to write a credential provider plugin for the CDK.
A CDK App can consist of multiple stacks, each of the stacks can be provided with an AWS Region and Account ID. When deploying, the CDK makes sure that the stacks are deployed to correct account and region.
If you don’t do anything specific, the CDK just checks that the current available AWS credentials correspond to the account and fails if this is not the case. When you provide a credential provider plugin, it will try to obtain the correct credentials from the plugin instead. These plugins are, as of writing, an undocumented experimental feature of the CDK.
The idea is simple, a credential provider plugin translates a 12-digit AWS Account ID into credentials for that account. There is a really simple credential provider source interface that you have to implement.

Obtaining Credentials

In our example credential provider implementation, we want a simple way to obtain the actual credentials. We have found this in the SharedIniFileCredentials class from the AWS SDK for JavaScript. This class has some quirks and requires an unusual ~/.aws/credentials file, we specify in this file both static credentials for an IAM User and profiles for IAM Roles that the user is allowed to assume. Normally you would split this in a ~/.aws/credentials file with the user credentials and a ~/.aws/config file with the profiles for the other accounts.
The trick here is that we name the profiles with account IDs so that we do not have to look up a profile name for an ID.

# ~/.aws/credentials

[thomas]
aws_access_key_id = AKIAIIIIIIIIIIIIIIII
aws_secret_access_key = AcXXXXXXXXXXXXXXXXXXXXXXXXXDJ+0

[112233445566]
role_arn = arn:aws:iam::112233445566:role/thomas-no-mfa
source_profile = thomas

[223344556677]
role_arn = arn:aws:iam::223344556677:role/thomas-no-mfa
source_profile = thomas

Example Credential Provider Source

The CDK wants to get credentials for a specific AWS Account ID. We implement a credential provider source that uses the SharedIniFileCredentials class to obtain credentials for profiles in your ~/.aws/credentials file with the AWS Account ID as the profile name. We discuss the implemented methods below.

// my-credential-provider-source.ts

import * as cdk from "aws-cdk";
import AWS = require("aws-sdk");

export class MyCredentialProviderSource implements cdk.CredentialProviderSource {
    name: string;

    constructor() {
        this.name = "MyCredentialProviderSource";
    }

    public async isAvailable(): Promise<boolean> {
        return true;
    }

    public async canProvideCredentials(accountId: string): Promise<boolean> {
        return true;
    }

    public async getProvider(accountId: string, mode: cdk.Mode): Promise<AWS.Credentials> {
        return new AWS.SharedIniFileCredentials({ profile: accountId });
    }
}

isAvailable

The isAvailable method, lets the provider tell CDK if it is done loading or not.
We have not yet investigated what would happen when the method returns false.

canProvideCredentials

The canProvideCredentials method should test if the provider can provide credentials for the provided accountId. Although in this example implementation we just return true, a real-world implementation should parse the ~/.aws/credentials file and see if a profile for the provided accountId exists.

getProvider

The getProvider method must return a Credentials object with the credentials for the account with the provided accountId. The mode argument allows the CDK to specify if credentials with read-only or read-write permissions are required. We have ignored the mode argument in our example implementation.

Example Plugin

The plugin itself just registers the credential provider source with the CDK.

// my-credential-provider-plugin.ts

import * as cdk from "aws-cdk";
import { MyCredentialProviderSource } from "./my-credential-provider-source";

export class MyCredentialProviderPlugin implements cdk.Plugin {
    public readonly version = "1";

    public init(host: cdk.PluginHost): void {
        host.registerCredentialProviderSource(new MyCredentialProviderSource());
    }
}

Packaging the Plugin

Since the credential provider source plugin is addressed by its package name by the CDK, we need to package the plugin.
To emphasize that the module must provide an object instance of the plugin, we show the index.ts entry point below. The complete example, including the last two essential files package.json and tsconfig.json, can be found in our git repository.
Since all code is in TypeScript, we need to build the project: npm run build

// index.ts

import { MyCredentialProviderPlugin } from "./my-credential-provider-plugin";

export = new MyCredentialProviderPlugin();

Using the Plugin

To use the plugin in your CDK project you have to install it. Without publishing your plugin to npm you can install it from your local project:

$ npm install --save-dev ../my-credential-provider

Now you can either specify the plugin as a command-line argument or configure it in the cdk.json file.

// cdk.json

{
    "app": "npx ts-node bin/demo.ts",
    "plugin": [
        "my-credential-provider"
    ]
}

Now when you execute npx cdk diff or npx cdk deploy the CDK will try to obtain the credentials from the plugin!

Now Go Build!

In the examples above we have shown you how to implement a credential provider plugin for the CDK. You can find the code for the examples in our git repository: https://github.com/binxio/cdk-plugin-example
Now, go build your own awesome plugin!

Thomas de Ruiter
Cloud Consultant with a passion for everything Cloud. Likes to automate all the things. Believes security is everyone's responsibility!
Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts