Skip to content

Commit

Permalink
feat: Terraform module to setup OIDC for AWS and GitHub (#2)
Browse files Browse the repository at this point in the history
* feat: Terraform module to setup OIDC for AWS and GitHub (initial version)

* chore: update terraform validate workflow

* chore: fix ci

* chore: fix docs

* chore: review comments

* chore: review comments

* chore: review comments

* chore: editor config

* chore: add vscode suggestions

* refactor: removed managed variable

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

Co-authored-by: Scott Guymer <[email protected]>
  • Loading branch information
npalm and ScottGuymer authored Feb 24, 2022
1 parent 5f9713e commit 6b44784
Show file tree
Hide file tree
Showing 32 changed files with 866 additions and 20 deletions.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[*]
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true

[*.md]
trim_trailing_whitespace = true
58 changes: 38 additions & 20 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,59 @@
name: Terraform code check
name: "Terraform checks"
on:
push:
branches:
- main
pull_request:
paths-ignore:
- "*.md"

jobs:
validate_module:
name: Check module
verify_module:
name: Verify module
strategy:
matrix:
terraform: [1.1.6]
terraform: [1.1.6, "latest"]
runs-on: ubuntu-latest
container:
image: hashicorp/terraform:${{ matrix.terraform }}
steps:
- uses: actions/checkout@v2
- run: terraform init
- run: terraform fmt --recursive -check=true
- run: terraform validate
- name: "Checkout"
uses: actions/checkout@v2
- name: terraform init
run: terraform init -get -backend=false -input=false
- if: contains(matrix.terraform, '1.1.')
name: check terraform formatting
run: terraform fmt -recursive -check=true -write=false
- if: contains(matrix.terraform, 'latest') # check formatting for the latest release but avoid failing the build
name: check terraform formatting
run: terraform fmt -recursive -check=true -write=false
continue-on-error: true
- name: validate terraform
run: terraform validate

validate_examples:
name: Check examples
verify_examples:
name: Verify examples
strategy:
fail-fast: false
matrix:
terraform: [1.1.6]
terraform: [1.1.6, "latest"]
example: ["single-repo", "multi-repo"]
defaults:
run:
working-directory: examples/${{ matrix.example }}
runs-on: ubuntu-latest
container:
image: hashicorp/terraform:${{ matrix.terraform }}
steps:
- uses: actions/checkout@v2
- name: Validate Examples
run: |
for example in $(find examples -maxdepth 1 -mindepth 1 -type d); do
cd $example
terraform init
terraform fmt --recursive -check=true
terraform validate
cd -
done
- name: terraform init
run: terraform init -get -backend=false -input=false
- if: contains(matrix.terraform, '1.1.')
name: check terraform formatting
run: terraform fmt -recursive -check=true -write=false
- if: contains(matrix.terraform, 'latest') # check formatting for the latest release but avoid failing the build
name: check terraform formatting
run: terraform fmt -recursive -check=true -write=false
continue-on-error: true
- name: validate terraform
run: terraform validate
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

# Created by https://www.toptal.com/developers/gitignore/api/terraform
# Edit at https://www.toptal.com/developers/gitignore?templates=terraform

### Terraform ###
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
#
*.tfvars

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc

# End of https://www.toptal.com/developers/gitignore/api/terraform
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
repos:
- repo: git://github.com/antonbabenko/pre-commit-terraform
rev: v1.64.0
hooks:
- id: terraform_fmt
- id: terraform_docs
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
"editorconfig.editorconfig",
"yzhang.markdown-all-in-one",
"hashicorp.terraform"
]
}
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Contributing

When contributing we follow semantic versioning and [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) in our Pull Requests and releases. You must prefix your PR and commit name with a type as below as well as a scope to identify what has changed, e.g. `type(scope): - Description`. Releases will be calculated based on PR labels that are automatically applied based on the PR title. Therefore the PR title needs also to follow the same rules as semantic commit.

Available types to use in your PR:

