mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:21:50 +08:00
265 lines
7.3 KiB
JavaScript
265 lines
7.3 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
FIXTURE,
|
|
LIMITATIONS,
|
|
validateApprovalManagementKubernetesLiveReport,
|
|
} = require('../../scripts/ql3-approval-management-kubernetes-live-audit.cjs');
|
|
|
|
function digest(character) {
|
|
return 'sha256:' + character.repeat(64);
|
|
}
|
|
|
|
function validReport() {
|
|
return {
|
|
schemaVersion: 1,
|
|
fixture: FIXTURE,
|
|
observedAt: '2026-08-10T12:00:00.000Z',
|
|
platform: {
|
|
distribution: 'k3s',
|
|
kubernetesVersion: 'v1.34.3+k3s1',
|
|
architecture: 'arm64',
|
|
kubernetesImageId: digest('1'),
|
|
managementImageId: digest('2'),
|
|
cniName: 'flannel',
|
|
cniDistributionBinding: 'rancher/k3s:v1.34.3-k3s1',
|
|
controlPlaneNodes: 1,
|
|
workerNodes: 2,
|
|
cniReadyNodes: 3,
|
|
},
|
|
database: {
|
|
operator: 'cloudnative-pg',
|
|
operatorVersion: '1.30.0',
|
|
postgresVersionNumber: 180004,
|
|
postgresImageId: digest('3'),
|
|
instances: 3,
|
|
readyInstances: 3,
|
|
managerRole: 'ql3_approval_manager',
|
|
migrationCount: 54,
|
|
controlCoreCapability: 53,
|
|
tlsVerified: true,
|
|
primaryChangedDuringFailover: true,
|
|
},
|
|
deployment: {
|
|
namespace: 'qinglong3-system',
|
|
service: 'ql3-approval-management',
|
|
port: 8447,
|
|
replicas: 2,
|
|
readyReplicas: 2,
|
|
podIdentitySha256: [digest('4'), digest('5')],
|
|
nodeIdentitySha256: [digest('6'), digest('7')],
|
|
serviceAccount: 'ql3-approval-management',
|
|
automountServiceAccountToken: false,
|
|
requiredPodAntiAffinity: true,
|
|
podDisruptionBudgetMinAvailable: 1,
|
|
maxUnavailable: 0,
|
|
maxConnectionsPerPod: 2,
|
|
},
|
|
client: {
|
|
binary: 'ql3-approval-client',
|
|
operations: ['approval.inspect', 'approval.decide'],
|
|
inputKind: 'Secret',
|
|
inputImmutable: true,
|
|
callerDrivenJob: true,
|
|
backoffLimit: 0,
|
|
serviceAccountTokenMounted: false,
|
|
rbacGranted: false,
|
|
transportProtocol: 'TLSv1.3',
|
|
mutualTls: true,
|
|
servernameVerified: true,
|
|
exactPodRequests: 5,
|
|
inspectStatuses: ['found', 'found', 'found'],
|
|
decisionStatuses: ['decided', 'existing'],
|
|
responseRedacted: true,
|
|
},
|
|
identityRotation: {
|
|
overlapOldAssertionAccepted: true,
|
|
overlapNewAssertionAccepted: true,
|
|
revokedOldAssertionRejected: true,
|
|
activeNewAssertionAccepted: true,
|
|
rollbackSurgeFailedClosed: true,
|
|
twoReadyReplicasPreserved: true,
|
|
durableGenerationReachedThree: true,
|
|
},
|
|
certificateRotation: {
|
|
previousSerialSha256: digest('8'),
|
|
currentSerialSha256: digest('9'),
|
|
previousBundleSha256: digest('a'),
|
|
currentBundleSha256: digest('b'),
|
|
oldClientAcceptedBefore: true,
|
|
replacementClientAcceptedBefore: true,
|
|
oldClientRejectedAfter: true,
|
|
replacementClientAcceptedAfter: true,
|
|
fullPodReplacement: true,
|
|
allReplicasReadyThroughout: true,
|
|
},
|
|
availability: {
|
|
databaseFailureWithdrewReadiness: true,
|
|
databaseFailurePreservedLiveness: true,
|
|
stalePodsDidNotRecoverInPlace: true,
|
|
freshPodsRecoveredAfterDatabase: true,
|
|
bothReplicasServedAfterRecovery: true,
|
|
},
|
|
isolation: {
|
|
labelledClientAllowed: true,
|
|
unlabelledClientDenied: true,
|
|
wrongPortDenied: true,
|
|
kubernetesApiEgressDenied: true,
|
|
publicInternetEgressDenied: true,
|
|
cloudNativePgEgressAllowed: true,
|
|
managerSecretReadDenied: true,
|
|
managerMutationRbacDenied: true,
|
|
},
|
|
durability: {
|
|
approvalVersion: 2,
|
|
approvalState: 'approved',
|
|
decisionIdSha256: digest('c'),
|
|
allowedAuditCount: 4,
|
|
deniedAuditCount: 1,
|
|
duplicateDecisionCount: 0,
|
|
identityGeneration: 3,
|
|
survivedCloudNativePgFailover: true,
|
|
},
|
|
gates: {
|
|
realThreeNodeKubernetes: true,
|
|
realCniPolicy: true,
|
|
threeInstanceCloudNativePg: true,
|
|
twoManagerPodsOnDistinctNodes: true,
|
|
tls13ProductClientAcrossBothPods: true,
|
|
strongUserDecision: true,
|
|
identityProjectionRotation: true,
|
|
certificateRevocationRollout: true,
|
|
databaseReadinessFence: true,
|
|
durableFactsSurvivedFailover: true,
|
|
leastPrivilege: true,
|
|
passed: true,
|
|
},
|
|
limitations: [...LIMITATIONS],
|
|
};
|
|
}
|
|
|
|
function mutate(change) {
|
|
const report = structuredClone(validReport());
|
|
change(report);
|
|
return validateApprovalManagementKubernetesLiveReport(report);
|
|
}
|
|
|
|
test('accepts the exact content-free Kubernetes approval live report', () => {
|
|
assert.deepEqual(
|
|
validateApprovalManagementKubernetesLiveReport(validReport()),
|
|
{
|
|
schemaVersion: 1,
|
|
fixture: FIXTURE,
|
|
findings: [],
|
|
compatible: true,
|
|
},
|
|
);
|
|
});
|
|
|
|
test('rejects topology, migration and approval deployment weakening', () => {
|
|
for (const [code, change] of [
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_PLATFORM',
|
|
(report) => {
|
|
report.platform.workerNodes = 1;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_DATABASE',
|
|
(report) => {
|
|
report.database.migrationCount = 52;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_DEPLOYMENT',
|
|
(report) => {
|
|
report.deployment.nodeIdentitySha256[1] =
|
|
report.deployment.nodeIdentitySha256[0];
|
|
},
|
|
],
|
|
]) {
|
|
assert.ok(mutate(change).findings.some((entry) => entry.code === code));
|
|
}
|
|
});
|
|
|
|
test('rejects widened client, incomplete rotations and false availability', () => {
|
|
for (const [code, change] of [
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_CLIENT',
|
|
(report) => {
|
|
report.client.rbacGranted = true;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_IDENTITY_ROTATION',
|
|
(report) => {
|
|
report.identityRotation.revokedOldAssertionRejected = false;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_CERTIFICATE_ROTATION',
|
|
(report) => {
|
|
report.certificateRotation.fullPodReplacement = false;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_AVAILABILITY',
|
|
(report) => {
|
|
report.availability.stalePodsDidNotRecoverInPlace = false;
|
|
},
|
|
],
|
|
]) {
|
|
assert.ok(mutate(change).findings.some((entry) => entry.code === code));
|
|
}
|
|
});
|
|
|
|
test('rejects incomplete isolation and durable approval drift', () => {
|
|
assert.ok(
|
|
mutate((report) => {
|
|
report.isolation.publicInternetEgressDenied = false;
|
|
}).findings.some(
|
|
({ code }) => code === 'QL3_APPROVAL_KUBERNETES_LIVE_ISOLATION',
|
|
),
|
|
);
|
|
assert.ok(
|
|
mutate((report) => {
|
|
report.durability.approvalVersion = 1;
|
|
}).findings.some(
|
|
({ code }) => code === 'QL3_APPROVAL_KUBERNETES_LIVE_DURABILITY',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('rejects secret material, widened schema, false gates and hidden limitations', () => {
|
|
for (const [code, change] of [
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_SECRET_EXPOSURE',
|
|
(report) => {
|
|
report.client.assertion =
|
|
'eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJvcGVyYXRvciJ9.signature0123456789';
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_REPORT_SHAPE',
|
|
(report) => {
|
|
report.debug = true;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_GATES',
|
|
(report) => {
|
|
report.gates.passed = false;
|
|
},
|
|
],
|
|
[
|
|
'QL3_APPROVAL_KUBERNETES_LIVE_LIMITATIONS',
|
|
(report) => {
|
|
report.limitations = [];
|
|
},
|
|
],
|
|
]) {
|
|
assert.ok(mutate(change).findings.some((entry) => entry.code === code));
|
|
}
|
|
});
|