cis_extensions.bucket_retention — version 1.5
Source: presets
Purpose
CIS 4.0 §2.3 / 5.0 §2.4 — retention on the log-export bucket.
Opt-in because it constrains EVERY bucket in the organisation, not only the log sink's: once a duration list is set, a bucket may carry a retention policy only if its duration is in the list. An estate with buckets on other durations loses the ability to update them.
The constraint is a MANAGED one carrying a parameter, not a bare boolean —
verified against a live organisation's OrgPolicy ListConstraints.
It is a contributes, not an implements: the control asks for retention
configured USING BUCKET LOCK, and no org policy locks a bucket. The
audit-logsink pack carries the same duty.
Use it
params {
cis_bucket_retention = true
// the pack's own defaults — copy only the lines you change
allowed_bucket_retention_seconds = [ "34560000" ]
}
use "presets/cis/bucket-retention.satz" when cis_bucket_retention
cis_bucket_retention is the gate: while it is falsy the pack contributes nothing — no resources, no params, no claims. It is declared by CIS_GCP_Foundation_4_0, 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.
Params
| param | default |
|---|---|
allowed |
[ "34560000" ] |
Questions
What to ask before this pack is configured, and what changing the answer costs.
| asks about | prompt | reversal | blast | recommends |
|---|---|---|---|---|
allowed |
The retention durations, in seconds, a bucket's retention policy may use | edit | low | — |
allowed_bucket_retention_seconds — The default is the 400 days the audit-logsink pack retains. Every bucket in the organisation with a retention policy on another duration can no longer be updated once this is enforced, so the estate's other durations have to be listed first.
Notices
What to run once this pack is switched on. satz shows each notice when the pack goes on and until the estate binds its param true; every command that writes to the organisation refuses while one whose severity is error is open.
| param | run | severity | what and why |
|---|---|---|---|
cis |
satz adopt <estate> --execute --import |
error | Google may already hold a policy this pack declares on the organisation, set by an administrator or by default, and an apply that creates a policy that exists stops on 409 POLICY_ALREADY_EXISTS. Run satz adopt once the pack is on, so every live policy is in the state before the apply; with --import it binds this param itself when it has run. |
Contributes
| resource type | labels |
|---|---|
google_org_policy_policy |
storage-managed-retention, storage-retention (off) |
Claims
| framework | control | title | kind | witnesses | interpretation |
|---|---|---|---|---|---|
| cis-gcp 4.0 | 2.3 | Retention on the log bucket | contributes | google |
A bucket may only carry a retention policy of an approved duration, so the log-export bucket's retention cannot be quietly shortened. The LOCK remains a human decision. |
| cis-gcp 5.0 | 2.4 | Retention on the log bucket | contributes | google |
As for 4.0 §2.3 — 5.0 §2.4 (5.0 inserted a new 2.2 for Workspace data sharing). |
What the controls ask (this project's paraphrase from the catalog — never the framework's own text):
- Retention on the log bucket (cis-gcp 4.0 §2.3, cis-gcp 5.0 §2.4) — The central log destination retains entries per policy; bucket-lock retention once the pipeline is validated.
Manual duties (a control stays partial until each is attested):
validate-then-lock(cis-gcp 4.0 §2.3, cis-gcp 5.0 §2.4) — After validating the pipeline, set retention_policy and is_locked on the audit bucket (one-way door; deliberately not preset-default).
Not every resource this pack contributes is a witness:
google— declared OFF (_org _policy _policy .storage _retentionPolicySeconds spec { reset = true }): a superseded legacy twin, which proves nothing by design.
History
| version | date | change |
|---|---|---|
| 1.5 | 2026-09-30 | every claim states its measure without satz: gcloud (the commands that meet the control the way the claim's resources do), gcloud_check (the commands that show whether it is met) and risk (what goes wrong without it); require and report-compliance carry them in each control's measures. Nothing emitted changes |
| 1.4 | 2026-09-20 | the notice's before = apply becomes severity = error: the same refusal, stated once by the pack instead of inside the two commands that read it — every command that writes to the organisation refuses while it is open. Nothing emitted changes, and the param that acknowledges it is unchanged |
| 1.3 | 2026-09-19 | a notice: once the pack is switched on, satz adopt <estate> --execute --import is to run before the apply, because a policy it declares may already be live and creating it stops on 409 POLICY_ALREADY_EXISTS. The estate acknowledges it with cis, which adopt --execute --import binds itself when it has run; transpile --apply and bootstrap refuse while it is open. The param is never emitted, so the plan does not move |
| 1.2 | 2026-09-10 | one question block on the allowed durations: every bucket on another duration becomes un-updatable once enforced. Nothing emitted changes |
| 1.1 | 2026-09-04 | declares its superseded legacy twin (storage) off |
| 1.0 | 2026-09-04 | CIS 4.0 2.3 / 5.0 2.4 as a contributes, opt-in: constrains EVERY bucket's retention duration, and locking stays a human decision |
The whole library's history: the changelog in presets/CHANGELOG.md.
Notes
It constrains EVERY bucket in the organisation, not only the log sink's, so an estate with buckets on other durations loses the ability to update them. The retention LOCK is a human step — the duty on the claim, shared with monitoring — and it cannot be undone: validate the pipeline before locking.