- feat: A new feature
- fix: A bugfix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- build: Changes that affect the build tool or external dependencies (example scopes: gulp, broccoli, npm)
- ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
- chore: Other changes that don't modify src or test files
- revert: Reverts a previous commit

This is enforced by the workflow [`semantic_commit_check.yaml`](.github/workflows/pr-lint.yaml) which is run on every PR.

## Releasing

Releases are automatically generated based on the PR labels automatically added with [workflow](.github/workflows/release.yml)
7 changes: 7 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The MIT License (MIT) Copyright © 2022 Koninklijke Philips N.V, https://www.philips.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2 changes: 2 additions & 0 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Scott Guymer <[email protected]>
Niek Palm <[email protected]>
126 changes: 126 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Terraform module AWS OICD integration GitHub Actions

This [Terraform](https://www.terraform.io/) module manages OpenID Connect (OIDC) integration between [GitHub Actions and AWS](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services).

## Description

The module is strict on the claim checks to avoid that creating an OpenID connect integration opens your AWS account to any GitHub repo. However this strictness is not taking all the risk away. Ensure you familiarize yourself with OpenID Connect and the docs provided by GitHub and AWS. As always think about minimizing the privileges.

The module can manage the following:

- The OpenID Connect identity provider for GitHub in your AWS account.
- A role and assume role policy to check to check OIDC claims.

### Manage the OIDC identity provider

The module provides two options for creating an OpenID connect provider. The first one is for the simple case you only need to create a single role, for one repo in one AWS account. In this case you should not set the `openid_connect_provider_arn` property. The second option is using the internal `provider` module to create the OpenID Connect provider. This configuration will create the provider and output the ARN. This output can be passed to other instances of the module to setup roles for multiple repositories on the same provider.

### Manage roles for a repo

The module creates a role with an assume role policy to check the OIDC claims for the given repo. Be default the policy is set to only allow actions running on the main branch and deny pull request actions. You can choose based on your need one (or more) of the default conditions to check. Additionally, a list of conditions can be provided. The role can only be assumed when all conditions evaluate to true. The following default conditions can be set.

- `allow_main` : Allow GitHub Actions only running on the main branch.
- `allow_environment`: Allow GitHub Actions only for environments, by setting `github_environments` you can limit to a dedicated environment.
- `deny_pull_request`: Denies assuming the role for a pull request.
- `allow_all` : Allow GitHub Actions for any claim for the repository. Be careful, this allows forks as well to assume the role!


## Usages

Setup for a single repository in a single AWS account, see also the examples.

```hcl
module "oidc" {
source = "github.com/philips-labs/terraform-aws-github-oidc?ref=<version>"
repo = var.repo
role_name = "repo-s3"
}
```

Setup for multiple repositories connecting to a single AWS account, see also the examples.

```hcl
module "oidc_provider" {
source = "github.com/philips-labs/terraform-aws-github-oidc/?ref=<version>//modules/provider"
}
module "oidc_repo_s3" {
source = "github.com/philips-labs/terraform-aws-github-oidc?ref=<version>"
openid_connect_provider_arn = module.oidc_provider.openid_connect_provider.arn
repo = var.repo_s3
role_name = "repo-s3"
}
```


## Examples

The following examples are provided:

1. [Single repository](./examples/single-repo/README.md): using the module for a single repository and managing the identity provider by the same instance.
2. [Multiple repositories](./examples/multi-repo/README.md): using the module for multiple repositories and managing the identity provider in separate module instances.


<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3 |
| <a name="provider_random"></a> [random](#provider\_random) | n/a |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_oidc_provider"></a> [oidc\_provider](#module\_oidc\_provider) | ./modules/provider | n/a |

## Resources

| Name | Type |
|------|------|
| [aws_iam_role.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [random_string.random](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [aws_iam_policy_document.github_actions_assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_conditions"></a> [conditions](#input\_conditions) | (Optional) Additonal conditions for checking the OIDC claim. | <pre>list(object({<br> test = string<br> variable = string<br> values = list(string)<br> }))</pre> | `[]` | no |
| <a name="input_default_conditions"></a> [default\_conditions](#input\_default\_conditions) | (Optional) Default condtions to apply, at least one of the following is madatory: 'allow\_main', 'allow\_environment', 'deny\_pull\_request' and 'allow\_all'. | `list(string)` | <pre>[<br> "allow_main",<br> "deny_pull_request"<br>]</pre> | no |
| <a name="input_github_environments"></a> [github\_environments](#input\_github\_environments) | (Optional) Allow GitHub action to deploy to all (default) or to one of the environments in the list. | `list(string)` | <pre>[<br> "*"<br>]</pre> | no |
| <a name="input_openid_connect_provider_arn"></a> [openid\_connect\_provider\_arn](#input\_openid\_connect\_provider\_arn) | Set the openid connect provider ARN when the provider is not managed by the module. | `string` | `null` | no |
| <a name="input_repo"></a> [repo](#input\_repo) | (Optional) GitHub repository to grant access to assume a role via OIDC. When the repo is set, a role will be created. | `string` | `null` | no |
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | (Optional) role name of the created role, if not provided the `namespace` will be used. | `string` | `null` | no |
| <a name="input_role_path"></a> [role\_path](#input\_role\_path) | (Optional) Path for the created role, requires `repo` is set. | `string` | `"/github-actions/"` | no |
| <a name="input_role_permissions_boundary"></a> [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | (Optional) Boundary for the created role, requires `repo` is set. | `string` | `null` | no |
| <a name="input_thumbprint_list"></a> [thumbprint\_list](#input\_thumbprint\_list) | (Optional) A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s). | `list(string)` | <pre>[<br> "6938fd4d98bab03faadb97b34396831e3780aea1"<br>]</pre> | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_role"></a> [role](#output\_role) | The crated role that can be assumed for the configured repository. |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

## Contribution

We welcome contribution, please checkout the [contribution guide](CONTRIBUTING.md). Be-aware we use [pre commit hooks](https://pre-commit.com/) to update the docs.

## Release

Releases are create automated from the main branch using conventional commit messages.

## Contact

For question you can reach out to one of the [maintainers](./MAINTAINERS.md).
13 changes: 13 additions & 0 deletions bin/generate-thumbprint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
## Script to generate the Thumbprint
##
## https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
##
##
HOST=$(curl https://vstoken.actions.githubusercontent.com/.well-known/openid-configuration |
jq -r '.jwks_uri | split("/")[2]')
% echo | openssl s_client -servername $HOST -showcerts -connect $HOST:443 2>/dev/null |
sed -n -e '/BEGIN/h' -e '/BEGIN/,/END/H' -e '$x' -e '$p' | tail +2 |
openssl x509 -fingerprint -noout |
sed -e "s/.*=//" -e "s/://g" |
tr "ABCDEF" "abcdef"
39 changes: 39 additions & 0 deletions examples/multi-repo/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions examples/multi-repo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Managing multiple repo's for a single AWS account

The module provides an example how to setup roles to to use with OIDC for multiple repositories.

- A repository with some access to S3 (same as in the [single example](../single-repo/README.md))
- A repository with access to ECR with the tag `allow-gh-action-access`
- Environment for ECR repo (requires a paid GitHub subscription)

## Usages

Create a GitHub repositories (private) for S3 and ECR and set teh variable `repo` to the name of your created repo. Add as secret `AWS_ACCOUNT_ID` and set the value to your account.

```bash
terraform init
terraform apply
```

For the S3 repository follow the directions in the single example. On the console the name of the ECR repo and role are printed. Next update [workflow](../repositories/.github/workflows/../../repo-ecr/.github/workflows/ecr.yml) for the repo and role. Add, commit and push. The job should now push a busybox container to your ECR repo.

Finally you can clean up with `terraform destroy`
Loading

0 comments on commit 6b44784

Please sign in to comment.