Skip to main content

Configure File Upload Capture (Google Cloud Storage)

File Upload Capture archives a copy of every file your employees upload through the browser. Each file is encrypted on the device with your public key before it leaves the browser, and written directly to a Cloud Storage bucket in your own Google Cloud project. Anzenna mediates the upload but never holds the private key and never sees the file contents.

On Google Cloud, Anzenna authenticates to your storage with plain cross-project IAM: you grant a single Anzenna service account a write-only role scoped to one bucket. There is no federation, STS exchange, or SAS token — no keys, secrets, or tokens are ever exchanged, and you can revoke access at any time by removing the IAM binding.

Prerequisites

  • File Upload Capture must be turned on for your tenant first — contact Anzenna support at support@anzenna.ai before you begin.

  • An active Google Cloud project you own — this is where the Cloud Storage bucket you create below lives.

  • The Anzenna writer service account email for the environment that hosts your tenant — Anzenna support provides this. For production (app.anzenna.ai) it is:

    user-content@anzenna-live.iam.gserviceaccount.com

    This is Anzenna's upload-signing identity — the same account that signs Anzenna's own content-upload URLs. It is deliberately not the service account Anzenna's servers run as. On your bucket you grant it a create-only role and nothing else.

  • You need an account with rights to create the bucket and set its IAM policy — Storage Admin (roles/storage.admin) on the project, or a custom role with storage.buckets.create and storage.buckets.setIamPolicy. (Bucket creation is a project-level permission, so a bucket-scoped role cannot be used here.)

Anzenna cannot recover your data

The private key never leaves your custody. If every copy is lost, every file encrypted to the matching public key is unrecoverable — there is no Anzenna-side backdoor. Store the private key in a vetted password manager or HSM before registering the public key in Anzenna.

Step-by-Step Instructions

Part 1: Generate a recipient keypair

The extension encrypts each captured upload — using the

age encryption format

— to one or more recipient public keys that you register with Anzenna. The matching private keys stay with you and are used to decrypt archived files during an investigation.

  1. Install the age CLI (Homebrew: brew install age; apt: apt install age).

  2. Generate the keypair to a protected file:

    age-keygen -o anzenna-upload-archive.identity

    The command prints the public key on stdout; the file contains the matching private key prefixed with AGE-SECRET-KEY-1….

  3. Move anzenna-upload-archive.identity into a password manager entry or vault. Do not check it into source control or share it over chat.

  4. Copy the public key (the age1… line printed during generation). That is what you will paste into Anzenna in Part 3.

Part 2: Provision the destination in Google Cloud

You'll provision a hardened bucket in your own project and grant the Anzenna writer service account write access scoped to that one bucket.

Prefer infrastructure-as-code?

Rather provision with Terraform? Use the Provision with Terraform (Google Cloud Storage) guide instead — it creates the same bucket, encryption key, and bucket-scoped IAM binding in one terraform apply. Then come back and continue at Part 3 below.

2a. Create the bucket

Create a Cloud Storage bucket with this security posture. The console path is Cloud Storage > Buckets > Create, or use the gcloud commands below.

  • Uniform bucket-level access on — IAM only, no per-object ACLs.

  • Public access prevention enforced — the bucket can never be exposed publicly.

  • Object versioning on, with a lifecycle rule that expires objects per your retention policy.

  • (Optional hardening) a customer-managed encryption key (Cloud KMS) with rotation. Not required for archiving to work — GCS already encrypts every object at rest with Google-managed keys by default; add a CMEK only if your key-custody or data-residency policy calls for it.

gcloud storage buckets create gs://<your-bucket> \
--project=<your-project> \
--location=us-west1 \
--uniform-bucket-level-access \
--public-access-prevention

# object versioning
gcloud storage buckets update gs://<your-bucket> --versioning

# lifecycle: expire live objects after your retention window (here 365 days),
# and reap noncurrent versions 7 days after they're superseded
cat > lifecycle.json <<'EOF'
{
"rule": [
{ "action": { "type": "Delete" }, "condition": { "age": 365, "isLive": true } },
{ "action": { "type": "Delete" }, "condition": { "daysSinceNoncurrentTime": 7 } }
]
}
EOF
gcloud storage buckets update gs://<your-bucket> --lifecycle-file=lifecycle.json
Scope any lifecycle rule to the prefix, not to .anzu

Each "Test destination" run (Part 3b) writes a small, uniquely-named .anzenna-destination-test-… marker and never removes it — the create-only role Anzenna holds cannot delete or overwrite. A lifecycle rule that matches only .anzu archives lets those markers accumulate; apply the rule to the whole bucket (as in the sample above) or scope it to the object prefix (default uploads/) so it reaps them too.

2b. Grant the Anzenna writer service account write access

Grant Storage Object Creator (roles/storage.objectCreator) to the Anzenna writer service account, scoped to the bucket — not the project. This is least privilege: objectCreator is create-only, so the account can write new objects to this one bucket and cannot list, read, overwrite, or delete anything.

