Skip to content

Commit

Permalink
Serie GitHub Actions - OIDC - AWS (#79)
Browse files Browse the repository at this point in the history
* Serie GitHub Actions - OIDC - AWS

* Update part 2

* Update part 2

* Update part 2

* Update part 2

* typos
  • Loading branch information
npalm authored Dec 4, 2022
1 parent d4cc7d2 commit 4d09721
Show file tree
Hide file tree
Showing 7 changed files with 471 additions and 0 deletions.
Binary file added content/posts/2022-12-02-oidc-part1/cover.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
232 changes: 232 additions & 0 deletions content/posts/2022-12-02-oidc-part1/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
slug: '2022/12/02/oidc-part-1'
title: 'Are you still using keys?'
subtitle: 'Practical guide to deploy with GitHub Actions and Terraform to AWS.'
date: 2022-12-02
cover: ./cover.jpeg
coverDescription: 'Station Eindhoven Central - Glow 2022'
coverLink: 'https://goo.gl/maps/7MsYEypMR2yucPRm6'
type: post
comments: true
tags:
- aws
- cicd
- cloud
- github
- github actions
- terraform
- zero trust
authors:
- niek
---

_This blog post is the first in a series of two in to explain how you can deploy keyless with GitHub Actions to AWS. This first part covers the basics of setting up OIDC integration between GitHub Actions and AWS. In the [second](/2022/12/02/oidc-part-2) more advanced practices are explained, such as deploying using GitHub Action shareable workflows with custom OIDC claims. This blog is practical and shows how to use OIDC with GitHub Actions to deploy to AWS. The same patterns apply to deploy to other OIDC enabled clouds like Google or Azure._

<p style="text-align: right">
<a href="https://github.com/040code/blog-oidc-github-actions-aws" target="sourcecode">
<i class="fab fa-github" style="font-size: 200%">&nbsp;</i>Source code for this post</a></p>

## Deploy keyless with GitHub Actinns

Are you still using secrets to deploy from GitHub Actions to your cloud provider? Do you still define secrets in your workflow jobs and rotate them regularly? Do you know there is no need anymore to define secrets to connect to common cloud providers? You can avoid the need of using secrets when deploying with GitHub Actions to a provider supporting OpenID Connect (OIDC). New to OIDC, watch the talk ["OAauth 2.0 and OpenID Connect in plain English"](https://www.youtube.com/watch?v=996OiexHze0).

With OIDC you define a trust relation between GitHub Actions end your cloud, in our case AWS. You define a trusted relationship between your AWS account and the OIDC provider of GitHub Actions. And then define conditions under what circumstances an AWS IAM role can be assumed. New to assuming a role in AWS, watch [this two minute tutorial](https://www.youtube.com/watch?v=UOWx-dy9Q9c).

Once the integration is set up, your workflows can obtain a short-lived token. This token is allowed to assume a role to access resources in AWS, based on the policies defined for the role. This post provides you with a practical guide on how to set up the required infrastructure resources with IaC, and shows a simple example to validate the setup.

## Setup OIDC for GitHub Actions and AWS

GitHub provides detailed [documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) for authenticating Actions to AWS that nicely describes all the required steps. It is highly recommended to read the article on GitHub to get a deep understanding. Since we love to automate everything we are not setting up the required resources manually. Instead we use Terraform to automate the creation of the resources.

The example in this blog is using the repository *040code/blog-oidc-github-actions-aws*, you find a full example [here](https://github.com/040code/blog-oidc-github-actions-aws). You can run the example by cloning or forking the repository and run the Terraform code by setting the variable `repo` to your repository, e.g `owner/repo`.

### OIDC Provider

The first step is to define the OIDC provider. To define the OIDC provider we need to look-up the thumbprint for the GitHub Actions SSL certificate. The AWS web console can do this for you. The Terraform TLS provider has a data source that can do the look-up for us. Once we know the thumbprint, defining the OIDC provider is straightforward.

```hcl
data "tls_certificate" "github_actions" {
url = "https://token.actions.githubusercontent.com"
}
resource "aws_iam_openid_connect_provider" "github_actions" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = data.tls_certificate.github_actions.certificates.*.sha1_fingerprint
}
```

Now run `terrafrom apply` as a result the OIDC provider for GitHub is created in your AWS account, this is a global resource you only have to create it once per AWS account.

Next, define a role and trust relationship, this trust defines from which repository and under what condition the role can be assumed.

```hcl
resource "aws_iam_role" "github_actions" {
name = "blog"
path = "/github-actions/"
assume_role_policy = data.aws_iam_policy_document.github_actions_trusted_identity.json
}
data "aws_iam_policy_document" "github_actions_trusted_identity" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github_actions.arn]
}
condition {
test = "ForAllValues:StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = [
"sts.amazonaws.com",
"https://token.actions.githubusercontent.com"
]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:040code/blog-oidc-part-1:ref:refs/heads/main*"]
}
}
}
```

It is important that you take care defining the trust relation. One mistake, and you could open your AWS account to any repository on GitHub.

Assuming the role is allowed when all conditions are met. In general conditions are combined in a logical `and` and values in the test with a logical `or`. Ensure you are familiar with [IAM conditions](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_multi-value-conditions.html).

![iam-condition-block](iam-condition-block.png)

In the example above the `iss`, `aud` and `sub` attributes are checked in the policy. The first two are straightforward. Checking the `sub` requires care. When you accept any value in `sub` or even don't check the field. You will allow anyone access to your AWS role via GitHub Actions. And likely you don't want to open our AWS account to the whole universe!

For example, the condition below allows any Action running in the repository `040code/blog-oidc-github-actions-aws` from any ref (branch / tag) to perform the assume role operation and access the resources granted via this role.

```hcl
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:040code/blog-oidc-github-actions-aws*"]
}
```

By adding a condition like the one below, you can deny any pull request to assume the role. Similar checks can be done or allow or deny the ref, or context.

```hcl
condition {
test = "StringNotLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:040code/blog-oidc-part-1::pull_request"]
}
```

It should be clear, that you need to take care of defining the conditions for which you define the trust. In [part 2](/2022/12/02/oidc-part-2) you can read more about advanced subjects checks by customizing the `sub` field of the JWT token.

The final step is to allow access to some resources via the created role. This blog post only shows the pattern by example. Remember that it is good practice to write policies with the least privileged access needed. For simplicity we have created a quite coarse-grained role for the blog. Time to define the required Terraform resources.

- An S3 bucket (no policies, no encryption in this example)
- A policy that grants the role created before access to the bucket.

```hcl
resource "aws_s3_bucket" "blog" {
bucket = "040code-blog-oidc-github-actions-aws"
resource "aws_iam_role_policy" "s3" {
name = "s3-policy"
role = aws_iam_role.github_actions.name
policy = data.aws_iam_policy_document.s3.json
}
data "aws_iam_policy_document" "s3" {
statement {
actions = [
"s3:ListBucket",
"s3:GetObject",
"s3:PutOjbect"
]
resources = [
aws_s3_bucket.blog.arn, "${aws_s3_bucket.blog.arn}*"
]
}
}
```

For convenience add the following outputs to your terraform script. Those are handy when you create the workflow later. To apply this configuration, just run `terraform apply`


```
output "role" {
value = aws_iam_role.github_actions.arn
}
output "bucket" {
value = aws_s3_bucket.blog.name
}
```

## ✨ Test locally ✨

The role created can only be assumed via GitHub Actions, sometimes it is convenient to test from your own environment. This can be done by adding the following statement to the trust condition. After applying you can assume the role locally. Replace your the `user_id` by your user id ARN.

```hcl
statement {
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::<aws_account_id>:user/<user_id>"]
}
}
```

## Deploy without keys

Time to show this setup works, up to here no GitHub repository was needed. To test the setup, create a GitHub repository. Which can be a private or public one in any org or user space; it really does not matter. The easiest way is just to fork the example repository. Once you have created the repository, create a secret to hide you AWS Account ID. In this post we use a secret named `AWS_ACCOUNT_ID`. The ID should not be a secret but at least you make it one small step harder for an attacker.

Earlier you have defined the OIDC provider in your AWS account, this provider is used to provider AWS keys based on the JWT token provided by the GitHub OIDC provider. AWS provides an action: [`aws-actions/configure-aws-credentials`](https://github.com/aws-actions/configure-aws-credentials) to obtain an AWS STS token via OIDC. This action makes the whole process of obtaining short-lived secrets painless; you only need to add the action to your workflow and configure the role to assume. All secret handling is done by the action.

The [workflow](https://github.com/040code/blog-oidc-github-actions-aws/blob/main/.github/workflows/s3.yml) below brings it all together. The workflow shows how you can access an S3 bucket from GitHub Actions without configuring a secret.

```yaml
name: S3
on:
workflow_dispatch:
push:

jobs:
deploy:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: 16

- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions/blog
role-session-name: gh-actions
aws-region: eu-west-1

- name: deploy
run: |
npx cowsay -f ghostbusters "Running ${{ github.workflow }}" > message.txt
aws s3 cp message.txt s3://${{ github.repository_owner }}-${{ github.event.repository.name }}/${{ github.run_id }}.txt
rm message.txt
- name: check
run: |
aws s3 cp s3://${{ github.repository_owner }}-${{ github.event.repository.name }}/${{ github.run_id }}.txt result.txt
cat result.txt
```
Once you have pushed the workflow to your repository, a GitHub Action job starts running. The result shows some ascii art.
![workflow](workflow.png)
That was all, no keys needed anymore, no key rotation required. Time to stop using keys and start using OIDC. In [part 2](/2022/12/02/oidc-part-2) we discuss a more advanced use-case in which you can use OIDC to deploy with a shared workflow.
Binary file added content/posts/2022-12-02-oidc-part1/workflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added content/posts/2022-12-02-oidc-part2/cover.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added content/posts/2022-12-02-oidc-part2/deploy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4d09721

Please sign in to comment.