feat(cloud): remove static credentials and add host console
Tests / Test No test results found

This commit is contained in:
2026-07-13 19:45:53 +08:00
parent efeb3eb926
commit c162c2501b
61 changed files with 3118 additions and 1221 deletions
@@ -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
@@ -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.