Authority Bootstrap Modes
Audience: Operators and CI engineers preparing a fresh Stella Ops Authority deployment.
Purpose: Compare the supported ways to provision or recover administrative access, and explain which to choose for production, local, demo, and CI contexts.
Administrative users provide operator access to Authority. Provisioned service clients use the client-credentials flow independently. There are five supported ways to create or recover administrative access, each appropriate for a different deployment context. Pick one primary bootstrap mode and document the recovery path in your deployment runbook.
| Mode | Target | Trigger | Identity created |
|---|---|---|---|
| Setup wizard | Production / customer install | UI-driven, first browser visit to /setup-wizard/wizard | Operator-chosen username + password |
| Runtime default admin bootstrap | Local / CI clean install | STELLAOPS_ADMIN_PASS at Authority startup | admin in tenant default, password read from private env |
| Demo seed migration | Dev / demo / CI sample data | AUTHORITY_BOOTSTRAP_ENABLED=true at startup | Demo users/clients/ABAC rows only; historical default admin hash is scrubbed |
Driver-script setup | CI / scripted re-bootstrap | bash devops/compose/scripts/stella-connectors.sh setup | API-key-driven POST /internal/users against the running Authority |
| Compose password recovery helper | Local source checkout / compose stack | devops/compose/scripts/authority-password-set.* | Existing Authority user’s password is reset through the Stella CLI hashing path |
The setup wizard is the primary production path. Runtime default admin bootstrap is acceptable for local and scripted clean installs only when STELLAOPS_ADMIN_PASS comes from a private environment file. Demo seed migration is sample-data opt-in and must not be enabled for production.
Mode 1 - Setup Wizard
The wizard is the default production behavior on a fresh install. Authority starts with Bootstrap:Enabled=false and RunSeedMigrations=false; demo user rows are not seeded. The console UI detects the empty authority.users table on first navigation and routes the operator to /setup-wizard/wizard, which collects:
- Admin username + password (no defaults)
- Tenant name (defaults to
default) - Optional MFA enrollment
Submitting the wizard form posts to Authority’s /internal/users endpoint with the bootstrap API key (STELLAOPS_AUTHORITY_AUTHORITY__BOOTSTRAP__APIKEY) sourced from the deployment env. After the first user is created, the wizard endpoint refuses additional creates; subsequent admins must be added via the regular admin console.
Production checklist:
AUTHORITY_BOOTSTRAP_ENABLED=falsein your env file.STELLAOPS_AUTHORITY_AUTHORITY__BOOTSTRAP__APIKEYset to a unique secret and rotated on schedule.- Setup wizard reachable at
https://<your-host>/setup-wizard/wizard.
Mode 2 - Runtime Default Admin Bootstrap
Clean local compose defaults keep AUTHORITY_BOOTSTRAP_ENABLED=false; Seed-category migrations stay manual-only. The standard plugin still bootstraps the structural first administrator when defaultAdminBootstrap.enabled=true in etc/authority/plugins/standard.yaml.
That default also applies the Startup-category human CLI scope ceiling (migration 034). Existing surplus stellaops-cli grants are removed from the column and JSON mirror without enabling Seed migrations or running a manual repair script. The migration does not create the client; normal Standard provisioning remains its creation path. See the CLI scope census.
Authority reads the admin password from the environment variable configured by defaultAdminBootstrap.passwordEnvironmentVariable (default STELLAOPS_ADMIN_PASS). If the admin user is missing or has no password hash and the variable is unset or blank, startup fails closed with a clear configuration error. Stella Ops does not provide a repository default password.
The initial password must also satisfy the configured Standard-provider password policy. A policy rejection stops startup and records password_policy_violation; correct the private environment value and restart so normal bootstrap can converge. An existing populated credential remains unchanged when bootstrap runs again, including when the environment value has become invalid. A rejected optional bootstrapUser registration creates no account or additional role grant and leaves an existing account’s credential and privileges unchanged.
Accounts with an absent or empty password hash cannot use a password grant. They receive the existing OAuth invalid_grant response (HTTP 400), with the normal failed-attempt, lockout and audit handling. Existing lockout is checked first. Populated credentials still use the configured crypto provider and password policy. Use the supported provisioning or credential-recovery path; do not write a password hash directly to the database.
These refusal and bootstrap rules were verified against source commit 90f93b9dd2aba648f3dfaf04d4a9a94264c35fcc with the real Standard provider, source Authority host and isolated PostgreSQL. Recheck the provider controls with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StellaOps.Authority.Plugin.Standard.Tests.csproj -Class '*StandardUserCredentialStoreTests','*StandardPluginBootstrapperTests' -BuildProjectReferences -Restore; confirm the reported test totals. Native acceptance additionally requires /connect/token to refuse an incomplete credential, then issue a normally signed token after owner provisioning, and initial bootstrap to refuse a rejected password before serving.
The runtime bootstrap also ensures the admin role, full StellaOpsScopes.All permission catalog, and role assignment exist. First-party clients are provisioned from etc/authority/plugins/standard.yaml by StandardPluginBootstrapper.
Clean local/CI checklist:
AUTHORITY_BOOTSTRAP_ENABLED=falsein your env file.STELLAOPS_ADMIN_PASSset to a private value.- After every fresh wipe-and-up, sign in as
adminwith the privateSTELLAOPS_ADMIN_PASSvalue.
Mode 3 - Demo Seed Migration
Set AUTHORITY_BOOTSTRAP_ENABLED=true only when you intentionally want demo/sample Authority rows. Authority’s Program.cs reads this and passes runSeedMigrations: true to AddAuthorityPostgresStorage, which causes the shared StartupMigrationHost to apply migrations in the seed category in addition to the default startup category.
The live seed file is src/Authority/__Libraries/StellaOps.Authority.Persistence/Migrations/S001_v1_authority_operational_baseline.sql — the collapsed pre-1.0 seed chain (S001_demo_seed.sql … S033_*). It is forward-only history and is not edited in place; the originals sit under Migrations/_archived/pre_1.0/mig061/ and are excluded from embedding, so new seed rows go into fresh forward S0xx_*.sql files after the baseline (e.g. S034_registry_read_scope.sql, S036_scope_catalog_parity_repair.sql).
Behaviour folded into that seed baseline (originally shipped as separate migrations, now archived):
- Sprint 20260525.011 (
S999_scrub_demo_admin_password.sql) clears only the known historical demo admin password hash so the runtime default admin bootstrap still owns the real password throughSTELLAOPS_ADMIN_PASS. - Sprint 20260524_002 (
S022_developer_self_service_abac_seed.sql) gave an existingdeveloperrow in tenantdefaulta fixed development password hash, the rolerole/service-release-contributorand demo ABAC attributes (serviceIds,repositoryIds,imageNamespaces,environmentIds,maxEnvironmentType=development,directUpdateAllowed=true). The baseline no longer creates that row, so a fresh install never has it. The forward scrub below removes it from existing installations.
Production safety properties:
- The seed migration is
INSERT ... ON CONFLICT DO NOTHING. Re-running it on a populated DB is a no-op. RunSeedMigrationsdefaults tofalseinStartupMigrationOptions; the seed applies only when the env flag is explicitly true.StandardUserCredentialStore.EnsureSeededAdminPasswordAsyncremains available only whenseedAdminRecovery.enabled=true. It repairs empty historical seed-admin hashes and never overwrites a non-empty hash.
Demo checklist:
AUTHORITY_BOOTSTRAP_ENABLED=truein your env file.STELLAOPS_ADMIN_PASSset to a private value for the real admin.- Demo seed rows carry no loginable account other than the runtime-bootstrapped
admin; do not deploy this configuration to production.
Bootstrap Account Contract
- No migration leaves a loginable account. The folded S999 scrub clears the historical
adminseed hash, and the runtime bootstrap then sets theadminpassword from the privateSTELLAOPS_ADMIN_PASSvalue (Mode 2). - Every other credential comes from an operator: the setup wizard, the admin API, the driver-script
setup(Mode 4), a configured optionalbootstrapUser, or an identity provider. - The historical seeded
developeraccount is cleared by two forward migrations with the same statement: Startup039_authority_developer_seed_credential_scrub.sql, which runs in every posture, and its Seed companionS067_developer_seed_credential_scrub.sql, which runs after the folded baseline when Seed migrations are enabled. Both change only tenantdefault’sdeveloperrow, and only while its password hash still equals the development seed literal:- The password hash, salt and password-change timestamps are cleared, so a password grant for
developerfails. - When the role list and attributes are also still exactly the S022 values, both are removed from the account metadata and the account is disabled (
enabled = false, the field the admin disable API sets). - An operator-rotated password leaves the row unchanged, including its grants. Changed role or attribute values are kept; only the seed credential is cleared.
- Residual on the changed-grants branch: the account stays enabled, and the scrub revokes no tokens. Authority refuses a refresh token or authorization code only for a disabled account, and a cleared password does not invalidate them. Tokens obtained with the seed password before the scrub therefore stay usable until their refresh-token lifetime ends (
RefreshTokenLifetime, 30 days by default). To end them earlier, an administrator disables the account. On the exact-seed branch the account is disabled, so its outstanding tokens are refused. - One exception comes from the applied baseline, which cannot be edited. When
S001_v1_authority_operational_baseline.sqlis still pending and Seed migrations are enabled, its S022 section first rewrites the seed hash and grants over whatever the row held. S067 then clears them, so an operator-rotateddeveloperpassword does not survive that replay. - The row is not deleted, so its audit history stays.
admin, other accounts and other tenants are not touched.
- The password hash, salt and password-change timestamps are cleared, so a password grant for
- To use a
developeraccount after the scrub, an administrator re-enables it with the admin enable action, sets a new password with the compose recovery helper (Mode 5) or another supported credential-recovery path, and grants only the roles and attributes it needs.
Mode 4 - Driver-Script setup
For CI pipelines that wipe and rebuild on every run but cannot or do not want to use runtime default admin bootstrap (for example, they test the setup wizard flow itself or need a different admin username), the driver script ships a setup subcommand:
bash devops/compose/scripts/stella-connectors.sh setup
What it does:
- Counts
authority.usersviapsqlagainststellaops_authority(overridable viaPOSTGRES_AUTH_DB). - If the table is empty, starts a one-shot
curlimages/curlcontainer attached to thestellaopsdocker network and POSTs aBootstrapUserRequestto Authority’s internal port using the bootstrap API key resolved from private env or the running Authority container. - Re-counts and exits non-zero if the row is missing.
stella-connectors.sh login auto-invokes setup once when the token endpoint returns HTTP 400 / invalid_grant / a body mentioning username; this is the self-healing path for CI scripts that do not want to reason about whether the seed migration is on or off.
CI checklist:
AUTHORITY_BOOTSTRAP_ENABLEDmay befalse.- Either
STELLAOPS_BOOTSTRAP_KEYis exported in the CI runner env, or the script’sdocker execlookup of the running Authority container resolves it. - Re-running
setupagainst a populated DB is idempotent.
Mode 5 - Compose Password Recovery Helper
Use the compose recovery helper when a local source-checkout stack already has an Authority admin row, but the password in the operator’s ignored env file is stale, unknown, or the account is locked out. This is not a fresh bootstrap mode; it updates the existing user’s credential through the Stella CLI command and Authority password hashing path from a one-shot compose container.
PowerShell:
$env:STELLAOPS_ADMIN_PASS = '<new-private-password>'
$env:STELLAOPS_ADMIN_PASS |
.\devops\compose\scripts\authority-password-set.ps1 -Username admin -Tenant default -PasswordStdin
Bash:
printf '%s\n' "$STELLAOPS_ADMIN_PASS" |
./devops/compose/scripts/authority-password-set.sh --user admin --tenant default --password-stdin
After recovery, update the ignored local .env value used by the stack, refresh browser sessions, and verify sign-in or POST https://stella-ops.local/connect/token.
The helper publishes the current Stella CLI from source before it runs the one-shot container. If publish fails with MSB3541 and Null character in path, the current helper treats that as corrupt generated MSBuild state, runs publish with build servers disabled, removes only the named generated obj directory inside this repository, and retries once. Do not wipe databases, edit authority.users, or enable demo seed to work around a stale local admin password.
Choosing Between Modes
Mode 2 (runtime default admin bootstrap) is the clean local/CI path. It is appropriate when:
- You want a usable admin without demo data.
- CI has no need to test the setup wizard itself.
- The admin password can come from a private environment variable.
Mode 3 (demo seed migration) is appropriate when:
- You explicitly need sample tenants/users/ABAC rows for demo or regression evidence.
Mode 4 (driver script) is appropriate when:
- CI tests need to exercise the setup wizard end-to-end.
- A deployment needs a different admin username or tenant than the runtime default admin bootstrap.
- A bootstrap run is needed only when the Authority user table is still empty.
Mode 5 (compose password recovery helper) is appropriate when:
- A local compose stack already has
admin, but the password is stale or unknown. - You need to clear local lockout counters while preserving the database and audit chain.
- You are operating from a source checkout and can run the compose recovery helper.
The runtime default admin bootstrap and demo seed can coexist. Demo seed no longer owns the real admin password; STELLAOPS_ADMIN_PASS remains the credential source. If the driver script also runs against a populated DB, it skips with the “admin already exists” message.
The driver-script setup path is not an existing-admin password reset. Once authority.users is populated, use the compose recovery helper instead:
$env:STELLAOPS_ADMIN_PASS = '<new-private-password>'
$env:STELLAOPS_ADMIN_PASS |
.\devops\compose\scripts\authority-password-set.ps1 -Username admin -Tenant default -PasswordStdin
printf '%s\n' "$STELLAOPS_ADMIN_PASS" |
./devops/compose/scripts/authority-password-set.sh --user admin --tenant default --password-stdin
If the helper reports MSB3541 with Null character in path while publishing the local CLI, that is corrupt generated MSBuild state, not an Authority password failure. The current helper runs publish with build servers disabled, removes only the named generated obj directory inside this repository, and retries once. If the retry still fails, treat it as a code or documentation defect and fix the setup path before continuing.
Rotation And Rollback
After any mode creates a usable admin, treat the credential as a starting point and rotate immediately. The standard plugin will not overwrite a non-empty password_hash, so an operator-rotated password is safe across restarts even if demo seed remains enabled.
To return a deployment to a “no admin” state before re-running the wizard for a clean install demo:
bash devops/compose/scripts/wipe-postgres.sh --yes(full DB wipe, see Sprint 20260504_007).docker compose ... up -dwith the desired bootstrap mode env vars.- Browse to
/setup-wizard/wizard(Mode 1), setSTELLAOPS_ADMIN_PASSand use runtime bootstrap (Mode 2), enable demo seed (Mode 3), or runstella-connectors.sh setup(Mode 4).
Partial wipes (delete the row only) are not supported. The standard plugin caches user state, and an out-of-band delete will leave dangling sessions and break the audit chain.
See Also
- Break-Glass Account Operations — emergency access when normal authentication is unavailable.
- Authority Backup & Restore Runbook — protect and recover the admin identity and signing keys.
etc/authority/plugins/standard.yaml— runtime plugin config (tenant, allowed audiences, default admin bootstrap, recovery options).docs/INSTALL_GUIDE.md— end-to-end install path covering env file preparation and bootstrap selection.
