Operational Guides

Request an IAM Role

How to add a new IAM role for a service or CI/CD workflow.

Production

When You Need This

  • Your service needs an AWS role to access resources (S3, DynamoDB, Lambda, etc.)
  • Your CI/CD workflow needs a GitHub Actions OIDC role
  • You need an App Runner access or instance role

Steps

1. Create a Feature Branch

git checkout master && git pull
git checkout -b feat/iam-your-service-role

2. Define the Role

Edit global/iam/roles.tf and add your role in the appropriate section:

Service role (Lambda, App Runner, etc.):

resource "aws_iam_role" "your_service_role" {
  name        = "your-service-role-name"
  description = "Role for your service — brief purpose"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"  # or the relevant service
        }
        Action = "sts:AssumeRole"
      }
    ]
  })

  tags = {
    Name        = "your-service-role-name"
    Service     = "your-service"
    Environment = "dev"
    ManagedBy   = "terraform"
  }
}

3. Attach Policies

Edit global/iam/policies.tf to attach managed or custom policies:

resource "aws_iam_role_policy_attachment" "your_service_basic" {
  role       = aws_iam_role.your_service_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

4. Validate and Plan

task infra:validate
task infra:plan

5. Commit and Create PR

git add global/iam/roles.tf global/iam/policies.tf
git commit -m "feat(iam): add IAM role for your-service"
git push origin feat/iam-your-service-role
gh pr create --base master

Include in the PR description:

  • What the role is for
  • Which service or workflow will assume it
  • What permissions it needs and why

6. Apply After Approval

CONFIRM=yes task infra:apply

GitHub Actions OIDC Roles

GitHub Actions OIDC roles follow a three-tier trust model defined in ADR-003.

Trust Tiers

TierSubject PatternUse Case
CI (read)repo:ontopix/*:*Any branch — lint, test, build, read dependencies
Deploy (write)repo:ontopix/*:ref:refs/heads/{master,pre,dev}Deploy branches — push images, deploy code
Release (publish)repo:ontopix/*:ref:refs/tags/*Tags — publish packages, release artifacts

Some roles combine tiers: e.g. ECR Push and CodeArtifact Publish are Deploy + Release (they trust both deploy branches and tags).

Where to Add the Role

Target ServiceModuleFile
ECRglobal/ecr/iam.tf
CodeArtifactglobal/codeartifact/iam.tf
Other (S3, Lambda, CloudFront, etc.)global/iam/github_oidc_roles.tf

If the target service has its own module in this repo, the OIDC role belongs there. Otherwise, add it to global/iam/github_oidc_roles.tf.

Template: GitHub Actions OIDC Role

resource "aws_iam_role" "your_github_role" {
  name        = "GitHubActions-{Service}-{Access}Role"
  description = "Brief description of what this role allows"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          StringLike = {
            # Choose the subject pattern matching your trust tier (see table above)
            "token.actions.githubusercontent.com:sub" = "repo:ontopix/*:*"
          }
        }
      }
    ]
  })

  tags = {
    Name        = "GitHubActions-{Service}-{Access}Role"
    Environment = "global"
    ManagedBy   = "terraform"
    Purpose     = "github-actions-{tier}"  # ci, deploy, release, or deploy-release
  }
}

Important Constraints

  • Ref-based subjects only: Do not use environment: subjects in trust policies. GitHub Environments change the OIDC subject claim in ways that shift trust enforcement from AWS IAM to GitHub-side configuration. Ref-based subjects keep branch verification at the AWS level.
  • Org-wide by default: All roles use repo:ontopix/* (wildcard). Per-repo scoping can be layered on for sensitive operations.
  • Export the ARN: Add the role ARN to the module's outputs.tf and surface it in global/outputs.tf so consuming repos can reference it.

Lambda Deploy Convention

The GitHubActions-Lambda-DeployRole is designed to work with any project that follows the S3 artifact naming convention. You do not need to request a new IAM role for Lambda deployments.

What You Get Automatically

When your project creates an S3 bucket named {project}-{env}-lambda-artifacts[-suffix]:

  • S3 access: Upload and read Lambda ZIPs under the lambdas/ key prefix
  • Lambda deploy: Update function code from S3, read code metadata
  • Smoke tests: Invoke Lambda functions for post-deploy health checks
  • SSM read: Read SSM parameters for configuration drift detection

S3 Bucket Naming Convention

{project}-{env}-lambda-artifacts[-optional-suffix]

Examples:

  • maxcolchon-dev-lambda-artifacts
  • schemas-prod-lambda-artifacts-123456789012
  • myproject-pre-lambda-artifacts

S3 Key Convention

lambdas/{component}/{version}-{sha}.zip

Example: lambdas/handler-intake/1.2.0-abc1234.zip

Setup Steps

  1. Define the S3 bucket in your project's .infra/ directory following the naming convention
  2. Run terraform apply to create the bucket (developer action, per P1)
  3. Configure your deploy.yml workflow to upload ZIPs and call aws lambda update-function-code
  4. The GitHubActions-Lambda-DeployRole already has the necessary permissions — no infra PR needed

Naming Conventions

Role TypePatternExample
Service role{service}-{purpose}-{env}mcp-hello-world-apprunner-access-dev
GitHub OIDCGitHubActions-{Service}-{Access}RoleGitHubActions-ECR-PullRole
AmplifyAmplify{Purpose}AmplifyAdmin
CDKcdk-hnb659fds-{purpose}-{account}-{region}cdk-hnb659fds-cfn-exec-role-*-eu-central-1

Principles

  • Code vs Infrastructure boundary: GHA roles update code in existing resources; infrastructure lifecycle (terraform apply) is developer-only. See ADR-003 for details.
  • Least privilege: Request only the permissions your service actually needs
  • Convention over configuration: Follow naming conventions (e.g., S3 bucket naming) to leverage existing roles instead of requesting new ones
  • Descriptive names: Role names should clearly indicate their purpose
  • Proper tagging: Include Name, Service, Environment, and ManagedBy tags