mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
345 lines
17 KiB
Markdown
345 lines
17 KiB
Markdown
# Cluster read-only field Console
|
|
|
|
This Console is an operator-workstation process, not a resident QingLong
|
|
service. Native execution serves digest-bound assets on an ephemeral
|
|
`127.0.0.1` port and forwards only a fixed vocabulary of Run, Task, Workflow
|
|
and Copilot reads to existing Cluster APIs. It never accepts a browser-provided
|
|
URL or method. Do not deploy it as a Kubernetes workload, Ingress, shared LAN
|
|
listener, Edge component or legacy 2.x Web route.
|
|
|
|
Run cancellation availability is a second, explicit authority. It is disabled
|
|
by default. Supplying a separate Run management mTLS config and short-lived
|
|
User assertion adds only status, blocked-list and inspect reads to the same
|
|
loopback process; rearm, stop and retry remain absent.
|
|
|
|
Worker observation is a third, independent authority and is also disabled by
|
|
default. Supplying its generic Worker management mTLS config and short-lived
|
|
User assertion adds only a fixed 16-item list and point inspect to the same
|
|
loopback process. Credential mutation, drain, revoke, background polling and
|
|
automatic pagination remain absent.
|
|
|
|
Use `ql3-cluster-admin` from the same independently verified Admin release as
|
|
the Cluster deployment. D-328 also supports the image-carried
|
|
`docker-loopback.sh`: it uses an explicit container-only listener but publishes
|
|
the same port exclusively on host `127.0.0.1`. Arbitrary `0.0.0.0`, host
|
|
networking and LAN publication remain forbidden.
|
|
|
|
## Verify the distribution
|
|
|
|
The multi-architecture `qinglong3-cluster-admin@sha256:…` OCI image is the
|
|
distribution artifact. It already carries the exact launcher, examples and
|
|
this document under `/opt/qinglong/share/ql3-copilot-console/`; there is no
|
|
second Node archive or package dependency graph to trust.
|
|
|
|
From the exact reviewed source tag, run `verify-release.sh` with the immutable
|
|
image digest, repository, 40-hex source revision and full tag ref. The verifier
|
|
requires `cosign` and authenticated `gh`, then independently checks the keyless
|
|
release-workflow identity, SLSA provenance, CycloneDX SBOM and digest-bound OS
|
|
vulnerability evidence. It rejects tags and mutable image references.
|
|
|
|
```sh
|
|
deploy/console/ql3-cluster-copilot/verify-release.sh \
|
|
ghcr.io/replace-owner/qinglong3-cluster-admin@sha256:REPLACE_64_HEX \
|
|
replace-owner/qinglong \
|
|
REPLACE_40_HEX_SOURCE_REVISION \
|
|
refs/tags/v3.0.0-alpha.2
|
|
```
|
|
|
|
For a release acceptance ceremony, prefer the source-tag workstation runner.
|
|
Create a current-owner `0700` report directory and a canonical `0600` file
|
|
containing only a short-lived GitHub token. Pass canonical absolute executable
|
|
paths (resolve Homebrew symlinks first). The token is sent only to the four
|
|
`gh attestation verify` children, never in argv, the report or the `cosign` and
|
|
`docker` environments.
|
|
|
|
```sh
|
|
node scripts/ql3-cluster-admin-release-workstation-ceremony.cjs \
|
|
--image=ghcr.io/replace-owner/qinglong3-cluster-admin@sha256:REPLACE_64_HEX \
|
|
--repository=replace-owner/qinglong \
|
|
--source-revision=REPLACE_40_HEX_SOURCE_REVISION \
|
|
--source-ref=refs/tags/v3.0.0-alpha.2 \
|
|
--cosign=/canonical/absolute/cosign \
|
|
--gh=/canonical/absolute/gh \
|
|
--docker=/canonical/absolute/docker \
|
|
--github-token-file=/absolute/private/release/github-token \
|
|
--output=/absolute/private/release/admin-ceremony.json
|
|
```
|
|
|
|
In addition to the signature, provenance, CycloneDX, OS-vulnerability and
|
|
source-derived release-candidate contract
|
|
checks, the runner pulls the same immutable digest, confirms its local
|
|
`RepoDigests` binding, and runs the image-carried `evidence-verify` command on
|
|
a fixed non-sensitive vector with network disabled, a read-only root, no
|
|
capabilities, no-new-privileges, 128 MiB, 0.25 CPU and 32 PIDs. It writes one
|
|
new `0600` low-sensitive report and will not replace an existing file.
|
|
|
|
Audit that report independently against the expected public release identity:
|
|
|
|
```sh
|
|
node scripts/ql3-cluster-admin-release-workstation-ceremony-audit.cjs \
|
|
--report=/absolute/private/release/admin-ceremony.json \
|
|
--image=ghcr.io/replace-owner/qinglong3-cluster-admin@sha256:REPLACE_64_HEX \
|
|
--repository=replace-owner/qinglong \
|
|
--source-revision=REPLACE_40_HEX_SOURCE_REVISION \
|
|
--source-ref=refs/tags/v3.0.0-alpha.2
|
|
```
|
|
|
|
The offline audit verifies canonical encoding, exact structure, tool and
|
|
transcript digests, isolation claims and expected release binding. It does not
|
|
replay registry or transparency-log queries and cannot turn the unsigned local
|
|
report into an attestation or action authority. Keep the report private even
|
|
though it contains no credential or workstation identity.
|
|
|
|
After verification, pull that exact digest. The signature covers the embedded
|
|
host launcher and templates as part of the image filesystem. Operators may
|
|
either use the launcher from the matching reviewed tag or extract its exact
|
|
image-carried copy with `docker create` plus `docker cp` before execution.
|
|
|
|
## Prepare private authority
|
|
|
|
Create an absolute canonical directory with mode `0700`. For native execution
|
|
it is owned by the current operator; for the image-carried launcher it and all
|
|
files are owned by UID/GID `10001:10001`. Copy `client-config.example.json` to
|
|
`client.json`, install the reviewed
|
|
Cluster API CA as `ca.pem`, and install a separately issued `ql3c_` Project API
|
|
credential as `credential`. Give the credential only `run.read`, `task.read`
|
|
and `artifact.read`; `run.read` covers Run and Workflow observations, while
|
|
`task.read` covers Task list/detail and `artifact.read` covers an explicitly
|
|
requested Copilot output. The Console has no route for Run/Workflow start,
|
|
diagnosis creation or cancellation even if a wider credential is supplied.
|
|
|
|
To enable the optional cancellation drill-down, copy
|
|
`run-management-client-config.example.json` to `run-management-client.json`,
|
|
install its CA, client certificate and private key, and issue a short-lived
|
|
strong User assertion with only `run.read` into
|
|
`run-management-assertion.jwt`. These files are independent of the Project API
|
|
credential. Supplying only one of config/assertion is rejected before a
|
|
listener or Cluster request is created. The assertion is reread for every
|
|
explicit click so it can be rotated or removed while the Console is running.
|
|
|
|
To enable Worker observation, copy
|
|
`worker-management-client-config.example.json` to
|
|
`worker-management-client.json`, install its separate CA, client certificate
|
|
and private key, and issue a short-lived strong User assertion with only
|
|
`worker.manage` into `worker-management-assertion.jwt`. Its endpoint must be
|
|
the D-374 canonical `/api/v3/workers/management`; the Console cannot accept the
|
|
legacy credential-management path or a credential mutation command file.
|
|
|
|
To enable Plugin Package installation observation, copy
|
|
`package-management-client-config.example.json` to
|
|
`package-management-client.json`, install its CA, and issue a short-lived
|
|
strong User assertion with only `package.manage` into
|
|
`package-management-assertion.jwt`. Its endpoint is fixed to the canonical
|
|
`/api/v3/plugin-packages/management`. This authority is independent of the
|
|
Project, Run and Worker files; the Console exposes no Package command file or
|
|
lifecycle mutation.
|
|
|
|
Create an independent 256-bit browser session key without placing its value in
|
|
argv or an environment variable:
|
|
|
|
```sh
|
|
install -d -m 0700 /absolute/private/ql3-copilot-console
|
|
umask 077
|
|
node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))' > /absolute/private/ql3-copilot-console/session
|
|
chmod 0600 /absolute/private/ql3-copilot-console/client.json /absolute/private/ql3-copilot-console/ca.pem /absolute/private/ql3-copilot-console/credential /absolute/private/ql3-copilot-console/session
|
|
```
|
|
|
|
If optional Run management reads are enabled, apply the same owner-private,
|
|
canonical, non-symlink `0600` rule to `run-management-client.json`,
|
|
`run-management-ca.pem`, `run-management-client.crt`,
|
|
`run-management-client.key` and `run-management-assertion.jwt`.
|
|
Apply the same rule to `worker-management-client.json`,
|
|
`worker-management-ca.pem`, `worker-management-client.crt`,
|
|
`worker-management-client.key` and `worker-management-assertion.jwt` when
|
|
Worker observation is enabled.
|
|
Apply the same rule to `package-management-client.json`,
|
|
`package-management-ca.pem` and `package-management-assertion.jwt` when Package
|
|
observation is enabled.
|
|
|
|
Every file must be a current-owner, non-symlink, canonical regular file. The
|
|
session file contains exactly 43 base64url characters and no newline. It is a
|
|
browser-to-loopback secret only; it cannot authenticate to the Cluster API.
|
|
The `ql3c_` credential remains in the BFF process and is reread for every
|
|
upstream request so file rotation takes effect without browser disclosure.
|
|
|
|
## Check and start
|
|
|
|
Run the preflight first:
|
|
|
|
```sh
|
|
ql3-cluster-admin copilot-console --check \
|
|
--config /absolute/private/ql3-copilot-console/client.json \
|
|
--credential /absolute/private/ql3-copilot-console/credential \
|
|
--session /absolute/private/ql3-copilot-console/session
|
|
```
|
|
|
|
Append both flags to preflight and serve when the optional authority is
|
|
intended:
|
|
|
|
```sh
|
|
--run-management-config /absolute/private/ql3-copilot-console/run-management-client.json \
|
|
--run-management-assertion /absolute/private/ql3-copilot-console/run-management-assertion.jwt
|
|
```
|
|
|
|
Worker observation uses its own pair:
|
|
|
|
```sh
|
|
--worker-management-config /absolute/private/ql3-copilot-console/worker-management-client.json \
|
|
--worker-management-assertion /absolute/private/ql3-copilot-console/worker-management-assertion.jwt
|
|
```
|
|
|
|
Package observation also uses an independent pair:
|
|
|
|
```sh
|
|
--package-management-config /absolute/private/ql3-copilot-console/package-management-client.json \
|
|
--package-management-assertion /absolute/private/ql3-copilot-console/package-management-assertion.jwt
|
|
```
|
|
|
|
It validates every configured private authority and performs one unauthenticated
|
|
TLS 1.3 `GET /readyz`. It does not open the Console listener or reveal paths,
|
|
endpoint, credential, Project or Cluster identity.
|
|
|
|
Start a session with an ephemeral port:
|
|
|
|
```sh
|
|
ql3-cluster-admin copilot-console \
|
|
--config /absolute/private/ql3-copilot-console/client.json \
|
|
--credential /absolute/private/ql3-copilot-console/credential \
|
|
--session /absolute/private/ql3-copilot-console/session \
|
|
--port=0
|
|
```
|
|
|
|
Open only the exact `http://127.0.0.1:<port>` origin printed by the process,
|
|
then enter the session key from the private file. The browser keeps it only in
|
|
page memory; reloading locks the page. Stop the process with `SIGINT` or
|
|
`SIGTERM`, then remove or rotate the session file.
|
|
|
|
Unlock performs one authenticated same-origin
|
|
`POST /api/v1/session/capabilities` with one fixed schema-only body. This is a local configuration read with
|
|
`upstreamReads: 0`: it neither contacts the Cluster nor creates evidence. The
|
|
page hides optional operation groups that the process did not enable, while the
|
|
BFF independently checks the same immutable operation set before invoking any
|
|
executor. A disabled operation is masked as `404` even if a caller constructs
|
|
the fixed route manually. The thirteen base reads are mandatory; each optional
|
|
Run, Worker or Package authority group must be enabled completely or remains
|
|
disabled.
|
|
|
|
The BFF accepts at most two concurrent reads and sixteen connections, rejects
|
|
a third request without queueing, caps request bodies at 4 KiB and responses at
|
|
approximately 2 MiB, disables cache/cookies/frames/workers, and never polls.
|
|
Model text is rendered as plain text and remains untrusted advice. These limits
|
|
keep the workstation surface bounded, but this Cluster-only product is still
|
|
excluded from small router Edge/Standalone artifacts.
|
|
|
|
The default page exposes thirteen exact operations: Copilot `inspect|output`; Run
|
|
list/detail/events/steps; Task list/detail; and Workflow list plus Workflow Run
|
|
list/detail/events/steps. List responses use 32-row pages and offer an explicit
|
|
next-page read only when the upstream cursor says more data exists. There is no
|
|
automatic cascade from a list to details, steps or events, so each authority
|
|
read remains visible and intentional.
|
|
|
|
Explicit Run management authority raises the available vocabulary to sixteen:
|
|
one Project cancellation status, one fixed 16-item blocked snapshot page and
|
|
one low-sensitive Run cancellation inspection. An `attention_required` status
|
|
offers a user-clicked blocked-list step; each returned Run offers a user-clicked
|
|
inspect step. Pagination is also click-only. There is no polling, automatic
|
|
page traversal, bulk inspection or mutation route.
|
|
|
|
Explicit Worker management authority adds `worker_list|worker_inspect`. The
|
|
list is fixed at 16 items and exposes a next cursor only as a new user-clicked
|
|
read; each listed Worker can be inspected only by another explicit click. The
|
|
projection contains bounded lifecycle, compatibility, architecture, protocol
|
|
and capacity facts, but no credential, raw capability, label or Secret.
|
|
|
|
Explicit Package management authority adds `package_list|package_inspect`, for
|
|
a maximum vocabulary of twenty operations when every optional authority is
|
|
enabled. The list is fixed at 16 installations with click-only pagination and
|
|
click-only inspection. The product projection includes Package version,
|
|
installation state, availability and bounded recovery codes, but omits
|
|
installation IDs, locks, record digests, transport identity and every Package
|
|
mutation. Package authority remains disabled by default.
|
|
|
|
## Export a redacted evidence bundle
|
|
|
|
After at least one successful read, **Export redacted bundle** creates one
|
|
UTF-8 JSON file entirely in browser memory. Export performs zero BFF or Cluster
|
|
requests and includes only the evidence already visible in the current page.
|
|
The ledger retains at most the newest sixteen entries and 8 MiB of canonical
|
|
raw facts; reaching either limit removes the oldest visible and in-memory
|
|
entry. The generated file is capped at 512 KiB.
|
|
|
|
The fixed allowlist keeps operation, local observation time, reviewed status
|
|
enums, bounded numeric/boolean facts, pagination state and typed aliases.
|
|
Project, Run, Task, Workflow, Package, Step, Artifact, request and digest values
|
|
become per-bundle aliases without an exported mapping. Free text, names,
|
|
paths/URLs, commands, inputs/outputs, environment, errors/messages, credentials,
|
|
tokens, authorization, unknown fields and Copilot model text are omitted. A
|
|
canonical SHA-256 for each omitted raw fact allows a later local comparison
|
|
without embedding that fact.
|
|
|
|
The top-level SHA-256 detects changes to the redacted JSON, but it is not a
|
|
server signature, durable audit, origin attestation or action authority. Review
|
|
the file before sharing it. Generation uses no upload, clipboard/share API,
|
|
browser storage, worker, timer or service-side temporary file. **Clear page**
|
|
removes the current in-memory ledger without sending a request.
|
|
|
|
Verify the downloaded file independently with the same reviewed Admin release:
|
|
|
|
```sh
|
|
ql3-cluster-admin evidence-verify \
|
|
--bundle=/absolute/qinglong-cluster-evidence.json
|
|
```
|
|
|
|
The verifier performs one offline read-only file read, rejects noncanonical
|
|
JSON and structural/redaction/typed-alias drift, and independently recomputes
|
|
the bundle digest. Its output deliberately says that raw fact digests were not
|
|
recomputed because those sensitive facts are absent; it does not claim a
|
|
server signature, attestation, durable audit or action authority. The command
|
|
does not upload, mutate or write the bundle.
|
|
|
|
## Run the verified image
|
|
|
|
Create a dedicated Docker network whose egress is restricted by the host
|
|
firewall to DNS and the exact Cluster API destination. Copy
|
|
`host-environment.example.json` values into the launcher environment, replacing
|
|
the image with the verified digest and selecting one unused host port. The
|
|
launcher rejects `bridge|default|host|none`, mutable tags, noncanonical private
|
|
roots, ports outside `1024..65535` and unknown resource classes.
|
|
|
|
The image launcher keeps Run management disabled unless
|
|
`QL3_COPILOT_CONSOLE_RUN_MANAGEMENT=enabled` is set. Enabled mode reads
|
|
`run-management-client.json` and `run-management-assertion.jwt` from the same
|
|
read-only private mount; all certificate paths in the config must point into
|
|
that mount. `disabled` is the only default and unknown values fail closed.
|
|
Worker observation follows the independent
|
|
`QL3_COPILOT_CONSOLE_WORKER_MANAGEMENT=enabled` switch and reads only its
|
|
Worker config/assertion pair. Package observation follows
|
|
`QL3_COPILOT_CONSOLE_PACKAGE_MANAGEMENT=enabled` and reads only its Package
|
|
config/assertion pair. Enabling one management authority does not enable either
|
|
of the others.
|
|
|
|
| Resource class | Memory | CPU | PIDs | Console reads |
|
|
| --- | ---: | ---: | ---: | ---: |
|
|
| `compact` | 192 MiB | 0.25 | 32 | 2, no queue |
|
|
| `standard` | 512 MiB | 1 | 64 | 2, no queue |
|
|
|
|
Validate private authority and the upstream unauthenticated TLS 1.3 readiness
|
|
route without opening or publishing a listener:
|
|
|
|
```sh
|
|
deploy/console/ql3-cluster-copilot/docker-loopback.sh check
|
|
```
|
|
|
|
Then start the foreground session:
|
|
|
|
```sh
|
|
deploy/console/ql3-cluster-copilot/docker-loopback.sh serve
|
|
```
|
|
|
|
The launcher fixes non-root UID, read-only root, no capabilities,
|
|
no-new-privileges, bounded memory/CPU/PIDs, an 8 MiB noexec tmpfs, one read-only
|
|
private mount and `--pull never`. `serve` alone adds
|
|
`--publish 127.0.0.1:<port>:<port>/tcp`; `check` publishes nothing. The
|
|
container listener is reachable only through this reviewed publication and
|
|
continues to require the 256-bit browser session token plus exact Host/Origin.
|