Skip to main content

Setup a Delegated Repository

note

Automatic vending of delegated repositories by Account Factory is an Enterprise-only feature.

If you are an Enterprise customer, Account Factory will automatically provision delegated repositories for you, and you may not need to follow the steps in this guide. This guide is intended for customers who want to manually set up delegated repositories or understand how the process operates within Pipelines.

Introduction

Infrastructure management delegation is a key feature in DevOps Foundations. To learn more about delegated repositories, click here.

Delegating infrastructure management might be necessary for reasons such as:

  • Allowing a separate team to independently manage infrastructure relevant to a specific account.

  • Enabling a GitHub Actions workflow in a repository to make restricted changes to infrastructure in a specific account.

    For example, a repository with application code may need to build and push a container image to AWS ECR before deploying it to a Kubernetes cluster.

The following guide assumes you have completed the Pipelines Setup & Installation.

Step 1 - Verify the delegated account setup

Ensure the target account is prepared for delegation with the following:

  1. The account is created in AWS.
  2. An OIDC provider is configured in the account.
  3. The account includes the following roles:
    • infrastructure-live-access-control-plan
    • infrastructure-live-access-control-apply

These roles should already exist if the account was provisioned through Account Factory.

For more details, refer to GitHub OIDC documentation.

Step 2 - Confirm the infrastructure-live-access-control repository setup

The infrastructure-live-access-control repository is an optional but recommended component of DevOps Foundations for delegating access to infrastructure.

If this repository is not already set up, you can provision it using the steps in the infrastructure-live-root-template.

This repository will serve as the control point for managing IAM access for your delegated repository.

Step 3 - Provision the delegated role

To create a role for the delegated repository, add it to the infrastructure-live-access-control repository.

tip

CI roles for Pipelines are typically created in pairs: one for the plan stage and another for the apply stage. This structure limits permissions, granting read-only access during the plan stage.

For tasks such as pushing a container image to ECR, you might only need a single role.

Use Terragrunt Scaffold to create the new role in the infrastructure-live-access-control repository.

# Assuming your `infrastructure-live-access-control` repository is named exactly that,
# and the account you want to provision your new role in is called `acme`.
mkdir acme/_global/ecr-push-role
cd acme/_global/ecr-push-role
terragrunt scaffold 'git@github.com:gruntwork-io/terraform-aws-security.git//modules/github-actions-iam-role?ref=v0.73.2'

This will create a placeholder terragrunt.hcl file for a new role in your repository, which you can modify to suit your specific requirements.

Alternatively, you can use the example configuration below:

note

Pay attention to the allowed_sources value. This field should specify the organization, name, and ref of the repository being delegated to.

If you want to allow all refs in a repository to assume this role, you can set the value to ["*"].

terraform {
source = "git@github.com:gruntwork-io/terraform-aws-security.git//modules/github-actions-iam-role?ref=v0.73.2"
}

# Include the root `terragrunt.hcl` configuration, which has settings common across all environments & components.
include "root" {
path = find_in_parent_folders()
}

# Incorporate the component configuration. This includes settings that are shared across all environments for the component.
include "envcommon"
{
path = "${dirname(find_in_parent_folders("common.hcl"))}/_envcommon/landingzone/delegated-pipelines-plan-role.hcl"
merge_strategy = "deep"
}

inputs = {
github_actions_openid_connect_provider_arn = "arn:aws:iam::${get_aws_account_id()}:oidc-provider/token.actions.githubusercontent.com"
github_actions_openid_connect_provider_url = "https://token.actions.githubusercontent.com"

# ----------------------------------------------------------------------------------------------------------------
# This defines the map of repositories to refs that are permitted to assume this role.
#
# For a plan role, additional permissions are generally limited to read access, enabling Terragrunt
# to access the existing state of provisioned infrastructure. This ensures that a plan of proposed updates can be generated.
#
# Note that all refs are permitted to assume this role since plan roles are typically assumed by refs
# used as sources for pull requests. Ensure permissions are assigned with this in mind.
#
# Refer to the documentation on least privilege for further details.

# ----------------------------------------------------------------------------------------------------------------

allowed_sources = {
"<ORGANIZATION>/<REPO>" : ["<REF>"]
}

# ----------------------------------------------------------------------------------------------------------------
# Least privilege is a critical best practice but can be challenging to implement effectively.
#
# The `envcommon` include above provides the foundational permissions necessary to interact with Terraform state.
# Additional permissions, however, must be defined by the user based on the specific needs of a given workflow.
#
# The permissions should be continuously refined by iteratively granting additional access as workflows in CI evolve
# and then removing excess permissions through regular reviews.
#
# A typical approach to refining permissions involves running a pipeline with an initial guess of required permissions
# (or none at all), reviewing any access denied errors, and then adding only the permissions necessary to enable
# successful execution of the pipeline.
#
# Over time, as workload patterns stabilize, this repository will serve as a reference for permissions needed to
# support similar workflows, streamlining the process for future updates.
# ----------------------------------------------------------------------------------------------------------------

iam_policy = {
# Role workload permissions go here
}
}

Take note of the envcommon include, which incorporates the recommended baseline configurations for delegated roles within DevOps Foundations.

You will probably need to extend the iam_policy block to define permissions tailored to your specific workflow requirements.

For instance, if you require permissions to push to ECR, you might include the following:

iam_policy = {
"ECRPushPermissions" = {
effect = "Allow"
actions = [
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:BatchGetImage"
]
resources = "arn:aws:ecr:region:${<ACCOUNT_ID>}:repository/<REPOSITORY_NAME>"
},
"ECRAuthorizationToken" = {
effect = "Allow",
actions = ["ecr:GetAuthorizationToken"]
resources = ["*"]
}
}

Step 4 - Apply the role

Once you’ve customized the role configuration, create a pull request in the infrastructure-live-access-control repository. Review and approval of the pull request will ensure the role is applied to the AWS account.

git add .
git commit -m "feat: Add ECR push role for acme account"
git push
gh pr create --base main --title "feat: Add ECR push role for acme account" --body "This PR adds the ECR push role for the acme account."

Inspect the pull request thoroughly, review the associated plan output to confirm the role configuration aligns with your requirements, and merge the pull request to apply the changes.

Step 5 - Configure the delegated repository

The configuration of the delegated repository depends on the specific tasks it needs to perform during CI/CD workflows. For basic setups, the GitHub Actions workflow can include a file like the following placed in .github/workflows/ci.yml:

name: CI
on: [push]

permissions:
id-token: write
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:ecr:<REGION>:<ACCOUNT_ID>:repository/<REPOSITORY_NAME>
role-session-name: acme-ecr-push
aws-region: <AWS_REGION>

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build, tag, and push docker image to Amazon ECR
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
REPOSITORY: <IMAGE_NAME>
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG