feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,98 @@
# Isolated Prompt output lost-key recovery verifier
This caller-driven Job verifies one externally recovered Prompt output key in a
separate `qinglong3-recovery` namespace. It is not part of the default Cluster
Kustomization and does not provision, wrap, unwrap, export or reactivate key
material.
The deployment authority must prepare a private read-only PVC named
`ql3-prompt-output-external-recovery-workspace`. Copy `command.example.json` to
`command.json` and provide exactly the referenced files:
- one provider-neutral atomic custody bundle containing the signed content-free
receipt and its digest-bound wrapped blob;
- the 32-byte material produced by the external KMS/HSM unwrap ceremony;
- the exact durable `keyId/materialProof/catalogDigest` fact from an isolated
PostgreSQL/CNPG restore;
- the immutable encrypted Artifact from that same restore generation;
- the pinned custody signing public key;
- an unexpired recovery authorization signed by two different strong Users,
plus both pinned approver public keys.
The verifier never accepts the receipt and wrapped blob as separate files, so a
partial copy or cross-generation substitution cannot create a mixed recovery
input. Every workspace file must be a non-symlink, single-link, read-only
regular file.
Private files must not be readable by `other`; the recovered material must be
exactly 32 bytes. Provisioning the PVC, copying backup evidence and invoking the
KMS/HSM are deployment-authority responsibilities outside this Job.
The repository includes a concrete Vault Transit adapter without making Vault
part of the QingLong runtime. Copy `vault-transit-wrap-command.example.json`
or `vault-transit-unwrap-command.example.json` into a private authority,
replace every placeholder, make commands/keys/token/material read-only, and run:
```sh
pnpm custody:vault-transit:ql3 -- wrap --command-file /owner-private/wrap.json
pnpm custody:vault-transit:ql3 -- unwrap --command-file /owner-private/unwrap.json
```
Production mode requires HTTPS plus an explicit CA file and reads the Vault
token only from a private file. Wrap creates one no-replace `0400`
provider-neutral bundle; replay verifies that bundle without another Vault
call. Unwrap verifies the signing authority, bundle digest,
provider/key-version authority and exact material proof before creating one
no-replace `0400` recovered material file. Copy that bundle unchanged into the
verifier workspace as `custody-bundle.json`. The Vault token, endpoint and key
name are never embedded in the bundle. Do not run either Vault command in the
tokenless, deny-all verifier Job.
The opt-in live gate uses the exact reviewed Vault image digest and removes its
random container and private workspace on both success and failure:
```sh
QL3_RUN_VAULT_TRANSIT_LIVE=true pnpm test:vault-transit-custody-live:ql3
```
That gate proves the actual Vault 1.21.4 Transit API over TLS 1.3 with an
explicit private CA. It initializes a persistent file barrier with three seal
shares and a threshold of two, wraps the key, replaces the entire non-root
read-only-rootfs container, observes the persisted server sealed, re-unseals
it, verifies the Transit key survived and then unwraps the bundle. A different
CA is rejected before API access. This remains a single-host file-storage
fixture, not HA integrated storage, HSM auto-unseal, enterprise PKI/external
IdP or CNPG restore evidence.
The separate opt-in PostgreSQL composition gate proves that the exact durable
key fact and encrypted Artifact can cross a real logical backup boundary before
the offline verifier receives them:
```sh
QL3_RUN_POSTGRES_BACKUP_RECOVERY_LIVE=true \
pnpm test:postgres-backup-prompt-output-recovery-live:ql3
```
It uses the digest-pinned PostgreSQL 18 image on a random loopback-only port,
runs the complete QL3 core and AI migration streams, and persists a canonical
Package publication plus Prompt admission/start/completion/finalization,
encrypted Artifact and key-rotation record through production repositories. It
then creates a custom-format backup, removes the whole source container and
anonymous volume, restores into a different container/volume, reopens the
production Artifact and rotation repositories, and only then exports the
restored fact and Artifact to the existing two-User verifier. The restored
lineage includes the exact 52 core and 16 AI migration histories and one row for
each required publication and Prompt chain fact. The gate removes both random
containers, their anonymous volumes and its private directory on success or
failure. This is full production-schema logical PostgreSQL backup composition
evidence; it is not CloudNativePG Barman WAL/PITR or an external IdP ceremony.
The Pod has no Role or RoleBinding, disables ServiceAccount token projection,
mounts the PVC read-only and is selected by an ingress/egress deny-all
NetworkPolicy. It receives no PostgreSQL URL, Kubernetes credential, cloud
credential, KMS endpoint or HSM session. Successful stdout is a content-free,
authorization-bound recovery proof. Failure output contains only stable error
metadata. The CLI wipes its owned recovered and wrapped buffers before exit.
This verifies recoverability only. It does not authorize plaintext export,
production Secret mutation, retirement reversal, keyring reconstruction or
bulk re-encryption. Those operations require separate policy and evidence.
@@ -0,0 +1,67 @@
apiVersion: batch/v1
kind: Job
metadata:
name: ql3-prompt-output-external-recovery-verifier
namespace: qinglong3-recovery
labels:
app.kubernetes.io/name: ql3-prompt-output-external-recovery-verifier
app.kubernetes.io/component: ai-recovery-verifier
app.kubernetes.io/part-of: qinglong3
qinglong.io/execution-model: caller-driven
spec:
backoffLimit: 0
activeDeadlineSeconds: 120
ttlSecondsAfterFinished: 600
template:
metadata:
labels:
app.kubernetes.io/name: ql3-prompt-output-external-recovery-verifier
app.kubernetes.io/component: ai-recovery-verifier
app.kubernetes.io/part-of: qinglong3
qinglong.io/execution-model: caller-driven
spec:
serviceAccountName: ql3-prompt-output-external-recovery-verifier
automountServiceAccountToken: false
enableServiceLinks: false
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: verifier
image: qinglong3-cluster-admin:3.0.0-alpha.0
imagePullPolicy: IfNotPresent
command:
- node
- /opt/qinglong/node_modules/@qinglong/cluster-admin/dist/prompt-output/external-recovery/promptOutputExternalRecoveryCli.js
args:
- run
- --command-file
- /var/run/qinglong3/prompt-output-external-recovery/command.json
terminationMessagePolicy: File
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: recovery-workspace
mountPath: /var/run/qinglong3/prompt-output-external-recovery
readOnly: true
volumes:
- name: recovery-workspace
persistentVolumeClaim:
claimName: ql3-prompt-output-external-recovery-workspace
readOnly: true
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- service-account.yaml
- network-policy.yaml
- job.yaml
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: qinglong3-recovery
labels:
app.kubernetes.io/part-of: qinglong3
qinglong.io/security-domain: isolated-recovery
@@ -0,0 +1,14 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ql3-prompt-output-external-recovery-verifier-deny-all
namespace: qinglong3-recovery
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ql3-prompt-output-external-recovery-verifier
policyTypes:
- Ingress
- Egress
ingress: []
egress: []
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: ql3-prompt-output-external-recovery-verifier
namespace: qinglong3-recovery
labels:
app.kubernetes.io/name: ql3-prompt-output-external-recovery-verifier
app.kubernetes.io/part-of: qinglong3
automountServiceAccountToken: false
@@ -0,0 +1,20 @@
{
"schemaVersion": 1,
"operation": "cluster.prompt-output-key.verify-recovery",
"authorizationFile": "/var/run/qinglong3/prompt-output-external-recovery/authorization.json",
"custodyBundleFile": "/var/run/qinglong3/prompt-output-external-recovery/custody-bundle.json",
"recoveredMaterialFile": "/var/run/qinglong3/prompt-output-external-recovery/recovered-material.bin",
"durableKeyFactFile": "/var/run/qinglong3/prompt-output-external-recovery/durable-key-fact.json",
"artifactFile": "/var/run/qinglong3/prompt-output-external-recovery/artifact.json",
"custodyPublicKeyFile": "/var/run/qinglong3/prompt-output-external-recovery/custody-public-key.pem",
"approverPublicKeyFiles": [
{
"userId": "replace-with-first-reviewer-user-id",
"filePath": "/var/run/qinglong3/prompt-output-external-recovery/first-reviewer-public-key.pem"
},
{
"userId": "replace-with-second-reviewer-user-id",
"filePath": "/var/run/qinglong3/prompt-output-external-recovery/second-reviewer-public-key.pem"
}
]
}
@@ -0,0 +1,15 @@
{
"schemaVersion": 1,
"operation": "external.prompt-output-key.vault-transit.unwrap",
"vault": {
"transport": "https",
"endpoint": "https://vault.example.invalid:8200",
"caFile": "/owner-private/vault/ca.pem",
"tokenFile": "/owner-private/vault/unwrap-token",
"transitMount": "prompt-output-transit",
"keyName": "prompt-output-custody"
},
"bundleFile": "/owner-private/prompt-output/custody-bundle.json",
"custodyPublicKeyFile": "/owner-private/prompt-output/custody-signing-public.pem",
"recoveredMaterialOutputFile": "/owner-private/prompt-output/recovered-material.bin"
}
@@ -0,0 +1,22 @@
{
"schemaVersion": 1,
"operation": "external.prompt-output-key.vault-transit.wrap",
"vault": {
"transport": "https",
"endpoint": "https://vault.example.invalid:8200",
"caFile": "/owner-private/vault/ca.pem",
"tokenFile": "/owner-private/vault/wrap-token",
"transitMount": "prompt-output-transit",
"keyName": "prompt-output-custody"
},
"materialFile": "/owner-private/prompt-output/material.bin",
"bundleOutputFile": "/owner-private/prompt-output/custody-bundle.json",
"custody": {
"custodyId": "replace-custody-id",
"keyId": "replace-key-id",
"sourceGeneration": 1,
"sourceCatalogDigest": "0000000000000000000000000000000000000000000000000000000000000000",
"receiptSigningPrivateKeyFile": "/owner-private/prompt-output/custody-signing-private.pem",
"receiptSigningPublicKeyFile": "/owner-private/prompt-output/custody-signing-public.pem"
}
}