aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.51k stars 3.85k forks source link

[core] Assume an IAM role during build of a docker image asset #10193

Open tachyonics opened 4 years ago

tachyonics commented 4 years ago

More complex Docker-based build processes may need authentication to other AWS resources as part of building the asset. This is possible with a CodeBuild project and seems like a restriction of the current asset build process.

Use Case

Our asset build process requires pulling from CodeCommit repositories for dependencies and potentially CodeArtifact in the future.


This is a :rocket: Feature Request

eladb commented 4 years ago

Why not grant the role that runs the build these permissions? What’s the value in assuming a different role?

tachyonics commented 4 years ago

That would also be fine. Is this possible? How would you give permissions to this role and then use the role from within the build?

Currently my Dockerfile partially looks something like-

RUN yum install -y -q git aws-cli
RUN git config --global credential.helper '!aws codecommit credential-helper $@'
RUN git config --global credential.UseHttpPath true
RUN git clone https://git-codecommit.us-west-2.amazonaws.com/v1/repos/MyRepository

which produces the error-

Cloning into 'MyRepository'...
--
101 |  
102 | 'NoneType' object has no attribute 'secret_key'
103 | fatal: could not read Username for 'https://git-codecommit.us-west-2.amazonaws.com/v1/repos/MyRepository': No such device or address
tachyonics commented 3 years ago

Hi, is there any update for this?

dbadami commented 3 years ago

Hey,

I too am running into the same issue -- I got around it by creating my own asset publishing step in the CDK pipeline. I ran into two issues:

  1. The role created by the DockerImageAsset CDK construct doesn't allow one to add an IAM policy statement to it. I got around this by adding the permission myself in the console (not ideal but fine as a temporary work around). This however still didn't allow me to access the credentials in the asset image build step.
  2. During the build of the DockerImageAsset it's not possible to pass in credentials for a role. In the console (since there isn't a way to modify the buildspec.yml through CDK) I ended up modifying the buildspec.yml file so that putting the required credentials into a file and then adding a command in the DockerFile to use that file and export its contents to an environment variable.

Is there an update for this issue?

Use Case We need to access CodeArtifact during the asset build process.

rix0rrr commented 3 years ago

Seems like this role you're trying to assume would be asset-specific. Then shouldn't the CLI assume the same role while doing cdk deploy and bundling the asset there?

dbadami commented 3 years ago

How would one go about configuring the CLI to assume the role while doing the cdk deploy through CDK? The buildspec.yml for the asset publication stage is autogenerated; this is what I see for the stage in the console:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": "npm install -g cdk-assets"
    },
    "build": {
      "commands": [
        "cdk-assets --path ...
      ]
    }
  }
}

I can make changes to this to assume the required role and re-deploy but those are not captured in the CDK code and it resets to the original buildspec.yml after every deployment.

For context, I'm doing a build in my DockerFile which requires a CODEARTIFACT_TOKEN and I haven't been able to figure out how to get this token during the build of the Docker image asset through CDK. I modified the DockerFile to install the aws cli, so that I could export the token into an environment variable but was getting the following error: Unable to Locate Credentials... during the asset build stage. So the workaround I did was to modify the generated buildspec.yml to export the authorization token into a file and then copy that onto the file system of the container and export it into an environment variable. So even if I were able to assume into the required role how would I go about using those credentials when calling docker build other than exporting them as environment variables in the buildspec?

For now I ended up writing my own stage that builds the image and uploads to the required ECR repository but was hoping to be able to use the DockerImageAsset because it simplifies deploying across accounts and regions.

rix0rrr commented 3 years ago

How would one go about configuring the CLI to assume the role while doing the cdk deploy through CDK?

At this point it's not possible, but that's what I'm trying to get a sense for. Is this an "assets" feature or a "pipelines" feature?

What would you expect to happen if you did cdk deploy on a development copy of the stack?

rix0rrr commented 3 years ago

And is this purely about CodeArtifact or is there are more general need for IAM credentials inside a Docker build?

dbadami commented 3 years ago

My expectation is that the credentials profile used for the cdk deploy would be sufficient to build the asset and upload it to the required ECR repository for the DockerImageAsset. For the pipeline integration my two cents would be that this would be an assets feature because the asset is responsible for setting up the CodeBuild build project which deploys the asset, and the pipeline just runs it.

Allowing one to have IAM credentials inside of a Docker build would address the issue I'm having as well as @tachyonics (I believe) because I'd be able to use those IAM credentials to get the authorization token through the aws cli or assume a role that is able to.

dbadami commented 3 years ago

My current workaround for this to have a custom asset publication step. Each action in this step has the required permissions to assume a role which has the required permissions to build and publish the asset in the appropriate account's ECR repository.

tachyonics commented 3 years ago

Currently my use case requires credentials for CodeCommit although likely CodeArtifact in the future. My feeling is that if the Docker asset building process is going to be as flexible as using something like CodeBuild to build a Docker image, generalised access to IAM credentials will be required.

From the perspective of least privileges, I would think there should be an option for asset-specific credentials (with the ability for the CLI to assume these credentials).

tachyonics commented 3 years ago

Is this something that I could contribute to based on my understanding what what would need to be done (ie. adding the ability to pass in and assume a role in cdk-assets[1] with the credentials then passed into the docker build process) or does it require a more robust design from the core CDK team)

[1] https://github.com/aws/aws-cdk/blob/1fc1ab16476da22d8b9d5635e9612322bfff9a67/packages/cdk-assets/lib/private/handlers/container-images.ts#L66

