diff --git a/CODEOWNERS b/CODEOWNERS
index 1dd880c7..48670026 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,8 +1,8 @@
-google_deployment_accounts/ @jasonthomas @jbuck @bkochendorfer @smarnach @whd @mozilla/gcp-wg
-google_fastly_waf/ @jasonthomas @jbuck @bkochendorfer @whd @tibap @mozilla/gcp-wg
-google_gke/ @jasonthomas @ahoneiser @mikaeld @jbuck @bkochendorfer @mozilla/gcp-wg
-google_gke_tenant/ @jasonthomas @jbuck @bkochendorfer @mozilla/gcp-wg
-google_workgroup/ @jasonthomas @jbuck @bkochendorfer @mozilla/gcp-wg
-google_workload_identity/ @jasonthomas @jbuck @bkochendorfer @mozilla/gcp-wg
-pagerduty_service/ @jasonthomas @jbuck @bkochendorfer @whd @bwells-moz @cass-moz @mozilla/gcp-wg
-pagerduty_team/ @jasonthomas @jbuck @bkochendorfer @whd @bwells-moz @cass-moz @mozilla/gcp-wg
+google_deployment_accounts/ @mozilla/gcp-wg
+google_fastly_waf/ @tibap @mozilla/gcp-wg
+google_gke/ @ahoneiser @mikaeld @mozilla/gcp-wg
+google_gke_tenant/ @mozilla/gcp-wg
+google_workgroup/ @mozilla/gcp-wg
+google_workload_identity/ @mozilla/gcp-wg
+pagerduty_service/ @bwells-moz @cass-moz @mozilla/gcp-wg
+pagerduty_team/ @bwells-moz @cass-moz @mozilla/gcp-wg
diff --git a/google_deployment_accounts/.terraform-docs.yml b/google_deployment_accounts/.terraform-docs.yml
index 3481b924..d4d82648 100644
--- a/google_deployment_accounts/.terraform-docs.yml
+++ b/google_deployment_accounts/.terraform-docs.yml
@@ -4,19 +4,27 @@ content: |-
## Examples
```hcl
- {{ include "examples/circleci.tf" }}
+ {{ include "examples/gha.tf" }}
```
```hcl
- {{ include "examples/circleci2.tf" }}
+ {{ include "examples/gha_branch.tf" }}
```
```hcl
- {{ include "examples/circleci3.tf" }}
+ {{ include "examples/gha_attribute_specifiers.tf" }}
```
```hcl
- {{ include "examples/gha.tf" }}
+ {{ include "examples/circleci.tf" }}
+ ```
+
+ ```hcl
+ {{ include "examples/circleci2.tf" }}
+ ```
+
+ ```hcl
+ {{ include "examples/circleci3.tf" }}
```
{{ .Inputs }}
diff --git a/google_deployment_accounts/README.md b/google_deployment_accounts/README.md
index f86dddd9..cf86d60b 100644
--- a/google_deployment_accounts/README.md
+++ b/google_deployment_accounts/README.md
@@ -4,6 +4,74 @@ Creates a Cloud IAM service account which lets CI workflows authenticate to GCP.
## Examples
+```hcl
+# Allow OIDC access from GitHub Actions workflows running in the stage
+# environment of a specific repo
+data "terraform_remote_state" "wip_project" {
+ backend = "gcs"
+
+ config = {
+ bucket = "my-wip-project"
+ prefix = "wip-project/prefix"
+ }
+}
+
+module "google_deployment_accounts" {
+ source = "github.com/mozilla/terraform-modules//google_deployment_accounts?ref=main"
+ project = "my-project"
+ environment = "stage"
+ github_repository = "org/project"
+ wip_name = "github-actions"
+ wip_project_number = data.terraform_remote_state.wip_project.number
+}
+```
+
+```hcl
+# Allow OIDC access from GitHub Actions workflows triggered on the main branch
+# of a specific repo. Branch-based access is typically secured by strong
+# primary branch protection settings, not Github environment approval rules.
+module "google_deployment_accounts_branch" {
+ source = "github.com/mozilla/terraform-modules//google_deployment_accounts?ref=main"
+ project = "my-project"
+ environment = "prod"
+ github_repository = "mozilla/my-repo"
+ wip_name = "github-actions"
+ wip_project_number = 12345
+ gha_branches = ["main"]
+}
+```
+
+```hcl
+# A more complex example using attribute specifiers directly. Allow OIDC access
+# from GitHub Actions workflows running on the main branch of org/repo1 and
+# org/repo2, as well as any workflow running in the "production" environment.
+data "terraform_remote_state" "wip_project" {
+ backend = "gcs"
+
+ config = {
+ bucket = "my-wip-project"
+ prefix = "wip-project/prefix"
+ }
+}
+
+locals {
+ allowed_repo_refs = formatlist("attribute.repository_ref/org/%s:refs/heads/main", ["repo1", "repo2"])
+ allowed_environments = formatlist("attribute.environment/%s", ["production"])
+}
+
+module "google_deployment_accounts" {
+ source = "github.com/mozilla/terraform-modules//google_deployment_accounts?ref=main"
+ project = "my-project"
+ environment = "prod"
+ wip_name = "github-actions"
+ wip_project_number = data.terraform_remote_state.wip_project.number
+ gha_attribute_specifiers = setunion(
+ local.allowed_repo_refs,
+ local.allowed_environments,
+ )
+}
+```
+
```hcl
# Allow OIDC access from CircleCI jobs triggered in a specific repo
data "terraform_remote_state" "wip_project" {
@@ -81,26 +149,6 @@ module "google_deployment_accounts" {
}
```
-```hcl
-data "terraform_remote_state" "wip_project" {
- backend = "gcs"
-
- config = {
- bucket = "my-wip-project"
- prefix = "wip-project/prefix"
- }
-}
-
-module "google_deployment_accounts" {
- source = "github.com/mozilla/terraform-modules//google_deployment_accounts?ref=main"
- project = "my-project"
- environment = "stage"
- github_repository = "org/project"
- wip_name = "github-actions"
- wip_project_number = data.terraform_remote_state.wip_project.number
-}
-```
-
## Inputs
| Name | Description | Type | Default | Required |
@@ -111,7 +159,9 @@ module "google_deployment_accounts" {
| [circleci\_context\_ids](#input\_circleci\_context\_ids) | (CircleCI only) Contexts to allow deployments from. Not recommended when using merge queues since CircleCI Contexts are only accessible to members of your organization. | `set(string)` | `[]` | no |
| [display\_name](#input\_display\_name) | Display name for the service account. Defaults to "Deployment to the ENV environment". | `string` | `null` | no |
| [environment](#input\_environment) | Environment e.g., stage. Not used for OIDC configuration in CircleCI. | `string` | n/a | yes |
-| [gha\_environments](#input\_gha\_environments) | Github environments from which to deploy. If specified, this overrides the environment variable. | `list(string)` | `[]` | no |
+| [gha\_attribute\_specifiers](#input\_gha\_attribute\_specifiers) | Set of attribute specifiers to allow deploys from, in the form ATTR/ATTR\_VALUE. If specified, this overrides the github\_repository variable and any other GHA-specific variables. | `set(string)` | `[]` | no |
+| [gha\_branches](#input\_gha\_branches) | Branches to allow deployments from. If specified, takes precedence over gha\_environments and the environment variable, and is overridden by gha\_attribute\_specifiers. If unspecified, allow deployment from any branch via environment-based principals. | `set(string)` | `[]` | no |
+| [gha\_environments](#input\_gha\_environments) | Github environments from which to deploy. If specified, this overrides the environment variable. Overridden by gha\_branches and gha\_attribute\_specifiers when either is set. | `list(string)` | `[]` | no |
| [github\_repositories](#input\_github\_repositories) | The Github repositories running the deployment workflows in the format org/repository, will be used if github\_repository is not defined. | `list(string)` | `[]` | no |
| [github\_repository](#input\_github\_repository) | The Github repository running the deployment workflows in the format org/repository. Optional for CircleCI or when github\_repositories is specified. | `string` | `null` | no |
| [project](#input\_project) | n/a | `string` | `null` | no |
diff --git a/google_deployment_accounts/examples/gha.tf b/google_deployment_accounts/examples/gha.tf
index e0fd66a0..775a0fba 100644
--- a/google_deployment_accounts/examples/gha.tf
+++ b/google_deployment_accounts/examples/gha.tf
@@ -1,3 +1,5 @@
+# Allow OIDC access from GitHub Actions workflows running in the stage
+# environment of a specific repo
data "terraform_remote_state" "wip_project" {
backend = "gcs"
diff --git a/google_deployment_accounts/examples/gha_attribute_specifiers.tf b/google_deployment_accounts/examples/gha_attribute_specifiers.tf
new file mode 100644
index 00000000..7d33970f
--- /dev/null
+++ b/google_deployment_accounts/examples/gha_attribute_specifiers.tf
@@ -0,0 +1,28 @@
+# A more complex example using attribute specifiers directly. Allow OIDC access
+# from GitHub Actions workflows running on the main branch of org/repo1 and
+# org/repo2, as well as any workflow running in the "production" environment.
+data "terraform_remote_state" "wip_project" {
+ backend = "gcs"
+
+ config = {
+ bucket = "my-wip-project"
+ prefix = "wip-project/prefix"
+ }
+}
+
+locals {
+ allowed_repo_refs = formatlist("attribute.repository_ref/org/%s:refs/heads/main", ["repo1", "repo2"])
+ allowed_environments = formatlist("attribute.environment/%s", ["production"])
+}
+
+module "google_deployment_accounts" {
+ source = "github.com/mozilla/terraform-modules//google_deployment_accounts?ref=main"
+ project = "my-project"
+ environment = "prod"
+ wip_name = "github-actions"
+ wip_project_number = data.terraform_remote_state.wip_project.number
+ gha_attribute_specifiers = setunion(
+ local.allowed_repo_refs,
+ local.allowed_environments,
+ )
+}
diff --git a/google_deployment_accounts/examples/gha_branch.tf b/google_deployment_accounts/examples/gha_branch.tf
new file mode 100644
index 00000000..6c4accf0
--- /dev/null
+++ b/google_deployment_accounts/examples/gha_branch.tf
@@ -0,0 +1,12 @@
+# Allow OIDC access from GitHub Actions workflows triggered on the main branch
+# of a specific repo. Branch-based access is typically secured by strong
+# primary branch protection settings, not Github environment approval rules.
+module "google_deployment_accounts_branch" {
+ source = "github.com/mozilla/terraform-modules//google_deployment_accounts?ref=main"
+ project = "my-project"
+ environment = "prod"
+ github_repository = "mozilla/my-repo"
+ wip_name = "github-actions"
+ wip_project_number = 12345
+ gha_branches = ["main"]
+}
diff --git a/google_deployment_accounts/locals.tf b/google_deployment_accounts/locals.tf
index e009a728..66235900 100644
--- a/google_deployment_accounts/locals.tf
+++ b/google_deployment_accounts/locals.tf
@@ -3,7 +3,27 @@ locals {
environments = length(var.gha_environments) > 0 ? var.gha_environments : [var.environment]
- github_deploy_members = flatten([for repo in local.github_deploy_repositories : [for env in local.environments : [
+ # GHA principal resolution precedence:
+ # 1. gha_attribute_specifiers: full override
+ # 2. gha_branches: repository_ref-based principals per repo and branch
+ # 3. Default: environment-based principals
+ # A subject specifier maps to a single identity (principal://); all other
+ # attributes map to identity sets (principalSet://).
+ gha_attribute_assertions = [for attribute_specifier in var.gha_attribute_specifiers :
+ "${split("/", attribute_specifier)[0] == "subject" ? "principal" : "principalSet"}://iam.googleapis.com/projects/${var.wip_project_number}/locations/global/workloadIdentityPools/${var.wip_name}/${attribute_specifier}"
+ ]
+
+ gha_branch_assertions = flatten([for repo in local.github_deploy_repositories : [for branch in var.gha_branches :
+ "principalSet://iam.googleapis.com/projects/${var.wip_project_number}/locations/global/workloadIdentityPools/${var.wip_name}/attribute.repository_ref/${repo}:refs/heads/${branch}"
+ ]])
+
+ gha_environment_assertions = flatten([for repo in local.github_deploy_repositories : [for env in local.environments :
"principal://iam.googleapis.com/projects/${var.wip_project_number}/locations/global/workloadIdentityPools/${var.wip_name}/subject/repo:${repo}:environment:${env}"
- ]]])
-}
\ No newline at end of file
+ ]])
+
+ github_deploy_members = (
+ length(local.gha_attribute_assertions) > 0 ? local.gha_attribute_assertions :
+ length(local.gha_branch_assertions) > 0 ? local.gha_branch_assertions :
+ local.gha_environment_assertions
+ )
+}
diff --git a/google_deployment_accounts/main.tf b/google_deployment_accounts/main.tf
index 96dc5f74..7528e604 100644
--- a/google_deployment_accounts/main.tf
+++ b/google_deployment_accounts/main.tf
@@ -23,9 +23,11 @@ resource "google_service_account_iam_binding" "github-actions-access" {
locals {
circleci = var.wip_name == "circleci"
- # explicit attributes replace all other kinds of assertions
+ # explicit attributes replace all other kinds of assertions. A subject
+ # specifier maps to a single identity (principal://); all other attributes
+ # map to identity sets (principalSet://).
circleci_attribute_assertions = local.circleci ? [for attribute_specifier in var.circleci_attribute_specifiers :
- "principalSet://iam.googleapis.com/projects/${var.wip_project_number}/locations/global/workloadIdentityPools/${var.wip_name}/${attribute_specifier}"
+ "${split("/", attribute_specifier)[0] == "subject" ? "principal" : "principalSet"}://iam.googleapis.com/projects/${var.wip_project_number}/locations/global/workloadIdentityPools/${var.wip_name}/${attribute_specifier}"
] : []
# single repo, all branches
circleci_vcs_origin_assertions = local.circleci && var.github_repository != null && length(var.circleci_branches) == 0 ? ["principalSet://iam.googleapis.com/projects/${var.wip_project_number}/locations/global/workloadIdentityPools/${var.wip_name}/attribute.vcs_origin/github.com/${var.github_repository}",
diff --git a/google_deployment_accounts/variables.tf b/google_deployment_accounts/variables.tf
index 94875675..c5162d77 100644
--- a/google_deployment_accounts/variables.tf
+++ b/google_deployment_accounts/variables.tf
@@ -21,11 +21,45 @@ variable "environment" {
# requires manual approval for deploying to production, and that particular
# Github environment should be included in the gha_environments list.
variable "gha_environments" {
- description = "Github environments from which to deploy. If specified, this overrides the environment variable."
+ description = "Github environments from which to deploy. If specified, this overrides the environment variable. Overridden by gha_branches and gha_attribute_specifiers when either is set."
type = list(string)
default = []
}
+# Note that branch-based principals authorize any workflow running on the given
+# branch and rely entirely on branch protection rules for security. This is
+# acceptable because branch protection rules on the primary branch for which
+# this setting is likely to be used are typically strong.
+variable "gha_branches" {
+ description = "Branches to allow deployments from. If specified, takes precedence over gha_environments and the environment variable, and is overridden by gha_attribute_specifiers. If unspecified, allow deployment from any branch via environment-based principals."
+ type = set(string)
+ default = []
+}
+
+variable "gha_attribute_specifiers" {
+ description = "Set of attribute specifiers to allow deploys from, in the form ATTR/ATTR_VALUE. If specified, this overrides the github_repository variable and any other GHA-specific variables."
+ type = set(string)
+ default = []
+ validation {
+ condition = alltrue(
+ [for attribute_specifier in var.gha_attribute_specifiers :
+ contains(
+ [
+ "subject",
+ "attribute.actor",
+ "attribute.environment",
+ "attribute.ref",
+ "attribute.repository",
+ "attribute.repository_owner",
+ "attribute.repository_ref",
+ "attribute.workflow"
+ ], split("/", attribute_specifier)[0])
+ ]
+ )
+ error_message = "Attribute specifiers must contain a valid attribute prefix."
+ }
+}
+
# For CircleCI, the default options are to deploy from certain repositories
# (any branch) or allow deploys via a CircleCI Context. You can also limit
# CircleCI to deploy from specific branches. For more complex use