mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
146 lines
6.2 KiB
Markdown
146 lines
6.2 KiB
Markdown
# QingLong 3.0 Worker Credential Management External OIDC Ceremony
|
||
|
||
This operation records one external-identity, two-User management ceremony. It creates and approves a
|
||
secret-free Worker credential plan, then inspects it without consuming the approval or executing credential
|
||
delivery.
|
||
|
||
It is a short-lived operator command, not a controller. Run it only against an already reviewed Worker
|
||
management endpoint. A successful local/K3s fixture is not a substitute for this external report.
|
||
|
||
## Private inputs
|
||
|
||
Prepare five canonical absolute paths:
|
||
|
||
- mode 0600 production client config using the exact Worker endpoint path, TLS server name, CA file, client
|
||
certificate file, matching private-key file and a 1–30 second timeout;
|
||
- mode 0600 requester assertion;
|
||
- mode 0600 reviewer assertion for a different User from the same external issuer;
|
||
- mode 0600 ceremony JSON;
|
||
- an unused output path in a canonical private directory.
|
||
|
||
Both assertions must use:
|
||
|
||
```text
|
||
aud=qinglong3-worker-credential-management
|
||
typ=ql3-worker-credential-management+jwt
|
||
ql3_purpose=worker-credential-management
|
||
```
|
||
|
||
The client certificate is a separate transport-possession factor. It must be issued by the manager's reviewed
|
||
client CA, be absent from the current CRL and must not be reused as an assertion-signing key or Worker execution
|
||
identity. A valid certificate never substitutes for either external User assertion.
|
||
|
||
The ceremony JSON has this exact top-level shape:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"planRequest": {
|
||
"actionRef": "worker-credential:REVIEWED_WORKER:REVIEWED_GENERATION",
|
||
"authorityProjectId": "REVIEWED_AUTHORITY_PROJECT",
|
||
"action": "rotate",
|
||
"deliveryId": "REVIEWED_UUID",
|
||
"workerId": "REVIEWED_WORKER",
|
||
"credentialId": "REVIEWED_NEW_CREDENTIAL",
|
||
"previousCredentialId": "REVIEWED_PREVIOUS_CREDENTIAL",
|
||
"credentialNotBeforeAtMs": 0,
|
||
"credentialExpiresAtMs": 0,
|
||
"deploymentTargetDigest": "REVIEWED_64_LOWERCASE_HEX_DIGEST",
|
||
"deploymentGeneration": "REVIEWED_GENERATION"
|
||
},
|
||
"approvalRequestId": "REVIEWED_APPROVAL_ID",
|
||
"approvalAuditEventId": "REVIEWED_UUID",
|
||
"requesterDecisionId": "REVIEWED_SELF_DENY_PROBE_ID",
|
||
"requesterDecisionAuditEventId": "REVIEWED_UUID",
|
||
"reviewerDecisionId": "REVIEWED_REVIEWER_DECISION_ID",
|
||
"reviewerDecisionAuditEventId": "REVIEWED_UUID",
|
||
"decisionReasonCode": "reviewed",
|
||
"inspectionId": "REVIEWED_INSPECTION_ID"
|
||
}
|
||
```
|
||
|
||
Replace both timestamps with valid future millisecond values accepted by the Worker management plan contract.
|
||
Use new identifiers dedicated to evidence; do not reuse a production delivery that an executor may consume.
|
||
|
||
## Run and audit
|
||
|
||
```bash
|
||
export QL3_WORKER_CREDENTIAL_MANAGEMENT_LIVE_CEREMONY=1
|
||
|
||
pnpm evidence:worker-management-live-ceremony:ql3 -- \
|
||
--config=/absolute/private/client.json \
|
||
--requester-assertion=/absolute/private/requester.jwt \
|
||
--reviewer-assertion=/absolute/private/reviewer.jwt \
|
||
--ceremony=/absolute/private/ceremony.json \
|
||
--output=/absolute/private/worker-management-ceremony.json
|
||
|
||
unset QL3_WORKER_CREDENTIAL_MANAGEMENT_LIVE_CEREMONY
|
||
|
||
pnpm audit:worker-management-live-ceremony:ql3 -- \
|
||
--report=/absolute/private/worker-management-ceremony.json
|
||
```
|
||
|
||
The runner performs exactly five calls: requester plan, requester propose, requester self-decision rejection,
|
||
reviewer decision and reviewer inspect. It fails if self-decision is accepted, if the resulting approval is not
|
||
approved by the reviewer, or if inspect observes a dispatch/consumption.
|
||
|
||
The report contains no raw assertion, subject, JTI, request ID, Worker identifier, Project identifier, token,
|
||
Secret, DSN or private key. Retain it with the IdP/operator change record and the independent durable-audit
|
||
evidence.
|
||
|
||
## Collect independent durable-audit evidence
|
||
|
||
Create a short-lived PostgreSQL login role outside the QingLong migration stream. It must not inherit or be
|
||
granted any QingLong runtime role:
|
||
|
||
```sql
|
||
CREATE ROLE ql3_worker_management_evidence
|
||
LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE
|
||
NOREPLICATION NOBYPASSRLS;
|
||
|
||
GRANT CONNECT ON DATABASE qinglong
|
||
TO ql3_worker_management_evidence;
|
||
GRANT USAGE ON SCHEMA ql3
|
||
TO ql3_worker_management_evidence;
|
||
GRANT SELECT ON
|
||
ql3.worker_credential_management_plans,
|
||
ql3.approval_requests,
|
||
ql3.security_audit_events
|
||
TO ql3_worker_management_evidence;
|
||
```
|
||
|
||
Set its password through the deployment's private credential mechanism, not a checked-in SQL file. Prepare a
|
||
mode 0600 libpq service file and an independently protected passfile. The service entry should use
|
||
`sslmode=verify-full`, the reviewed hostname and an absolute CA path; do not put a DSN on the command line.
|
||
|
||
Run the second collector only after the ceremony report has passed its offline audit:
|
||
|
||
```bash
|
||
export QL3_WORKER_CREDENTIAL_MANAGEMENT_DURABLE_AUDIT_EVIDENCE=1
|
||
|
||
pnpm evidence:worker-management-durable-audit:ql3 -- \
|
||
--ceremony-report=/absolute/private/worker-management-ceremony.json \
|
||
--ceremony=/absolute/private/ceremony.json \
|
||
--pg-service-file=/absolute/private/pg_service.conf \
|
||
--pg-service=ql3_worker_management_evidence \
|
||
--output=/absolute/private/worker-management-durable-audit.json
|
||
|
||
unset QL3_WORKER_CREDENTIAL_MANAGEMENT_DURABLE_AUDIT_EVIDENCE
|
||
|
||
pnpm audit:worker-management-durable-audit:ql3 -- \
|
||
--report=/absolute/private/worker-management-durable-audit.json
|
||
```
|
||
|
||
The collector rejects a role that can read any other `ql3` table, mutate any `ql3` table, or become a
|
||
privileged QingLong role. It observes exactly two durable audit rows: proposal and reviewer decision. The
|
||
requester's rejected self-decision must have no audit row because separation-of-duty fails before the database
|
||
update/audit insert transaction can commit. The v1 evidence contract requires PostgreSQL 18.4 or a later
|
||
security patch in the reviewed 18.x major.
|
||
|
||
After retaining the report, revoke the three SELECT grants, schema usage and database connect grant, then drop
|
||
the short-lived role according to the deployment's credential revocation procedure. Do not retain its passfile
|
||
with the low-sensitive evidence reports.
|
||
|
||
If the runner stops after creating durable facts, inspect them before using any new identifiers. Do not infer
|
||
rollback from a missing response and do not hand this evidence-only approval to the credential executor.
|