Skip to content

Commit

Permalink
Merge pull request #71 from ministryofjustice/oidc-github
Browse files Browse the repository at this point in the history
Create OIDC integration for GitHub
  • Loading branch information
jakemulley authored May 3, 2023
2 parents 27be39f + a6f6f93 commit 0235d4e
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 8 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,47 @@ No modules.
| [aws_ecr_lifecycle_policy.lifecycle_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_lifecycle_policy) | resource |
| [aws_ecr_repository.repo](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository) | resource |
| [aws_iam_access_key.key_2023](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_policy.ecr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.ecr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_user.user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
| [github_actions_environment_secret.ecr_access_key](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) | resource |
| [github_actions_environment_secret.ecr_name](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) | resource |
| [github_actions_environment_secret.ecr_secret_key](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) | resource |
| [github_actions_environment_secret.ecr_url](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) | resource |
| [github_actions_environment_secret.role_to_assume](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) | resource |
| [github_actions_environment_variable.ecr_region](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_variable) | resource |
| [github_actions_environment_variable.ecr_repository](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_variable) | resource |
| [github_actions_secret.ecr_access_key](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource |
| [github_actions_secret.ecr_name](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource |
| [github_actions_secret.ecr_secret_key](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource |
| [github_actions_secret.ecr_url](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource |
| [github_actions_secret.role_to_assume](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource |
| [github_actions_variable.ecr_region](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_variable) | resource |
| [github_actions_variable.ecr_repository](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_variable) | resource |
| [random_id.oidc](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
| [random_id.user](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_openid_connect_provider.github](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_openid_connect_provider) | data source |
| [aws_iam_policy_document.ecr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.github](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_github_actions_prefix"></a> [github\_actions\_prefix](#input\_github\_actions\_prefix) | String prefix for GitHub Actions variable and secrets key | `string` | `""` | no |
| <a name="input_github_actions_secret_ecr_access_key"></a> [github\_actions\_secret\_ecr\_access\_key](#input\_github\_actions\_secret\_ecr\_access\_key) | The name of the github actions secret containing the ECR AWS access key | `string` | `"ECR_AWS_ACCESS_KEY_ID"` | no |
| <a name="input_github_actions_secret_ecr_name"></a> [github\_actions\_secret\_ecr\_name](#input\_github\_actions\_secret\_ecr\_name) | The name of the github actions secret containing the ECR name | `string` | `"ECR_NAME"` | no |
| <a name="input_github_actions_secret_ecr_secret_key"></a> [github\_actions\_secret\_ecr\_secret\_key](#input\_github\_actions\_secret\_ecr\_secret\_key) | The name of the github actions secret containing the ECR AWS secret key | `string` | `"ECR_AWS_SECRET_ACCESS_KEY"` | no |
| <a name="input_github_actions_secret_ecr_url"></a> [github\_actions\_secret\_ecr\_url](#input\_github\_actions\_secret\_ecr\_url) | The name of the github actions secret containing the ECR URL | `string` | `"ECR_URL"` | no |
| <a name="input_github_environments"></a> [github\_environments](#input\_github\_environments) | GitHub environment in which to create github actions secrets | `list(string)` | `[]` | no |
| <a name="input_github_repositories"></a> [github\_repositories](#input\_github\_repositories) | GitHub repositories in which to create github actions secrets | `list(string)` | `[]` | no |
| <a name="input_lifecycle_policy"></a> [lifecycle\_policy](#input\_lifecycle\_policy) | A lifecycle policy consists of one or more rules that determine which images in a repository should be expired. | `string` | `null` | no |
| <a name="input_oidc_providers"></a> [oidc\_providers](#input\_oidc\_providers) | OIDC providers for this ECR repository, valid values are "github" | `list(string)` | `[]` | no |
| <a name="input_repo_name"></a> [repo\_name](#input\_repo\_name) | Name of the repository to be created | `string` | n/a | yes |
| <a name="input_scan_on_push"></a> [scan\_on\_push](#input\_scan\_on\_push) | Whether images are scanned after being pushed to the repository (true) or not (false) | `bool` | `true` | no |
| <a name="input_team_name"></a> [team\_name](#input\_team\_name) | Name of the team creating the credentials | `string` | n/a | yes |
Expand Down
190 changes: 182 additions & 8 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,38 @@ locals {
repository = repository
}
])

github_environments = toset([
for environment in var.github_environments : {
environment = environment
}
])

github_repo_environments = [
for pair in setproduct(local.github_repositories, local.github_environments) : {
repository = pair[0].repository
environment = pair[1].environment

}
]
}

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# ECR repository
resource "aws_ecr_repository" "repo" {
name = "${var.team_name}/${var.repo_name}"
image_scanning_configuration {
scan_on_push = var.scan_on_push
}
}

# Lifecycle policy
# ECR lifecycle policy
resource "aws_ecr_lifecycle_policy" "lifecycle_policy" {
count = var.lifecycle_policy == null ? 0 : 1
repository = aws_ecr_repository.repo.name
policy = var.lifecycle_policy
}

# Legacy access (IAM access keys)
resource "random_id" "user" {
byte_length = 8
}
Expand All @@ -47,7 +46,7 @@ resource "aws_iam_user" "user" {
}

resource "aws_iam_access_key" "key_2023" {
user = aws_iam_user.user.name
user = aws_iam_user.user.name
}

data "aws_iam_policy_document" "policy" {
Expand Down Expand Up @@ -94,6 +93,7 @@ resource "aws_iam_user_policy" "policy" {
user = aws_iam_user.user.name
}

# Legacy GitHub integration: create GitHub Actions secrets
resource "github_actions_secret" "ecr_url" {
for_each = toset(var.github_repositories)
repository = each.key
Expand Down Expand Up @@ -122,9 +122,7 @@ resource "github_actions_secret" "ecr_secret_key" {
plaintext_value = aws_iam_access_key.key_2023.secret
}


# Create environment secrets

# Legacy GitHub integration: Create environment secrets
resource "github_actions_environment_secret" "ecr_url" {
for_each = {
for i in local.github_repo_environments : "${i.repository}.${i.environment}" => i
Expand Down Expand Up @@ -164,3 +162,179 @@ resource "github_actions_environment_secret" "ecr_secret_key" {
secret_name = var.github_actions_secret_ecr_secret_key
plaintext_value = aws_iam_access_key.key_2023.secret
}

####################
# OIDC integration #
####################
locals {
oidc_providers = {
github = "token.actions.githubusercontent.com"
}

oidc_providers_assume_role_policies = {
github = data.aws_iam_policy_document.github.json
}

identifier_oidc = "cloud-platform-ecr-${random_id.oidc.hex}"
}

# Random ID for identifiers
resource "random_id" "oidc" {
byte_length = 8
}

# GitHub: OIDC provider
data "aws_iam_openid_connect_provider" "github" {
url = "https://${local.oidc_providers.github}"
}

# GitHub: Assume role policy
# See: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws
data "aws_iam_policy_document" "github" {
version = "2012-10-17"

statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]

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

condition {
test = length(var.github_repositories) == 1 ? "StringLike" : "ForAnyValue:StringLike"
variable = "${local.oidc_providers.github}:sub"
values = formatlist("repo:ministryofjustice/%s:*", toset(var.github_repositories))
}

condition {
test = "StringEquals"
variable = "${local.oidc_providers.github}:aud"
values = ["sts.amazonaws.com"]
}
}
}

# ECR policy
# See: https://github.com/aws-actions/amazon-ecr-login#permissions
data "aws_iam_policy_document" "ecr" {
version = "2012-10-17"
statement {
sid = "AllowLogin"
effect = "Allow"
actions = ["ecr:GetAuthorizationToken"]
resources = ["*"]
}

statement {
sid = "AllowPushPull"
effect = "Allow"
actions = [
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
]
resources = [aws_ecr_repository.repo.arn]
}
}

# IAM roles, policies, and policy attachments
resource "aws_iam_role" "oidc" {
for_each = toset(var.oidc_providers) # one role per provider

name = "${local.identifier_oidc}-${each.key}"
assume_role_policy = local.oidc_providers_assume_role_policies[each.key]
}

resource "aws_iam_policy" "ecr" {
name = local.identifier_oidc
policy = data.aws_iam_policy_document.ecr.json
}

resource "aws_iam_role_policy_attachment" "ecr" {
for_each = aws_iam_role.oidc

role = each.value.name
policy_arn = aws_iam_policy.ecr.arn
}

# GitHub Actions variables and secrets
locals {
github_variable_names = {
ECR_ROLE_TO_ASSUME = join("_", compact([var.github_actions_prefix, "ECR_ROLE_TO_ASSUME"]))
ECR_REGION = join("_", compact([var.github_actions_prefix, "ECR_REGION"]))
ECR_REPOSITORY = join("_", compact([var.github_actions_prefix, "ECR_REPOSITORY"]))
}

github_repos = toset(var.github_repositories)
github_envs = toset(var.github_environments)
github_repo_envs = {
for pair in setproduct(local.github_repos, local.github_envs) :
"${pair[0]}.${pair[1]}" => {
repository = pair[0]
environment = pair[1]
}
}
}

# Actions
resource "github_actions_secret" "role_to_assume" {
for_each = local.github_repos

repository = each.key
secret_name = local.github_variable_names["ECR_ROLE_TO_ASSUME"]
plaintext_value = aws_iam_role.oidc["github"].arn

depends_on = [aws_iam_role.oidc]
}

resource "github_actions_variable" "ecr_region" {
for_each = local.github_repos

repository = each.key
variable_name = local.github_variable_names["ECR_REGION"]
value = data.aws_region.current.name
}

resource "github_actions_variable" "ecr_repository" {
for_each = local.github_repos

repository = each.key
variable_name = local.github_variable_names["ECR_REPOSITORY"]
value = aws_ecr_repository.repo.name
}

# Environments
resource "github_actions_environment_secret" "role_to_assume" {
for_each = local.github_repo_envs

repository = each.value.repository
environment = each.value.environment
secret_name = local.github_variable_names["ECR_ROLE_TO_ASSUME"]
plaintext_value = aws_iam_role.oidc["github"].arn

depends_on = [aws_iam_role.oidc]
}

resource "github_actions_environment_variable" "ecr_region" {
for_each = local.github_repo_envs

repository = each.value.repository
environment = each.value.environment
variable_name = local.github_variable_names["ECR_REGION"]
value = data.aws_region.current.name
}

resource "github_actions_environment_variable" "ecr_repository" {
for_each = local.github_repo_envs

repository = each.value.repository
environment = each.value.environment
variable_name = local.github_variable_names["ECR_REPOSITORY"]
value = aws_ecr_repository.repo.name
}
15 changes: 15 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,18 @@ variable "lifecycle_policy" {
type = string
default = null
}

########
# OIDC #
########
variable "oidc_providers" {
description = "OIDC providers for this ECR repository, valid values are \"github\""
type = list(string)
default = []
}

variable "github_actions_prefix" {
description = "String prefix for GitHub Actions variable and secrets key"
type = string
default = ""
}

0 comments on commit 0235d4e

Please sign in to comment.