diff --git a/README.md b/README.md index 3bc98e2f..dff29526 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,37 @@ module "iam_eks_role" { } ``` +`iam-github-oidc-provider`: + +```hcl +module "iam_github_oidc_provider" { + source = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-provider" + + tags = { + Environment = "test" + } +} +``` + +`iam-github-oidc-role`: + +```hcl +module "iam_github_oidc_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-role" + + # This should be updated to suit your organization, repository, references/branches, etc. + subjects = ["terraform-aws-modules/terraform-aws-iam:*"] + + policies = { + S3ReadOnly = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + } + + tags = { + Environment = "test" + } +} +``` + `iam-group-with-assumable-roles-policy`: ```hcl diff --git a/examples/iam-github-oidc/README.md b/examples/iam-github-oidc/README.md new file mode 100644 index 00000000..20f25525 --- /dev/null +++ b/examples/iam-github-oidc/README.md @@ -0,0 +1,63 @@ +# IAM GitHub OIDC + +- Creates an IAM identity provider for GitHub OIDC +- Creates an IAM role that trust the IAM GitHub OIDC provider + - GitHub reference: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services + - AWS IAM role reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html#idp_oidc_Create_GitHub + +Note: an IAM provider is 1 per account per given URL. This module would be provisioned once per AWS account, and multiple roles created with this provider as the trusted identity (typically 1 role per GitHub repository). + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_github\_oidc\_provider](#module\_iam\_github\_oidc\_provider) | ../../modules/iam-github-oidc-provider | n/a | +| [iam\_github\_oidc\_provider\_disabled](#module\_iam\_github\_oidc\_provider\_disabled) | ../../modules/iam-github-oidc-provider | n/a | +| [iam\_github\_oidc\_role](#module\_iam\_github\_oidc\_role) | ../../modules/iam-github-oidc-role | n/a | +| [iam\_github\_oidc\_role\_disabled](#module\_iam\_github\_oidc\_role\_disabled) | ../../modules/iam-github-oidc-role | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | +| [provider\_arn](#output\_provider\_arn) | The ARN assigned by AWS for this provider | +| [provider\_url](#output\_provider\_url) | The URL of the identity provider. Corresponds to the iss claim | + diff --git a/examples/iam-github-oidc/main.tf b/examples/iam-github-oidc/main.tf new file mode 100644 index 00000000..a3cd5edc --- /dev/null +++ b/examples/iam-github-oidc/main.tf @@ -0,0 +1,79 @@ +provider "aws" { + region = local.region +} + +locals { + name = "ex-iam-github-oidc" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-iam" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# GitHub OIDC Provider +# Note: This is one per AWS account +################################################################################ + +module "iam_github_oidc_provider" { + source = "../../modules/iam-github-oidc-provider" + + tags = local.tags +} + +module "iam_github_oidc_provider_disabled" { + source = "../../modules/iam-github-oidc-provider" + + create = false +} + +################################################################################ +# GitHub OIDC Role +################################################################################ + +module "iam_github_oidc_role" { + source = "../../modules/iam-github-oidc-role" + + # This should be updated to suit your organization, repository, references/branches, etc. + subjects = ["terraform-aws-modules/terraform-aws-iam:*"] + + policies = { + additional = aws_iam_policy.additional.arn + S3ReadOnly = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + } + + tags = local.tags +} + +module "iam_github_oidc_role_disabled" { + source = "../../modules/iam-github-oidc-role" + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +resource "aws_iam_policy" "additional" { + name = "${local.name}-additional" + description = "Additional test policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:Describe*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) + + tags = local.tags +} diff --git a/examples/iam-github-oidc/outputs.tf b/examples/iam-github-oidc/outputs.tf new file mode 100644 index 00000000..a578aad9 --- /dev/null +++ b/examples/iam-github-oidc/outputs.tf @@ -0,0 +1,37 @@ +################################################################################ +# GitHub OIDC Provider +################################################################################ + +output "provider_arn" { + description = "The ARN assigned by AWS for this provider" + value = module.iam_github_oidc_provider.arn +} + +output "provider_url" { + description = "The URL of the identity provider. Corresponds to the iss claim" + value = module.iam_github_oidc_provider.url +} + +################################################################################ +# GitHub OIDC Role +################################################################################ + +output "iam_role_arn" { + description = "ARN of IAM role" + value = module.iam_github_oidc_role.arn +} + +output "iam_role_name" { + description = "Name of IAM role" + value = module.iam_github_oidc_role.name +} + +output "iam_role_path" { + description = "Path of IAM role" + value = module.iam_github_oidc_role.path +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.iam_github_oidc_role.unique_id +} diff --git a/examples/iam-github-oidc/variables.tf b/examples/iam-github-oidc/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/iam-github-oidc/versions.tf b/examples/iam-github-oidc/versions.tf new file mode 100644 index 00000000..d8dd1a44 --- /dev/null +++ b/examples/iam-github-oidc/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + } +} diff --git a/modules/iam-github-oidc-provider/README.md b/modules/iam-github-oidc-provider/README.md new file mode 100644 index 00000000..cd5025af --- /dev/null +++ b/modules/iam-github-oidc-provider/README.md @@ -0,0 +1,62 @@ +# IAM GitHub OIDC Provider + +Creates an IAM identity provider for GitHub OIDC. See more details here https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services + +Note: an IAM provider is 1 per account per given URL. This module would be provisioned once per AWS account, and multiple roles created with this provider as the trusted identity (typically 1 role per GitHub repository). + +## Usage + +```hcl +module "iam_github_oidc_provider" { + source = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-provider" + + tags = { + Environment = "test" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | +| [tls](#requirement\_tls) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | +| [tls](#provider\_tls) | >= 3.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_openid_connect_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [tls_certificate.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [client\_id\_list](#input\_client\_id\_list) | List of client IDs (also known as audiences) for the IAM OIDC provider. Defaults to STS service if not values are provided | `list(string)` | `[]` | no | +| [create](#input\_create) | Controls if resources should be created (affects all resources) | `bool` | `true` | no | +| [tags](#input\_tags) | A map of tags to add to the resources created | `map(any)` | `{}` | no | +| [url](#input\_url) | The URL of the identity provider. Corresponds to the iss claim | `string` | `"https://token.actions.githubusercontent.com"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS for this provider | +| [url](#output\_url) | The URL of the identity provider. Corresponds to the iss claim | + diff --git a/modules/iam-github-oidc-provider/main.tf b/modules/iam-github-oidc-provider/main.tf new file mode 100644 index 00000000..b68afb44 --- /dev/null +++ b/modules/iam-github-oidc-provider/main.tf @@ -0,0 +1,21 @@ +data "aws_partition" "current" {} + +################################################################################ +# GitHub OIDC Provider +################################################################################ + +data "tls_certificate" "this" { + count = var.create ? 1 : 0 + + url = var.url +} + +resource "aws_iam_openid_connect_provider" "this" { + count = var.create ? 1 : 0 + + url = var.url + client_id_list = coalescelist(var.client_id_list, ["sts.${data.aws_partition.current.dns_suffix}"]) + thumbprint_list = data.tls_certificate.this[0].certificates[*].sha1_fingerprint + + tags = var.tags +} diff --git a/modules/iam-github-oidc-provider/outputs.tf b/modules/iam-github-oidc-provider/outputs.tf new file mode 100644 index 00000000..d388fb95 --- /dev/null +++ b/modules/iam-github-oidc-provider/outputs.tf @@ -0,0 +1,13 @@ +################################################################################ +# GitHub OIDC Provider +################################################################################ + +output "arn" { + description = "The ARN assigned by AWS for this provider" + value = try(aws_iam_openid_connect_provider.this[0].arn, null) +} + +output "url" { + description = "The URL of the identity provider. Corresponds to the iss claim" + value = try(aws_iam_openid_connect_provider.this[0].url, null) +} diff --git a/modules/iam-github-oidc-provider/variables.tf b/modules/iam-github-oidc-provider/variables.tf new file mode 100644 index 00000000..e405bb93 --- /dev/null +++ b/modules/iam-github-oidc-provider/variables.tf @@ -0,0 +1,23 @@ +variable "create" { + description = "Controls if resources should be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to the resources created" + type = map(any) + default = {} +} + +variable "client_id_list" { + description = "List of client IDs (also known as audiences) for the IAM OIDC provider. Defaults to STS service if not values are provided" + type = list(string) + default = [] +} + +variable "url" { + description = "The URL of the identity provider. Corresponds to the iss claim" + type = string + default = "https://token.actions.githubusercontent.com" +} diff --git a/modules/iam-github-oidc-provider/versions.tf b/modules/iam-github-oidc-provider/versions.tf new file mode 100644 index 00000000..3501ad27 --- /dev/null +++ b/modules/iam-github-oidc-provider/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.0" + } + } +} diff --git a/modules/iam-github-oidc-role/README.md b/modules/iam-github-oidc-role/README.md new file mode 100644 index 00000000..8de49883 --- /dev/null +++ b/modules/iam-github-oidc-role/README.md @@ -0,0 +1,108 @@ +# IAM GitHub OIDC Role + +Creates an IAM role that trust the IAM GitHub OIDC provider. +- GitHub reference: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services +- AWS IAM role reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html#idp_oidc_Create_GitHub + +## Usage + +### [GitHub Free, Pro, & Team](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) + +The defaults provided by the module are suitable for GitHub Free, Pro, & Team, including use with the official [AWS GitHub action](https://github.com/aws-actions/configure-aws-credentials). + +```hcl +module "iam_github_oidc_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-role" + + # This should be updated to suit your organization, repository, references/branches, etc. + subjects = ["terraform-aws-modules/terraform-aws-iam:*"] + + policies = { + S3ReadOnly = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + } + + tags = { + Environment = "test" + } +} +``` + +### [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@3.7/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) + +For GitHub Enterprise Server, users will need to provide value for the `audience` and `provider_url` to suit their `` installation: + +```hcl +module "iam_github_oidc_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-github-oidc-role" + + audience = "https://mygithub.com/" + provider_url = "mygithub.com/_services/token" + + # This should be updated to suit your organization, repository, references/branches, etc. + subjects = ["/terraform-aws-iam:*"] + + policies = { + S3ReadOnly = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" + } + + tags = { + Environment = "test" + } +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.0 | +| [tls](#requirement\_tls) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [audience](#input\_audience) | Audience to use for OIDC role. Defaults to `sts.amazonaws.com` for use with the [official AWS GitHub action](https://github.com/aws-actions/configure-aws-credentials) | `string` | `"sts.amazonaws.com"` | no | +| [create](#input\_create) | Controls if resources should be created (affects all resources) | `bool` | `true` | no | +| [description](#input\_description) | IAM Role description | `string` | `null` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [name](#input\_name) | Name of IAM role | `string` | `null` | no | +| [name\_prefix](#input\_name\_prefix) | IAM role name prefix | `string` | `null` | no | +| [path](#input\_path) | Path of IAM role | `string` | `"/"` | no | +| [permissions\_boundary\_arn](#input\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `null` | no | +| [policies](#input\_policies) | Policies to attach to the IAM role in `{'static_name' = 'policy_arn'}` format | `map(string)` | `{}` | no | +| [provider\_url](#input\_provider\_url) | The URL of the identity provider. Corresponds to the iss claim | `string` | `"token.actions.githubusercontent.com"` | no | +| [subjects](#input\_subjects) | List of GitHub OIDC subjects that are permitted by the trust policy. You do not need to prefix with `repo:` as this is provided. Example: `['my-org/my-repo:*', 'octo-org/octo-repo:ref:refs/heads/octo-branch']` | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to the resources created | `map(any)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | ARN of IAM role | +| [name](#output\_name) | Name of IAM role | +| [path](#output\_path) | Path of IAM role | +| [unique\_id](#output\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/iam-github-oidc-role/main.tf b/modules/iam-github-oidc-role/main.tf new file mode 100644 index 00000000..04f9d73f --- /dev/null +++ b/modules/iam-github-oidc-role/main.tf @@ -0,0 +1,73 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + # Just extra safety incase someone passes in a url with `https://` + provider_url = replace(var.provider_url, "https://", "") + + account_id = data.aws_caller_identity.current.account_id + partition = data.aws_partition.current.partition +} + +################################################################################ +# GitHub OIDC Role +################################################################################ + +data "aws_iam_policy_document" "this" { + count = var.create ? 1 : 0 + + statement { + sid = "GithubOidcAuth" + effect = "Allow" + actions = [ + "sts:TagSession", + "sts:AssumeRoleWithWebIdentity" + ] + + principals { + type = "Federated" + identifiers = ["arn:${local.partition}:iam::${local.account_id}:oidc-provider/${local.provider_url}"] + } + + condition { + test = "ForAllValues:StringEquals" + variable = "token.actions.githubusercontent.com:iss" + values = ["http://token.actions.githubusercontent.com"] + } + + condition { + test = "ForAllValues:StringEquals" + variable = "${local.provider_url}:aud" + values = [var.audience] + } + + condition { + test = "StringLike" + variable = "${local.provider_url}:sub" + values = [for subject in var.subjects : "repo:${subject}"] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create ? 1 : 0 + + name = var.name + name_prefix = var.name_prefix + path = var.path + description = var.description + + assume_role_policy = data.aws_iam_policy_document.this[0].json + max_session_duration = var.max_session_duration + permissions_boundary = var.permissions_boundary_arn + force_detach_policies = var.force_detach_policies + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = { for k, v in var.policies : k => v if var.create } + + role = aws_iam_role.this[0].name + policy_arn = each.value +} diff --git a/modules/iam-github-oidc-role/outputs.tf b/modules/iam-github-oidc-role/outputs.tf new file mode 100644 index 00000000..29f9a29d --- /dev/null +++ b/modules/iam-github-oidc-role/outputs.tf @@ -0,0 +1,23 @@ +################################################################################ +# GitHub OIDC Role +################################################################################ + +output "arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, null) +} + +output "name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, null) +} + +output "path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, null) +} + +output "unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, null) +} diff --git a/modules/iam-github-oidc-role/variables.tf b/modules/iam-github-oidc-role/variables.tf new file mode 100644 index 00000000..0d9e3a9e --- /dev/null +++ b/modules/iam-github-oidc-role/variables.tf @@ -0,0 +1,81 @@ +variable "create" { + description = "Controls if resources should be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to the resources created" + type = map(any) + default = {} +} + +################################################################################ +# GitHub OIDC Role +################################################################################ + +variable "name" { + description = "Name of IAM role" + type = string + default = null +} + +variable "path" { + description = "Path of IAM role" + type = string + default = "/" +} + +variable "permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = null +} + +variable "description" { + description = "IAM Role description" + type = string + default = null +} + +variable "name_prefix" { + description = "IAM role name prefix" + type = string + default = null +} + +variable "policies" { + description = "Policies to attach to the IAM role in `{'static_name' = 'policy_arn'}` format" + type = map(string) + default = {} +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = true +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = null +} + +variable "audience" { + description = "Audience to use for OIDC role. Defaults to `sts.amazonaws.com` for use with the [official AWS GitHub action](https://github.com/aws-actions/configure-aws-credentials)" + type = string + default = "sts.amazonaws.com" +} + +variable "subjects" { + description = "List of GitHub OIDC subjects that are permitted by the trust policy. You do not need to prefix with `repo:` as this is provided. Example: `['my-org/my-repo:*', 'octo-org/octo-repo:ref:refs/heads/octo-branch']`" + type = list(string) + default = [] +} + +variable "provider_url" { + description = "The URL of the identity provider. Corresponds to the iss claim" + type = string + default = "token.actions.githubusercontent.com" +} diff --git a/modules/iam-github-oidc-role/versions.tf b/modules/iam-github-oidc-role/versions.tf new file mode 100644 index 00000000..3501ad27 --- /dev/null +++ b/modules/iam-github-oidc-role/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + tls = { + source = "hashicorp/tls" + version = ">= 3.0" + } + } +}