Jaygee Lastname
Jaygee Lastname

Reputation: 11

Docker build secrets in CodePipeline stages in CDK

I am using a private Github repo in my CodePipeline build, as our Docker container involves using poetry install to pull dependencies during the creation of the image that gets uploaded to ECR.

I am also using a Docker build secret to "inject" the private key required to read the git repo during this step.

This all works correctly with a local build of the docker image; if I specify the --secret on the docker build commandline, I get a healthy image out.

But I am struggling to understand how to set up the CodePipeline steps in CDK such that I can:

  1. retrieve the private key from an AWS secret (using a ShellStep for this)
  2. cat this secret into a file in the step's FileSet
  3. Get this file "within reach" of the DockerImageAsset so that Docker's --secret command doesn't fail to find the file.

My attempt involved a pipeline Stage whose only stack is producing the DockerImageAsset, and when adding this stage to the pipeline, I specify a pre step, which is the ShellStep that retrieves the AWS secret.

This is the snippet that adds the docker stage and secret-retrieval step to the pipeline:

  addArtifactBuildStage(props: SelfUpdatingPipelineStackProps): DockerBuildStage {
    const pathToReaderSecret = './secret.dat'

    const getPrivkeyStep = new pipelines.ShellStep('extractPrivkeyStep', {
      commands: [
        `aws secretsmanager get-secret-value --secret-id ${privkeySecretName} >${pathToReaderSecret}`,
      ],
    })
    getPrivkeyStep.addOutputDirectory('.')

    const dockerBuildStage = new DockerBuildStage(this, 'BuildDeployableArtifactStage', {
      env: props.env!,
      apiSourcePath: this.apiSourcePath,
      pathToReaderSecret,
    })

    this.codePipeline.addStage(dockerBuildStage, {
      stackSteps: [
        {
          pre: [getPrivkeyStep],
          stack: dockerBuildStage.artifactStack,
        },
      ],
    })

    return dockerBuildStage
  }

And here is the docker stage:

export interface DockerBuildStageProps extends cdk.StageProps {
  env: cdk.Environment
  apiSourcePath: string
  pathToReaderSecret: string
}

export class DockerBuildStage extends cdk.Stage {
  readonly artifactStack: DeployableArtifactStack

  constructor(scope: Construct, id: string, props: DockerBuildStageProps) {
    super(scope, id, {
      ...props,
      stageName: 'Api-BuildFetch',
    })

    const pathToApiTaskDockerfile = `${props.apiSourcePath}/Dockerfile`
    const { base: file, dir: directory } = parse(pathToApiTaskDockerfile)
    const { pathToReaderSecret } = props

    this.artifactStack = new DeployableArtifactStack(this, 'DeployedArtifactStack', {
      stackName: `Api-DeployedArtifact`,
      file,
      directory,
      pathToReaderSecret,
    })
  }
}

Finally, the DeployableArtifactStack:

export interface DeployableArtifactProps extends StackProps {
  directory: string
  file: string
  pathToReaderSecret: string
}

export class DeployableArtifactStack extends Stack {
  readonly dockerImageAsset: DockerImageAsset

  constructor(scope: Construct, id: string, props: DeployableArtifactProps) {
    super(scope, id, props)
    
    const { directory, file, pathToReaderSecret } = props
    this.dockerImageAsset = new DockerImageAsset(this, 'DockerImageAsset', {
      directory,
      file,
      platform: Platform.LINUX_AMD64,
      networkMode: NetworkMode.HOST,
      buildSecrets: {
        readerkey: DockerBuildSecret.fromSrc(pathToReaderSecret),
      },
    })
  }
}

Upvotes: 0

Views: 288

Answers (0)

Related Questions