feat(ql3): add opt-in security administration job

This commit is contained in:
whyour
2026-08-25 03:15:37 +08:00
parent fbedae0116
commit 931f9eee38
31 changed files with 1606 additions and 14 deletions
@@ -0,0 +1,431 @@
import {
closeSync,
constants,
fstatSync,
fsyncSync,
linkSync,
lstatSync,
mkdirSync,
openSync,
readSync,
realpathSync,
rmdirSync,
unlinkSync,
writeSync,
} from 'node:fs';
import {
dirname,
isAbsolute,
join,
normalize,
parse,
relative,
sep,
} from 'node:path';
const INPUTS = Object.freeze([
Object.freeze({ name: 'command.json', maximumBytes: 64 * 1024 }),
Object.freeze({ name: 'assertion.jwt', maximumBytes: 16 * 1024 }),
Object.freeze({ name: 'keyset.json', maximumBytes: 256 * 1024 }),
Object.freeze({ name: 'pepper', maximumBytes: 256 }),
]);
export interface ClusterAdministrationKubernetesInputStagePaths {
readonly sourceDirectory: string;
readonly targetDirectory: string;
readonly deliveryDirectory?: string;
}
export interface ClusterAdministrationKubernetesInputStageResult {
readonly schemaVersion: 1;
readonly component: 'qinglong3-security-administration-kubernetes-input-stage';
readonly stagedFileCount: 4;
readonly deliveryDirectoryPrepared: boolean;
}
export class ClusterAdministrationKubernetesInputStageError extends TypeError {
readonly code = 'QL3_CLUSTER_ADMINISTRATION_KUBERNETES_INPUT_STAGE_INVALID';
constructor(message: string, readonly cause?: unknown) {
super(
`Cluster administration Kubernetes input stage is invalid: ${message}`,
);
this.name = 'ClusterAdministrationKubernetesInputStageError';
}
}
function exactObject(
value: unknown,
): asserts value is ClusterAdministrationKubernetesInputStagePaths {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new ClusterAdministrationKubernetesInputStageError(
'paths must be an object',
);
}
const actual = Object.keys(value).sort();
const expected = [
'sourceDirectory',
'targetDirectory',
...('deliveryDirectory' in value ? ['deliveryDirectory'] : []),
].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new ClusterAdministrationKubernetesInputStageError(
'paths shape is invalid',
);
}
}
function directoryPath(value: unknown, label: string): string {
if (
typeof value !== 'string' ||
!isAbsolute(value) ||
normalize(value) !== value ||
parse(value).root === value ||
value.includes('\0') ||
Buffer.byteLength(value, 'utf8') > 4_096
) {
throw new ClusterAdministrationKubernetesInputStageError(
`${label} must be a normalized absolute non-root path`,
);
}
return value;
}
function sameFileState(
left: Readonly<{
dev: number;
ino: number;
size: number;
mtimeMs: number;
ctimeMs: number;
}>,
right: Readonly<{
dev: number;
ino: number;
size: number;
mtimeMs: number;
ctimeMs: number;
}>,
): boolean {
return (
left.dev === right.dev &&
left.ino === right.ino &&
left.size === right.size &&
left.mtimeMs === right.mtimeMs &&
left.ctimeMs === right.ctimeMs
);
}
function verifySourceDirectory(sourceDirectory: string): string {
const status = lstatSync(sourceDirectory, { throwIfNoEntry: false });
if (
status === undefined ||
!status.isDirectory() ||
status.isSymbolicLink() ||
(status.mode & 0o002) !== 0
) {
throw new ClusterAdministrationKubernetesInputStageError(
'projected source directory authority is invalid',
);
}
try {
return realpathSync(sourceDirectory);
} catch (error) {
throw new ClusterAdministrationKubernetesInputStageError(
'projected source directory cannot be resolved',
error,
);
}
}
function confinedSourceFile(
sourceDirectory: string,
sourceRealDirectory: string,
name: string,
): string {
const candidate = join(sourceDirectory, name);
let resolved: string;
try {
resolved = realpathSync(candidate);
} catch (error) {
throw new ClusterAdministrationKubernetesInputStageError(
'projected input cannot be resolved',
error,
);
}
const pathFromSource = relative(sourceRealDirectory, resolved);
if (
pathFromSource === '' ||
pathFromSource === '..' ||
pathFromSource.startsWith(`..${sep}`) ||
isAbsolute(pathFromSource)
) {
throw new ClusterAdministrationKubernetesInputStageError(
'projected input escapes its source directory',
);
}
return resolved;
}
function readStableSourceFile(filePath: string, maximumBytes: number): Buffer {
let descriptor: number | undefined;
let bytes: Buffer | undefined;
try {
descriptor = openSync(
filePath,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
);
const before = fstatSync(descriptor);
if (
!before.isFile() ||
before.size < 1 ||
before.size > maximumBytes ||
(before.mode & 0o027) !== 0
) {
throw new ClusterAdministrationKubernetesInputStageError(
'projected input file authority is invalid',
);
}
bytes = Buffer.alloc(before.size + 1);
let offset = 0;
while (offset < bytes.length) {
const count = readSync(
descriptor,
bytes,
offset,
bytes.length - offset,
offset,
);
if (count === 0) break;
offset += count;
}
const after = fstatSync(descriptor);
if (offset !== before.size || !sameFileState(before, after)) {
throw new ClusterAdministrationKubernetesInputStageError(
'projected input changed while being read',
);
}
return bytes.subarray(0, offset);
} catch (error) {
bytes?.fill(0);
if (error instanceof ClusterAdministrationKubernetesInputStageError) {
throw error;
}
throw new ClusterAdministrationKubernetesInputStageError(
'projected input cannot be read',
error,
);
} finally {
if (descriptor !== undefined) closeSync(descriptor);
}
}
function verifyPrivateDirectory(directory: string, label: string): void {
const status = lstatSync(directory, { throwIfNoEntry: false });
const effectiveUser = process.geteuid?.();
if (
status === undefined ||
!status.isDirectory() ||
status.isSymbolicLink() ||
(status.mode & 0o777) !== 0o700 ||
(effectiveUser !== undefined && status.uid !== effectiveUser)
) {
throw new ClusterAdministrationKubernetesInputStageError(
`${label} authority is invalid`,
);
}
}
function verifyWritableParent(parent: string, label: string): void {
const status = lstatSync(parent, { throwIfNoEntry: false });
if (
status === undefined ||
!status.isDirectory() ||
status.isSymbolicLink() ||
(status.mode & 0o002) !== 0
) {
throw new ClusterAdministrationKubernetesInputStageError(
`${label} parent authority is invalid`,
);
}
}
function syncDirectory(directory: string): void {
const descriptor = openSync(
directory,
constants.O_RDONLY | (constants.O_DIRECTORY ?? 0),
);
try {
fsyncSync(descriptor);
} finally {
closeSync(descriptor);
}
}
function createPrivateDirectory(directory: string, label: string): void {
const parent = dirname(directory);
verifyWritableParent(parent, label);
try {
mkdirSync(directory, { mode: 0o700 });
} catch (error) {
throw new ClusterAdministrationKubernetesInputStageError(
`${label} cannot be created`,
error,
);
}
try {
verifyPrivateDirectory(directory, label);
syncDirectory(parent);
} catch (error) {
try {
rmdirSync(directory);
} catch {
// Preserve the original authority failure.
}
throw error;
}
}
function prepareDeliveryDirectory(directory: string): void {
const existing = lstatSync(directory, { throwIfNoEntry: false });
if (existing === undefined) {
createPrivateDirectory(directory, 'delivery directory');
return;
}
verifyPrivateDirectory(directory, 'delivery directory');
}
function publishPrivateFile(filePath: string, bytes: Buffer): void {
const temporary = `${filePath}.stage`;
let descriptor: number | undefined;
try {
descriptor = openSync(
temporary,
constants.O_WRONLY |
constants.O_CREAT |
constants.O_EXCL |
(constants.O_NOFOLLOW ?? 0),
0o600,
);
let offset = 0;
while (offset < bytes.length) {
offset += writeSync(
descriptor,
bytes,
offset,
bytes.length - offset,
offset,
);
}
fsyncSync(descriptor);
const status = fstatSync(descriptor);
if (
!status.isFile() ||
status.size !== bytes.length ||
(status.mode & 0o077) !== 0
) {
throw new ClusterAdministrationKubernetesInputStageError(
'private staged input file authority is invalid',
);
}
closeSync(descriptor);
descriptor = undefined;
linkSync(temporary, filePath);
unlinkSync(temporary);
} catch (error) {
if (descriptor !== undefined) closeSync(descriptor);
try {
unlinkSync(temporary);
} catch {
// Preserve the original publication failure.
}
if (error instanceof ClusterAdministrationKubernetesInputStageError) {
throw error;
}
throw new ClusterAdministrationKubernetesInputStageError(
'private staged input cannot be published',
error,
);
}
}
export function stageClusterAdministrationKubernetesInputs(
pathsValue: ClusterAdministrationKubernetesInputStagePaths,
): Readonly<ClusterAdministrationKubernetesInputStageResult> {
exactObject(pathsValue);
const sourceDirectory = directoryPath(
pathsValue.sourceDirectory,
'sourceDirectory',
);
const targetDirectory = directoryPath(
pathsValue.targetDirectory,
'targetDirectory',
);
const deliveryDirectory =
pathsValue.deliveryDirectory === undefined
? undefined
: directoryPath(pathsValue.deliveryDirectory, 'deliveryDirectory');
if (
sourceDirectory === targetDirectory ||
sourceDirectory === deliveryDirectory ||
targetDirectory === deliveryDirectory
) {
throw new ClusterAdministrationKubernetesInputStageError(
'source, target and delivery directories must be distinct',
);
}
const sourceRealDirectory = verifySourceDirectory(sourceDirectory);
if (lstatSync(targetDirectory, { throwIfNoEntry: false }) !== undefined) {
throw new ClusterAdministrationKubernetesInputStageError(
'target directory must not already exist',
);
}
createPrivateDirectory(targetDirectory, 'target directory');
const published: string[] = [];
try {
for (const input of INPUTS) {
const sourceFile = confinedSourceFile(
sourceDirectory,
sourceRealDirectory,
input.name,
);
const bytes = readStableSourceFile(sourceFile, input.maximumBytes);
const targetFile = join(targetDirectory, input.name);
try {
publishPrivateFile(targetFile, bytes);
published.push(targetFile);
} finally {
bytes.fill(0);
}
}
syncDirectory(targetDirectory);
if (deliveryDirectory !== undefined) {
prepareDeliveryDirectory(deliveryDirectory);
}
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-security-administration-kubernetes-input-stage',
stagedFileCount: 4,
deliveryDirectoryPrepared: deliveryDirectory !== undefined,
});
} catch (error) {
for (const targetFile of published.reverse()) {
try {
unlinkSync(targetFile);
} catch {
// The failed stage remains fail-closed and the Pod never starts main.
}
}
try {
syncDirectory(targetDirectory);
rmdirSync(targetDirectory);
} catch {
// Preserve the original staging failure.
}
throw error;
}
}
@@ -0,0 +1,84 @@
#!/usr/bin/env node
import {
ClusterAdministrationKubernetesInputStageError,
stageClusterAdministrationKubernetesInputs,
} from './clusterAdministrationKubernetesInputStage';
const USAGE =
'Usage: ql3-security-admin-kubernetes-stage --source=/absolute/projected-input --target=/absolute/private-input [--delivery-directory=/absolute/private-delivery]';
function argumentsFrom(argv: readonly string[]) {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
return Object.freeze({ kind: 'help' as const });
}
const values = new Map<string, string>();
for (const argument of argv) {
const match = /^--(source|target|delivery-directory)=(\/.+)$/.exec(
argument,
);
if (!match || values.has(match[1]!)) {
throw new ClusterAdministrationKubernetesInputStageError(
'CLI arguments are invalid',
);
}
values.set(match[1]!, match[2]!);
}
if (!values.has('source') || !values.has('target')) {
throw new ClusterAdministrationKubernetesInputStageError(
'CLI arguments are invalid',
);
}
return Object.freeze({
kind: 'run' as const,
paths: Object.freeze({
sourceDirectory: values.get('source')!,
targetDirectory: values.get('target')!,
...(values.has('delivery-directory')
? { deliveryDirectory: values.get('delivery-directory')! }
: {}),
}),
});
}
function failure(error: unknown): Readonly<Record<string, unknown>> {
const candidate = error as {
readonly name?: unknown;
readonly code?: unknown;
};
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-security-administration-kubernetes-input-stage',
event: 'stage_failed',
name:
typeof candidate?.name === 'string' && candidate.name.length <= 128
? candidate.name
: 'Error',
...(typeof candidate?.code === 'string' && candidate.code.length <= 128
? { code: candidate.code }
: {}),
});
}
function main(argv: readonly string[]): void {
try {
const parsed = argumentsFrom(argv);
if (parsed.kind === 'help') {
process.stdout.write(`${USAGE}\n`);
return;
}
process.stdout.write(
`${JSON.stringify(
stageClusterAdministrationKubernetesInputs(parsed.paths),
)}\n`,
);
} catch (error) {
process.stderr.write(`${JSON.stringify(failure(error))}\n`);
process.exitCode =
error instanceof ClusterAdministrationKubernetesInputStageError ? 64 : 1;
}
}
if (require.main === module) {
main(process.argv.slice(2));
}
@@ -0,0 +1,187 @@
const assert = require('node:assert/strict');
const {
chmodSync,
lstatSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
symlinkSync,
unlinkSync,
writeFileSync,
} = require('node:fs');
const { tmpdir } = require('node:os');
const { join, resolve } = require('node:path');
const { spawnSync } = require('node:child_process');
const { afterEach, test } = require('node:test');
const {
ClusterAdministrationKubernetesInputStageError,
stageClusterAdministrationKubernetesInputs,
} = require('../dist/security-administration/clusterAdministrationKubernetesInputStage.js');
const roots = [];
afterEach(() => {
for (const root of roots.splice(0)) {
rmSync(root, { force: true, recursive: true });
}
});
function projectedInput() {
const root = mkdtempSync(join(tmpdir(), 'ql3-security-admin-stage-'));
roots.push(root);
const sourceDirectory = join(root, 'projected');
const versionDirectory = join(sourceDirectory, '..2026_08_25_00_00_00');
mkdirSync(versionDirectory, { mode: 0o700, recursive: true });
const inputs = {
'command.json': '{"schemaVersion":1,"operation":"audit.list"}\n',
'assertion.jwt': 'signed.assertion.value',
'keyset.json': '{"keys":[]}',
pepper: 'A'.repeat(43),
};
for (const [name, value] of Object.entries(inputs)) {
const versionFile = join(versionDirectory, name);
writeFileSync(versionFile, value, { mode: 0o440 });
symlinkSync(join('..data', name), join(sourceDirectory, name));
}
symlinkSync('..2026_08_25_00_00_00', join(sourceDirectory, '..data'));
return {
root,
sourceDirectory,
targetDirectory: join(root, 'private-input'),
deliveryDirectory: join(root, 'private-delivery'),
inputs,
};
}
function mode(filePath) {
return lstatSync(filePath).mode & 0o777;
}
test('copies a Kubernetes projected Secret into a private immutable input boundary', () => {
const fixture = projectedInput();
const result = stageClusterAdministrationKubernetesInputs({
sourceDirectory: fixture.sourceDirectory,
targetDirectory: fixture.targetDirectory,
});
assert.deepEqual(result, {
schemaVersion: 1,
component: 'qinglong3-security-administration-kubernetes-input-stage',
stagedFileCount: 4,
deliveryDirectoryPrepared: false,
});
assert.equal(mode(fixture.targetDirectory), 0o700);
for (const [name, value] of Object.entries(fixture.inputs)) {
const target = join(fixture.targetDirectory, name);
assert.equal(mode(target), 0o600);
assert.equal(readFileSync(target, 'utf8'), value);
assert.equal(lstatSync(target).isSymbolicLink(), false);
}
assert.equal(
JSON.stringify(result).includes('signed.assertion.value'),
false,
);
assert.equal(JSON.stringify(result).includes('A'.repeat(43)), false);
});
test('prepares a private persistent delivery directory without weakening it', () => {
const fixture = projectedInput();
const first = stageClusterAdministrationKubernetesInputs({
sourceDirectory: fixture.sourceDirectory,
targetDirectory: fixture.targetDirectory,
deliveryDirectory: fixture.deliveryDirectory,
});
assert.equal(first.deliveryDirectoryPrepared, true);
assert.equal(mode(fixture.deliveryDirectory), 0o700);
const secondTarget = join(fixture.root, 'second-private-input');
const second = stageClusterAdministrationKubernetesInputs({
sourceDirectory: fixture.sourceDirectory,
targetDirectory: secondTarget,
deliveryDirectory: fixture.deliveryDirectory,
});
assert.equal(second.deliveryDirectoryPrepared, true);
assert.equal(mode(fixture.deliveryDirectory), 0o700);
});
test('rejects a projected input symlink that escapes the Secret authority', () => {
const fixture = projectedInput();
const external = join(fixture.root, 'external-command.json');
writeFileSync(external, 'outside', { mode: 0o400 });
unlinkSync(join(fixture.sourceDirectory, 'command.json'));
symlinkSync(external, join(fixture.sourceDirectory, 'command.json'));
assert.throws(
() =>
stageClusterAdministrationKubernetesInputs({
sourceDirectory: fixture.sourceDirectory,
targetDirectory: fixture.targetDirectory,
}),
(error) =>
error instanceof ClusterAdministrationKubernetesInputStageError &&
/escapes/.test(error.message),
);
assert.throws(() => lstatSync(fixture.targetDirectory));
});
test('rejects source material readable by every local process', () => {
const fixture = projectedInput();
chmodSync(resolve(fixture.sourceDirectory, '..data', 'assertion.jwt'), 0o444);
assert.throws(
() =>
stageClusterAdministrationKubernetesInputs({
sourceDirectory: fixture.sourceDirectory,
targetDirectory: fixture.targetDirectory,
}),
/file authority is invalid/,
);
assert.throws(() => lstatSync(fixture.targetDirectory));
});
test('never replaces an existing private input directory', () => {
const fixture = projectedInput();
mkdirSync(fixture.targetDirectory, { mode: 0o700 });
const sentinel = join(fixture.targetDirectory, 'sentinel');
writeFileSync(sentinel, 'preserve', { mode: 0o600 });
assert.throws(
() =>
stageClusterAdministrationKubernetesInputs({
sourceDirectory: fixture.sourceDirectory,
targetDirectory: fixture.targetDirectory,
}),
/must not already exist/,
);
assert.equal(readFileSync(sentinel, 'utf8'), 'preserve');
});
test('CLI emits only bounded content-free failures', () => {
const cli = join(
__dirname,
'../dist/security-administration/clusterAdministrationKubernetesInputStageCli.js',
);
const sensitive = 'ql3c_private-token-material';
const result = spawnSync(
process.execPath,
[cli, `--source=/${sensitive}`, '--target=relative'],
{ encoding: 'utf8' },
);
assert.equal(result.status, 64);
assert.equal(result.stdout, '');
assert.equal(result.stderr.includes(sensitive), false);
assert.deepEqual(JSON.parse(result.stderr), {
schemaVersion: 1,
component: 'qinglong3-security-administration-kubernetes-input-stage',
event: 'stage_failed',
name: 'ClusterAdministrationKubernetesInputStageError',
code: 'QL3_CLUSTER_ADMINISTRATION_KUBERNETES_INPUT_STAGE_INVALID',
});
});