Think of AWS accounts like two different houses:
Your CodeBuild project lives in the tools-account, but it needs to put files into an S3 bucket inside the dev-account.
By default, AWS accounts do NOT trust each other.
So you need:
AWS CodeBuild itself is just a service.
It needs an identity to act as.
That identity is:
CodeBuildRole
This is why you have this trust relationship:
{
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
This means:
"Hey AWS, the CodeBuild service is allowed to temporarily become this role."
Without this trust policy:
When the build starts:
"Can I assume CodeBuildRole?"
AWS checks the trust relationship.
If allowed:
✓ AWS gives temporary credentials
Now CodeBuild is effectively acting as:
arn:aws:iam::<tools-account-id>:role/CodeBuildRole
Now CodeBuild tries:
aws s3 sync ...
or
PutObject
GetObject
AWS now checks TWO things:
AWS checks:
"Does CodeBuildRole itself allow these S3 actions?"
Your role policy says:
{
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::bucket",
"arn:aws:s3:::bucket/*"
]
}
So:
✓ The role IS ALLOWED to attempt these actions.
But that's not enough yet.
Now AWS asks the bucket owner:
"Do YOU allow this external role from another account?"
That is your bucket policy:
{
"Principal": {
"AWS": "arn:aws:iam::<tools-account-id>:role/CodeBuildRole"
}
}
This means:
"I, the bucket owner, trust this role from the tools-account."
Now both sides agree:
| Side | Says |
|---|---|
| IAM Role | "I am allowed to access bucket" |
| Bucket | "I allow this external role" |
So access succeeds.
For cross-account access:
You usually need permission from BOTH SIDES.
Think of it like entering a private apartment:
Both must approve.
Because the bucket is in ANOTHER AWS account.
Without bucket policy:
"I have permission to access the bucket"
BUT...
The bucket owner says:
"I never allowed you."
So AWS denies access.
Even though CodeBuildRole has:
s3:GetObject
s3:PutObject
the request fails with:
AccessDenied
because cross-account resource access requires the resource owner to allow it.
Imagine:
"Can enter library"
But the library belongs to another school.
That school must ALSO say:
"We allow students from your school."
That second permission is the bucket policy.
Because CodeBuild service itself has no permissions.
AWS services don't magically get admin access.
The role is the identity CodeBuild uses.
Then CodeBuild has:
Build fails immediately.
These are DIFFERENT things.
{
"Principal": {
"Service": "codebuild.amazonaws.com"
}
}
This answers:
"Who is allowed to assume this role?"
Answer:
✓ CodeBuild service
{
"Action": [
"s3:PutObject"
]
}
This answers:
"Once someone becomes this role, what are they allowed to do?"
Answer:
✓ Access S3 bucket