Skip to content

Commit 8479e4b

Browse files
RFC: scope secretmanager.secrets.setIamPolicy via IAM condition (opt-in)
Add an opt-in variable scope_secret_iam_to_runner_prefix (default false, preserving today's behavior). When set to true: - Drop secretmanager.secrets.getIamPolicy/setIamPolicy from the runner's project-level custom role. - Create a small custom role runner_secret_iam_manager holding only those two permissions, and grant it to the runner SA with an IAM condition restricting the binding to secrets whose resource name starts with var.runner_name. Opt-in because the change narrows the default IAM scope, which could break downstream deployments whose runner SAs manage IAM on non-runner-prefixed secrets.
1 parent 7fb2cf9 commit 8479e4b

2 files changed

Lines changed: 65 additions & 5 deletions

File tree

iam.tf

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ resource "google_project_iam_custom_role" "runner" {
112112
description = "Minimal permissions for runner infrastructure management"
113113
project = var.project_id
114114

115-
permissions = [
115+
permissions = concat([
116116
# Instance lifecycle management
117117
"compute.instances.create",
118118
"compute.instances.delete",
@@ -189,13 +189,16 @@ resource "google_project_iam_custom_role" "runner" {
189189
"artifactregistry.repositories.downloadArtifacts",
190190
"artifactregistry.repositories.uploadArtifacts",
191191

192-
# Secret Manager permissions for environment secrets
192+
# Secret Manager permissions for environment secrets.
193+
# getIamPolicy/setIamPolicy on secrets are held at project scope by
194+
# default. When var.scope_secret_iam_to_runner_prefix = true those two
195+
# permissions move to runner_secret_iam_manager (bound with an IAM
196+
# condition restricting them to secrets whose name starts with
197+
# var.runner_name) and are dropped from this role.
193198
"secretmanager.secrets.create",
194199
"secretmanager.secrets.delete",
195200
"secretmanager.secrets.get",
196201
"secretmanager.secrets.list",
197-
"secretmanager.secrets.getIamPolicy",
198-
"secretmanager.secrets.setIamPolicy",
199202
"secretmanager.versions.access",
200203
"secretmanager.versions.add",
201204
"secretmanager.versions.destroy",
@@ -248,7 +251,15 @@ resource "google_project_iam_custom_role" "runner" {
248251
"logging.logEntries.list", # Read environment logs from Cloud Logging
249252
"logging.logEntries.create", # Write prebuild logs to Cloud Logging
250253
"logging.logs.delete", # Delete prebuild logs when prebuild is deleted
251-
]
254+
],
255+
# Secret Manager IAM-policy permissions remain at project scope unless
256+
# var.scope_secret_iam_to_runner_prefix opts in to per-prefix scoping,
257+
# in which case they move to runner_secret_iam_manager.
258+
var.scope_secret_iam_to_runner_prefix ? [] : [
259+
"secretmanager.secrets.getIamPolicy",
260+
"secretmanager.secrets.setIamPolicy",
261+
],
262+
)
252263
}
253264

254265
# Bind custom role to runner control plane
@@ -730,3 +741,46 @@ resource "google_service_account_iam_member" "runner_actas_proxy_vm" {
730741
google_service_account.proxy_vm,
731742
]
732743
}
744+
745+
# ================================
746+
# RUNNER SECRET IAM MANAGER (PREFIX-SCOPED)
747+
# ================================
748+
# Opt-in via var.scope_secret_iam_to_runner_prefix. When enabled:
749+
# - secretmanager.secrets.getIamPolicy and .setIamPolicy are dropped from
750+
# the runner's project-level custom role (handled above).
751+
# - A small custom role holding only those two permissions is granted to
752+
# the runner SA with an IAM condition restricting the binding to
753+
# secrets whose resource name starts with the runner name.
754+
#
755+
# This narrows the runner's IAM-management blast radius on Secret Manager
756+
# from "every secret in the project" to "secrets the module/runtime
757+
# create with the runner-name prefix". Opt-in because existing
758+
# deployments may have runner-managed secrets that don't follow that
759+
# prefix; users with non-prefixed secrets should leave this false.
760+
resource "google_project_iam_custom_role" "runner_secret_iam_manager" {
761+
count = var.scope_secret_iam_to_runner_prefix && var.pre_created_service_accounts.runner == "" ? 1 : 0
762+
763+
role_id = "${replace(var.runner_name, "-", "_")}_secret_iam_mgr"
764+
title = "Ona Runner Secret IAM Manager"
765+
description = "Manage IAM on runner-prefixed secrets only (conditioned on resource name)"
766+
project = var.project_id
767+
768+
permissions = [
769+
"secretmanager.secrets.getIamPolicy",
770+
"secretmanager.secrets.setIamPolicy",
771+
]
772+
}
773+
774+
resource "google_project_iam_member" "runner_secret_iam_conditioned" {
775+
count = var.scope_secret_iam_to_runner_prefix && !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
776+
777+
project = var.project_id
778+
role = google_project_iam_custom_role.runner_secret_iam_manager[0].id
779+
member = "serviceAccount:${local.runner_sa_email}"
780+
781+
condition {
782+
title = "restrict-to-runner-prefixed-secrets"
783+
description = "Allow IAM management only on secrets whose name starts with the runner name"
784+
expression = "resource.name.startsWith(\"projects/${var.project_id}/secrets/${var.runner_name}\")"
785+
}
786+
}

variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,10 @@ variable "use_authoritative_project_metadata" {
343343
default = true
344344
}
345345

346+
variable "scope_secret_iam_to_runner_prefix" {
347+
description = "When true, moves secretmanager.secrets.{get,set}IamPolicy out of the runner's project-level custom role and into a separate role bound with an IAM condition restricting it to secrets whose name starts with var.runner_name. When false (default), the runner holds those permissions at project scope (current behavior). Opt in to narrow the runner's IAM-management blast radius on Secret Manager."
348+
type = bool
349+
default = false
350+
}
351+
346352

0 commit comments

Comments
 (0)