This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-07-13
|
||||
@@ -0,0 +1,118 @@
|
||||
## 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.
|
||||
@@ -0,0 +1,49 @@
|
||||
## Why
|
||||
|
||||
The Cloud deployment currently requires operators to hand-maintain public API,
|
||||
Host, and enrollment bearer tokens in Compose environment variables. Cloud
|
||||
operator accounts now provide the human authentication boundary, while a Host
|
||||
Agent can establish its own durable identity by registering directly with the
|
||||
trusted control plane. Retaining both models makes deployment error-prone and
|
||||
leaves secrets in configuration without serving the intended workflow.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING** Remove configured public, static Host, and enrollment bearer
|
||||
credentials from the Cloud Control Plane configuration and deployment
|
||||
examples.
|
||||
- Authorize human Cloud API access exclusively through persistent Cloud user
|
||||
sessions; remove the Console's bearer-token path and user-directory UI.
|
||||
Interactive `device-cloud-admin` commands remain the account provisioning and
|
||||
recovery surface.
|
||||
- Enable a fresh Host Agent to register directly with the trusted Cloud API
|
||||
without a pre-shared enrollment token, then use its persisted, cloud-bound
|
||||
secret for all later Host operations.
|
||||
- Bake the packaged Console static directory into the production image rather
|
||||
than repeating it in Compose, and remove Compose entries whose values merely
|
||||
duplicate application defaults or have no runtime effect.
|
||||
- Update deployment documentation and tests to describe and enforce the
|
||||
tokenless production flow.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `cloud-operator-authentication`: Cloud operator sign-in, CLI account
|
||||
provisioning, and a token-free Console experience.
|
||||
- `credentialless-host-bootstrap`: Direct Host registration and durable
|
||||
post-registration identity without static deployment credentials.
|
||||
|
||||
### Modified Capabilities
|
||||
- `platform-sdk`: Replace configured bearer credentials as the production
|
||||
authorization prerequisite with Cloud user-session authorization.
|
||||
- `host-agent-protocol`: Add unauthenticated first registration while retaining
|
||||
host-bound authentication for every subsequent Host operation.
|
||||
|
||||
## Impact
|
||||
|
||||
Affected areas include the Cloud control configuration and authentication
|
||||
composition, public and internal Cloud routers, Host Agent configuration and
|
||||
enrollment client, Console UI, Docker/Compose deployment assets, tests, and
|
||||
`docs/CLOUD_DEPLOYMENT.md`. Existing deployments using the removed static token
|
||||
variables must create an administrator and re-enroll Hosts through the new
|
||||
flow before upgrading.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cloud operator sessions are the only human API authentication path
|
||||
The system SHALL authorize human Cloud API operations through persistent Cloud
|
||||
user sessions and SHALL not require configured static bearer credentials for
|
||||
production startup or normal Console operation.
|
||||
|
||||
#### Scenario: Production starts without credential JSON
|
||||
- **WHEN** the Cloud API starts in production with a current database schema
|
||||
and no configured public, Host, or enrollment credential JSON
|
||||
- **THEN** the application starts, exposes health and authentication routes,
|
||||
and rejects unauthenticated protected API requests
|
||||
|
||||
#### Scenario: Signed-in operator uses a protected API route
|
||||
- **WHEN** an enabled Cloud user has a valid session and calls an operation
|
||||
allowed by the user's role scopes
|
||||
- **THEN** the operation is authorized without an Authorization bearer header
|
||||
|
||||
### Requirement: The Console contains no token or user-directory management surface
|
||||
The Console SHALL offer login, logout, current-account display, and password
|
||||
change, and SHALL not render bearer-token controls, token persistence, Users
|
||||
navigation, or user-management forms.
|
||||
|
||||
#### Scenario: Unauthenticated operator opens the Console
|
||||
- **WHEN** no valid user session exists
|
||||
- **THEN** the Console presents the username/password login flow without an API
|
||||
token alternative
|
||||
|
||||
#### Scenario: Administrator opens the Console
|
||||
- **WHEN** an administrator signs in
|
||||
- **THEN** the Console presents normal authorized operational views but no
|
||||
user-directory navigation or account lifecycle form
|
||||
|
||||
### Requirement: Account provisioning and recovery remain interactive administration operations
|
||||
The system SHALL retain interactive, non-echoed administration CLI commands to
|
||||
create, reset, enable, and revoke Cloud user accounts without accepting
|
||||
passwords through Compose configuration or command-line arguments.
|
||||
|
||||
#### Scenario: Initial administrator is created after deployment
|
||||
- **WHEN** an operator runs `device-cloud-admin users create` against the
|
||||
migrated Cloud database and completes the password prompts
|
||||
- **THEN** an enabled administrator account is created without a deployment
|
||||
token or plaintext password in process arguments
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Fresh Hosts register directly with the trusted Cloud API
|
||||
The Cloud API SHALL accept a first Host registration without a configured
|
||||
enrollment token or Authorization header, assign a durable `host_id`, and store
|
||||
only the digest of the Host-generated high-entropy secret.
|
||||
|
||||
#### Scenario: Fresh Host registers without static configuration
|
||||
- **WHEN** a Host Agent with no cached identity sends a valid instance
|
||||
identifier and generated Host secret to the enrollment endpoint
|
||||
- **THEN** the Cloud API creates or returns the durable Host identity and does
|
||||
not require a pre-shared deployment credential
|
||||
|
||||
#### Scenario: Registration is retried by the same Host
|
||||
- **WHEN** the same Host retries registration with its original instance
|
||||
identifier and candidate secret after losing the response
|
||||
- **THEN** the Cloud API returns the existing Host identity without creating a
|
||||
duplicate Host row
|
||||
|
||||
### Requirement: Registered Hosts authenticate all later operational requests
|
||||
The system SHALL require the persisted Host secret for device enrollment,
|
||||
heartbeat, claim, renewal, and result operations after first registration, and
|
||||
SHALL bind each accepted request to its registered `host_id`.
|
||||
|
||||
#### Scenario: Registered Host sends a heartbeat
|
||||
- **WHEN** a Host presents its persisted secret for its own Host identity
|
||||
- **THEN** the Cloud API accepts the heartbeat subject to normal validation
|
||||
|
||||
#### Scenario: Caller attempts a Host operation without its secret
|
||||
- **WHEN** a caller accesses any post-registration Host operation without a
|
||||
valid secret bound to the path Host identity
|
||||
- **THEN** the Cloud API rejects the operation without changing Host state
|
||||
|
||||
### Requirement: Host Agent configuration contains no static Cloud credential
|
||||
The Host Agent SHALL use its cached identity when present and otherwise perform
|
||||
direct registration, without `HOST_AGENT_HOST_ID`, `HOST_AGENT_TOKEN`, or
|
||||
`HOST_AGENT_ENROLLMENT_TOKEN` configuration.
|
||||
|
||||
#### Scenario: Fresh Host starts with only its local state path
|
||||
- **WHEN** a Host Agent starts with no cached Cloud identity and no static Host
|
||||
credential environment values
|
||||
- **THEN** it generates and persists an identity through direct registration
|
||||
before starting heartbeat or assignment polling
|
||||
@@ -0,0 +1,39 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Host identity can be established by direct registration
|
||||
The internal Host Agent API SHALL accept a fresh Host registration without a
|
||||
pre-shared deployment credential, SHALL assign a durable Host identifier, and
|
||||
SHALL store only the digest of the Host-generated secret for subsequent
|
||||
host-scoped authentication.
|
||||
|
||||
#### Scenario: Fresh Host establishes an identity
|
||||
- **WHEN** a fresh Host Agent submits a valid registration request containing
|
||||
its instance identifier and generated secret
|
||||
- **THEN** the control plane returns a durable Host identifier and stores only
|
||||
the secret digest bound to that Host
|
||||
|
||||
#### Scenario: Registration request is retried
|
||||
- **WHEN** the same Host Agent repeats registration with its original instance
|
||||
identifier and secret after a lost response
|
||||
- **THEN** the control plane returns the existing Host identifier without
|
||||
creating a second Host identity
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Host identity is authenticated and bound to one host id
|
||||
After direct registration, the internal Host Agent API SHALL require a
|
||||
host-scoped bearer principal for all Host operational requests and SHALL reject
|
||||
any request that attempts to act for a `host_id` different from the
|
||||
authenticated principal's bound host. The initial registration endpoint is the
|
||||
only exception and establishes that bound principal.
|
||||
|
||||
#### Scenario: Host authenticates as itself after registration
|
||||
- **WHEN** a Host Agent presents the persisted generated secret bound to its
|
||||
requested `host_id`
|
||||
- **THEN** the internal API authorizes permitted heartbeat, claim, renewal, and
|
||||
result operations
|
||||
|
||||
#### Scenario: Host attempts to impersonate another host
|
||||
- **WHEN** valid credentials bound to host A are used on a request for host B
|
||||
- **THEN** the internal API rejects the request without reading or modifying
|
||||
host B's state
|
||||
@@ -0,0 +1,36 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Pluggable authentication hook with a safe default
|
||||
The system SHALL evaluate every platform SDK route through a configurable
|
||||
scope-aware `AuthProvider` hook. The deployable Cloud Control Plane SHALL
|
||||
reject anonymous access outside an explicit insecure-development override, but
|
||||
production startup SHALL not require configured static bearer credentials.
|
||||
|
||||
#### Scenario: Production starts without configured bearer credentials
|
||||
- **WHEN** the Cloud Control Plane is configured as production with a usable
|
||||
database and no static bearer credential configuration
|
||||
- **THEN** startup succeeds and protected platform routes reject unauthenticated
|
||||
requests
|
||||
|
||||
#### Scenario: Explicit local anonymous override
|
||||
- **WHEN** a non-production operator explicitly enables the insecure
|
||||
anonymous-development override
|
||||
- **THEN** platform routes may use an anonymous principal and the application
|
||||
records that insecure mode is active
|
||||
|
||||
#### Scenario: Custom AuthProvider is honored
|
||||
- **WHEN** a caller configures a custom `AuthProvider` that rejects a request
|
||||
or omits its required scope
|
||||
- **THEN** the platform SDK route returns an authentication or authorization
|
||||
error without executing its handler operation
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Python SDK supports authenticated requests
|
||||
**Reason**: The deployment no longer provisions or accepts static bearer
|
||||
credentials for public platform access; human operations use Cloud user
|
||||
sessions.
|
||||
|
||||
**Migration**: Replace bearer-token SDK workflows with authenticated Console
|
||||
user-session workflows. A non-human service credential model is outside this
|
||||
change and must be designed separately before reintroducing SDK automation.
|
||||
@@ -0,0 +1,30 @@
|
||||
## 1. Cloud authentication configuration
|
||||
|
||||
- [x] 1.1 Remove configured public and static-Host bearer credential parsing and production credential validation from `CloudControlConfig`, while retaining the optional trusted-proxy setting used by login throttling.
|
||||
- [x] 1.2 Compose public authorization from Cloud user sessions and dynamic repository-backed Host credentials only; remove configured enrollment-token authentication.
|
||||
- [x] 1.3 Update Cloud configuration and application tests to prove production starts without JSON credentials, protected API routes reject anonymous requests, and user sessions retain scoped authorization.
|
||||
|
||||
## 2. Direct Host bootstrap
|
||||
|
||||
- [x] 2.1 Remove static Host and enrollment-token settings from Host Agent configuration while retaining persistent identity-file validation and control-plane URL overrides.
|
||||
- [x] 2.2 Change the Host enrollment client and identity resolution to perform unauthenticated first registration and persist the returned Host identity and generated secret.
|
||||
- [x] 2.3 Change the Cloud enrollment route to accept direct registration, preserve idempotency for the same instance, and require the persisted Host secret for all later Host operations.
|
||||
- [x] 2.4 Add Cloud and Host Agent tests for direct registration, idempotent retry, missing/invalid post-registration Host credentials, and absence of static credential settings.
|
||||
|
||||
## 3. Console token and user-directory removal
|
||||
|
||||
- [x] 3.1 Remove bearer-token compatibility state, controls, client behavior, and related tests from the Cloud Console.
|
||||
- [x] 3.2 Remove Console Users navigation, views, client calls, and tests while retaining login, logout, current-user, and password-change behavior.
|
||||
- [x] 3.3 Add or update Console tests covering session-only login and the absence of token and user-directory UI paths.
|
||||
|
||||
## 4. Deployment contract and documentation
|
||||
|
||||
- [x] 4.1 Bake the Console static directory into the Docker image environment and remove it, credential JSON, static Host credentials, and redundant defaults from Compose and `.env.example`.
|
||||
- [x] 4.2 Update deployment documentation for user-account bootstrap, direct Host registration, required runtime configuration, rollback, and the actual deploy Compose topology.
|
||||
- [x] 4.3 Update deployment-configuration tests to enforce the reduced environment contract and image-provided Console path.
|
||||
|
||||
## 5. Verification and change validation
|
||||
|
||||
- [x] 5.1 Run focused Cloud API, Cloud platform, Host Agent, deployment, and Console test suites; fix failures caused by the removed credential paths.
|
||||
- [ ] 5.2 Render `compose.deploy.yaml` with representative non-secret settings and verify the Cloud API command, health check, and environment contract.
|
||||
- [ ] 5.3 Run the full non-integration workspace suite, Console build, and strict OpenSpec validation; record any environment-gated checks that cannot run locally.
|
||||
Reference in New Issue
Block a user