1

我有一个跨账户管道在一个账户 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 密钥来加密工件,或者以任何其他方式如何在管道中访问这些工件?

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

这是我的代码片段(部署在 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
4

4 回答 4

2

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

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

于 2018-12-20T00:34:28.607 回答
0

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))}
    )
于 2019-06-17T08:31:48.870 回答
0

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

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

它肯定比这更复杂,但我绝不会运行 lambda 来更改工件的对象所有者。在 CodePipeline 中创建一个使用来自另一个 AWS 账户的资源的管道,其中包含有关您需要执行哪些操作才能使其正常工作的详细信息。

于 2021-05-07T14:38:07.663 回答
-1

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

于 2019-01-15T00:24:15.237 回答