exemptions.exemption_tag — version 2.0

Source: presets/exemptions/exemption-tag.satz

Purpose

Exemption classes: one organisation tag key, one value per KIND of exemption, and nothing bound to any of them.

An org policy is all-or-nothing per node. To let ONE service account create ONE key, the policy has to be lowered for a whole project or folder and raised again afterwards — a lift WINDOW during which nothing under that node is enforced, and which nobody remembers to close. A Resource Manager TAG replaces the window: unlike a label it is IAM-governed and an org policy's rule can CONDITION on it, so the policy stays enforced everywhere and named resources are let out one at a time.

Google does this themselves. Since 2024 a new organisation gets iam.disableServiceAccountKeyCreation carrying resource.matchTagId('tagKeys/…', 'tagValues/…') against a built-in key, so binding the exempting value to one service account exempts that account and nothing else.

---- WHY ONE VALUE PER CLASS, AND NOT ONE VALUE -----------------------------------

IAM is set on a tag VALUE. One blanket not_enforced would mean that anyone allowed to exempt ANYTHING may exempt EVERYTHING — the team that needs a public bucket could switch off customer-managed encryption just as easily. So the values below are CLASSES, each covering one kind of risk, and roles/resourcemanager.tagUser on one value delegates exactly that kind and no other.

The classes are deliberately narrow. A wide class is a grant that hands over more than the person asking for it described.

---- THE TWO HALVES OF "WHO MAY EXEMPT" -------------------------------------------

Binding a tag value needs BOTH, and the pair is the scope control:

A team holding tagUser on public-endpoint and createTagBinding on their own folder can exempt public endpoints in that folder, and nothing else, anywhere else.

---- WHAT A BINDING REACHES, WHICH IS THE PART PEOPLE GET WRONG -------------------

Tag bindings are INHERITED. Bound to a service account it reaches that account. Bound to a PROJECT it reaches everything in that project — including every resource created in it afterwards, for as long as the binding exists. Bound to a folder, everything below. A project-level binding is the widest and quietest exemption available here; prefer binding the individual resource.

---- WHAT HAS NO CLASS, ON PURPOSE ------------------------------------------------

Audit logging (gcp.detailedAuditLoggingMode, iam.disableAuditLoggingExemption), VPC flow logs, DNS query logging, and domain-restricted sharing (iam.managed.allowedPolicyMembers) carry no class. Exempting the record of what happened, or letting an outside identity in, is not a delegation — it is a decision for whoever owns the baseline, made by editing the policy where the change is visible. The CIS pack already carries the service-agent caveat for domain-restricted sharing as a parameter (allowed_policy_member_subjects), which is the route there.

---- USING ONE --------------------------------------------------------------------

The constraint has to condition on the class. iam.managed.disableServiceAccountKeyCreation does already, through the CIS baseline's cis_sa_key_creation_rules param; another constraint gets the same treatment when a customer needs it. Then, in the estate:

google_tags_tag_binding { "svc-legacy-etl-key" { // Owner: the platform team. Review: the next access review. // Reason: the vendor's connector cannot federate; migration ticket OPS-1234. parent = "//iam.googleapis.com/projects/{infra_project_name}/serviceAccounts/…" tag_value = "${{google_tags_tag_value.exempt_service_account_keys.name}}" } }

require reads the conditional rule from the estate and prints it under the control (↳ exempted: …) rather than letting a conditional policy read as plain "enforced". The org-wide answer to "who is exempted, and from what" is Cloud Asset Inventory:

gcloud asset search-all-resources --scope=organizations/ORG
--query='tagValues:{customer_shortname}-exemption/service-account-keys'

---- BEFORE GRANTING ANY EXEMPTION ------------------------------------------------

Does the consumer need it at all? A workload inside Google Cloud, a Cloud Run service and external CI on GitHub or GitLab can all use Workload Identity Federation or impersonation and leave the control intact. The tag route is the exception with a named owner, never the default.

Use it

params {
  use_exemption_tag = true
}

use "presets/exemptions/exemption-tag.satz" when use_exemption_tag

use_exemption_tag is the gate: while it is falsy the pack contributes nothing — no resources, no params, no claims. It is declared by estate_map, and the estate's own binding wins.

Needs from outside the pack:

Params

None — the pack takes everything from the estate's params.

Contributes

resource type labels
google_tags_tag_key exemption
google_tags_tag_value exempt_service_account_keys, exempt_public_endpoint, exempt_public_storage, exempt_vm_image, exempt_vm_access, exempt_data_residency, exempt_encryption, exempt_network_appliance

Claims

None — this pack proves no control by itself.

History

version date change
2.0 2026-09-13 one value per exemption CLASS instead of a single not_enforced: service-account-keys, public-endpoint, public-storage, vm-image, vm-access, data-residency, encryption, network-appliance. IAM is set on a tag VALUE, so one blanket value meant anyone allowed to exempt anything could exempt everything — the team needing a public bucket could switch off customer-managed encryption just as easily. The classes are deliberately narrow: a wide class is a grant that hands over more than the person asking described. Audit logging, flow logs, DNS logging and domain-restricted sharing carry NO class on purpose — exempting the record of what happened, or letting an outside identity in, is a decision for whoever owns the baseline, not a delegation. The enforced value is GONE: its only job was leaving a trace instead of deleting a binding, which the estate's own history already does, and it had no meaning once values became classes
1.0 2026-09-13 first version: the VOCABULARY for a tag-conditional exemption — one organisation tag key <shortname>-exemption with the values enforced and not_enforced, and nothing bound to either. An organisation policy is all-or-nothing per node, so letting one service account out of a control means lowering the policy for a whole folder and raising it again — a window during which nothing is enforced. A Resource Manager tag is IAM-governed and a policy rule can condition on it, which is how Google ships iam.disableServiceAccountKeyCreation themselves. The pack ships the ABILITY and no exemptions: a library that ships convenient exemptions lowers the baseline by default. The binding that exempts a resource and the condition on the constraint that honours it are the estate's, and the pack header shows both

The whole library's history: the changelog in presets/CHANGELOG.md.

Notes

No notes yet.