Customers and isolation¶
A customer — tenant in the code — is the platform's unit of isolation.
Every object belongs to one: its devices, its agents, its credentials, its
backups, its logs, its reports.
What a customer owns outright¶
Creating a customer is not merely adding a row to a table. The customer receives:
| Element | Consequence |
|---|---|
| A storage prefix | its objects live under tenants/<slug>-<suffix>/, apart from everyone else's |
| Its own encryption key | its backups are readable only with it |
| Its own deduplicated repository | deduplication never operates across two customers |
| A service account on the store | confined to its prefix, with no delete permission |
Prefix and repository key¶
The prefix carries a random suffix: acme becomes tenants/acme-06d37b2a/.
Two customers with the same name at two providers, or a customer recreated after
deletion, therefore never end up writing to the same place.
The repository key is envelope-encrypted under the platform master key
(BKP_MASTER_KEY) and exists in clear only in memory, long enough to hand an
agent the elements of a job. It is what makes
crypto-shredding possible: destroying that one
key makes the customer's entire backup set unreadable on the spot.
The service account¶
The customer also receives its own identity on the object store — the account with which its agents write. It is not the platform's account: it is confined to that customer's prefix, it cannot read another customer's objects, and it holds no delete permission at all.
Without it, the agents fall back to the platform credentials. They keep working, and that is exactly what makes the gap easy to miss: backups succeed, the screens stay green. But an agent then holds an identity that can reach every customer's prefix. Storage isolation no longer rests on anything but encryption, and immutability on nothing but the object lock — two layers where there should be three, and the one that disappears is the one nobody can see fail. A compromised agent host, or a collector writing to the wrong prefix, stops being contained by the store itself.
A customer with no service account is flagged on its own page, under Storage, and by Settings → Storage compliance.
Creating it. It is created with the customer. When the object store was unreachable at that moment, catch up from Customers → the customer → Storage → Create the service account, or from the command line:
Renewing it. The same gesture renews. The object store never gives back the secret of an existing account, so renewing always issues a new one and the previous key stops being accepted at once. Agents receive the new key with their next job; a backup already running with the old one fails and is retried. Renew when an agent host has been compromised or decommissioned — the key it held is then no longer worth anything.
When per-customer identities are turned off. BKP_S3_PER_TENANT_IDENTITIES
exists for a development environment with no object store admin API. Turned off,
no service account is created and every agent writes with the platform
credentials — a posture, not an accident, and the storage compliance check
reports it as such.
Why deduplication does not cross customers. A shared deduplicated repository would let someone write a piece of content and observe whether it is stored or merely referenced — confirming that another customer already holds it. The space saved is not worth that side channel.
Creating a customer¶

From the interface: Customers → + Customer. Three fields.
| Field | Constraint | Purpose |
|---|---|---|
Short name (slug) |
lower case, digits and hyphens, 2 to 63 characters | serves as storage prefix and command-line reference |
| Legal name | free | what is displayed everywhere |
| Contact | optional | recipient of the reports |
The short name cannot be changed: it is carved into the storage prefix.
On the command line:
Also via the API.
Requires thecurl -s -X POST "$BASE/api/tenants" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"slug":"acme","name":"ACME Industries","contact_email":"noc@acme.example"}'tenant:managepermission, hence theplatform_adminrole.
If the object store is unreachable¶
Creation fails explicitly rather than leaving a half-made customer. A customer without its service account would have its agents writing with the platform credentials, and nothing on screen would say so. Catch up from the customer's page, or:
Also via the API.
Onboarding a customer: customer and agent in one step¶
The everyday gesture is not “create a customer” but “take on a customer”. The Agents → + Agent screen therefore lets you create the customer and its first agent in the same panel, then issue the enrolment token straight after. That is the recommended path for a new customer; see Agents.
A customer's page¶
It gathers what decides its service:
- that customer's fleet status: devices, compliant, in trouble, coverage;
- storage: prefix, service account, measured usage, trend and estimated saturation date (see Capacity);
- the fleet breakdown by platform;
- its agents and its devices;
- its trust posture and its retention, showing where each value in the cascade comes from;
- the withdrawal zone, kept apart at the foot of the page.
Withdrawing a customer¶
A customer is never deleted. Its page remains as a record: proving that and when you deleted is part of deleting, and it is what an auditor will ask for.
Withdrawal happens in three distinct steps, only one of which is reversible.
| Step | Effect | Reversible |
|---|---|---|
| Withdrawal from service | no more scheduling, agents revoked, queue cleared | yes |
| Key destruction | backups unreadable immediately, including for the provider | no |
| Storage purge | bytes erased, space returned | no |
backup tenant decommission acme # 1. reversible
backup tenant shred acme # 2. irreversible
backup tenant purge acme # 3. once the lock expires
Each step requires typing the customer's short name. This is not decoration: it is the only operation on the platform whose effect becomes irreversible within minutes, and a yes/no confirmation is not enough to commit to it.
Also via the API.
# Withdrawal — reversible. “confirm” must equal the customer slug. curl -s -X POST "$BASE/api/tenants/$CUSTOMER/decommission?confirm=acme" \ -H "Authorization: Bearer $TOKEN" # Put back in service curl -s -X POST "$BASE/api/tenants/$CUSTOMER/reinstate" -H "Authorization: Bearer $TOKEN" # Key destruction — irreversible, requires the “purge:force” permission curl -s -X POST "$BASE/api/tenants/$CUSTOMER/shred?confirm=acme" \ -H "Authorization: Bearer $TOKEN"
Why destroy the key before erasing the bytes¶
A customer asking for its data to be deleted cannot be served immediately at the byte level: the object lock forbids any deletion for its floor duration, and that is precisely its job in the face of ransomware. The maintenance policy forbids itself from bypassing it.
Destroying the key resolves the contradiction. The repository becomes unusable on the spot; space is released when the technical deadline passes. That stands up contractually as it does in front of an auditor: the data is unrecoverable at the moment you announce it.
The purge delay (tenant_purge_after_days, 45 days by default) must stay
greater than the lock floor (object_lock_days, 30 days by default), or
deletions would fail every time. The platform says so at withdrawal if that is
not the case.
What a customer account can see¶
Customers have no identity federation: they get local accounts confined to their own scope. Such an account cannot discover that another customer exists — not through a partial name search, nor through an identifier guessed in a URL. See Security model and Accounts.