Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -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
16 changes: 12 additions & 4 deletions google_deployment_accounts/.terraform-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
92 changes: 71 additions & 21 deletions google_deployment_accounts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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 |
Expand All @@ -111,7 +159,9 @@ module "google_deployment_accounts" {
| <a name="input_circleci_context_ids"></a> [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 |
| <a name="input_display_name"></a> [display\_name](#input\_display\_name) | Display name for the service account. Defaults to "Deployment to the ENV environment". | `string` | `null` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | Environment e.g., stage. Not used for OIDC configuration in CircleCI. | `string` | n/a | yes |
| <a name="input_gha_environments"></a> [gha\_environments](#input\_gha\_environments) | Github environments from which to deploy. If specified, this overrides the environment variable. | `list(string)` | `[]` | no |
| <a name="input_gha_attribute_specifiers"></a> [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 |
| <a name="input_gha_branches"></a> [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 |
| <a name="input_gha_environments"></a> [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 |
| <a name="input_github_repositories"></a> [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 |
| <a name="input_github_repository"></a> [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 |
| <a name="input_project"></a> [project](#input\_project) | n/a | `string` | `null` | no |
Expand Down
2 changes: 2 additions & 0 deletions google_deployment_accounts/examples/gha.tf
Original file line number Diff line number Diff line change
@@ -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"

Expand Down
28 changes: 28 additions & 0 deletions google_deployment_accounts/examples/gha_attribute_specifiers.tf
Original file line number Diff line number Diff line change
@@ -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,
)
}
12 changes: 12 additions & 0 deletions google_deployment_accounts/examples/gha_branch.tf
Original file line number Diff line number Diff line change
@@ -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"]
}
26 changes: 23 additions & 3 deletions google_deployment_accounts/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
]]])
}
]])

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
)
}
6 changes: 4 additions & 2 deletions google_deployment_accounts/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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

@whd whd Jun 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 🤖 caught this edge case that wasn't accounted for before. It doesn't affect any existing config and isn't likely to affect any config in the future, but it is technically correct to make this change so I'm fixing it for CircleCI and the new GHA logic has the same logic.

# 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}",
Expand Down
36 changes: 35 additions & 1 deletion google_deployment_accounts/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 :

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This set of attributes is statically encoded for additional safety but it does introduce an extra manual step if we add and rely on new attributes. I believe the additional plan-time safety is warranted especially since attribute updates are uncommon.

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
Expand Down
Loading