feat(ql3): distribute copilot console via signed admin image

This commit is contained in:
whyour
2026-08-16 05:14:17 +08:00
parent c4a1238a92
commit fba8dfb602
22 changed files with 1176 additions and 29 deletions
@@ -49,7 +49,7 @@ test('fails closed before Docker without explicit opt-in', () => {
assert.equal(result.stderr.includes('spawn'), false);
});
test('binds the live image gate to loopback Console assets and shutdown', () => {
test('binds the live image gate to native and container-published loopback', () => {
const source = fs.readFileSync(script, 'utf8');
assert.match(source, /function runConsoleContract\(image\)/);
assert.match(source, /\[facade, 'copilot-console'/);
@@ -57,4 +57,12 @@ test('binds the live image gate to loopback Console assets and shutdown', () =>
assert.match(source, /runConsoleContract\(image\);/);
assert.match(source, /consoleLoopback: true/);
assert.match(source, /consoleAssets: true/);
assert.match(source, /function runPublishedConsoleContract\(image\)/);
assert.match(
source,
/127\.0\.0\.1:\$\{containerPort\}:\$\{containerPort\}\/tcp/,
);
assert.match(source, /runPublishedConsoleContract\(image\);/);
assert.match(source, /consolePublishedHostAddress: '127\.0\.0\.1'/);
assert.match(source, /consoleDistributionEmbedded: true/);
});
@@ -0,0 +1,111 @@
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const ROOT = path.resolve(__dirname, '../..');
const verifier = path.join(
ROOT,
'deploy/console/ql3-cluster-copilot/verify-release.sh',
);
const image = `ghcr.io/example/qinglong3-cluster-admin@sha256:${'b'.repeat(64)}`;
const revision = 'c'.repeat(40);
function fixture(t) {
const directory = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-admin-verifier-')),
);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
const bin = path.join(directory, 'bin');
const capture = path.join(directory, 'calls');
fs.mkdirSync(bin, { mode: 0o700 });
for (const command of ['cosign', 'gh']) {
fs.writeFileSync(
path.join(bin, command),
`#!/bin/sh\nprintf '${command}\\n' >> "$QL3_TEST_VERIFY_CALLS"\nprintf 'arg=%s\\n' "$@" >> "$QL3_TEST_VERIFY_CALLS"\n`,
{ mode: 0o700 },
);
}
return {
capture,
env: {
...process.env,
PATH: `${bin}:${process.env.PATH ?? ''}`,
QL3_TEST_VERIFY_CALLS: capture,
},
};
}
function invoke(args, env) {
return spawnSync(verifier, args, {
cwd: ROOT,
env,
encoding: 'utf8',
});
}
test('verifies one signature and three digest-bound GitHub attestations', (t) => {
assert.equal(fs.statSync(verifier).mode & 0o777, 0o755);
const value = fixture(t);
const result = invoke(
[image, 'example/qinglong', revision, 'refs/tags/v3.0.0-alpha.1'],
value.env,
);
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(JSON.parse(result.stdout), {
schemaVersion: 1,
component: 'qinglong3-cluster-admin-release-verifier',
signature: true,
provenance: true,
sbom: true,
osVulnerabilityEvidence: true,
compatible: true,
});
const calls = fs.readFileSync(value.capture, 'utf8');
assert.equal((calls.match(/^cosign$/gmu) ?? []).length, 1);
assert.equal((calls.match(/^gh$/gmu) ?? []).length, 3);
for (const required of [
'arg=--certificate-identity',
'arg=https://github.com/example/qinglong/.github/workflows/ql3-image-release.yml@refs/tags/v3.0.0-alpha.1',
'arg=--certificate-oidc-issuer',
'arg=https://token.actions.githubusercontent.com',
`arg=${image}`,
`arg=oci://${image}`,
'arg=--repo',
'arg=example/qinglong',
'arg=--signer-workflow',
'arg=example/qinglong/.github/workflows/ql3-image-release.yml',
'arg=--source-digest',
`arg=${revision}`,
'arg=--source-ref',
'arg=refs/tags/v3.0.0-alpha.1',
'arg=https://cyclonedx.org/bom',
'arg=https://qinglong.dev/attestations/image-os-vulnerability/v1',
'arg=--deny-self-hosted-runners',
'arg=--bundle-from-oci',
]) {
assert.match(calls, new RegExp(`^${required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'mu'));
}
});
test('rejects mutable or source-unbound inputs before invoking trust tools', (t) => {
const value = fixture(t);
for (const args of [
['ghcr.io/example/qinglong3-cluster-admin:latest', 'example/qinglong', revision, 'refs/tags/v3.0.0'],
[image, 'other/qinglong', revision, 'refs/tags/v3.0.0'],
[image, 'example/qinglong', 'short', 'refs/tags/v3.0.0'],
[image, 'example/qinglong', revision, 'refs/heads/next'],
]) {
const rejected = invoke(args, value.env);
assert.equal(rejected.status, 78);
assert.equal(rejected.stdout, '');
assert.deepEqual(JSON.parse(rejected.stderr), {
schemaVersion: 1,
component: 'qinglong3-cluster-admin-release-verifier',
event: 'verification_failed',
});
assert.equal(fs.existsSync(value.capture), false);
}
});
@@ -0,0 +1,86 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const {
auditClusterCopilotConsoleDistribution,
} = require('../../scripts/ql3-cluster-copilot-console-distribution-audit.cjs');
const ROOT = path.resolve(__dirname, '../..');
function intercept(target, transform) {
return (relativePath) => {
const source = fs.readFileSync(path.join(ROOT, relativePath), 'utf8');
return relativePath === target ? transform(source) : source;
};
}
test('accepts the signed multi-architecture Admin OCI workstation distribution', () => {
assert.deepEqual(auditClusterCopilotConsoleDistribution({ root: ROOT }), {
schemaVersion: 1,
component: 'cluster-copilot-console-distribution',
artifact: 'signed-admin-oci',
architectures: ['amd64', 'arm64'],
hostPublication: '127.0.0.1',
kubernetesResident: false,
additionalWorkspacePackages: 0,
findings: [],
compatible: true,
});
});
test('rejects remote publication and weakened image runtime authority', () => {
for (const transform of [
(source) => source.replace('127.0.0.1:$port:$port/tcp', '0.0.0.0:$port:$port/tcp'),
(source) => source.replace('--cap-drop ALL', '--privileged'),
(source) => source.replace('--network "$network"', '--network host'),
]) {
const report = auditClusterCopilotConsoleDistribution({
root: ROOT,
readFile: intercept(
'deploy/console/ql3-cluster-copilot/docker-loopback.sh',
transform,
),
});
assert.equal(report.compatible, false);
assert.ok(
report.findings.some(({ code }) =>
code.startsWith('QL3_COPILOT_CONSOLE_LAUNCHER_'),
),
);
}
});
test('rejects verifier, embedded artifact and release workflow drift', () => {
const fixtures = [
[
'deploy/console/ql3-cluster-copilot/verify-release.sh',
(source) => source.replace('--deny-self-hosted-runners', ''),
'QL3_CLUSTER_ADMIN_RELEASE_VERIFIER_DRIFT',
],
[
'deploy/containers/ql3-cluster-admin/Dockerfile',
(source) => source.replace('COPY --chmod=0555', 'COPY --chmod=0777'),
'QL3_COPILOT_CONSOLE_IMAGE_DISTRIBUTION_DRIFT',
],
[
'.github/workflows/ql3-image-release.yml',
(source) => source.replace(
'Promote only the verified digest to immutable release tags',
'Promote mutable release tags',
),
'QL3_CLUSTER_ADMIN_RELEASE_WORKFLOW_DRIFT',
],
];
for (const [target, transform, code] of fixtures) {
const report = auditClusterCopilotConsoleDistribution({
root: ROOT,
readFile: intercept(target, transform),
});
assert.equal(report.compatible, false);
assert.ok(report.findings.some((finding) => finding.code === code));
}
});
@@ -0,0 +1,143 @@
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const ROOT = path.resolve(__dirname, '../..');
const launcher = path.join(
ROOT,
'deploy/console/ql3-cluster-copilot/docker-loopback.sh',
);
const image = `ghcr.io/example/qinglong3-cluster-admin@sha256:${'a'.repeat(64)}`;
function fixture(t) {
const directory = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-copilot-console-launcher-')),
);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
const privateRoot = path.join(directory, 'private');
const bin = path.join(directory, 'bin');
fs.mkdirSync(privateRoot, { mode: 0o700 });
fs.mkdirSync(bin, { mode: 0o700 });
const capture = path.join(directory, 'docker-args');
fs.writeFileSync(
path.join(bin, 'docker'),
'#!/bin/sh\nprintf \'%s\\n\' "$@" > "$QL3_TEST_DOCKER_ARGS"\n',
{ mode: 0o700 },
);
return {
privateRoot: fs.realpathSync(privateRoot),
capture,
env: {
...process.env,
PATH: `${bin}:${process.env.PATH ?? ''}`,
QL3_TEST_DOCKER_ARGS: capture,
QL3_COPILOT_CONSOLE_IMAGE: image,
QL3_COPILOT_CONSOLE_PRIVATE_ROOT: fs.realpathSync(privateRoot),
QL3_COPILOT_CONSOLE_NETWORK: 'qinglong3-console-egress',
QL3_COPILOT_CONSOLE_PORT: '5701',
QL3_COPILOT_CONSOLE_RESOURCE_CLASS: 'compact',
},
};
}
function invoke(mode, env) {
return spawnSync(launcher, [mode], {
cwd: ROOT,
env,
encoding: 'utf8',
});
}
test('runs compact preflight without opening or publishing the Console', (t) => {
assert.equal(fs.statSync(launcher).mode & 0o777, 0o755);
const value = fixture(t);
const result = invoke('check', value.env);
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, '');
assert.equal(result.stderr, '');
assert.deepEqual(
fs.readFileSync(value.capture, 'utf8').trimEnd().split('\n'),
[
'run',
'--rm',
'--pull',
'never',
'--init',
'--read-only',
'--network',
'qinglong3-console-egress',
'--cap-drop',
'ALL',
'--security-opt',
'no-new-privileges',
'--user',
'10001:10001',
'--pids-limit',
'32',
'--memory',
'192m',
'--cpus',
'0.25',
'--stop-timeout',
'3',
'--tmpfs',
'/tmp:rw,noexec,nosuid,nodev,size=8m,mode=700,uid=10001,gid=10001',
'--mount',
`type=bind,src=${value.privateRoot},dst=/var/run/secrets/qinglong3/copilot-console,readonly`,
image,
'copilot-console',
'--container-published-loopback',
'--port=5701',
'--config',
'/var/run/secrets/qinglong3/copilot-console/client.json',
'--credential',
'/var/run/secrets/qinglong3/copilot-console/credential',
'--session',
'/var/run/secrets/qinglong3/copilot-console/session',
'--check',
],
);
});
test('publishes standard serve only on host loopback', (t) => {
const value = fixture(t);
const result = invoke('serve', {
...value.env,
QL3_COPILOT_CONSOLE_RESOURCE_CLASS: 'standard',
});
assert.equal(result.status, 0, result.stderr);
const args = fs.readFileSync(value.capture, 'utf8').trimEnd().split('\n');
assert.equal(args.includes('--check'), false);
assert.equal(args[args.indexOf('--memory') + 1], '512m');
assert.equal(args[args.indexOf('--cpus') + 1], '1');
assert.equal(args[args.indexOf('--pids-limit') + 1], '64');
assert.equal(
args[args.indexOf('--publish') + 1],
'127.0.0.1:5701:5701/tcp',
);
});
test('rejects mutable, ambient and malformed host inputs before Docker', (t) => {
const value = fixture(t);
for (const environment of [
{ ...value.env, QL3_COPILOT_CONSOLE_IMAGE: 'ghcr.io/example/admin:latest' },
{ ...value.env, QL3_COPILOT_CONSOLE_NETWORK: 'host' },
{ ...value.env, QL3_COPILOT_CONSOLE_PORT: '80' },
{ ...value.env, QL3_COPILOT_CONSOLE_PRIVATE_ROOT: `${value.privateRoot}:rw` },
{ ...value.env, QL3_COPILOT_CONSOLE_RESOURCE_CLASS: 'unbounded' },
]) {
const rejected = invoke('serve', environment);
assert.equal(rejected.status, 78);
assert.equal(rejected.stdout, '');
assert.deepEqual(JSON.parse(rejected.stderr), {
schemaVersion: 1,
component: 'qinglong3-cluster-copilot-console-launcher',
event: 'launch_failed',
});
assert.equal(fs.existsSync(value.capture), false);
assert.doesNotMatch(rejected.stderr, /latest|host|private|unbounded/);
}
});