mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): prove security administration custody live
This commit is contained in:
@@ -48,6 +48,26 @@ test('rejects Kubernetes API token authority in the administration Job', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects recursive fsGroup rewrites of persistent credential custody', () => {
|
||||
const report = auditSecurityAdministrationKubernetes({
|
||||
root: ROOT,
|
||||
readFile: intercept(
|
||||
'deploy/kubernetes/ql3-cluster/operations/security-administration/base/job.yaml',
|
||||
(value) => value.replace(
|
||||
'fsGroupChangePolicy: OnRootMismatch',
|
||||
'fsGroupChangePolicy: Always',
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
assert.equal(report.compatible, false);
|
||||
assert.ok(
|
||||
report.findings.some(
|
||||
({ code }) => code === 'QL3_SECURITY_ADMIN_KUBERNETES_JOB_BOUNDARY_INVALID',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a non-persistent credential delivery boundary', () => {
|
||||
const report = auditSecurityAdministrationKubernetes({
|
||||
root: ROOT,
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
FIXTURE,
|
||||
LIMITATIONS,
|
||||
validateSecurityAdministrationKubernetesLiveReport,
|
||||
} = require('../../scripts/ql3-security-administration-kubernetes-live-audit.cjs');
|
||||
|
||||
const digest = (value) => `sha256:${value.repeat(64)}`;
|
||||
|
||||
function report() {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
fixture: FIXTURE,
|
||||
observedAt: '2026-08-25T04:00:00.000Z',
|
||||
platform: {
|
||||
distribution: 'k3s',
|
||||
kubernetesVersion: 'v1.34.3+k3s1',
|
||||
architecture: 'amd64',
|
||||
kubernetesImageId: digest('1'),
|
||||
administrationImageId: digest('2'),
|
||||
cniName: 'flannel',
|
||||
cniDistributionBinding: 'rancher/k3s:v1.34.3-k3s1',
|
||||
controlPlaneNodes: 1,
|
||||
workerNodes: 2,
|
||||
readyNodes: 3,
|
||||
},
|
||||
database: {
|
||||
operator: 'cloudnative-pg',
|
||||
operatorVersion: '1.30.0',
|
||||
postgresVersionNumber: 180004,
|
||||
postgresImageId: digest('3'),
|
||||
instances: 3,
|
||||
readyInstances: 3,
|
||||
administrationRole: 'ql3_admin',
|
||||
roleConnectionLimit: 4,
|
||||
commandConnectionLimit: 1,
|
||||
migrationCount: 71,
|
||||
controlCoreCapability: 70,
|
||||
tlsVerified: true,
|
||||
leastPrivilege: true,
|
||||
},
|
||||
ceremony: {
|
||||
operations: [
|
||||
'identity.register',
|
||||
'audit.list',
|
||||
'credential.issue',
|
||||
'credential.issue.replay',
|
||||
'credential.rotate',
|
||||
'credential.revoke',
|
||||
],
|
||||
completedJobs: 6,
|
||||
failedJobs: 1,
|
||||
callerDriven: true,
|
||||
backoffLimit: 0,
|
||||
activeDeadlineSeconds: 300,
|
||||
ttlSecondsAfterFinished: 600,
|
||||
serviceAccount: 'ql3-security-administration',
|
||||
serviceAccountTokenMounted: false,
|
||||
rbacGranted: false,
|
||||
responseLossReplayObserved: true,
|
||||
sensitiveMaterialReported: false,
|
||||
},
|
||||
inputBoundary: {
|
||||
immutableSecret: true,
|
||||
projectedMode0440: true,
|
||||
memoryBackedPrivateStage: true,
|
||||
targetDirectoryMode0700: true,
|
||||
targetFilesMode0600: true,
|
||||
kubeletAtomicWriterProjectionAccepted: true,
|
||||
worldReadableProjectionRejected: true,
|
||||
mainContainerNotStartedAfterStageFailure: true,
|
||||
},
|
||||
deliveryCustody: {
|
||||
persistentVolumeClaim: true,
|
||||
accessMode: 'ReadWriteOnce',
|
||||
fixtureRootProvisioned: true,
|
||||
fixtureRootMode: '2770',
|
||||
fixtureProvisionerRanAsRoot: true,
|
||||
privateDirectoryMode: '0700',
|
||||
fileMode: '0600',
|
||||
fileCount: 2,
|
||||
issueDigest: digest('4'),
|
||||
rotationDigest: digest('5'),
|
||||
distinctRotationMaterial: true,
|
||||
persistentAcrossJobs: true,
|
||||
noReplaceReplayPreserved: true,
|
||||
deliverySchemaValidated: true,
|
||||
bearerFormatValidatedInPod: true,
|
||||
sensitiveMaterialReported: false,
|
||||
},
|
||||
isolation: {
|
||||
dnsAndDatabaseEgressAllowed: true,
|
||||
kubernetesApiEgressDenied: true,
|
||||
publicInternetEgressDenied: true,
|
||||
secretReadRbacDenied: true,
|
||||
jobMutationRbacDenied: true,
|
||||
},
|
||||
durability: {
|
||||
identityVersion: 1,
|
||||
identityStatus: 'active',
|
||||
credentialVersion: 3,
|
||||
credentialState: 'revoked',
|
||||
identityMutationCount: 1,
|
||||
credentialMutationCount: 3,
|
||||
issueMutationCount: 1,
|
||||
credentialVersionCount: 3,
|
||||
allowedAuditCount: 4,
|
||||
},
|
||||
cleanup: {
|
||||
jobsDeleted: true,
|
||||
inputSecretsDeleted: true,
|
||||
evidenceJobsDeleted: true,
|
||||
storageProvisionJobDeleted: true,
|
||||
deliveryVolumeClaimDeleted: true,
|
||||
},
|
||||
gates: {
|
||||
realThreeNodeKubernetes: true,
|
||||
realCloudNativePg: true,
|
||||
realKubeletSecretProjection: true,
|
||||
realAdministrationProductCommands: true,
|
||||
realPersistentCredentialCustody: true,
|
||||
responseLossReplay: true,
|
||||
failedInputStageClosed: true,
|
||||
leastPrivilege: true,
|
||||
contentFreeEvidence: true,
|
||||
passed: true,
|
||||
},
|
||||
limitations: [...LIMITATIONS],
|
||||
};
|
||||
}
|
||||
|
||||
test('accepts the exact content-free Security Administration live report', () => {
|
||||
const result = validateSecurityAdministrationKubernetesLiveReport(report());
|
||||
assert.equal(result.compatible, true);
|
||||
assert.deepEqual(result.findings, []);
|
||||
});
|
||||
|
||||
test('rejects widened authority, false custody and replay duplication', () => {
|
||||
const candidate = report();
|
||||
candidate.ceremony.serviceAccountTokenMounted = true;
|
||||
candidate.deliveryCustody.noReplaceReplayPreserved = false;
|
||||
candidate.durability.issueMutationCount = 2;
|
||||
candidate.gates.passed = false;
|
||||
const codes = validateSecurityAdministrationKubernetesLiveReport(
|
||||
candidate,
|
||||
).findings.map((finding) => finding.code);
|
||||
assert.ok(
|
||||
codes.includes('QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE_CEREMONY'),
|
||||
);
|
||||
assert.ok(
|
||||
codes.includes('QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE_DELIVERY'),
|
||||
);
|
||||
assert.ok(
|
||||
codes.includes('QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE_DURABILITY'),
|
||||
);
|
||||
assert.ok(codes.includes('QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE_GATES'));
|
||||
});
|
||||
|
||||
test('rejects any credential or assertion material in evidence', () => {
|
||||
const candidate = report();
|
||||
candidate.deliveryCustody.proof = {
|
||||
token: 'ql3c_example_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
};
|
||||
const result = validateSecurityAdministrationKubernetesLiveReport(candidate);
|
||||
assert.equal(result.compatible, false);
|
||||
assert.ok(
|
||||
result.findings.some(
|
||||
(finding) =>
|
||||
finding.code ===
|
||||
'QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE_MATERIAL_EXPOSURE',
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
auditListCommand,
|
||||
credentialIssueCommand,
|
||||
credentialRevokeCommand,
|
||||
credentialRotateCommand,
|
||||
custodyEvidenceSource,
|
||||
deliveryVolumeProvisionSource,
|
||||
identity,
|
||||
identityRegisterCommand,
|
||||
inputAuthorityEvidenceSource,
|
||||
} = require('../../scripts/ql3-security-administration-kubernetes-live-contract.cjs');
|
||||
|
||||
const values = Object.freeze({
|
||||
subject: Object.freeze({ type: 'api_app', id: 'd406-test-client' }),
|
||||
credentialId: 'd406-test-client',
|
||||
identityMutationId: '10000000-0000-4000-8000-000000000001',
|
||||
issueMutationId: '10000000-0000-4000-8000-000000000002',
|
||||
rotateMutationId: '10000000-0000-4000-8000-000000000003',
|
||||
revokeMutationId: '10000000-0000-4000-8000-000000000004',
|
||||
registerRequestId: 'd406-register-test',
|
||||
issueRequestId: 'd406-issue-test',
|
||||
rotateRequestId: 'd406-rotate-test',
|
||||
revokeRequestId: 'd406-revoke-test',
|
||||
notBeforeAtMs: 2_000_000_000_000,
|
||||
expiresAtMs: 2_000_003_600_000,
|
||||
});
|
||||
|
||||
test('requires a private report path before any Docker or Kubernetes mutation', () => {
|
||||
const script = path.resolve(
|
||||
__dirname,
|
||||
'../../scripts/ql3-security-administration-kubernetes-live-contract.cjs',
|
||||
);
|
||||
const result = spawnSync(process.execPath, [script], {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE: '1',
|
||||
},
|
||||
});
|
||||
assert.equal(result.status, 1);
|
||||
assert.match(result.stderr, /--report=\/absolute\/private-report\.json/);
|
||||
assert.doesNotMatch(result.stderr, /Docker\/Kubernetes/);
|
||||
});
|
||||
|
||||
test('builds the exact register, audit, issue, rotate and revoke commands', () => {
|
||||
assert.deepEqual(identityRegisterCommand(values), {
|
||||
schemaVersion: 1,
|
||||
operation: 'identity.register',
|
||||
request: {
|
||||
mutationId: values.identityMutationId,
|
||||
requestId: values.registerRequestId,
|
||||
expectedCurrentVersion: 0,
|
||||
subject: values.subject,
|
||||
},
|
||||
});
|
||||
assert.deepEqual(auditListCommand(), {
|
||||
schemaVersion: 1,
|
||||
operation: 'audit.list',
|
||||
request: { limit: 25, filter: { outcome: 'allowed' } },
|
||||
});
|
||||
assert.equal(credentialIssueCommand(values).request.expectedCurrentVersion, 0);
|
||||
assert.equal(credentialRotateCommand(values).request.expectedCurrentVersion, 1);
|
||||
assert.equal(credentialRevokeCommand(values).request.expectedCurrentVersion, 2);
|
||||
assert.equal('notBeforeAtMs' in credentialRevokeCommand(values).request, false);
|
||||
});
|
||||
|
||||
test('binds the live assertion to the isolated Security Administration purpose', () => {
|
||||
const key = identity.reviewedKey('security-administration-unit-key');
|
||||
const document = identity.keyset(1, [key]);
|
||||
assert.equal(document.audience, 'qinglong3-security-administration');
|
||||
const assertion = identity.assertion(key, 'unit-test');
|
||||
const header = JSON.parse(Buffer.from(assertion.split('.')[0], 'base64url'));
|
||||
const payload = JSON.parse(Buffer.from(assertion.split('.')[1], 'base64url'));
|
||||
assert.equal(header.typ, 'ql3-security-administration+jwt');
|
||||
assert.equal(payload.ql3_purpose, 'security-administration');
|
||||
assert.equal(payload.sub, 'security-owner');
|
||||
assert.equal(payload.acr, 'urn:ql3:mfa');
|
||||
assert.deepEqual(payload.amr, ['pwd', 'otp']);
|
||||
});
|
||||
|
||||
test('keeps the in-Pod custody verifier content-free and fail-closed', () => {
|
||||
const source = custodyEvidenceSource();
|
||||
assert.match(source, /bytes\.fill\(0\)/);
|
||||
assert.match(source, /kubernetesApiConnected/);
|
||||
assert.match(source, /publicInternetConnected/);
|
||||
assert.match(source, /status\.nlink!==1/);
|
||||
assert.doesNotMatch(source, /value\.token[,}]/);
|
||||
assert.doesNotMatch(source, /console\.log/);
|
||||
});
|
||||
|
||||
test('inspects projected input authority without reading private material', () => {
|
||||
const source = inputAuthorityEvidenceSource();
|
||||
assert.match(source, /lstatSync/);
|
||||
assert.match(source, /realpathSync/);
|
||||
assert.match(source, /confined/);
|
||||
assert.doesNotMatch(source, /readFile/);
|
||||
assert.doesNotMatch(source, /createReadStream/);
|
||||
});
|
||||
|
||||
test('constrains only the exact local-path fixture root without network authority', () => {
|
||||
const source = deliveryVolumeProvisionSource();
|
||||
assert.match(source, /before\.mode!==['"]2777['"]/);
|
||||
assert.match(source, /chmodSync\(root,0o2770\)/);
|
||||
assert.match(source, /after\.mode!==['"]2770['"]/);
|
||||
assert.doesNotMatch(source, /child_process/);
|
||||
assert.doesNotMatch(source, /net|fetch|http/);
|
||||
});
|
||||
|
||||
test('live runner remains opt-in, reviewed, cleanup-bound and log-free', () => {
|
||||
const source = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../scripts/ql3-security-administration-kubernetes-live-contract.cjs',
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/QL3_SECURITY_ADMINISTRATION_KUBERNETES_LIVE !== '1'/,
|
||||
);
|
||||
assert.match(source, /reviewedOperatorManifest\(operatorManifestFile\)/);
|
||||
assert.match(source, /validateSecurityAdministrationKubernetesLiveReport/);
|
||||
assert.match(source, /projectedMode: 0o444/);
|
||||
assert.match(source, /credential\.issue\.replay/);
|
||||
assert.match(source, /FallbackToLogsOnError/);
|
||||
assert.match(source, /failureMessage: 'rejected'/);
|
||||
assert.match(
|
||||
source,
|
||||
/deploy\/kubernetes\/ql3-cluster\/base\/service-account\.yaml/,
|
||||
);
|
||||
assert.match(source, /persistentvolumeclaim\/\$\{DELIVERY_CLAIM\}/);
|
||||
assert.match(source, /await fixture\.cleanup\(\)/);
|
||||
assert.doesNotMatch(source, /kubectl\([^)]*logs/);
|
||||
});
|
||||
Reference in New Issue
Block a user