Tenant Admission

Tenant Admission #

The operator’s half of creating a tenant. The operator admits a name; the tenant then declares everything else itself, through the Deevnet API ( ADR-0015). The tenant’s half is Tenant Operations.

Admission is the only substrate act a new tenant needs. The operator never edits an inventory file to make a tenant, and the tenant never holds a Proxmox credential, a vault password or an index.


Before you start #

The APIhttps://api.mobile.deevnet.net:8080, reachable from the Builder
The operator tokenvault_deevnet_api_token, in the inventory’s deevnet_api group vault
The site CAansible-collection-deevnet.mgmt/.openbao/site-ca.pem on the control node
The providerthe tenant installs deevnet/deevnet 0.4.x itself with install-provider.sh from the tenant downloads ( Before You Start). No role installs it. Before a meetup, check the downloads tree is current: the provider repo’s make stage, the image factory’s make pi-backend-publish, then deevnet.mgmt site.yml --tags tenant-downloads

1. Admit the name #

curl -sS --cacert site-ca.pem \
  -H "Authorization: Bearer $OPERATOR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"mabell"}' \
  https://api.mobile.deevnet.net:8080/v1/admissions
{ "name": "mabell", "enrollment_token": "s.…", "expires_at": "2026-09-26T…Z" }
The name^[a-z][a-z0-9]{0,7}$ — it becomes a PVE SDN zone ID, a DNS label and a state-store user
The tokensingle-use, expires after DEEVNET_ENROLLMENT_TTL (72h by default)
A name already registeredanswers 409. Admission creates nothing; it only authorizes

2. Hand over three things #

The tenant needs exactly these, and nothing else from the substrate:

  1. the enrollment token
  2. the API endpoint, https://api.mobile.deevnet.net:8080
  3. the site CA, site-ca.pem

The token is bound to the name. Presenting it for a different name spends it and answers 401, so a mistyped name costs a new admission.

Delivery is by hand today. ADR-0012 §9 says the token is delivered age-encrypted to the tenant’s own key; that tooling is not built. Until it is, hand the token over on a channel you would trust with a password, and admit close to when the tenant will apply — an unspent token is a credential for 72 hours.

3. Where the tenant applies from #

Every apply has to reach the API, and after the first one the state store too. Give the tenant the DVNTM-TD key (deevnet_wifi_psk.tenant_dev): that segment reaches the API, the state store, the broker, the log store and Grafana, and nothing else ( CHG-0022, CHG-0024). Guest is internet-only and IoT cannot reach the API. A trusted seat still works, but it reaches the management plane, so don’t offer it to a visitor. See Before You Start for how the tenant side reads this.


What the substrate does on the first apply #

Networkan EVPN zone and VRF on the tenant hypervisor, one VNet, a /24 from 10.20.128.0/18, anycast .1, SNAT at the exit node
DNS<tenant>.<site>.deevnet.net and its reverse, delegated, with a TSIG key the tenant writes records with
Statea bucket prefix and keys in the state store
Logspartitions (index, 0..2), an ingest and a read token, and the vmauth routes that separate them from every other tenant ( ADR-0027)
Dashboardsa Grafana organization named for the tenant, one Editor login, and three log data sources carrying its read token, with fixed UIDs ( ADR-0024). A delete renames the emptied organization deleted-<tenant>-<id>, because Grafana 13 cannot delete one
Devices, if declareda PPSK per trust class, registry entries, and MQTT accounts confined to the tenant’s own topic prefix

The index is the tenant’s number in all of it — subnet, VXLAN VNI, log account — so it is allocated once, against both the registry and the live fabric, and never by hand.

To confirm the fabric side, pvesh get /cluster/sdn/zones on the tenant hypervisor shows vrf_<tenant> with its own VNI.


Operator-only calls #

CallUse
GET /v1/tenantsevery tenant, by index, without secrets. The list is lean — ask GET /v1/tenants/{name} before concluding something is missing
POST /v1/tenants/{name}/reconcilere-ensure every backend with the secrets the registry holds. The repair after a backend is rebuilt, and how a tenant created before a service existed is handed that service’s credentials

When it goes wrong #

SymptomWhat it means
409 on admissionthe name is already registered. The tenant uses its own token, not a new admission
401 on the tenant’s first applythe enrollment token was issued for a different name, and is now spent. Admit again
501 on every tenant routethe API has no site configured, or no OpenBao
A tenant lists with no log storethe list endpoint returns lean records. Ask the detail endpoint
Page last modified: September 26, 2026