skomp commented 3 years ago

I would like to add another use case, possibly this could be a separate ticket, feedback is welcome. So, during our DockerAssetImage build, we need to install a private pip module. While we could use CodeArtifact and auth via IAM (which ofc requires credentials) we went down the easy road first and just installed from github directly. For that we need to have a github api token at hand, which is stored in the secret manager. So we tried to inject it via build args, but the secrets do not get resolved during the asset build step (I think this refers to @tachyonics comment as well). Next thing we tried was to have a multi stage docker build and first pull the secret via aws cli and then copy it over to the actual build container, which also fails because of missing credentials. My next step would have been to try and pull the artifact from CodeArtifact, but after reading this thread, it does not make sense to try and rather go for the last resort and build the image manually, i guess.

dbadami commented 3 years ago

We pull artifacts from CodeArtifact as a part of our build process and need an authorization token for it. What we ended up doing is that in the Build stage of our CodePipeline we export the CodeArtifact authorization token as an environment variable and then copy it into the DockerFile in our code using sed. The output of the Build stage is all the files so our modified DockerFile gets sent as input to the Assets which builds the actual Docker image and publishes it to the appropriate ECR repository. This workaround allowed us to get rid of separate publishing steps in our pipeline and allowed us to rely on the DockerImageAsset.

paya-cz commented 2 years ago

Pulling from CodeCommit or CodeArtifact during multi-stage Docker build is only natural. Yet, AWS CDK does not really support this well, as is evident by #10193, #9942 and #10298.

My recent struggle was that I coudn't use AWS SDK to get CodeArtifact token, because AWS CDK does not support async/await code. The workaround I used for CodeArtifact and node.js is:

1. Generate CodeArtifact token

This must be synchronous because AWS CDK does not support async code

import { execSync } from 'child_process';

export const CodeArtifactToken = execSync(
    'aws codeartifact get-authorization-token --domain my-domain --query authorizationToken --output text --profile my-profile',
    {
        encoding: 'utf8',
    },
).trim();

2. Pass the token to Docker build

ecs.ContainerImage.fromAsset(
    path.join(__dirname, 'runtime'),
    {
        buildArgs: {
            CODEARTIFACT_TOKEN: CodeArtifactToken,
        },
        // TODO: add hashBuildArgs - https://github.com/aws/aws-cdk/issues/15936
    },
),

3. Create .npmrc file next to Dockerfile

@mycompany:registry=https://project-112233445566.d.codeartifact.region.amazonaws.com/npm/project/
//project-112233445566.d.codeartifact.region.amazonaws.com/npm/project/:always-auth=true
//project-112233445566.d.codeartifact.region.amazonaws.com/npm/project/:_authToken=${CODEARTIFACT_TOKEN}

4. Declare ARG in Dockerfile

ARG CODEARTIFACT_TOKEN
ENV CODEARTIFACT_TOKEN=${CODEARTIFACT_TOKEN}

COPY ["package.json", "package-lock.json*", ".npmrc", "./"]
RUN npm install
gnovack commented 1 year ago

Hi, is there any update on when this feature will be available? I am running into this issue as well while trying to pip install dependencies from CodeArtifact during a Docker image asset build.

The workaround described by @paya-cz works pretty well, but it leads to very slow builds, since the Docker layer cache is invalidated every time we generate a new CodeArtifact token.

flkas commented 1 year ago

Inspired by @paya-cz, I had to solve this for the combination of Typescript CDK and PIP Packages.

1. Generate CodeArtifact token

  1. --domain-owner is necessary if you do this command cross-account
  2. --duration-seconds it's recommended to limit the token time-to-live for security reasons
  3. --profile is necessary if you initially provision the CDK Pipeline (if you use it to provision your Stack) and needs to be removed afterward.
import { execSync } from 'child_process';

export const CodeArtifactToken = execSync(
    'aws codeartifact get-authorization-token --domain my-domain --query authorizationToken --domain-owner 111111111111 --output text  --duration-seconds 900  --profile my-profile',
    {
        encoding: 'utf8',
    },
).trim();

2. Pass the token to Docker build

The token will be passed as a Build Argument to the Docker Build Process

this.exampleLambda = new DockerImageFunction(this, `exampleLambda`, {
       code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', 'assets'), {
           buildArgs: {
               CODEARTIFACT_TOKEN: CodeArtifactToken,
           },
           cmd: ["example.lambda_handler"],
       }),
       vpc: vpc,
       vpcSubnets: subnets,
       securityGroups: [securityGroup],
       memorySize: 512,
       timeout: Duration.minutes(1),
       retryAttempts: 0,
       environment: {
           ENVIRONMENT: props.stage,
       }
});

3. Use it within Dockerfile

The best way to make sure everything is set up correctly, you can generate the PIP Global Config on the fly:

ARG CODEARTIFACT_TOKEN

RUN echo "[global]" > /etc/pip.conf && \
    echo "index-url = https://aws:${CODEARTIFACT_TOKEN}@MYDOMAIN-ACCOUNTID.d.codeartifact.eu-central-1.amazonaws.com/pypi/REPONAME/simple/" >> /etc/pip.conf

Important IAM Policy You would need to add this IAM policy to your CodePipeline to be able to access the CodeArtifact Repository:

new PolicyStatement({
    actions: [
        "codeartifact:GetAuthorizationToken",
        "codeartifact:GetRepositoryEndpoint",
        "codeartifact:ReadFromRepository",
        "sts:GetServiceBearerToken"
    ],
    resources: ["*"]
}),