Files
2026-07-13 19:45:53 +08:00

119 lines
6.0 KiB
Markdown

## Context
The Cloud API currently chains configured bearer credentials, browser user
sessions, and repository-backed Host credentials. Three JSON environment
variables populate the first provider and a second provider protects first Host
enrollment. The deployment image already contains the Console bundle, but
Compose separately supplies its fixed location. The Console also retains a
bearer-token compatibility screen and an administrator-only Users view.
The intended deployment has one trusted Cloud endpoint. A human operator signs
in with a Cloud account created or recovered through `device-cloud-admin`; a
new Host Agent registers itself once, generates its own high-entropy Host
secret, and persists the returned Host identity locally. Subsequent Host
requests continue to authenticate against the stored digest.
## Goals / Non-Goals
**Goals:**
- Start Cloud production without any static credential JSON or startup
requirement for one.
- Authorize browser operations by the existing user session and preserve its
role, CSRF, expiry, audit, and interactive CLI controls.
- Accept direct first Host registration, then strictly bind all later Host
operations to the registered Host identity and persisted secret.
- Remove obsolete Console and Compose configuration paths while keeping the
Jenkins-built Console available at `/console/`.
**Non-Goals:**
- No personal-access-token, service-account, or replacement SDK credential
scheme in this change.
- No user-directory view in the Console. Account provisioning and recovery
remain administration-CLI operations.
- No Host enrollment approval, rate limiting, or multi-tenant trust policy.
Network reachability to the trusted Cloud endpoint remains the boundary
explicitly chosen for this deployment.
- No change to durable task, lease, device, or user database schema.
## Decisions
### D1. Remove configured bearer providers instead of leaving empty compatibility configuration
`CloudControlConfig` will no longer parse the three credential JSON variables
or require credentials in production. The Cloud application will compose public
authorization from `UserSessionAuthProvider` only, while Host operational
authorization remains `RepositoryHostAuthProvider`. An empty production user
table is allowed so an administrator can execute the interactive bootstrap CLI
after migrations complete.
Leaving the variables optional was rejected because it preserves two
authentication modes and makes operators believe a deployed secret is needed.
Adding user-issued API tokens was rejected because the requested outcome is no
manual token configuration and it introduces a separate credential lifecycle.
### D2. Direct enrollment is a single unauthenticated bootstrap operation
The enrollment endpoint accepts a fresh Host's generated instance identifier
and candidate secret without an `Authorization` header. It stores only the
candidate secret digest and returns a generated `host_id`; the Host persists
that result atomically. The configured enrollment-token provider and static
Host configuration are removed. Heartbeat, device enrollment, claim, renewal,
and result endpoints remain Host-bound and require the persisted bearer secret.
An opt-in enrollment flag was rejected because the target deployment always
uses direct trust and the flag is another operational switch that can silently
block first-run setup. Keeping token enrollment as a fallback was rejected for
the same reason as D1.
### D3. Keep user administration off the Console, not out of the control plane
The Console retains login, logout, current-account display, and password
change. It removes bearer-token controls, token storage, and all Users routes,
navigation, client calls, and components. The authenticated user administration
API and `device-cloud-admin` CLI remain available for controlled provisioning
and recovery; the CLI is the documented initial-admin path.
Removing the administration API entirely was rejected because it would make
recovery tooling less complete and is unrelated to eliminating deployment
tokens.
### D4. Treat packaged static assets as an image contract
The Dockerfile sets `CLOUD_CONSOLE_STATIC_DIR=/app/console-static` after copying
the built SPA. Compose does not repeat this invariant. Application code keeps
the variable optional for source-based local development, where no bundled
directory exists.
## Risks / Trade-offs
- **[Risk] Any network caller can create a Host identity.** → The accepted
mitigation is reverse-proxy/firewall control of the trusted Cloud endpoint;
every operation after enrollment still requires the Host-generated secret.
- **[Risk] Existing integrations using static bearer tokens stop working.** →
This is intentional; create Cloud user accounts before deployment and migrate
human workflows to session login.
- **[Risk] An upgrade before an administrator exists leaves no human API
access.** → Run the documented interactive `device-cloud-admin users create`
command immediately after migration and before exposing the Console.
- **[Risk] Existing static or token-enrolled Hosts cannot rely on removed
configuration after upgrade.** → Preserve their already persisted dynamic
identities where present; otherwise perform a fresh direct enrollment.
## Migration Plan
1. Build and deploy the image with the schema migration already included.
2. Start the Cloud API using only database, production, and network settings.
3. Run `device-cloud-admin users create` interactively to bootstrap an
administrator, then verify Console login over HTTPS.
4. Start each new Host Agent with its persistent identity path; it directly
enrolls once and then uses its durable secret for future starts.
5. Remove the legacy credential JSON values from the deployment secret store.
Rollback requires restoring an earlier image and its matching static
credentials before restarting any Host that lacks a durable enrolled identity.
## Open Questions
- None. The user explicitly accepts direct Host enrollment behind the network
perimeter and does not require a Console user-directory surface.