Skip to content

Commit 4d09721

Browse files
authored
Serie GitHub Actions - OIDC - AWS (#79)
* Serie GitHub Actions - OIDC - AWS * Update part 2 * Update part 2 * Update part 2 * Update part 2 * typos
1 parent d4cc7d2 commit 4d09721

File tree

7 files changed

+471
-0
lines changed

7 files changed

+471
-0
lines changed

Diff for: content/posts/2022-12-02-oidc-part1/cover.jpeg

620 KB
Loading
7.61 KB
Loading

Diff for: content/posts/2022-12-02-oidc-part1/index.md

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
slug: '2022/12/02/oidc-part-1'
3+
title: 'Are you still using keys?'
4+
subtitle: 'Practical guide to deploy with GitHub Actions and Terraform to AWS.'
5+
date: 2022-12-02
6+
cover: ./cover.jpeg
7+
coverDescription: 'Station Eindhoven Central - Glow 2022'
8+
coverLink: 'https://goo.gl/maps/7MsYEypMR2yucPRm6'
9+
type: post
10+
comments: true
11+
tags:
12+
- aws
13+
- cicd
14+
- cloud
15+
- github
16+
- github actions
17+
- terraform
18+
- zero trust
19+
authors:
20+
- niek
21+
---
22+
23+
_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._
24+
25+
<p style="text-align: right">
26+
<a href="https://github.com/040code/blog-oidc-github-actions-aws" target="sourcecode">
27+
<i class="fab fa-github" style="font-size: 200%">&nbsp;</i>Source code for this post</a></p>
28+
29+
## Deploy keyless with GitHub Actinns
30+
31+
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).
32+
33+
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).
34+
35+
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.
36+
37+
## Setup OIDC for GitHub Actions and AWS
38+
39+
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.
40+
41+
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`.
42+
43+
### OIDC Provider
44+
45+
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.
46+
47+
```hcl
48+
data "tls_certificate" "github_actions" {
49+
url = "https://token.actions.githubusercontent.com"
50+
}
51+
52+
resource "aws_iam_openid_connect_provider" "github_actions" {
53+
url = "https://token.actions.githubusercontent.com"
54+
client_id_list = ["sts.amazonaws.com"]
55+
thumbprint_list = data.tls_certificate.github_actions.certificates.*.sha1_fingerprint
56+
}
57+
```
58+
59+
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.
60+
61+
Next, define a role and trust relationship, this trust defines from which repository and under what condition the role can be assumed.
62+
63+
```hcl
64+
resource "aws_iam_role" "github_actions" {
65+
name = "blog"
66+
path = "/github-actions/"
67+
assume_role_policy = data.aws_iam_policy_document.github_actions_trusted_identity.json
68+
}
69+
70+
data "aws_iam_policy_document" "github_actions_trusted_identity" {
71+
statement {
72+
actions = ["sts:AssumeRoleWithWebIdentity"]
73+
principals {
74+
type = "Federated"
75+
identifiers = [aws_iam_openid_connect_provider.github_actions.arn]
76+
}
77+
78+
condition {
79+
test = "ForAllValues:StringEquals"
80+
variable = "token.actions.githubusercontent.com:aud"
81+
values = [
82+
"sts.amazonaws.com",
83+
"https://token.actions.githubusercontent.com"
84+
]
85+
}
86+
87+
condition {
88+
test = "StringLike"
89+
variable = "token.actions.githubusercontent.com:sub"
90+
values = ["repo:040code/blog-oidc-part-1:ref:refs/heads/main*"]
91+
}
92+
}
93+
}
94+
```
95+
96+
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.
97+
98+
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).
99+
100+
![iam-condition-block](iam-condition-block.png)
101+
102+
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!
103+
104+
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.
105+
106+
```hcl
107+
condition {
108+
test = "StringLike"
109+
variable = "token.actions.githubusercontent.com:sub"
110+
values = ["repo:040code/blog-oidc-github-actions-aws*"]
111+
}
112+
```
113+
114+
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.
115+
116+
```hcl
117+
condition {
118+
test = "StringNotLike"
119+
variable = "token.actions.githubusercontent.com:sub"
120+
values = ["repo:040code/blog-oidc-part-1::pull_request"]
121+
}
122+
```
123+
124+
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.
125+
126+
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.
127+
128+
- An S3 bucket (no policies, no encryption in this example)
129+
- A policy that grants the role created before access to the bucket.
130+
131+
```hcl
132+
resource "aws_s3_bucket" "blog" {
133+
bucket = "040code-blog-oidc-github-actions-aws"
134+
135+
resource "aws_iam_role_policy" "s3" {
136+
name = "s3-policy"
137+
role = aws_iam_role.github_actions.name
138+
policy = data.aws_iam_policy_document.s3.json
139+
}
140+
141+
data "aws_iam_policy_document" "s3" {
142+
statement {
143+
actions = [
144+
"s3:ListBucket",
145+
"s3:GetObject",
146+
"s3:PutOjbect"
147+
]
148+
149+
resources = [
150+
aws_s3_bucket.blog.arn, "${aws_s3_bucket.blog.arn}*"
151+
]
152+
}
153+
}
154+
```
155+
156+
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`
157+
158+
159+
```
160+
161+
output "role" {
162+
value = aws_iam_role.github_actions.arn
163+
}
164+
165+
output "bucket" {
166+
value = aws_s3_bucket.blog.name
167+
}
168+
```
169+
170+
## ✨ Test locally ✨
171+
172+
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.
173+
174+
```hcl
175+
statement {
176+
actions = ["sts:AssumeRole"]
177+
principals {
178+
type = "AWS"
179+
identifiers = ["arn:aws:iam::<aws_account_id>:user/<user_id>"]
180+
}
181+
}
182+
```
183+
184+
## Deploy without keys
185+
186+
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.
187+
188+
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.
189+
190+
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.
191+
192+
```yaml
193+
name: S3
194+
on:
195+
workflow_dispatch:
196+
push:
197+
198+
jobs:
199+
deploy:
200+
permissions:
201+
id-token: write
202+
runs-on: ubuntu-latest
203+
steps:
204+
- uses: actions/setup-node@v3
205+
with:
206+
node-version: 16
207+
208+
- name: configure aws credentials
209+
uses: aws-actions/configure-aws-credentials@v1-node16
210+
with:
211+
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions/blog
212+
role-session-name: gh-actions
213+
aws-region: eu-west-1
214+
215+
- name: deploy
216+
run: |
217+
npx cowsay -f ghostbusters "Running ${{ github.workflow }}" > message.txt
218+
aws s3 cp message.txt s3://${{ github.repository_owner }}-${{ github.event.repository.name }}/${{ github.run_id }}.txt
219+
rm message.txt
220+
221+
- name: check
222+
run: |
223+
aws s3 cp s3://${{ github.repository_owner }}-${{ github.event.repository.name }}/${{ github.run_id }}.txt result.txt
224+
cat result.txt
225+
226+
```
227+
228+
Once you have pushed the workflow to your repository, a GitHub Action job starts running. The result shows some ascii art.
229+
230+
![workflow](workflow.png)
231+
232+
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.

Diff for: content/posts/2022-12-02-oidc-part1/workflow.png

59.5 KB
Loading

Diff for: content/posts/2022-12-02-oidc-part2/cover.jpeg

414 KB
Loading

Diff for: content/posts/2022-12-02-oidc-part2/deploy.png

37.9 KB
Loading

0 commit comments

Comments
 (0)