codebuild: Race condition between CfnProject and IAM Policy for CODECONNECTIONS source authentication causes OAuthProviderException
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the bug
When creating a new `aws_codebuild.Project` with `Source.gitHub(...)` configured to use **AWS CodeConnections** (`auth: { type: 'CODECONNECTIONS', resource: connectionArn }`), CloudFormation deployment fails during `AWS::CodeBuild::Project` resource creation (`CREATE`) with the following error:
```text
Resource handler returned message: "User is not authorized to access connection arn:aws:codeconnections:us-east-1:123456789012:connection/84941bd4-6795-4871-99f6-e2e4697138da (Service: AWSCodeBuild; Status Code: 400; Error Code: OAuthProviderException; Request ID: c956eb0c-3654-499e-ac75-188f8a0508ea; Proxy: null)"
```
#### Root Cause Analysis:
1. When CloudFormation provisions an `AWS::CodeBuild::Project` resource using `CODECONNECTIONS` authentication, the AWS CodeBuild service handler validates access to the CodeConnections connection ARN **at the moment the `CreateProject` API call is executed**.
2. This validation call uses the project's Service Role (`AWS::IAM::Role`). Therefore, the Service Role must have `codeconnections:UseConnection`, `codeconnections:GetConnection`, and `codeconnections:GetConnectionToken` policies **fully attached and propagated before `AWS::CodeBuild::Project` creation starts**.
3. However, `aws-cdk-lib/aws_codebuild` creates IAM policies (such as `RoleDefaultPolicy` or custom policies attached via `addToRolePolicy`) **without generating a CloudFormation `DependsOn` dependency** on the `AWS::CodeBuild::Project` resource. In fact, if CloudWatch log group ARNs reference the `Project` resource (`Fn::Join` with `Ref: Project`), CDK introduces a dependency direction where `AWS::IAM::Policy` depends on `AWS::CodeBuild::Project`, forcing CloudFormation to attempt `CreateProject` **before** the IAM Policy is attached to the role.
4. As a result, when CloudFormation executes `CreateProject`, the IAM policy granting `codeconnections:UseConnection` is not yet active on the role. CodeBuild receives an authorization error when accessing CodeConnections and translates it into an `OAuthProviderException`.
### Regression Issue
- [ ] Select this option if this issue appears to be a regression.
### Last Known Working CDK Library Version
_No response_
### Expected Behavior
`aws-cdk-lib/aws_codebuild` should automatically establish a CloudFormation resource dependency (`DependsOn`) between `AWS::CodeBuild::Project` and any `AWS::IAM::Policy` that grants CodeConnections permissions (`codeconnections:UseConnection`, `codeconnections:GetConnectionToken`, etc.) to the project's Service Role.
CloudFormation should wait for the IAM Policy granting CodeConnections permissions to finish attaching to the Service Role BEFORE initiating creation of `AWS::CodeBuild::Project`.
### Current Behavior
CloudFormation attempts to create `AWS::CodeBuild::Project` concurrently with or before the `AWS::IAM::Policy` granting `codeconnections:UseConnection` is attached to the CodeBuild Service Role.
This results in the following deployment failure:
```text
MyStack/BuildProject/Resource (AWS::CodeBuild::Project BuildProject097C5DB7)
Resource handler returned message: "User is not authorized to access connection arn:aws:codeconnections:us-east-1:123456789012:connection/84941bd4-6795-4871-99f6-e2e4697138da (Service: AWSCodeBuild; Status Code: 400; Error Code: OAuthProviderException)"
```
### Reproduction Steps
#### ❌Bug Reproduction Code (Fails with OAuthProviderException)
In standard CDK usage, calling `project.addToRolePolicy(...)` attaches an IAM policy to the role, but CDK does not synthesize a CloudFormation `DependsOn` dependency from `AWS::CodeBuild::Project` to the `AWS::IAM::Policy`.
```typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as iam from 'aws-cdk-lib/aws-iam';
export class CodeBuildBugReproStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const connectionArn = 'arn:aws:codeconnections:us-east-1:123456789012:connection/84941bd4-6795-4871-99f6-e2e4697138da';
const gitHubSource = codebuild.Source.gitHub({
owner: 'my-org',
repo: 'my-repo',
webhook: false,
});
const project = new codebuild.Project(this, 'BuildProject', {
projectName: 'my-repo-build',
source: gitHubSource,
environment: {
buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2023_STANDARD_3_0,
computeType: codebuild.ComputeType.SMALL,
},
});
// Override Source to use CodeConnections
const cfnProject = project.node.defaultChild as codebuild.CfnProject;
const currentSource = cfnProject.source as codebuild.CfnProject.SourceProperty;
cfnProject.source = {
...currentSource,
type: 'GITHUB',
location: 'https://github.com/my-org/my-repo.git',
auth: {
type: 'CODECONNECTIONS',
resource: connectionArn,
},
};
cfnProject.triggers = undefined;
// Attach permissions via addToRolePolicy (CDK does NOT generate DependsOn on AWS::CodeBuild::Project)
project.addToRolePolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'codeconnections:UseConnection',
'codeconnections:GetConnection',
'codeconnections:GetConnectionToken',
'codestar-connections:UseConnection',
'codestar-connections:GetConnection',
'codestar-connections:GetConnectionToken',
],
resources: [connectionArn],
})
);
}
}
```
**Synthesized CloudFormation Output (Bug):**
In the synthesized CloudFormation template, `AWS::CodeBuild::Project` has **no `DependsOn` entry** pointing to the `AWS::IAM::Policy` that grants `codeconnections:UseConnection`:
```json
{
"Resources": {
"BuildProjectRoleF32A1C0B": {
"Type": "AWS::IAM::Role"
},
"BuildProjectRoleDefaultPolicy69A0E080": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": ["codeconnections:UseConnection", "codeconnections:GetConnectionToken"],
"Resource": "arn:aws:codeconnections:..."
}
]
},
"Roles": [{ "Ref": "BuildProjectRoleF32A1C0B" }]
}
},
"BuildProject8375C9A4": {
"Type": "AWS::CodeBuild::Project",
"Properties": {
"ServiceRole": { "Fn::GetAtt": ["BuildProjectRoleF32A1C0B", "Arn"] },
"Source": {
"Auth": {
"Resource": "arn:aws:codeconnections:...",
"Type": "CODECONNECTIONS"
},
"Location": "https://github.com/my-org/my-repo.git",
"Type": "GITHUB"
}
}
/* MISSING: "DependsOn": ["BuildProjectRoleDefaultPolicy69A0E080"] */
}
}
}
```
### Possible Solution
`aws_codebuild` constructs should automatically detect when `CODECONNECTIONS` / `CODESTAR_CONNECTIONS` is used and ensure `AWS::CodeBuild::Project` has an explicit `DependsOn` dependency on the `AWS::IAM::Policy` granting those connection permissions.
#### ✅ Workaround Code (Succeeds Deterministically)
By creating a dedicated `iam.Policy` and explicitly attaching `addResourceDependency`, CloudFormation waits for IAM policy propagation before creating the CodeBuild project.
```typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as iam from 'aws-cdk-lib/aws-iam';
export class CodeBuildWorkaroundStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const connectionArn = 'arn:aws:codeconnections:us-east-1:123456789012:connection/84941bd4-6795-4871-99f6-e2e4697138da';
const gitHubSource = codebuild.Source.gitHub({
owner: 'my-org',
repo: 'my-repo',
webhook: false,
});
const project = new codebuild.Project(this, 'BuildProject', {
projectName: 'my-repo-build',
source: gitHubSource,
environment: {
buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2023_STANDARD_3_0,
computeType: codebuild.ComputeType.SMALL,
},
});
const cfnProject = project.node.defaultChild as codebuild.CfnProject;
const currentSource = cfnProject.source as codebuild.CfnProject.SourceProperty;
cfnProject.source = {
...currentSource,
type: 'GITHUB',
location: 'https://github.com/my-org/my-repo.git',
auth: {
type: 'CODECONNECTIONS',
resource: connectionArn,
},
};
cfnProject.triggers = undefined;
// 1. Create dedicated IAM Policy for CodeConnections
const connPolicy = new iam.Policy(this, 'ConnPolicy', {
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'codeconnections:UseConnection',
'codeconnections:GetConnection',
'codeconnections:GetConnectionToken',
'codestar-connections:UseConnection',
'codestar-connections:GetConnection',
'codestar-connections:GetConnectionToken',
],
resources: [connectionArn],
}),
],
});
if (project.role) {
connPolicy.attachToRole(project.role);
}
// 2. Explicitly tell CloudFormation that AWS::CodeBuild::Project DEPENDS ON ConnPolicy
const cfnConnPolicy = connPolicy.node.defaultChild as cdk.CfnResource;
if (cfnConnPolicy) {
cfnProject.addResourceDependency(cfnConnPolicy);
}
}
}
```
**Synthesized CloudFormation Output (Workaround / Fixed):**
```json
"BuildProject8375C9A4": {
"Type": "AWS::CodeBuild::Project",
"DependsOn": [
"ConnPolicy6421E4D7"
],
"Properties": { ... }
}
```
### Additional Information/Context
_No response_
### AWS CDK Library version (aws-cdk-lib)
2.263.0
### AWS CDK CLI version
2.1133.0
### Node.js Version
v26.6.0
### OS
macOS 26
### Language
TypeScript
### Language Version
TypeScript (5.9.3)
### Other information
This behavior affects any AWS CodeBuild project relying on AWS CodeConnections / CodeStar Connections without manual CloudFormation `DependsOn` dependency overrides.
Contributor guide
Research direction
Start in the aws_codebuild Project construct and trace how its AWS::CodeBuild::Project and service-role IAM policies are synthesized. Synthesize the provided CodeConnections reproduction and inspect the CloudFormation dependency graph; done means the project explicitly waits for the relevant policy and regression coverage verifies the dependency without breaking ordinary projects.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100