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.comThis 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 withstorage.buckets.createandstorage.buckets.setIamPolicy. (Bucket creation is a project-level permission, so a bucket-scoped role cannot be used here.)
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.
Install the
ageCLI (Homebrew:brew install age; apt:apt install age).Generate the keypair to a protected file:
age-keygen -o anzenna-upload-archive.identityThe command prints the public key on stdout; the file contains the matching private key prefixed with
AGE-SECRET-KEY-1….Move
anzenna-upload-archive.identityinto a password manager entry or vault. Do not check it into source control or share it over chat.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.
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
.anzuEach "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.
objectCreator, not a broader roleOnly 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
Log into
Anzenna
.
Navigate to Settings > Preferences > Browser Extension.
Find the File Upload Capture section.
3a. Add the recipient public key
Click Add new public key.
Acknowledge the private-key custody warning.
Paste the public key from Part 1 (starts with
age1…) and click Add key.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
Click Add destination and choose Google Cloud Storage as the provider.
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 ags://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.
Click Save destination.
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.
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
Flip the File Upload Capture toggle at the top of the section to on.
Save the Browser Extension settings.
Part 4: Verify capture end-to-end
Pick a test device that has the Anzenna extension installed and is enrolled in the Anzenna agent.
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.
On that device, upload a small test file to any web destination from the browser (e.g. attach a file to a Gmail draft).
In the Google Cloud console, open the bucket and confirm a new object appears within ~1 minute. Archived files use the
.anzuextension.Download the object and decrypt it (next section) to confirm the round trip works before relying on it during an investigation.
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.
Download the
.anzuobject from the bucket to the workstation that holds the private key.Print the manifest to confirm what the file is and when it was captured:
./anzenna-upload-decrypt -i envelope.anzu --print-manifestDecrypt it:
./anzenna-upload-decrypt -i envelope.anzu -keyfile anzenna-upload-archive.identityThe plaintext is written to
./<filename-from-manifest>by default; override with-o.Compare the SHA-256 of the decrypted output against the
sha256field in the manifest to confirm the file matches what was uploaded.
Need help? Contact Anzenna Support at support@anzenna.ai for assistance.