gcloud storage buckets add-iam-policy-binding gs://<your-bucket> \
--member="serviceAccount:user-content@anzenna-live.iam.gserviceaccount.com" \
--role="roles/storage.objectCreator"

Or in the console: open the bucket, go to Permissions > Grant access, paste the writer service account as the principal, pick the Storage Object Creator role, and save.

Use objectCreator, not a broader role

Only switch to roles/storage.objectUser if you've explicitly agreed Anzenna will overwrite a stable path. Archival writes each object under a unique name, so create-only is the correct fit and the smallest grant.

2b (only if your org enforces domain-restricted sharing)

If your organization enforces the iam.allowedPolicyMemberDomains org policy (domain-restricted sharing), the grant in 2b is rejected — the Anzenna service account belongs to a foreign directory. The constraint is keyed by Google Workspace customer ID, not email domain, so add Anzenna's customer ID to your allowed list:

  • Anzenna directory customer ID:C03htzkal

Add it to the policy's allowed values (alongside your own customer ID) at the project, folder, or organization scope that covers this bucket, then re-run the grant in 2b.

Part 3: Register everything in Anzenna

  1. Log into

    Anzenna

    .

  2. Navigate to Settings > Preferences > Browser Extension.

  3. Find the File Upload Capture section.

3a. Add the recipient public key

  1. Click Add new public key.

  2. Acknowledge the private-key custody warning.

  3. Paste the public key from Part 1 (starts with age1…) and click Add key.

  4. Repeat for any additional recipients. Each archived file is a single object — adding more recipients just means more private keys can decrypt that one object, which is useful as a break-glass spare.

3b. Add the archive destination

  1. Click Add destination and choose Google Cloud Storage as the provider.

  2. Fill in the fields with the values from provisioning:

    • Bucket name — the bucket you created in 2a (e.g. anzenna-upload-…), the name only, not a gs:// URL. GCS bucket names are globally unique, so this is all Anzenna needs to address it.

    • Object key prefix — optional. Leave blank to use the default uploads/ prefix, or set your own (e.g. archive/) to nest archived objects under it.

  3. Click Save destination.

  4. Click Test destination. Anzenna signs a short-lived upload URL and writes a small test object to the bucket. A green Test upload succeeded confirms the IAM grant is wired correctly. If it fails, the error names the specific Google Cloud denial to fix.

No credential is stored

Nothing secret is saved here — Anzenna stores only your bucket name. Uploads are authorized by a V4 signed URL that Anzenna signs as the writer service account (via Google's IAM signing API — no key is ever exposed). The only thing standing between Anzenna and your data is the bucket-scoped IAM grant, which you control and can revoke at any time.

3c. Turn capture on

  1. Flip the File Upload Capture toggle at the top of the section to on.

  2. Save the Browser Extension settings.

Part 4: Verify capture end-to-end

  1. Pick a test device that has the Anzenna extension installed and is enrolled in the Anzenna agent.

  2. Confirm the device is currently considered managed. Upload credentials only mint for devices that have recently checked in with the agent. There is no self-serve way to check this today — contact Anzenna support at support@anzenna.ai and ask them to confirm the device shows a recent check-in before you test.

  3. On that device, upload a small test file to any web destination from the browser (e.g. attach a file to a Gmail draft).

  4. In the Google Cloud console, open the bucket and confirm a new object appears within ~1 minute. Archived files use the .anzu extension.

  5. Download the object and decrypt it (next section) to confirm the round trip works before relying on it during an investigation.

Requests arrive from end-user IPs

The browser extension PUTs the encrypted archive directly to your bucket, so the request originates from the employee's device, not from Anzenna's servers. Do not IP-allowlist Anzenna's egress range on this bucket — authorization is still evaluated as the writer service account (the signed URL carries its identity), so the 2b binding is what grants and revokes access.

Part 5: Decrypt an archived file

Each archived .anzu object is a small envelope: a 10-byte fixed header, a plaintext JSON manifest (filename, SHA-256, upload destination, timestamp, person ID, recipient list), then the age-encrypted file body. Decryption happens on your workstation with the matching private key — Anzenna is not in the path.

Anzenna ships a small Go CLI, anzenna-upload-decrypt, that reads the envelope, prints the manifest, and writes the decrypted file. Contact Anzenna support at support@anzenna.ai for the binary. The wire format is also documented (4-byte magic ANZU, 2-byte version, 4-byte big-endian manifest length, JSON manifest, then a standard age v1 ciphertext) so any age-compatible tool can decrypt the body once the header is stripped.

  1. Download the .anzu object from the bucket to the workstation that holds the private key.

  2. Print the manifest to confirm what the file is and when it was captured:

    ./anzenna-upload-decrypt -i envelope.anzu --print-manifest
  3. Decrypt it:

    ./anzenna-upload-decrypt -i envelope.anzu -keyfile anzenna-upload-archive.identity

    The plaintext is written to ./<filename-from-manifest> by default; override with -o.

  4. Compare the SHA-256 of the decrypted output against the sha256 field in the manifest to confirm the file matches what was uploaded.


Need help? Contact Anzenna Support at support@anzenna.ai for assistance.