AWS CodePipeline error: Cross-account pass role is not allowed

johnnywhoop picture johnnywhoop · Jan 21, 2018 · Viewed 17.7k times · Source

I am trying to create an AWS CodePipeline that deploys the production code to a separate account. The code consists of a lambda function which is setup using a sam template and cloudformation. I have it currently deploying to the same account without error. I added another stage that has a manual approval action and after approval it should deploy to the other account. It fails with the following error:

Cross-account pass role is not allowed (Service: AmazonCloudFormation; Status Code: 403; Error Code: AccessDenied; Request ID: d880bdd7-fe3f-11e7-8a8c-7dcffeae19ae)

I have a role in the production account that has a trust relationship back to the dev account that has the pipeline. I gave the pipeline role and the production role administrator policies just to make sure it was not a policy issue. I edited the pipeline using the technique in this walkthrough. I am following the walkthrough loosely since they are setting their scenario up just slightly different from what I am doing.

The deploy section in my pipeline looks like:

{
   "name": "my-stack",
   "actionTypeId": {
       "category": "Deploy",
       "owner": "AWS",
       "provider": "CloudFormation",
       "version": "1"
   },
   "runOrder": 2,
   "configuration": {
       "ActionMode": "CHANGE_SET_REPLACE",
           "Capabilities": "CAPABILITY_IAM",
       "ChangeSetName": "ProductionChangeSet",
       "RoleArn": "arn:aws:iam::000000000000:role/role-to-assume",
       "StackName": "MyProductionStack",
       "TemplatePath": "BuildArtifact::NewSamTemplate.yaml"
   },
   "outputArtifacts": [],
   "inputArtifacts": [
       {
           "name": "BuildArtifact"
      }
   ]
}

I am able to assume into the role in the production account using the console. I am not sure how passrole is different but from everything I have read it requires the same assume role trust relationship.

How can I configure IAM for cross account pipelines?

Answer

ttulka picture ttulka · Apr 12, 2018

Generally, if you want to do anything across multiple accounts you have to allow this on the both sides. This is done via role-assuming.

The pipeline distributed parts communicate via pipeline artifacts which are saved in a S3 bucket and de/encrypted with a KMS encryption key. This key must be accesible from all the accounts where the pipeline is distributed in.

key in the CI account

KMSKey:
  Type: AWS::KMS::Key
  Properties:
    EnableKeyRotation: true
    KeyPolicy:
      Version: "2012-10-17"
      Id: pipeline-kms-key
      Statement:
        - Sid: Allows admin of the key
          Effect: Allow
          Principal:
            AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
          Action: ["kms:*"]
          Resource: "*"
        - Sid: Allow use of the key from the other accounts
          Effect: Allow
          Principal:
            AWS:
              - !Sub "arn:aws:iam::${DevAccountId}:root"
              - !GetAtt CodePipelineRole.Arn
          Action:
            - kms:Encrypt
            - kms:Decrypt
            - kms:ReEncrypt*
            - kms:GenerateDataKey*
            - kms:DescribeKey
          Resource: "*"
KMSAlias:
  Type: AWS::KMS::Alias
  Properties:
    AliasName: !Sub alias/codepipeline-crossaccounts
    TargetKeyId: !Ref KMSKey

The S3 bucket must allow the access from different accounts via a policy:

pipeline stack in the CI account

S3ArtifactBucketPolicy:
  Type: AWS::S3::BucketPolicy
  Properties:
    Bucket: !Ref S3ArtifactBucket
    PolicyDocument:
      Statement:
      - Action: ["s3:*"]
        Effect: Allow
        Resource:
        - !Sub "arn:aws:s3:::${S3ArtifactBucket}"
        - !Sub "arn:aws:s3:::${S3ArtifactBucket}/*"
        Principal:
          AWS:
          - !GetAtt CodePipelineRole.Arn
          - !Sub "arn:aws:iam::${DevAccountId}:role/cross-account-role"
          - !Sub "arn:aws:iam::${DevAccountId}:role/cloudformation-role"

CodePipeline:
  Type: AWS::CodePipeline::Pipeline
  Properties:
    ArtifactStore:
      Type: S3
      Location: !Ref S3ArtifactBucket
      EncryptionKey:
        Id: !Ref KMSKey
        Type: KMS
    ...

The pipeline (CI account) has to have a permission to assume a role in the other (DEV) account:

pipeline stack in the CI account

CodePipelinePolicy:
  Type: AWS::IAM::Policy
  Properties:
     PolicyDocument:
        Statement:
          - Action: ["sts:AssumeRole"]
            Resource: !Sub "arn:aws:iam::${DevAccountId}:role/cross-account-role
            Effect: Allow
          ...

And that role has to allow to be assumed to the pipeline:

pipeline stack in the DEV account

CrossAccountRole:
  Type: AWS::IAM::Role
  Properties:
    RoleName: cross-account-role
    Path: /
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Principal:
            AWS: !Sub "arn:aws:iam::${CIAccountId}:root"
          Action: sts:AssumeRole

CrossAccountPolicy:
  Type: AWS::IAM::Policy
  Properties:
    PolicyName: CrossAccountPolicy
    PolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Action:
            - cloudformation:*
            - codebuild:*
            - s3:*
            - iam:PassRole
          Resource: "*"
        - Effect: Allow
          Action: ["kms:Decrypt", "kms:Encrypt"]
          Resource: !Ref KMSKey
    Roles: [!Ref CrossAccountRole]

The pipeline (managed and executed from the CI account) must assume a role from the other account to execute the action from within the account:

pipeline stack in the CI account

CodePipeline:
  Type: AWS::CodePipeline::Pipeline
  Properties:
    Name: pipeline
    RoleArn: !GetAtt CodePipelineRole.Arn
    Stages:
      ...
      - Name: StagingDev
      Actions:
      - Name: create-changeset
        InputArtifacts:
        - Name: BuildArtifact
        OutputArtifacts: []
        ActionTypeId:
          Category: Deploy
          Owner: AWS
          Version: "1"
          Provider: CloudFormation
        Configuration:
          StackName: app-stack-dev
          ActionMode: CHANGE_SET_REPLACE
          ChangeSetName: app-changeset-dev
          Capabilities: CAPABILITY_NAMED_IAM
          TemplatePath: "BuildArtifact::template.yml"
          RoleArn: !Sub "arn:aws:iam::${DevAccountId}:role/cloudformation-role"  # the action will be executed with this role
        RoleArn: !Sub "arn:aws:iam::${DevAccountId}:role/cross-account-role" # the pipeline assume this role to execute this action
  ...

The code above shows how to execute a CloudFormation action in a different account, the approach is the same for different actions like CodeBuild or CodeDeploy.

There is a nice sample https://github.com/awslabs/aws-refarch-cross-account-pipeline from AWS team.

Another example is here https://github.com/adcreare/cloudformation/tree/master/code-pipeline-cross-account

Or you can take a look at my whole working code here https://github.com/ttulka/aws-samples/tree/master/cross-account-pipeline