AWS STS for gitlab-ci

While using gitlab-ci to execute the pipeline, it will need the AWS credential for gaining the ability to create resources or get resources.

However, even if using a self-hosted gitlab-ci runner machine, embedding the AWS credential in the gitlab-ci pipeline secret parameter still will be a little concerned for revealing the credential.

The better way to solve this problem is to use AWS Security Token Service (AWS STS) to create a temporary task credential with a permitted Service Role with defined resources, which will minimize the execution power for gitlab-ci runner.

For gitlab-ci, WebIdentity with OpenID Connect (OIDC) is a convenient way.

Example: create a gitlab-ci runner for execute get Caller

Setup AWS Identity Provider

AWS Role & Permissions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<YOUR_ACCOUNT_ID>:oidc-provider/<YOUR_IDENTITY_PROVIDER_NAME>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "gitlab.com:aud": "https://gitlab.com"
        },
        "StringLike": {
          "gitlab.com:sub": "project_path:<YOUR_PROJECT_OR_GROUP>*"
        }
      }
    }
  ]
}

Gitlab Pipeline

before_script:
  - mkdir -p ~/.aws
  - echo "${CI_JOB_JWT_V2}" > /tmp/web_identity_token
  - echo -e "[profile oidc]\nrole_arn=${ROLE_ARN}\nweb_identity_token_file=/tmp/web_identity_token" > ~/.aws/config
image: 
  name: amazon/aws-cli:latest
  entrypoint: [""]

assume role:
  variables:
    AWS_REGION: eu-west-1
    AWS_PROFILE: oidc
  script:
    - >
      export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
      $(aws sts assume-role-with-web-identity
      --role-arn ${ROLE_ARN}
      --role-session-name "GitLabRunner-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
      --web-identity-token $CI_JOB_JWT_V2
      --duration-seconds 3600
      --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
      --output text))
    - export AWS_ACCESS_KEY_ID="${STS[0]}"
    - export AWS_SECRET_ACCESS_KEY="${STS[1]}"
    - export AWS_SESSION_TOKEN="${STS[2]}"
    - aws sts get-caller-identity

Execution result

before_script: This section is for setup execution pipeline environment, In my example is setup aws profile for execution aws cli

${CI_JOB_JWT_V2}:

Is one of the predefined variables that gitlab creates when you execute a gitlab pipeline. It contains the JWT signed by gitlab which can be used for the identity federation with AWS and allows our pipeline runner to assume our IAM Role. You can find more information about those variables in the following link. https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

${ROLE_ARN}

I saved the ARN of the role I created in my gitlab repository variables, so the ci pipeline remains clean and can be reused without having to edit it every time I set up a new pipeline. You could also replace the variable with the actual Role ARN instead.

Alternative

Instead of creating an shared profile you could also use sts directly to retrieve the access token. Tho this increases the risk of credentials ending up in an CI log output - and that’s nothing we want to happen.

test-access:
  image:
    name: "amazon/aws-cli:latest"
    entrypoint: [""]
  script:
    - aws sts assume-role-with-web-identity --duration-seconds 900 --role-session-name "cicd" --role-arn <YOUR_ROLE_ARN> --web-identity-token "${CI_JOB_JWT_V2}"

Conclusion

its convenient to set up OIDC as identity provider to deploy AWS resources via gitlab ci. It is even possible to narrow down the access to only one gitlab repository, project or even the used tag - and you don’t have to worry about rotating keys ever again