【问题标题】:Cross-Account AWS CodePipeline cannot access CloudFormation deploy artifacts跨账户 AWS CodePipeline 无法访问 CloudFormation 部署工件
【发布时间】:2018-12-19 14:21:10
【问题描述】:

我有一个在帐户 CI 中运行的跨帐户管道,通过 CloudFormation 在另一个帐户 DEV 中部署资源。 部署后,我将工件输出保存为 JSON 文件,并希望通过 CodeBuild 在另一个管道操作中访问它。 CodeBuild 在 DOWNLOAD_SOURCE 阶段失败,并显示以下消息:

CLIENT_ERROR: AccessDenied: Access Denied status code: 403, request id: 123456789, host id: xxxxx/yyyy/zzzz/xxxx= 主要来源和 源版本 arn:aws:s3:::my-bucket/my-pipeline/DeployArti/XcUNqOP

问题很可能是 CloudFormation 在不同帐户中执行时使用与管道本身不同的密钥加密工件。

是否可以为 CloudFormation 提供一个明确的 KMS 密钥来加密工件,或者以任何其他方式访问管道中的这些工件?

在单个帐户中执行时一切正常。

这是我的代码 sn-p(部署在 CI 帐户中):

  MyCodeBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Environment: ...
      Name: !Sub "my-codebuild"
      ServiceRole: !Ref CodeBuildRole
      EncryptionKey: !GetAtt KMSKey.Arn
      Source:
        Type: CODEPIPELINE
        BuildSpec: ...

  CrossAccountCodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: "my-pipeline"
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
      - Name: Source
        ...
      - Name: StagingDev
        Actions:
        - Name: create-stack-in-DEV-account
          InputArtifacts:
          - Name: SourceArtifact
          OutputArtifacts:
          - Name: DeployArtifact
          ActionTypeId:
            Category: Deploy
            Owner: AWS
            Version: "1"
            Provider: CloudFormation
          Configuration:
            StackName: "my-dev-stack"
            ChangeSetName: !Sub "my-changeset"
            ActionMode: CREATE_UPDATE
            Capabilities: CAPABILITY_NAMED_IAM
            # this is the artifact I want to access from the next action 
            # within this CI account pipeline
            OutputFileName: "my-DEV-output.json"   
            TemplatePath: !Sub "SourceArtifact::stack/my-stack.yml"
            RoleArn: !Sub "arn:aws:iam::${DevAccountId}:role/dev-cloudformation-role"
          RoleArn: !Sub "arn:aws:iam::${DevAccountId}:role/dev-cross-account-role"
          RunOrder: 1
        - Name: process-DEV-outputs
          InputArtifacts:
          - Name: DeployArtifact
          ActionTypeId:
            Category: Build
            Owner: AWS
            Version: "1"
            Provider: CodeBuild
          Configuration:
            ProjectName: !Ref MyCodeBuild
          RunOrder: 2
      ArtifactStore:
        Type: S3
        Location: !Ref S3ArtifactBucket
        EncryptionKey:
          Id: !GetAtt KMSKey.Arn
          Type: KMS

