Skip to main content

Administration console — design

Scope: what the administration console is, what each of its screens does and which service answers it.

Companion docs: architecture.md, iam.md, security/design.md, multi-tenancy.md, routing.md, store-contract.md, resource-plans.md.


1. Purpose​

Every tenant needs a place to govern who belongs to it and which apps each person may use. The administration console is that place. It is an app: a component with its own profile (admin-console, shipped by the operator chart with placement: {tenants: every}), so every tenant has one at admin.<tenant domain>, behind the tenant's session. It reaches a person as a tile on the desktop, shown to whoever holds can_administer on the tenant and to nobody else. The platform tenant has one too, at admin.platform.<KERNEL_DOMAIN>.

A console administers the tenant it is installed in and no other. A platform administrator reaches another tenant through that tenant's own console.

The code is in gentian-ui, apps/admin-console: a React frontend and a small backend. The console holds no credential, keeps no state and decides nothing. Its backend checks the token the edge forwards and relays each call, with that token, to one of four services in kernel-control; what a person may see or change is that service's answer from OpenFGA.

ServiceAsked for
directorEverything that is declared state: a write is a commit to the deployments repository, authored as the person. Also actions that happen once (take an export, publish a notice, purge an app's data)
usherReads of what the cluster holds: installed components and their state, resources, exports, notices
registrarPeople and groups. It holds one Keycloak credential per realm; the console holds none
custodianCredentials a person sets: repository credentials, the backup identity

1.1 Screens​

ScreenWhat it doesAnswered by
Tenants (platform administrator)Create, retire and purge tenants, issue a tenant administrator's activation linkdirector, registrar
MembersList, invite, update, switch off, remove; password reset; require or remove an authenticator; mailboxes of removed people (§4.3a)registrar
GroupsCustom groups and who is in each group, the app groups includedregistrar
AppsThe tenant's installed apps. Per app: state, who has access and whether it is for everyone, integrations, what it asked of the platform and what was approved, public addresses and their approval, uninstall, purge. Uninstall keeps the app's data; purge destroys the data of an app that is no longer installed (store-contract.md §8)director, usher
ResourcesPlan, ceiling, usage history (§4.8)usher (read), director (write)
ExportTake one export now; download or delete the exports a person started. Scheduled backups are not listedusher (read), director
SecurityThe realm's password, session and lockout policy (§4.5)director
IntegrationsWhat the tenant's apps consume from each other, and the grantsdirector
CredentialsRepository credentials, backup identitycustodian, director
NotificationsPublish a notice to the tenant's people (§5)director, usher (read)
AuditChanges to declared state, from git (§4.7)director
Cluster settings, Platform security, Customization, Usage Statistics (platform administrator)The Cluster claim's settings, permitted waivers, customization debt, the last usage statistics reportdirector
Role holders (platform administrator)Who holds cluster#security_officer or cluster#auditor by name: a picker over every tenant's administrators and a role, the holders as the Cluster claim declares them (§7)registrar (the picker), director
Approvals (security officer; the console of the officer's own tenant)Every tenant's app requests of a privilege that weakens what protects the node — pod-security waivers, cluster roles — with the profile's reason and who asked for it; Approve with a reason. The list is the usher's (GET /v1/clusters/{c}/approvals, can_approve or can_configure); the approval is the director's privilege route, decided by the kind (store-contract.md §8)usher (read), director
Models (platform administrator)The models the model gateway offers, which are the Cluster claim's: providers and their models, the models the cluster serves itself, the two serving switches, and the switch for the gateway's own console. Each model is flagged when it cannot answer. No token is entered here (§6a)director; custodian (read, for the token's state)
CatalogsHidden. The cluster renders no catalog of its own; the screen is kept and not linked

Nothing is installed from the console. Apps come from the App Store app, a component of its own beside the console (store-contract.md §6), or from kubectl gentian apps install where there is no store.

1.2 Three interfaces, one rule​

The console is one of three interfaces the platform serves on a cluster, beside the desktop and the App Store app, and one rule holds for all of them: a UI carries no authority. It renders what an API returns and turns clicks and key presses into API calls made with the signed-in person's token. It holds no ServiceAccount with write verbs, no admin credential, no master key, and decides nothing — every "may this person see or do X" is a verdict it received from a named enforcement point (AD-1, security/principles.md §3), never a branch it evaluated.

A single-page app cannot keep a confidential client secret or a refresh token safely, so something server-side has to hold the session. That something is the edge: the Gateway runs the code flow for the whole tenant zone and forwards the token on the routes whose entry asks for it (forwardToken, routing.md). A backend-for-frontend therefore exists only for same-origin relaying and for the small amount of UI state an interface keeps — for the desktop, per-viewer preferences and the notification inbox, in the tenant's own database, granted as a requirement of its profile. It runs no code flow, holds no OIDC client secret and has no Kubernetes RBAC: it relays an identity the edge established.

InterfaceWhereFor whomShowsDoes
Desktopon the cluster, per tenanteveryonethe tiles the signed-in person may open: the usher's answer (GET /v1/tenants/{t}/tiles), filtered for the caller before it arrivesopens them. Nothing else: no install, uninstall, add-on, access, repository or store logic
Administration console (this document)on the cluster, per tenantadministratorspeople and groups, policies, resources, credentials, and under Apps every installed app: its state, who has access and whether it is for everyone, its integrations, its privilegesadministers: gives and takes away access, sets "for everyone", approves privileges, uninstalls, purges. It installs nothing and lists no catalog
App Store app (store-contract.md §6)on the cluster, per tenant; absent where the cluster offers no storepeople who may install apps in the tenantthe store's data, at once and with no store account: apps, descriptions, pictures, reviews, evaluations and reports, versions, list prices. Signed in at the store: what the tenant has acquiredacquires from the store, which is what the sign-in to the store is for; installs through the director and sets the repository credential through the custodian
The store (store-contract.md §1)outside the cluster, run by a vendor—nothing on the cluster. It serves data to the App Store app: the catalog to anyone who asks, with no token; a tenant's standing and acquisitions to a person signed in to itdata and commerce only. It never calls the cluster
Operations Consoleon the cluster, where installedwhoever looks after the clusteran add-on's own screens (data-ownership.md §5)what the add-on does; it is not part of the OS
Command linethe administrator's machineadministrators—kubectl gentian apps install … through the director. With no store it is the only way to install an app

The three interfaces on the cluster are separate components — the desktop and apps/admin-console in the gentian-ui repository, and the App Store app, which the store's vendor publishes — and none of them is a mode of another.

What comes from where. The App Store app and the console never show the same fact from two places:

From the store, shown by the App Store appFrom the cluster, shown by the console
What an app is: name, summary, description, picturesWhat is installed, at which digest
Reviews and their summaryIts state: installing, ready, failing, and why
Evaluations and reportsWho has access, and the "for everyone" setting
Editions, versions and the digest of eachIts integrations
PricesIts privileges: what it requests of the platform and what was approved
What the tenant has acquiredWhat its data is, when it is uninstalled and when it is purged

The App Store app needs one fact from the cluster to be useful — which of the store's entries this tenant has installed — and reads it from the director like any caller. It sends the store none of it.


2. Placement in the security model​

Tenant isolation rests on two independent layers (see security/design.md):

LayerMechanism
MAC backbonetenant-<name> namespaces, default-deny NetworkPolicy, Kyverno
Identity domainOne Keycloak realm per tenant

Keycloak Organizations (one realm, logical tenants) are not used: they share one user database. See iam.md §1.

Authority in the console is not read from a token's groups. Which screens a person gets is decided from two answers of the director: the verbs they hold on the tenant and on the cluster. Every relayed call is checked again by the service that receives it.

The desktop is a relay for tiles. It is the app of people who hold no privilege. Code that installs, removes or grants, shipped to every member's browser and reachable on every member's origin, is code a member can try to drive; the checks behind it would hold, but the cheapest way to be sure a path is not misused is for it not to be there. So the desktop carries no install, uninstall, purge, add-on, access-granting, repository or store logic — no route in its backend, no screen in its bundle — and the administrative paths live in the two interfaces only administrators are served: this console and the App Store app, which an administrator's tiles lead to. The desktop does not know what an administrator is: the usher's answer comes back already filtered for the caller, and can_launch derives from the app's own entitlement group, not from tenant membership. Its AI widget calls the model gateway with the desktop component's own granted credential, an llm requirement of its profile, never a master key.


3. Identity topology​

Realms, sign-in, the group names and the member/administrator split are described once, in iam.md §1.1–§1.3. What the console relies on:

  • People and groups of a tenant are in the tenant's own realm; the platform tenant's are in the kernel realm.
  • The login name is the email address. gentian.inviteEmail is an optional second address used for the invitation, password reset and recovery.
  • Administrator and member are separate accounts: can_use on an app excludes a tenant's administrators, so an administrator's desktop shows administration tiles and a member's shows apps.

4. What the screens do​

4.1 One console, scoped by what the person holds​

The same image runs for every tenant. The screens marked platform administrator in §1.1 are shown only to a person who holds the cluster's verbs; they are reachable from any console such a person can open.

The console's context (/admin/context) is refused to anyone who does not hold can_administer on the tenant, with one widening: a person who holds cluster#can_approve — the security officer — is let in with isTenantAdmin: false and sees the Approvals screen alone, nothing of the tenant's. A security officer who holds the role by name holds it signed in to their own tenant (iam.md §1.1c) and acts in that tenant's console; the App Store's install step links them there (?tab=approvals). The tenant console keeps its per-app Approve on the Apps screen for the egress requests, which are the tenant's perimeter approver's (can_approve_privilege).

4.2 First administrators​

  • Platform administrator. The installer creates admin@<KERNEL_DOMAIN> in the kernel realm with no password and issues a single-use activation link (./install.sh --activate-admin for a new one).

  • Tenant administrator. Provisioning creates the realm, its groups and the administrator account (status.adminEmail on the Tenant), a member of gentian:tenant:<t>:admins, with no password. The registrar issues the activation link: kubectl gentian tenants activate-admin <tenant>, or the Tenants screen.

    Tenants → Create asks for the administrator's recovery address. With an address, the console hands it to the registrar as soon as the tenant is committed, through its backend and with the person's token, and says that the invitation will be mailed to that address when the tenant is ready and that the page can be closed. The console keeps nothing: the registrar keeps the request and sends the invitation (iam.md §1.4). While the page stays open it shows the request's state. In the list, a tenant with a pending request shows Invitation pending in place of Activate administrator; when the invitation expired or could not be mailed, the button is back. Without an address nothing changes: the console waits for the tenant and shows the link once. If the registrar cannot keep the request, the console says so and waits with the page open.

No password is derived, stored or printed for either (iam.md §1.4).

Inviting a member. Members → Invite: address, name, optional recovery address, the app groups to join (an app installed for everyone is preselected). The registrar creates the account with no password and has Keycloak mail a link with VERIFY_EMAIL and UPDATE_PASSWORD.

4.3 Password reset and recovery​

ActionMechanism
Reset by an administratorThe registrar has Keycloak mail a link with UPDATE_PASSWORD, to gentian.inviteEmail if set, else the primary address

4.3a Removing a member​

Members → open the member → Remove member. The account is deleted.

Where the member has a mailbox on the cluster's own mail server, a dialog asks what becomes of it, with nothing selected:

  • Archive the mailbox. The mail is kept, out of the live mailboxes. Nobody receives or signs in at the address; a new person given the address starts empty.
  • Delete the mailbox. The mail is destroyed. It cannot be undone, and is confirmed a second time.

The button stays off until one is chosen. Switching a member off (May sign in) asks nothing and keeps the mailbox.

Mailboxes of removed people, on the same screen, lists what was decided for each and what became of it: being archived or deleted, archived (with who chose, when, and its size), deleted, or failed with the reason. An archived mailbox is deleted from that list, after a confirmation. Who may do all of this: whoever may manage the tenant's people (can_manage_users). mail.md §5c says what happens on the mail server.

4.4 Second factor​

Keycloak's built-in TOTP. An administrator requires an authenticator for one person (at the next sign-in, or by a mailed link) and removes a person's authenticators for a lost device. A realm-wide rule ("everybody must have one") cannot be set from the console: Keycloak expresses it as an authentication flow, not a realm setting. WebAuthn and passkeys are not configured.

4.5 Security policies​

Policy areaSettings
PasswordMinimum length, digits, lower and upper case, special characters, history, maximum age
SessionIdle time, maximum length, remember-me
LockoutOn or off, failures allowed, lockout duration

The policy is declared state: the director commits it as security-policy.yaml beside the tenant's manifest (can_set_policy), and the tenant Composition writes it into the realm. Nothing in this path holds a Keycloak credential, and a realm rebuilt from scratch comes back with its policy.

This is the one way to set it. The console relays the signed-in person's call to the director and holds no right of its own; the registrar has no action for the password policy and its Keycloak client holds no role over realm settings. The Composition always declares the realm's password policy: a tenant that states no password rules has an empty one, which is Keycloak's own default, so rules taken out of git are taken out of the realm, and a policy set by hand at Keycloak is replaced by what git says.

4.6 Sessions​

The console has no Sessions screen.

What ends a session when an administrator switches a member off or removes them is the realm's refusal to renew it, within five minutes; nobody ends it by hand. The whole of it is in iam.md §1.12.

4.7 Audit​

The screen shows changes to declared state: every such change is a commit the director authored as the person, with the relation and object that permitted it. It needs no store of its own.

Not in it: sign-ins, refused requests and reads of data, for which no store exists, and changes to people. For those the registrar keeps a record of who asked and what permitted it, with a retention period, beside Keycloak's own admin events; the console does not show either. The screen says what it leaves out. There is no export.

4.8 Resources​

Maps to the OS question "how much of this machine may this account use, and how much is it using?" — with the part a desktop OS has no answer for: what that came to over a month.

Full design in resource-plans.md; what matters to the console is the shape.

CapabilityTenant adminPlatform admin
Current ceilingOwn tenant: enforced limits paired with committed use, and live consumption where a metrics source existsAny tenant, plus an all-tenants headroom table
Plan catalogThe cluster's plans, each one the tenant could not move to carrying its reasonSame
Another planThe cluster's notice on where to order one; no choiceChanged in the Operations Console or with kubectl gentian resources set
Usage historyOwn tenant, per resource, over 7d–12mAny tenant
Billed intervalsThe stretches the window resolves to, with the SKU in effect over eachSame

Three properties make this different from an editable quota field:

The API takes a plan name, never a quantity. A ceiling that can be any number can be any number that was never sold, so every ceiling reachable through the console is one the platform has priced — which is what makes a month resolve to SKUs rather than to numbers somebody downstream has to interpret.

The write is a commit, not a patch, and it is the cluster's. Moving a tenant is the director committing resource-plan.yaml to the deployments repository as the cluster's administrator who asked, after checking can_configure on the cluster and validating the choice against the operator's answer for that tenant. A tenant orders another plan where the cluster's notice says. The reads are the operator's, relayed by the usher; kubectl gentian resources reads them the same way and writes through the director (set).

A downgrade below current use is refused. Kubernetes does not evict pods to fit a shrunken quota — it refuses the next create — so shrinking a tenant too far fails silently, hours later, at the next restart. The console names the resource and both numbers instead.

A plan change is audited as the commit it is, authored by the person and trailered with the decision that allowed it, and as the plan event the operator records once the change has landed; a refusal is the director's decision-log entry. The attempt is the interesting half when a tenant later asks why nothing changed.


5. Notifications​

An administrator publishes a notice (a title and a text) to the people of the tenant. Publishing is an action at the director (can_administer), which records who published it. Notices are stored in the tenant's own database, in the table the desktop reads; the console keeps no copy and reads the history through the usher.


6. App access​

Who may use an installed app is membership of the app's Keycloak group, gentian:tenant:<t>:app:<profile>, and nothing else. The console sets it: per person on Members, per group on Groups, and on Apps whether the app is for everyone. How membership becomes a decision at the front door and a tile on the desktop is iam.md §1.6.

Accounts inside apps. The platform does not create or remove a person's account in an app; an app makes one at the person's first sign-in. The one thing synchronized is who administers an app: members of gentian:tenant:<t>:app-admins are given the administrator role an app's profile declares (spec.hooks.provisioning, iam.md §1.8).


6a. Models​

The model gateway serves the models the Cluster claim declares and no others (llms.md §5). The Models tab is where the cluster's administrator reads and changes them: GET and PUT /v1/clusters/{c}/models of the director, under can_audit and can_configure on the cluster. A tenant's administrator has no such tab and is refused by the director.

A save is one commit of the claim. What is not on the screen when it is committed is removed from the gateway. The gateway restarts with the new list once Argo CD has synced.

The tab flags a model that cannot answer, without asking the gateway: a model the cluster would serve itself is "not served", because the platform starts no server for it; a provider's model is "token missing" or "no credential" when the custodian's list says so. A token that is there is shown as supplied, not as working.

A provider's token is entered on the Credentials tab, under llm-provider-<name>. The cluster declares that credential for every provider on the claim, so a new provider's appears there once the cluster has applied the commit. Which property of it holds the token follows from the provider's name; the tab shows it and does not ask for it. The director refuses a provider address that is not a public https address.

The tab also switches the gateway's own console on and off (llm.console.enabled), with a warning that it opens a public address for the gateway's console and API. The route and the gateway's network rule both follow that commit.


7. Platform roles​

Which Keycloak group holds admin and breakGlass is stated on the Cluster claim (spec.platformRoles; admin defaults to gentian:platform:admin, breakGlass has no holder unless a group is named). The operator projects it into OpenFGA. Who is in those groups is not changed from this console by hand: the registrar refuses every write to a platform role's group and to its members. Every other cluster role -- security officer, auditor, service admin, shared-apps admin, perimeter approver -- is held by name: the Role holders page lists the candidates (every person of the platform tenant and every other tenant's administrators), the cluster's administrator picks one, a role and a reason, optionally an expiry, and the director commits the choice to the Cluster claim (spec.platformAccess.roleHolders); the operator writes the role for that account of that tenant's realm, over the cluster or, for a tenant's perimeter approver, over that tenant (iam.md §1.1c, commands.md §7). A platform administrator administers a tenant the cluster operates through that tenant's console, and signs in to it only where the tenant's administrators switched that on (Security → Platform administrators may enter this tenant, Tenant.spec.platformAccess.adminsEnter); the Tenants page then offers Enter tenant.


8. Non-goals​

These stay outside the console:

  • Installing apps (the App Store app, or the command line)
  • Scheduled backups, the backup policy and its destinations (the Operations Console)
  • Editing NetworkPolicy, Kyverno policy or other MAC rules
  • Authoring a ComponentProfile or a catalog

9. References​

TopicLocation
Roles and sign-iniam.md
Hosts, the front door, sessionsrouting.md
MAC backbonesecurity/design.md
OpenFGA modelauthz/model/v1/model.fga
Tenant identity objectsiam.md §1.9
The App Store appstore-contract.md
Resource plans and usageresource-plans.md