# 58 WORM off-tenant log archive

> v0.1.3 · role: Assure · edition: All (expanded events: Ent Std+) · [policy: #18 · #11](https://docs.google.com/spreadsheets/d/1nOztaPd1Y7eNeRSR_hdovYy-ncpx-bAx/edit?usp=sharing&ouid=115159875779023172526&rtpof=true&sd=true)

Audit logs that live where the tenant's own admins can reach them — the Admin console, the BigQuery export ([31 Audit-log export to SIEM/BigQuery](audit-log-export.md)) — are still deletable by whoever administers that surface, and that is often the same super admin the logs are evidence against. This control lands a second copy in a Cloud Storage bucket with a LOCKED Bucket Lock retention policy, in a project whose IAM is separate from the Workspace super admins being audited. Locked means write-once: the retention period cannot be shortened and objects cannot be deleted before it expires. The lock only places a lien against deleting the project, though — anyone who can remove liens (included in roles/owner) can delete the whole project, bucket and all, which is why the IAM split below is load-bearing. The source is Workspace data sharing with Google Cloud, which places the audit logs in Cloud Logging at the organization level; the BigQuery export is a separate pipe that never passes through Cloud Logging.

Documentation: [Audit logs for Google Workspace](https://docs.cloud.google.com/logging/docs/audit/gsuite-audit-logging)

## Caveats

- Bucket Lock is irreversible — a mistaken retention period cannot be shortened, and you will pay to store the mistake for its full term.
- The control fails if the same super admin can delete both the tenant logs and the sink — the split of custody, not the bucket, is what makes it WORM.
- Expanded and late log events (Ent Std+) arrive with a delay — the archive is only ever as complete as the export that feeds it.

## Setup steps

1. Enable Google Workspace data sharing with Google Cloud, which places the Workspace audit logs in Cloud Logging at the ORGANIZATION level. This sharing — rather than the BigQuery export ([№31](audit-log-export.md)), which never passes through Cloud Logging — is what feeds the archive. — `Account › Account settings › Legal and compliance`

   Sharing options = Enabled (share Google Workspace data with Google Cloud services; super admin only)

   docs: [Share data with Google Cloud services](https://knowledge.workspace.google.com/admin/getting-started/share-data-with-google-cloud-services)

2. In GCP, create an ORG-LEVEL Cloud Logging sink into a GCS bucket dedicated to the archive — a project-level sink sees none of the organization-level Workspace audit logs. Do NOT pass --include-children: it would route every child project's Cloud Audit Logs into a bucket whose locked retention (next step) cannot be undone, and scope the filter to the Workspace log sources.

   ```
   gcloud logging sinks create ws-audit-archive storage.googleapis.com/<archive-bucket> --organization=<ORG_ID> --log-filter='protoPayload.serviceName=("admin.googleapis.com" OR "login.googleapis.com" OR "cloudidentity.googleapis.com" OR "oauth2.googleapis.com")'
   ```

   docs: [View and manage audit logs for Google Workspace](https://docs.cloud.google.com/logging/docs/audit/configure-gsuite-audit-logs) · [Collate and route organization- and folder-level logs to supported destinations](https://docs.cloud.google.com/logging/docs/export/aggregated_sinks)

3. Apply a Bucket Lock retention policy to the bucket and LOCK it — an unlocked retention policy can simply be removed by the same attacker who owns the project.

   ```
   gcloud storage buckets update gs://… --retention-period=94608000s (3 years)
   gcloud storage buckets update gs://… --lock-retention-period
   ```

   docs: [Bucket Lock](https://docs.cloud.google.com/storage/docs/bucket-lock) · [Use and lock retention policies](https://docs.cloud.google.com/storage/docs/using-bucket-lock)

4. Split custody: the GCP project holding the locked bucket must not be administrable by the Workspace super-admins it is auditing.

   Separate GCP org/project IAM; no shared identity

## Ongoing maintenance

- **[automatable: script]** Weekly: alert on sink export errors and on the bucket receiving no new objects.

## How to verify

1. Confirm the retention policy is LOCKED (not merely set) and that objects are still arriving.

   ```
   gcloud storage buckets describe gs://<archive-bucket> --format='default(retention_policy)'
   gcloud storage ls -l --recursive gs://<archive-bucket> | tail -3
   ```

2. Attempt to delete a recent object with project-owner credentials — the locked policy must refuse it. The refusal is the control.
