mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): validate cluster credential key rotation
This commit is contained in:
@@ -20,6 +20,7 @@ function report() {
|
||||
architecture: 'amd64',
|
||||
kubernetesImageId: digest('1'),
|
||||
administrationImageId: digest('2'),
|
||||
controlImageId: digest('4'),
|
||||
cniName: 'flannel',
|
||||
cniDistributionBinding: 'rancher/k3s:v1.34.3-k3s1',
|
||||
controlPlaneNodes: 1,
|
||||
@@ -45,13 +46,21 @@ function report() {
|
||||
operations: [
|
||||
'identity.register',
|
||||
'audit.list',
|
||||
'credential.issue',
|
||||
'credential.issue.replay',
|
||||
'credential.rotate',
|
||||
'credential.revoke',
|
||||
'credential.issue.old',
|
||||
'credential.issue.old.replay',
|
||||
'credential.key-references.before-activate',
|
||||
'credential.issue.new',
|
||||
'credential.rotate.new',
|
||||
'credential.key-references.after-activate',
|
||||
'credential.revoke.old',
|
||||
'credential.key-references.after-converge',
|
||||
],
|
||||
completedJobs: 6,
|
||||
completedJobs: 10,
|
||||
failedJobs: 1,
|
||||
authenticationProbeJobs: 5,
|
||||
controlReplicas: 2,
|
||||
controlRollouts: 3,
|
||||
controlReplicaAntiAffinity: true,
|
||||
callerDriven: true,
|
||||
backoffLimit: 0,
|
||||
activeDeadlineSeconds: 300,
|
||||
@@ -60,6 +69,18 @@ function report() {
|
||||
serviceAccountTokenMounted: false,
|
||||
rbacGranted: false,
|
||||
responseLossReplayObserved: true,
|
||||
overlapGenerationCount: 2,
|
||||
contractedGenerationCount: 1,
|
||||
activeGenerationChanged: true,
|
||||
oldReferencesBeforeActivation: 1,
|
||||
oldReferencesAfterActivation: 1,
|
||||
oldReferencesAfterConvergence: 0,
|
||||
oldAuthenticationBeforeActivation: true,
|
||||
oldAuthenticationDuringOverlap: true,
|
||||
newAuthenticationDuringOverlap: true,
|
||||
oldAuthenticationRejectedAfterConvergence: true,
|
||||
newAuthenticationAfterContraction: true,
|
||||
contractedToActiveGeneration: true,
|
||||
sensitiveMaterialReported: false,
|
||||
},
|
||||
inputBoundary: {
|
||||
@@ -80,7 +101,7 @@ function report() {
|
||||
fixtureProvisionerRanAsRoot: true,
|
||||
privateDirectoryMode: '0700',
|
||||
fileMode: '0600',
|
||||
fileCount: 2,
|
||||
fileCount: 3,
|
||||
issueDigest: digest('4'),
|
||||
rotationDigest: digest('5'),
|
||||
distinctRotationMaterial: true,
|
||||
@@ -100,13 +121,20 @@ function report() {
|
||||
durability: {
|
||||
identityVersion: 1,
|
||||
identityStatus: 'active',
|
||||
credentialVersion: 3,
|
||||
credentialState: 'revoked',
|
||||
oldCredentialVersion: 2,
|
||||
oldCredentialState: 'revoked',
|
||||
newCredentialVersion: 2,
|
||||
newCredentialState: 'active',
|
||||
identityMutationCount: 1,
|
||||
credentialMutationCount: 3,
|
||||
credentialMutationCount: 4,
|
||||
issueMutationCount: 1,
|
||||
credentialVersionCount: 3,
|
||||
allowedAuditCount: 4,
|
||||
credentialVersionCount: 4,
|
||||
oldGenerationVersionCount: 1,
|
||||
newGenerationVersionCount: 3,
|
||||
latestGenerationsAreNew: true,
|
||||
allowedAuditCount: 5,
|
||||
authenticationDeniedAuditCount: 4,
|
||||
authenticationRejectedAuditCount: 1,
|
||||
},
|
||||
cleanup: {
|
||||
jobsDeleted: true,
|
||||
@@ -114,6 +142,9 @@ function report() {
|
||||
evidenceJobsDeleted: true,
|
||||
storageProvisionJobDeleted: true,
|
||||
deliveryVolumeClaimDeleted: true,
|
||||
controlDeploymentDeleted: true,
|
||||
controlServiceDeleted: true,
|
||||
controlRuntimeSecretDeleted: true,
|
||||
},
|
||||
gates: {
|
||||
realThreeNodeKubernetes: true,
|
||||
@@ -121,6 +152,7 @@ function report() {
|
||||
realKubeletSecretProjection: true,
|
||||
realAdministrationProductCommands: true,
|
||||
realPersistentCredentialCustody: true,
|
||||
realClusterControlAuthenticationRotation: true,
|
||||
responseLossReplay: true,
|
||||
failedInputStageClosed: true,
|
||||
leastPrivilege: true,
|
||||
@@ -142,6 +174,7 @@ test('rejects widened authority, false custody and replay duplication', () => {
|
||||
candidate.ceremony.serviceAccountTokenMounted = true;
|
||||
candidate.deliveryCustody.noReplaceReplayPreserved = false;
|
||||
candidate.durability.issueMutationCount = 2;
|
||||
candidate.ceremony.oldReferencesAfterConvergence = 1;
|
||||
candidate.gates.passed = false;
|
||||
const codes = validateSecurityAdministrationKubernetesLiveReport(
|
||||
candidate,
|
||||
|
||||
@@ -6,6 +6,8 @@ const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
auditListCommand,
|
||||
clusterControlResources,
|
||||
credentialAuthenticationProbeSource,
|
||||
credentialIssueCommand,
|
||||
credentialRevokeCommand,
|
||||
credentialRotateCommand,
|
||||
@@ -188,6 +190,43 @@ test('waits for per-Pod network policy before mounting private material', () =>
|
||||
assert.doesNotMatch(source, /readFile|process\.env|console\.log/);
|
||||
});
|
||||
|
||||
test('runs the credential ceremony against two real anti-affine control replicas', () => {
|
||||
const [service, deployment] = clusterControlResources(
|
||||
'qinglong3-cluster-control:test',
|
||||
);
|
||||
assert.equal(service.kind, 'Service');
|
||||
assert.equal(service.spec.ports[0].port, 5800);
|
||||
assert.equal(deployment.kind, 'Deployment');
|
||||
assert.equal(deployment.spec.replicas, 2);
|
||||
assert.equal(deployment.spec.strategy.rollingUpdate.maxUnavailable, 0);
|
||||
assert.equal(
|
||||
deployment.spec.template.spec.affinity.podAntiAffinity
|
||||
.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey,
|
||||
'kubernetes.io/hostname',
|
||||
);
|
||||
const environment = deployment.spec.template.spec.containers[0].env;
|
||||
assert.ok(
|
||||
environment.some(
|
||||
(entry) =>
|
||||
entry.name === 'QL3_API_CREDENTIAL_PEPPER_KEYRING_FILE' &&
|
||||
entry.value.endsWith('/keyring.json'),
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
environment.some((entry) => entry.name === 'QL3_API_CREDENTIAL_PEPPER'),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps the real authentication probe content-free', () => {
|
||||
const source = credentialAuthenticationProbeSource();
|
||||
assert.match(source, /ql3-security-live-control/);
|
||||
assert.match(source, /\/api\/v3\/projects\/prj_default\/runs\?limit=1/);
|
||||
assert.match(source, /observedStatus:observed/);
|
||||
assert.match(source, /bytes\?\.fill\(0\)/);
|
||||
assert.doesNotMatch(source, /console\.log|process\.env/);
|
||||
});
|
||||
|
||||
test('live runner remains opt-in, reviewed, cleanup-bound and log-free', () => {
|
||||
const source = fs.readFileSync(
|
||||
path.resolve(
|
||||
@@ -206,7 +245,15 @@ test('live runner remains opt-in, reviewed, cleanup-bound and log-free', () => {
|
||||
assert.match(source, /net\.bridge\.bridge-nf-call-iptables=1/);
|
||||
assert.match(source, /wait-network-policy/);
|
||||
assert.match(source, /projectedMode: 0o444/);
|
||||
assert.match(source, /credential\.issue\.replay/);
|
||||
assert.match(source, /credential\.issue\.old\.replay/);
|
||||
assert.match(source, /ql3-security-live-auth-old-before-activate/);
|
||||
assert.match(source, /ql3-security-live-auth-old-overlap/);
|
||||
assert.match(source, /ql3-security-live-auth-new-overlap/);
|
||||
assert.match(source, /ql3-security-live-auth-old-contracted/);
|
||||
assert.match(source, /ql3-security-live-auth-new-contracted/);
|
||||
assert.match(source, /expectedStatus: 401/);
|
||||
assert.match(source, /expectedStatus: 403/);
|
||||
assert.match(source, /controlRollouts: 3/);
|
||||
assert.match(source, /FallbackToLogsOnError/);
|
||||
assert.match(source, /failureMessage: 'rejected'/);
|
||||
assert.match(
|
||||
|
||||
Reference in New Issue
Block a user