CodePipeline cross-account and cross-region deployments sit at the centre of every realistic enterprise CI/CD architecture, and DOP-C02 tests them harder than any other certification. The exam is rarely happy with a single-account, single-region pipeline. Instead it places you in a scenario with a tooling account hosting CodePipeline, a development account producing artifacts, a staging account approving releases, and a production account deployed across two regions for disaster recovery — and asks which combination of artifact buckets, KMS keys, IAM roles, and approval gates safely shepherds a build from commit to global rollout.
This guide focuses exclusively on the Pro-level mechanics: which IAM role does which action in CodePipeline, how KMS grants enable artifact decryption across accounts, why the artifact bucket must exist in every region the pipeline touches, when to use CloudFormation StackSet actions versus per-region deploy actions, and how to layer manual approval to satisfy change-management policy without breaking automation. By the end you should be able to look at any CodePipeline cross-account question and recognise the missing piece — almost always either a KMS key policy, a bucket policy, or an AssumeRole trust relationship — within thirty seconds.
Why Cross-Account Cross-Region Pipelines Dominate DOP-C02
Cross-account pipelines are not an academic optimisation. They are how every non-trivial AWS organisation enforces separation of duties: build runs in one account, integration tests run in another, production lives somewhere only a small group can deploy to. The exam knows this, which is why CodePipeline questions almost never describe a single-account topology. Cross-region adds the disaster-recovery dimension — the pipeline must deploy the same release to us-east-1 and eu-west-1 atomically, or else fail in a way operators can detect and rerun.
Three forces push these designs to the top of the syllabus. First, least-privilege IAM: only a pipeline service role with explicit sts:AssumeRole permission and a matching trust policy in the target account can promote artifacts across the boundary. Second, encrypted artifact transport: the artifact bucket is encrypted with a customer-managed KMS key, and every account that consumes artifacts needs an explicit grant on that key. Third, regional artifact buckets: CodePipeline does not stream artifacts across regions; it requires an artifact bucket in every region the pipeline touches and replicates objects on demand. Miss any one of these and the pipeline fails with cryptic AccessDenied errors that exam stems love to dangle as red herrings.
- Tooling account: the account that owns the CodePipeline pipeline, CodeBuild projects, and central artifact bucket; sometimes called the CI/CD account or pipeline account.
- Target account: any account into which CodePipeline deploys resources via an action role assumed across the trust boundary.
- Pipeline service role: the IAM role assumed by CodePipeline itself; controls which actions the pipeline can list, start, and stop.
- Action role: an IAM role assumed for a specific stage action, often in another account, with permissions narrowly scoped to that action.
- Artifact bucket: an S3 bucket per region holding pipeline input/output artifacts; encrypted with a customer-managed KMS key for cross-account reads.
- KMS grant: a temporary or persistent permission on a KMS key that allows another principal (often a cross-account role) to decrypt artifact objects.
- Cross-region action: a CodePipeline action whose
regionfield differs from the pipeline's home region; CodePipeline replicates input artifacts to the target region's artifact bucket. - Manual approval action: a CodePipeline action type that pauses execution until a principal with
codepipeline:PutApprovalResultpermission approves or rejects. - Reference: https://docs.aws.amazon.com/codepipeline/latest/userguide/concepts-how-it-works.html
Plain-Language Explanation: CodePipeline Cross-Account Cross-Region
Cross-account cross-region CodePipeline mechanics are abstract because too many moving parts (IAM, KMS, S3, regions) interact at once. Three analogies from different domains make the trade-offs stick.
Analogy 1: International Postal System
Think of a multinational courier moving a sealed parcel from a Tokyo warehouse to branch offices in London and Frankfurt. The artifact bucket is the regional sorting depot — Tokyo has one, London has one, Frankfurt has one. CodePipeline is the head logistics planner sitting in Tokyo, deciding which depots receive copies of the parcel and in what order. The KMS key is the wax seal on the parcel; only depots whose master key has been registered (a KMS grant) can open and verify it. The pipeline service role is the planner's badge — it lets the planner instruct depots to move parcels but does not let the planner unseal them personally. The cross-account action role is the local courier's permit in London or Frankfurt; the planner hands the permit briefly (AssumeRole) and the courier delivers the parcel to the customer (the target service like CloudFormation or CodeDeploy).
Cross-region replication is just sea freight versus air freight: when you ask Tokyo to deploy in Frankfurt, the planner ships a copy of the parcel from Tokyo's depot to Frankfurt's depot first, then dispatches the local courier. If you forget to register Frankfurt as a depot, the parcel never arrives — pipeline fails with Artifact bucket does not exist in region.
Analogy 2: Hotel Chain Master-Key Architecture
Picture a hotel chain headquartered in New York with branches in Singapore and Dublin. The central artifact bucket is the chain's secure document vault in New York. The KMS customer-managed key is the master key; it lives in the headquarters but grants are issued to specific branch managers so they can read sealed envelopes (artifacts). When a new policy memo (the build artifact) is published, headquarters drops it into the vault, then sends couriers (cross-region replication) to deliver sealed copies to Singapore and Dublin. The branch manager (the action role) opens the envelope using a copy of the master key and acts on it (creates a CloudFormation stack, runs a CodeDeploy deployment). If headquarters revokes the branch manager's grant, the envelope is undecryptable on arrival — a perfect illustration of why KMS key policy is the single most common cause of cross-account pipeline failures.
The manual approval action is the regional director's countersignature — the memo is delivered, but execution is paused until the director signs off. This maps directly to production-deployment governance.
Analogy 3: Embassy Diplomatic Pouch
A diplomatic pouch travels from one country's embassy to another, sealed and inviolable. The CodePipeline pipeline is the embassy in the home country (the tooling account). The diplomatic immunity of the pouch is the KMS encryption plus the bucket policy — only the receiving embassy with the right credentials may open it. The AssumeRole trust is the bilateral treaty: country A's pipeline has explicit permission, signed in country B's IAM, to step onto country B's soil and act on its behalf for narrow purposes. Without the treaty (trust policy), no diplomatic pouch is delivered. The cross-region replication is the secondary embassy in the target region — diplomatic pouches are not flown directly between capitals; they go through the in-country embassy first. This is precisely why CodePipeline needs an artifact bucket per region.
The postal-system analogy is best for visualising replication and KMS grants. The hotel master-key analogy maps cleanest to KMS key policy questions. The embassy analogy is the right model when the exam stem emphasises trust, manual approval, and governance. Reference: https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create-cross-account.html
Tooling Account vs Target Account Topology
The reference DOP-C02 topology is one tooling account, many target accounts, multiple regions. The tooling account hosts the pipeline definition, the central artifact bucket, the customer-managed KMS key, and the CodeBuild projects that compile and test code. Target accounts host the application infrastructure (CloudFormation stacks, ECS services, Lambda functions) and an action role per pipeline action that the tooling pipeline assumes.
Why split this way? Blast radius: if the tooling account is compromised, the attacker can push malicious artifacts but cannot directly modify production runtime if action role permissions are tight. Auditability: every cross-account AssumeRole emits a CloudTrail event in both accounts, giving a clean audit trail. Cost allocation: production resource bills land in the production account, separated from tooling. Compliance: separation-of-duties controls (PCI, SOC2) require that the team running pipelines is not the same team that authorises production changes.
Anti-pattern: putting CodePipeline directly in the production account. Convenient for a 5-person startup, but the exam will mark it wrong as soon as the scenario mentions "compliance" or "separation of duties".
Required IAM Roles and Their Boundaries
CodePipeline cross-account work is held together by four distinct IAM roles, and exam questions love to scramble which role does what.
The pipeline service role lives in the tooling account and is assumed by CodePipeline itself. Its job is to orchestrate: invoke CodeBuild, read source from CodeCommit/S3, list pipeline state, and most importantly assume action roles in target accounts. Its trust policy lists codepipeline.amazonaws.com as principal. Its permissions policy includes sts:AssumeRole on the action role ARNs in target accounts.
The action role lives in the target account and trusts the pipeline service role's ARN (not just the account). It carries narrowly scoped permissions: for a CloudFormation deploy action, it has cloudformation:CreateStack, cloudformation:UpdateStack, plus all the resource permissions the template needs. For a CodeDeploy action, it has codedeploy:CreateDeployment plus iam:PassRole for the CodeDeploy service role.
The CloudFormation execution role (or CodeDeploy service role) is a third layer — the role passed to the deploying service so it can create resources on the operator's behalf. Its trust principal is cloudformation.amazonaws.com (or codedeploy.amazonaws.com), not the pipeline.
The CodeBuild service role authorises CodeBuild to read source, write logs, and pull base images; relevant in tooling-account context but easy to confuse with the pipeline service role.
The pipeline service role lives in the tooling account and authorises CodePipeline to call AWS APIs in that account. The action role lives in the target account and authorises a single stage action. Their trust relationships are different (codepipeline.amazonaws.com vs the pipeline service role ARN). When a cross-account pipeline fails with User: arn:aws:sts::TOOLING:assumed-role/PipelineServiceRole/... is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::TARGET:role/ActionRole, ninety percent of the time the action role's trust policy is missing the pipeline service role ARN. Reference: https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create-cross-account.html
KMS Key Policy and Cross-Account Decryption
Because the artifact bucket is encrypted with a customer-managed KMS key, every account that needs to read or write artifacts must be granted KMS permissions explicitly. The default aws/s3 AWS-managed key cannot be shared cross-account, so cross-account pipelines must use a customer-managed key (CMK).
The CMK lives in the tooling account. Its key policy must grant kms:Decrypt, kms:DescribeKey, and kms:GenerateDataKey to the action roles in target accounts (or to the target account's root, with downstream IAM filtering). Failing to extend the key policy is the second-most-common cross-account misfire — the artifact arrives in the target account but the action role cannot decrypt it, and CloudTrail shows KMS.AccessDeniedException.
A clean pattern: the key policy explicitly enumerates the target-account principals that may decrypt, while the action role's IAM policy includes a kms:Decrypt allow on the same key ARN. Both sides must agree, just like cross-account S3 access.
The default aws/s3 AWS-managed KMS key cannot be modified, which means you cannot grant cross-account principals decrypt permission on it. If the exam stem says "the artifact bucket is encrypted with the default S3 KMS key", and the pipeline is cross-account, the answer is always "switch to a customer-managed CMK and update the key policy". A surprising number of candidates remember KMS but not the AWS-managed-vs-customer-managed distinction. Reference: https://docs.aws.amazon.com/codepipeline/latest/userguide/concepts-how-it-works.html
Cross-Region Action Mechanics
When you add an action whose region field differs from the pipeline's home region, CodePipeline does not stream artifacts on the fly. It requires that you create a separate artifact bucket in the target region and register it via the artifactStores map in the pipeline definition. CodePipeline replicates the input artifact from the home-region bucket to the target-region bucket before invoking the action, then runs the action against the local copy.
Consequences for design: every region the pipeline touches needs (1) an artifact bucket, (2) a KMS key (or a multi-region KMS key shared across regions), and (3) the action role's trust and permissions extended to those keys and buckets. The pipeline definition uses the artifactStores map (one entry per region) instead of the simpler single artifactStore.
Multi-region KMS keys, introduced after CodePipeline launched, simplify this: one logical key replicated to multiple regions with the same key ID, so you write the key policy once and trust the same ARN pattern everywhere. This is a frequent DOP-C02 distractor pattern — the exam asks which technique reduces operational overhead for multi-region pipelines, and multi-region KMS keys are usually the right answer.
CloudFormation StackSets vs Per-Region Deploy Actions
For multi-account multi-region deployments of infrastructure, you have two main pipeline-level options.
CloudFormation StackSet action (action provider CloudFormation, action mode CREATE_UPDATE_STACK_SET or CREATE_UPDATE_STACK_INSTANCES) lets a single pipeline action deploy the same template to many accounts and regions in one step. With service-managed permissions and AWS Organizations, you can target an entire OU, and StackSets will auto-deploy when accounts are added or removed. This is the right tool when the same template applies uniformly across the fleet — IAM baseline, Config rules, GuardDuty enablement.
Per-region deploy actions are individual CloudFormation deploy actions, one per region/account combination, often arranged as parallel actions in a single stage. This is the right tool when each region has slightly different parameters (region-specific endpoint URLs, region-specific AMI IDs, region-specific approval gates) or when you need to fan-out to a small number of targets that StackSets cannot easily express.
Choose StackSets when the deployment is uniform across many targets (more than ~5 accounts/regions) and ideally driven from AWS Organizations. Choose per-region actions when the deployment is non-uniform (different parameters, different approval gates) or when targets are few and orchestration complexity is acceptable. The exam often tests the boundary case: 4 regions in 1 account with identical templates — StackSets is overkill; 4 actions in parallel is cleaner. Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-concepts.html
Manual Approval Gates Between Stages
Manual approval is a built-in CodePipeline action type that pauses the pipeline until a principal with codepipeline:PutApprovalResult permission posts approve or reject. It is the standard mechanism for governing promotion from staging to production.
Approval actions have two key properties. First, they can include an SNS notification target — when the action enters the pending state, an email or chat notification fires automatically. Second, they accept a custom review URL, typically pointing to a dashboard, change ticket, or test report.
In a multi-account pipeline, the approval action is itself a tooling-account concern (it lives in the pipeline definition). The principal granted approval permission can be a CI/CD lead, a release manager, or a Lambda function (yes, automated approvals are possible — uncommon but legal).
Anti-pattern: chaining manual approval after deployment. Approval gates belong before the production deploy stage, never after.
End-to-End Reference Architecture
A canonical DOP-C02 cross-account pipeline assembles like this. Source stage in the tooling account pulls from CodeCommit (or GitHub via CodeStarConnections). Build stage runs a CodeBuild project that compiles, runs unit tests, and produces a build artifact stored in the home-region artifact bucket. Test stage deploys to a dev account using a CloudFormation deploy action against the dev account's action role. Approval stage pauses with SNS notification. Pre-prod stage deploys to the staging account using a parallel set of CloudFormation deploy actions across regions us-east-1 and eu-west-1, each using its own regional artifact bucket and a multi-region KMS key. Production stage uses a CloudFormation StackSet action deploying to the prod OU.
Every cross-boundary hop is held together by exactly the same primitives: action role trust policy, KMS key policy, bucket policy. Memorise the shape and any cross-account pipeline question becomes a quick triage of which primitive the stem is missing.
For every cross-account or cross-region action, verify five pieces:
- Action role exists in target account and trusts the pipeline service role's ARN.
- Pipeline service role can
sts:AssumeRolethe action role's ARN. - KMS key policy grants
kms:Decryptto the action role. - Artifact bucket policy grants
s3:GetObjectto the action role. - For cross-region actions, an artifact bucket exists in each target region and is registered in
artifactStores.
If a cross-account pipeline question mentions an AccessDenied error, mentally walk this list and the answer is almost always one of these five. Reference: https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create-cross-account.html
Pipeline Variables, Source Action Outputs, and Parameter Overrides
Pipeline variables let stages pass dynamic values forward. Source actions emit variables like CommitId, BranchName, RepositoryName that downstream actions reference using #{SourceVariables.CommitId} syntax. Build actions (CodeBuild) can emit custom variables via exported-variables in buildspec, propagated to later actions.
For CloudFormation deploy actions, parameter overrides at the pipeline level let you inject values without modifying the template — typical use is passing a region-specific configuration value or a build-tagged image URI. A canonical pattern: build action exports IMAGE_URI, deploy action references it via { "ImageUri": "#{BuildVariables.IMAGE_URI}" }.
Misusing pipeline variables is a hidden trap. They are evaluated at pipeline configuration time, not runtime, so dynamic SSM lookups inside variable expressions do not work. For runtime configuration, use SSM Parameter Store reads from inside CloudFormation templates or CodeBuild scripts.
Encryption, Audit, and CloudTrail Visibility
Every cross-account AssumeRole emits two CloudTrail events: one in the tooling account showing the pipeline service role calling sts:AssumeRole, and one in the target account showing the action role being assumed. Both events include the source identity, target role, and external context, giving a clean audit trail.
For end-to-end visibility, configure a CloudTrail organisation trail aggregating to a central log archive account. CodePipeline pipeline state changes also surface as EventBridge events; ingest them via a rule into CloudWatch Logs or SNS for near-real-time monitoring.
Always encrypt the artifact bucket with the customer-managed KMS key, enable bucket versioning, and apply a deny-on-unencrypted-PutObject bucket policy. The exam treats "artifact bucket without bucket-level encryption" as a security flaw worth points.
EventBridge Integration for Pipeline Lifecycle Events
CodePipeline emits EventBridge events for stage transitions, action successes and failures, manual approval prompts, and pipeline executions. These events drive several common automation patterns: a Lambda that posts execution summaries to Slack, a Step Functions workflow that orchestrates rollback when a production stage fails, and an SNS topic for on-call paging.
A subtle DOP-C02 pattern: production deploy failure should auto-trigger CodeDeploy rollback (configured at the deploy action level) and an EventBridge rule that creates a Systems Manager OpsItem with the failure details for the on-call engineer. Combining native rollback with EventBridge-driven incident creation is the Pro-tier expected design.
Common Trap Patterns
Several misfires recur across the exam.
Trap one: forgetting that KMS key policy and IAM policy must agree. Granting kms:Decrypt only via IAM does not work cross-account; the key policy must explicitly list the principal.
Trap two: assuming CodePipeline can stream artifacts cross-region without a target-region bucket. It cannot. The pipeline definition fails validation.
Trap three: using the default aws/s3 KMS key for the artifact bucket in a cross-account pipeline. AWS-managed keys cannot be shared cross-account.
Trap four: putting the CloudFormation execution role's trust policy on the pipeline service role. The execution role trusts cloudformation.amazonaws.com, not CodePipeline.
Trap five: enabling cross-account access on the source S3 bucket but not on the artifact KMS key. Artifacts encrypted in source-account S3 are unreadable in tooling-account CodeBuild.
When the source action is S3, the source bucket and the artifact bucket are different things. The artifact bucket is created by CodePipeline (or you provide it) and holds the output of every stage. The source bucket is upstream of the pipeline. Cross-account permissions on the source bucket do not automatically propagate to the artifact bucket; both need their own bucket and KMS policies. This trips candidates who think "I gave my source bucket cross-account access, why doesn't the deploy stage work?". Reference: https://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html
常考陷阱(Common Exam Traps)
- AWS-managed KMS key in cross-account pipeline —
aws/s3cannot be shared; must switch to customer-managed CMK with explicit key policy grantingkms:Decryptto target action roles. - Action role trust policy missing pipeline service role ARN — most common cause of
AccessDenied: sts:AssumeRoleerrors; trust must list the pipeline service role, not just the tooling account root. - Missing artifact bucket in target region — cross-region actions require a regional bucket registered in
artifactStores; CodePipeline does not stream artifacts across regions. - Approval action placed after production deploy — approval gates belong before the deploy, not after; placing them after defeats the purpose of human review.
- CloudFormation execution role conflated with pipeline service role — the execution role trusts
cloudformation.amazonaws.comand is passed to CloudFormation; the pipeline service role trustscodepipeline.amazonaws.comand orchestrates the pipeline. Different trust principals, different scopes.
FAQ
Q1: Can a single pipeline deploy to both us-east-1 and eu-west-1 in parallel?
Yes — define both regional deploy actions inside the same stage with no runOrder overlap, and CodePipeline executes them concurrently. You need an artifact bucket in each region, registered in the pipeline definition's artifactStores map, plus an action role per region in the target account.
Q2: How do I rotate the customer-managed KMS key without breaking running pipelines? Enable automatic key rotation on the CMK; AWS rotates the cryptographic material annually while keeping the key ARN stable. Pipelines, bucket policies, and IAM policies referencing the ARN keep working without changes. For manual rotation (key replacement), grant access to the new key in all relevant policies before re-encrypting artifacts and deleting the old key.
Q3: What is the difference between CodePipeline's source action role and action role? The source action role authorises the pipeline to read from the source repository (CodeCommit, S3, GitHub via CodeStarConnections). The action role for downstream stages authorises the action to perform its work in the target account. Both can be cross-account; both need explicit trust policies.
Q4: When should I use CodePipeline V2 over V1 for cross-account scenarios?
CodePipeline V2 (the type-V2 pipeline) supports pipeline-level variables, conditional execution, and per-stage triggers. For cross-account cross-region work, V2's pipeline-level variables make parameter override across stages cleaner. New pipelines should default to V2 unless an existing automation depends on V1 behaviour.
Q5: How do I prevent a developer from approving their own change in a manual approval action?
Bind the codepipeline:PutApprovalResult permission to a role distinct from the developer roles. Optionally enforce aws:PrincipalTag/Role: ReleaseManager in the action's resource policy. For stricter enforcement, use AWS Identity Center permission sets that exclude developers from the release manager group.
Q6: Does CloudTrail capture cross-account AssumeRole calls in both accounts?
Yes. The tooling account logs sts:AssumeRole originating from the pipeline service role; the target account logs the role being assumed (with the source ARN visible in userIdentity.sessionContext.sourceIdentity). Aggregate both via an organisation trail to a central log archive for end-to-end audit.
Q7: Can I use AWS WAF or VPC endpoints to restrict where pipeline approvals come from?
Manual approval API calls go through the global CodePipeline endpoint, not VPC endpoints (until you enable a CodePipeline VPC endpoint, which exists). Combine VPC endpoint policies with IAM condition keys like aws:SourceVpce and aws:SourceIp to restrict approval to corporate networks; this is a Pro-tier governance pattern.
Q8: How do I handle a partial failure where us-east-1 deploys but eu-west-1 fails? Two options. (1) Use CodeDeploy or CloudFormation auto-rollback at the action level so the failed region rolls back automatically. (2) Add an EventBridge rule that watches for action failure events and triggers a Step Functions state machine to roll back the previously successful region. The exam favours auto-rollback for simplicity unless the scenario explicitly demands cross-region orchestration.