Introduction
GitHub Actions workflows deploy applications to cloud providers (AWS, GCP, Azure) using credentials stored as repository secrets. When these credentials lack the necessary IAM permissions, the deployment step fails with permission denied errors. This is common after cloud provider IAM policy changes, credential rotation, or when deploying to new environments with different permission requirements.
Symptoms
- Deployment step fails with
AccessDeniedorForbiddenerrors - Cloud provider CLI shows permission error for specific API actions
- Workflow works for staging but fails for production deployment
- Error message:
User: arn:aws:iam::123456:user/github-actions is not authorized to perform: ecs:UpdateService - Error message:
Error: googleapi: Error 403: The caller does not have permission
Common Causes
- IAM role or user lacks permissions for the specific deployment action
- Credential scope restricted to certain resources that do not include the target
- OIDC trust policy misconfigured, preventing GitHub from assuming the role
- Deployment to a new region or resource type not covered by existing IAM policy
- Cross-account deployment without proper role chaining configuration
Step-by-Step Fix
- 1.Identify the missing permission from the error message: Find the denied action.
- 2.
` - 3.# From the error message:
- 4.# "not authorized to perform: ecs:UpdateService"
- 5.# This tells us exactly which IAM action is missing
- 6.
` - 7.Verify the current IAM permissions: Check what the credentials can do.
- 8.```bash
- 9.# AWS: Simulate the policy to check permissions
- 10.aws iam simulate-principal-policy \
- 11.--policy-source-arn arn:aws:iam::123456:user/github-actions \
- 12.--action-names ecs:UpdateService ecs:DescribeServices
- 13.
` - 14.Update the IAM policy with the missing permissions: Grant the required access.
- 15.```json
- 16.{
- 17."Version": "2012-10-17",
- 18."Statement": [
- 19.{
- 20."Effect": "Allow",
- 21."Action": [
- 22."ecs:UpdateService",
- 23."ecs:DescribeServices",
- 24."ecs:DescribeTaskDefinition"
- 25.],
- 26."Resource": "arn:aws:ecs:us-east-1:123456:service/my-cluster/*"
- 27.}
- 28.]
- 29.}
- 30.
` - 31.If using OIDC, verify the trust policy: Ensure GitHub can assume the role.
- 32.```json
- 33.{
- 34."Version": "2012-10-17",
- 35."Statement": [
- 36.{
- 37."Effect": "Allow",
- 38."Principal": {
- 39."Federated": "arn:aws:iam::123456:oidc-provider/token.actions.githubusercontent.com"
- 40.},
- 41."Action": "sts:AssumeRoleWithWebIdentity",
- 42."Condition": {
- 43."StringEquals": {
- 44."token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
- 45.},
- 46."StringLike": {
- 47."token.actions.githubusercontent.com:sub": "repo:owner/repo:*"
- 48.}
- 49.}
- 50.}
- 51.]
- 52.}
- 53.
` - 54.Test the deployment with updated permissions: Verify the fix works.
- 55.```yaml
- 56.- name: Deploy to production
- 57.run: |
- 58.aws ecs update-service --cluster prod --service my-app --force-new-deployment
- 59.
`
Prevention
- Use OIDC authentication instead of long-lived access keys for cloud provider access
- Apply the principle of least privilege -- grant only the specific actions needed
- Test deployment permissions in a staging environment that mirrors production IAM policies
- Document required IAM permissions for each deployment target in the repository
- Implement permission checks as a pre-deployment step in the workflow
- Regularly audit and rotate cloud provider credentials and IAM policies