Back to blog
Best PracticesCloud SecurityGCPOperations & ComplianceStorage

Storage Bucket Has No Retention Policy: GCP Remediation Guide

Learn why GCP Storage buckets need a retention policy, the risks of leaving one off, and how to set, lock, and enforce retention with CLI, Terraform, and org policy.

TL;DR

This check flags GCP Storage buckets with no retention policy, which means objects can be deleted or overwritten at any time. That puts audit logs, backups, and compliance data at risk. Set a retention policy with gcloud storage buckets update --retention-period and lock it for data that must be tamper-proof.

Object storage tends to become the default dumping ground for the data that matters most: audit trails, database backups, billing exports, and the kind of records auditors ask about. The problem is that by default a Cloud Storage bucket lets anyone with write access delete or overwrite those objects instantly. A retention policy is the guardrail that stops that from happening, and this check exists because most buckets are created without one.


What this check detects

The storage_noretentionpolicy check looks at every Cloud Storage bucket in your GCP project and flags any bucket that has no retention policy attached. A retention policy defines a minimum amount of time that objects in the bucket must be kept before they can be deleted or replaced. While the policy is active, Cloud Storage refuses any delete or overwrite request on an object that has not yet met the retention period.

If the API returns no retentionPolicy field for a bucket, the check fails. That is the entire signal: the bucket has no minimum retention enforced at the storage layer.

Note: A retention policy is different from object versioning and from lifecycle rules. Versioning keeps old copies of overwritten objects, and lifecycle rules delete or transition objects on a schedule. A retention policy is the only one of the three that actively blocks deletion until a time threshold is met.


Why it matters

The risk here is straightforward but easy to underestimate. Without a retention policy, the durability of your most sensitive objects depends entirely on IAM being perfect and on nobody making a mistake. That is a weak position to be in.

Accidental and malicious deletion

A misconfigured Terraform run, a fat-fingered gsutil rm -r, or a CI job with broader permissions than it should have can wipe an entire bucket of backups in seconds. With a locked retention policy in place, those delete calls are rejected at the API level regardless of who issues them.

The malicious case is worse. If an attacker gains write credentials to a bucket holding audit logs, the first thing they often do is delete the evidence of how they got in. A retention policy turns those logs into write-once records that even a compromised admin account cannot remove early.

Compliance and legal hold

Frameworks like SOC 2, HIPAA, PCI DSS, and various financial regulations require that certain records be retained for fixed periods, and that they cannot be tampered with during that window. A locked retention policy combined with a bucket-level lock is how you satisfy the immutability requirement, sometimes called WORM (write once, read many). Auditors increasingly ask to see this configured rather than taking your word that nobody deletes the data.

Warning: A retention policy increases storage costs over time, because objects you might otherwise delete are forced to stick around until the period expires. Size your retention window to the actual compliance or recovery requirement rather than picking a large round number.


How to fix it

You can set a retention policy on an existing bucket without recreating it. The policy applies to all current and future objects in the bucket.

Using the gcloud CLI

Set a retention period of, for example, 90 days (the value is specified in seconds, days, months, or years):

gcloud storage buckets update gs://my-audit-logs \
  --retention-period=90d

Verify it took effect:

gcloud storage buckets describe gs://my-audit-logs \
  --format="default(retention_policy)"

For data that must be truly immutable, lock the policy. Once locked, the retention period can be increased but never decreased or removed, and the policy cannot be deleted. This is what satisfies most WORM compliance requirements.

Danger: Locking a retention policy is irreversible. After this command runs, you cannot shorten the period or delete the bucket until every object has aged past the retention window. Test the period on a non-production bucket first and be certain the value is correct.

gcloud storage buckets update gs://my-audit-logs \
  --lock-retention-period

Using the console

  1. Open Cloud Storage and select the bucket.
  2. Go to the Protection tab.
  3. Under Retention policy, click Edit and set the retention period.
  4. Save. To make it immutable, click Lock and confirm.

Using Terraform

If you manage buckets as code, add the retention_policy block to the resource:

resource "google_storage_bucket" "audit_logs" {
  name     = "my-audit-logs"
  location = "US"

  uniform_bucket_level_access = true

  retention_policy {
    retention_period = 7776000 # 90 days in seconds
    is_locked        = true
  }
}

Warning: Setting is_locked = true in Terraform locks the policy on the next apply. Once locked, Terraform can no longer destroy the bucket until all objects expire, and a terraform destroy will fail. Keep locked buckets in a separate state or protected with lifecycle { prevent_destroy = true }.


How to prevent it from happening again

Fixing one bucket is fine. The goal is to make sure no new bucket ships without a retention policy where one is needed.

Organization Policy constraints

GCP provides a built-in org policy constraint that enforces a minimum retention period across all buckets in a project, folder, or organization. Apply it and new buckets cannot be created without meeting the threshold.

gcloud resource-manager org-policies set-policy policy.yaml

Where policy.yaml targets constraints/storage.retentionPolicySeconds with your minimum value.

Policy as code in CI/CD

If you provision with Terraform, add a check to your pipeline that fails any plan introducing a bucket without a retention_policy block. A Conftest/OPA rule does this cleanly:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "google_storage_bucket"
  not resource.change.after.retention_policy
  msg := sprintf("Bucket %s has no retention policy", [resource.address])
}

Tip: Pair the gate with a continuous Lensix scan. CI catches new buckets defined in code, and the scheduled scan catches buckets created by hand in the console or by another team that bypassed your pipeline. You need both, because the console will always be the gap in code-only enforcement.


Best practices

  • Apply retention only where it earns its keep. Audit logs, backups, financial records, and legal-hold data are the obvious candidates. Scratch buckets and temporary build artifacts do not need it and the extra cost is wasted.
  • Lock policies on truly sensitive data. An unlocked policy can be removed by anyone with bucket admin rights, which defeats the purpose against an attacker. Lock the ones that protect against insider or compromised-credential scenarios.
  • Combine retention with versioning. Retention stops early deletion; versioning protects against overwrites by keeping prior generations. Together they give you the strongest tamper resistance.
  • Use uniform bucket-level access. Object-level ACLs make it far harder to reason about who can delete what. Uniform access keeps permissions managed entirely through IAM, which is easier to audit.
  • Match the period to the requirement. If your compliance window is seven years, set seven years. Do not guess high, since you cannot shorten a locked policy and you will pay for every extra year.

A retention policy is one of the cheapest controls you can add to object storage, and it removes an entire class of incident: the irreversible deletion. Set it on the buckets that hold data you would hate to lose, lock the ones that need to be tamper-proof, and gate new buckets in CI so the protection does not quietly erode as your environment grows.