--- name: gcp-hardening description: "(💛) Use when configuring, auditing, hardening, or open-sourcing projects that use Google Cloud Platform (GCP) resources — especially Google Cloud Storage (GCS) buckets, IAM roles, V4 Signed URLs, or pre-release repository security checks." compatibility: gemini-cli metadata: version: "1.0.0" author: "Riccardo Carlesso" tags: "gcp, security, hardening, gcs, iam, oss-hygiene" --- # 🛡️ GCP Hardening & Pre-Release Security Guide (`gcp-hardening`) ## Overview Actionable security hardening rules and audit workflows for Google Cloud Platform (GCP) resources and open-source repositories that interact with GCP. Designed to be modular and extensible by GCP product area (starting with **Google Cloud Storage (GCS)** and **Pre-Open-Source GCP Leak Audits**). --- ## 1. 🪣 Google Cloud Storage (GCS) Hardening ### 1.1 The `objectViewer` vs `legacyObjectReader` Trap (Bucket Listing Exfiltration) When hosting static reports, media galleries, or capability-URL pages (e.g., `storagify` with `--salt`, or unguessable folder prefixes) on a GCS bucket where direct browser links must work without login: > [!CAUTION] > **NEVER grant `roles/storage.objectViewer` (or `roles/storage.legacyBucketReader`) to `allUsers` or `allAuthenticatedUsers`!** | IAM Role on `allUsers` | Permissions Granted | Direct Object GET (`/.../file.html`) | Root Bucket XML Listing (`https://storage.googleapis.com//`) | Security Posture | | :--- | :--- | :--- | :--- | :--- | | `roles/storage.objectViewer` | `storage.objects.get`
**`storage.objects.list`** | `200 OK` | **`200 OK` (``)** 🚨 | **INSECURE**: Leaks every object key, manifest, and salted filename in the bucket! | | `roles/storage.legacyObjectReader` | `storage.objects.get` **ONLY** | `200 OK` | **`403 Forbidden`** ✅ | **SECURE**: Direct links work; directory enumeration is blocked. | #### Why this matters If a bucket name is ever mentioned in a public repository, issue tracker, or shared URL, and `allUsers` has `roles/storage.objectViewer`, anyone can run: ```bash curl -s "https://storage.googleapis.com//" ``` and receive a full XML `` dump of every object in the bucket — completely bypassing 16-character URL salts (`[salt]-index.html`), hidden metadata directories (`.storagify/entries/*.json`), and unlisted subfolders. #### Mandatory GCS Remediation & Verification Always apply `legacyObjectReader` **before** removing `objectViewer` to ensure zero downtime for existing shared links: ```bash # 1. Grant direct object read access ONLY (no directory listing) gcloud storage buckets add-iam-policy-binding gs:// \ --member=allUsers \ --role=roles/storage.legacyObjectReader \ --project= --quiet # 2. Remove objectViewer (blocks public XML bucket listing) gcloud storage buckets remove-iam-policy-binding gs:// \ --member=allUsers \ --role=roles/storage.objectViewer \ --project= --quiet # 3. Verify: Root listing MUST return 403, direct object MUST return 200 curl -s -o /dev/null -w "Root Listing HTTP (expect 403): %{http_code}\n" "https://storage.googleapis.com//" curl -s -o /dev/null -w "Direct Object HTTP (expect 200): %{http_code}\n" "https://storage.googleapis.com//" ``` ### 1.2 Private Buckets & Ephemeral V4 Signed URLs ("Snapchat Mode") When data is sensitive and should expire automatically rather than relying on public object reads: 1. **Enforce Public Access Prevention**: ```bash gcloud storage buckets update gs:// --public-access-prevention --project= --quiet ``` 2. **Use GCS V4 Signed URLs (`version="v4"`)**: - Maximum TTL is **7 days** (`604800` seconds). - **Self-Contained HTML**: When serving HTML via V4 Signed URLs on a private bucket, inline local CSS/JS (`