【问题讨论】:

    标签: amazon-web-services aws-codepipeline aws-codebuild aws-kms multiple-accounts


    【解决方案1】:

    CloudFormation 生成输出工件,对其进行压缩,然后将文件上传到 S3。 它不添加 ACL,后者授予存储桶所有者访问权限。因此,当您尝试进一步使用 CloudFormation 输出工件时,您会得到 403。

    解决方法是在 CLoudFormation 操作后立即在您的管道中再执行一项操作,例如:可以承担目标账户角色并更新对象 acl ex:bucket-owner-full-control 的 Lambda 函数。

    【讨论】:

    • 这也是假设......我害怕的是......谢谢!
    【解决方案2】:

    mockora 的回答是正确的。以下是修复问题的 Python 中的示例 Lambda 函数,您可以在跨账户 CloudFormation 部署后立即将其配置为 Invoke 操作。

    在此示例中,您将 Lambda 调用操作用户参数设置配置为您希望 Lambda 函数在远程账户中代入的角色的 ARN,以修复 S3 对象 ACL。显然,您的 Lambda 函数需要该角色的 sts:AssumeRole 权限,而远程账户角色需要管道存储桶工件的 s3:PutObjectAcl 权限。

    import os
    import logging, datetime, json
    import boto3
    from aws_xray_sdk.core import xray_recorder
    from aws_xray_sdk.core import patch_all
    
    # X-Ray
    patch_all()
    
    # Configure logging
    logging.basicConfig()
    log = logging.getLogger()
    log.setLevel(os.environ.get('LOG_LEVEL','INFO'))
    def format_json(data):
      return json.dumps(data, default=lambda d: d.isoformat() if isinstance(d, datetime.datetime) else str(d))
    
    # Boto3 Client
    client = boto3.client
    codepipeline = client('codepipeline')
    sts = client('sts')
    
    # S3 Object ACLs Handler
    def s3_acl_handler(event, context):
      log.info(f'Received event: {format_json(event)}')
      # Get Job
      jobId = event['CodePipeline.job']['id']
      jobData = event['CodePipeline.job']['data']
      # Ensure we return a success or failure result
      try:
        # Assume IAM role from user parameters
        credentials = sts.assume_role(
          RoleArn=jobData['actionConfiguration']['configuration']['UserParameters'],
          RoleSessionName='codepipeline',
          DurationSeconds=900
        )['Credentials']
        # Create S3 client from assumed role credentials
        s3 = client('s3',
          aws_access_key_id=credentials['AccessKeyId'],
          aws_secret_access_key=credentials['SecretAccessKey'],
          aws_session_token=credentials['SessionToken']
        )
        # Set S3 object ACL for each input artifact
        for inputArtifact in jobData['inputArtifacts']:
          s3.put_object_acl(
            ACL='bucket-owner-full-control',
            Bucket=inputArtifact['location']['s3Location']['bucketName'],
            Key=inputArtifact['location']['s3Location']['objectKey']
          )
        codepipeline.put_job_success_result(jobId=jobId)
      except Exception as e:
        logging.exception('An exception occurred')
        codepipeline.put_job_failure_result(
          jobId=jobId,
          failureDetails={'type': 'JobFailed','message': getattr(e, 'message', repr(e))}
        )
    

    【讨论】:

      【解决方案3】:

      几年来,我一直在使用 CodePipeline 进行跨账户部署。我什至有一个GitHub project 来简化使用组织的流程。它有几个关键要素。

      1. 确保您的 S3 存储桶使用的是 CMK,而不是默认加密密钥。
      2. 确保将对该密钥的访问权限授予要部署到的帐户。例如,当您有一个 CloudFormation 模板在与模板所在的不同账户上运行时,在该账户上使用的角色需要具有访问密钥(和 S3 存储桶)的权限。

      它肯定比这更复杂,但我绝不会运行 lambda 来更改工件的对象所有者。 Create a pipeline in CodePipeline that uses resources from another AWS account 提供了有关您需要做什么才能使其正常工作的详细信息。

      【讨论】:

        【解决方案4】:

        CloudFormation 应使用管道的工件存储定义中提供的 KMS 加密密钥:https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ArtifactStore.html#CodePipeline-Type-ArtifactStore-encryptionKey

        因此,只要您在那里为其提供自定义密钥并允许其他帐户也使用该密钥,它应该可以工作。

        本文档主要介绍了这一点:https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create-cross-account.html

        【讨论】:

        • 这已经在我发布的代码中实现了,在这种情况下不起作用...... CF 不使用管道的 KMS,也没有属性可以强制它这样做。跨度>
        • 我检查了 CloudFormation 操作的实际实现,看起来它应该使用 CodePipeline 键。我无法从您发布的 sn-p 中看出 KMSKey 策略授予了哪些 KMS 权限。我相信 S3 会返回一条拒绝访问消息,就像您看到它是否无权访问 KMS 密钥一样。
        • 授予所有权限。当我查看 S3 对象(CF 输出 - JSON)时,我没有看到预期的 KMS(正如我在所有其他工件中看到的那样)。似乎只有 CF 输出工件不能以预期的方式工作...... :-(
        • 明确一点:所有其他工件都有效。例如,CodeBuild 可以毫无问题地访问 SourceArtifact。只有 CF 部署输出 atifact 不起作用,因为管道的 KMS 显然没有被其他人使用...
        • 应该支持您的用例。我向 CloudFormation 团队提交了一份内部请求,以便为您进行调查。
        猜你喜欢
        • 2020-10-07
        • 1970-01-01
        • 2017-07-23
        • 1970-01-01
        • 1970-01-01
        • 2020-07-17
        • 2018-06-17
        • 2019-08-08
        • 1970-01-01
        相关资源
        最近更新 更多