Skip to content

IDToken - AWS

AWS supports federation with external identity providers. Using this, you can allow identities managed by an external identity provider to perform actions in your AWS account.

In this scenario the external identity provider is Concourse and the identities are teams/pipelines/jobs. This means you are able to grant a specific pipeline or job permission to perform actions in AWS (like deploying something), all without managing IAM users or dealing with long-lived credentials.

Create OIDC Provider

First you need to create an OpenID Connect identity provider in your AWS Account:

  1. Set url to the external URL of your Concourse server (or the --oidc-issuer-url if you're using a separate OIDC issuer - see Configuring a Separate OIDC Issuer).
  2. For client_id_list, you can choose any string you like, but using a value like sts.amazonaws.com is recommended.

Note

You have to use the same string later in the configuration of your idtoken var source.

terraform {
  required_providers {
    tls = {
      source  = "hashicorp/tls"
      version = "4.2.1"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "6.36.0"
    }
  }
}

variable "aws_concourse_url" {
  description = "Concourse URL"
  type        = string

  default = "https://ci.concourse-ci.org"
}

data "tls_certificate" "root_certificate" {
  url = var.aws_concourse_url
}

resource "aws_iam_openid_connect_provider" "oidc_provider" {
  url = var.aws_concourse_url

  client_id_list = [
    "sts.amazonaws.com"
  ]

  thumbprint_list = [
    data.tls_certificate.root_certificate.certificates[0].sha1_fingerprint
  ]
}

Allow Role Assumption

Next you will need to create an IAM-Role that can be assumed using your JWT.

  1. Create a policy document that has allows for the Provider to call sts:AssumeRoleWithWebIdentity
  2. Add a condition on the sub-claim with type StringEquals and value main/deploy-to-aws
  3. Add a condition on the audience with type StringEquals and value sts.amazonaws.com

Info

This will allow ONLY that specific pipeline (and any instanced versions of it) to assume that IAM Role using a JWT.

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    sid     = "ConcourseOIDCWebIdentity"
    effect  = "Allow"
    actions = [
      "sts:AssumeRoleWithWebIdentity"
    ]

    principals {
      type        = "Federated"
      identifiers = [
        aws_iam_openid_connect_provider.oidc_provider.arn
      ]
    }

    condition {
      test     = "StringEquals"
      variable = "${aws_iam_openid_connect_provider.oidc_provider.arn}:aud"
      values   = [
        "sts.amazonaws.com"
      ]
    }

    condition {
      test     = "StringEquals"
      variable = "${aws_iam_openid_connect_provider.oidc_provider.arn}:sub"
      values   = [
        "main/deploy-to-aws"
      ]
    }
  }
}

resource "aws_iam_role" "role" {
  name               = "s3_manager"
  assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
}

Allow for an Action

Now, assign the assumed role a policy that the Pipeline can use.

data "aws_iam_policy_document" "manage_s3" {
  statement {
    sid    = "ManageS3"
    effect = "Allow"

    actions = [
      "s3:*"
    ]

    resources = [
      "*"
    ]
  }
}

resource "aws_iam_policy" "assumed_policy" {
  name   = "s3_manager_policy"
  policy = data.aws_iam_policy_document.manage_s3.json
}

resource "aws_iam_role_policy_attachment" "role_policy_attachment" {
  role       = aws_iam_role.role.name
  policy_arn = aws_iam_policy.assumed_policy.arn
}

Use within the Pipeline

Now you can use the AWS AssumeRoleWithWebIdentity API operation to assume your role via a JWT issued by Concourse.

The easiest way is to do this is via the assume-role-with-web-identity AWS CLI command:

var_sources:
  - name: awstoken
    type: idtoken
    config:
      audience:
        - "sts.amazonaws.com"

jobs:
  - name: aws-login
    plan:
      - task: print
        config:
          platform: linux
          image_resource:
            type: registry-image
            source:
              repository: amazon/aws-cli
          run:
            path: bash
            args:
              - -e
              - -c
              - |
                aws sts assume-role-with-web-identity \
                  --role-session-name Concourse \
                  --role-arn arn:aws:iam::<your_account>:role/s3_manager \
                  --web-identity-token ((awstoken:token)) > creds.json
                echo "Now do something with the temporary credentials in creds.json"