feat(ql3): add owner-private cluster operator context

This commit is contained in:
whyour
2026-08-13 00:08:42 +08:00
parent 2934123155
commit ea3bbe35c3
14 changed files with 741 additions and 8 deletions
@@ -70,12 +70,79 @@ function runImage(image, args) {
'--cpus',
'0.25',
'--tmpfs',
'/tmp:rw,noexec,nosuid,nodev,size=8m,mode=700',
'/tmp:rw,noexec,nosuid,nodev,size=8m,mode=700,uid=10001,gid=10001',
image,
...args,
]);
}
function runOperatorContextContract(image) {
const source = String.raw`
const { spawnSync } = require('node:child_process');
const { writeFileSync } = require('node:fs');
const facade = '/opt/qinglong/node_modules/@qinglong/cluster-admin/dist/product-cli/cli.js';
const config = '/tmp/run-client.json';
const command = '/tmp/command.json';
const assertion = '/tmp/assertion.jwt';
const context = '/tmp/operator-context.json';
for (const [file, contents] of [
[config, '{}'],
[command, '{}'],
[assertion, 'a.b.c'],
[context, JSON.stringify({ schemaVersion: 1, commands: { run: { configFile: config } } })],
]) writeFileSync(file, contents, { mode: 0o600 });
const injected = spawnSync(process.execPath, [facade, 'run', '--context=' + context, '--command=' + command, '--assertion=' + assertion], { encoding: 'utf8' });
let injectedFailure;
try { injectedFailure = JSON.parse(injected.stderr); } catch { process.exit(21); }
if (injected.status !== 1 || injectedFailure.code !== 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_CONFIG_INVALID' || injected.stdout !== '') process.exit(22);
writeFileSync(context, JSON.stringify({ schemaVersion: 1, commands: { run: { configFile: config, assertionFile: assertion } } }), { mode: 0o600 });
const rejected = spawnSync(process.execPath, [facade, 'run', '--context=' + context, '--command=' + command, '--assertion=' + assertion], { encoding: 'utf8' });
let rejectedFailure;
try { rejectedFailure = JSON.parse(rejected.stderr); } catch { process.exit(23); }
if (rejected.status !== 78 || rejectedFailure.code !== 'QL3_CLUSTER_PRODUCT_CONTEXT_INVALID' || rejected.stdout !== '' || rejected.stderr.includes('/tmp/') || rejected.stderr.includes('assertion.jwt')) process.exit(24);
process.stdout.write(JSON.stringify({ schemaVersion: 1, injected: true, secretFieldsRejected: true }));
`;
const output = docker([
'run',
'--rm',
'--read-only',
'--network',
'none',
'--cap-drop',
'ALL',
'--security-opt',
'no-new-privileges',
'--user',
'10001:10001',
'--pids-limit',
'32',
'--memory',
'128m',
'--cpus',
'0.25',
'--tmpfs',
'/tmp:rw,noexec,nosuid,nodev,size=8m,mode=700,uid=10001,gid=10001',
'--entrypoint',
'node',
image,
'-e',
source,
]);
let result;
try {
result = JSON.parse(output);
} catch {
fail('operator context result is invalid');
}
if (
result?.schemaVersion !== 1 ||
result?.injected !== true ||
result?.secretFieldsRejected !== true
) {
fail('operator context contract drifted');
}
}
function main() {
if (process.env.QL3_CLUSTER_ADMIN_PRODUCT_LIVE !== '1') {
fail('QL3_CLUSTER_ADMIN_PRODUCT_LIVE=1 is required');
@@ -114,6 +181,7 @@ function main() {
}
const version = runImage(image, ['--version']).trim();
if (version !== '3.0.0-alpha.0') fail('product version contract drifted');
runOperatorContextContract(image);
process.stdout.write(
`${JSON.stringify({
@@ -123,6 +191,7 @@ function main() {
user: fact.Config.User,
imageBytes: fact.Size,
commandCount: COMMANDS.length,
operatorContext: true,
isolation: Object.freeze({
readOnlyRoot: true,
network: 'none',
+21 -1
View File
@@ -39,7 +39,15 @@ function requireExactOccurrences(source, pattern, expected, finding) {
}
}
function auditClusterImageCiWorkflow(source) {
function auditClusterImageCiWorkflow(
source,
adminProductLiveContract = readBoundedText(
path.join(
DEFAULT_ROOT,
'scripts/ql3-cluster-admin-product-live-contract.cjs',
),
),
) {
const workflow = yaml.load(source);
const clusterImageJob = workflow?.jobs?.['cluster-image'];
const localImageJob = workflow?.jobs?.['local-image'];
@@ -279,6 +287,11 @@ function auditClusterImageCiWorkflow(source) {
/name: Run the bounded Cluster Admin product facade\s+if: matrix\.image == 'admin'\s+env:\s+IMAGE: qinglong3-cluster-admin:ci-\$\{\{ matrix\.image_arch \}\}\s+QL3_CLUSTER_ADMIN_PRODUCT_LIVE: '1'\s+run: node scripts\/ql3-cluster-admin-product-live-contract\.cjs --image="\$\{IMAGE\}"/,
'native admin image CI must run the bounded product facade contract',
);
requirePattern(
adminProductLiveContract,
/runOperatorContextContract\(image\);[\s\S]*operatorContext: true/,
'native admin image contract must verify owner-private operator context injection',
);
requirePattern(
source,
/^ image-oci:\s*$/m,
@@ -320,6 +333,7 @@ function auditClusterImageCiWorkflow(source) {
nativeArchitectures: ['amd64', 'arm64'],
runtimeInventory: true,
clusterAdminProductFacade: true,
clusterAdminOperatorContext: true,
ociAttestations: true,
osVulnerabilityScan: {
scanner: 'trivy@0.70.0',
@@ -926,6 +940,12 @@ function auditClusterImageRelease(root = DEFAULT_ROOT) {
const resolvedRoot = path.resolve(root);
const ci = auditClusterImageCiWorkflow(
readBoundedText(path.join(resolvedRoot, CI_WORKFLOW_PATH)),
readBoundedText(
path.join(
resolvedRoot,
'scripts/ql3-cluster-admin-product-live-contract.cjs',
),
),
);
const release = auditReleaseWorkflow(
readBoundedText(path.join(resolvedRoot, RELEASE_WORKFLOW_PATH)),