exemptions.exemption_tag — version 2.0
Source: presets
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 carrying
resource 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 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:
rolesON THE VALUE — WHICH class of exemption this principal may grant at all. Declared in the estate; see the grant block at the bottom of this file for the shape./resourcemanager .tagUser - the
createTagBindingpermission ON THE TARGET — WHERE they may apply it. Granted at the organisation, a folder or a project, and that is the project/folder/org restriction. It belongs in the estate rather than here, because which folder a team owns is the customer's hierarchy, not the library's.
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, iam),
VPC flow logs, DNS query logging, and domain-restricted sharing
(iam) 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), which is the route there.
---- USING ONE --------------------------------------------------------------------
The constraint has to condition on the class. iam
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:
customer_organization_id— declared byestate_core; use one of them alongside, or declare it in the estate.customer_shortname— declared byestate_core; use one of them alongside, or declare it in the estate.
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 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.