mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
WorkerCredentialDeliveryUnavailableError,
|
||||
} = require('@qinglong/runtime-core/worker-credential-delivery');
|
||||
const {
|
||||
WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_SECONDS,
|
||||
WorkerCredentialKubernetesTokenRequestUnavailableError,
|
||||
createWorkerCredentialKubernetesTokenRequestSession,
|
||||
} = require(
|
||||
'@qinglong/cluster-admin/worker-credential-kubernetes-token-request'
|
||||
);
|
||||
|
||||
const NOW_MS = 1_800_000_000_000;
|
||||
const DELIVERY = Object.freeze({
|
||||
clusterIdentity: 'cluster-production-a',
|
||||
stageNamespace: 'qinglong-worker-a-stage',
|
||||
namespace: 'qinglong-worker-a',
|
||||
targetSecretName: 'worker-a-credential',
|
||||
targetDeploymentName: 'worker-a',
|
||||
targetDataKey: 'credential-token',
|
||||
});
|
||||
|
||||
function segment(value) {
|
||||
return Buffer.from(JSON.stringify(value), 'utf8').toString('base64url');
|
||||
}
|
||||
|
||||
function jwt(overrides = {}) {
|
||||
const issuedAt = Math.floor(NOW_MS / 1_000);
|
||||
const header = { alg: 'RS256', typ: 'JWT', ...(overrides.header ?? {}) };
|
||||
const claims = {
|
||||
sub:
|
||||
'system:serviceaccount:qinglong-worker-a-stage:' +
|
||||
'ql3-worker-credential-admin',
|
||||
iat: issuedAt,
|
||||
exp: issuedAt + WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_SECONDS,
|
||||
aud: ['https://kubernetes.default.svc'],
|
||||
...(overrides.claims ?? {}),
|
||||
};
|
||||
return `${segment(header)}.${segment(claims)}.${Buffer.from('signature').toString('base64url')}`;
|
||||
}
|
||||
|
||||
function permission(attributes) {
|
||||
if (
|
||||
attributes.namespace === DELIVERY.stageNamespace &&
|
||||
attributes.resource === 'secrets'
|
||||
) {
|
||||
return ['get', 'list', 'create', 'delete'].includes(attributes.verb);
|
||||
}
|
||||
if (
|
||||
attributes.namespace === DELIVERY.namespace &&
|
||||
attributes.resource === 'secrets' &&
|
||||
attributes.name === DELIVERY.targetSecretName
|
||||
) {
|
||||
return ['get', 'update'].includes(attributes.verb);
|
||||
}
|
||||
return attributes.namespace === DELIVERY.namespace &&
|
||||
attributes.group === 'apps' &&
|
||||
attributes.resource === 'deployments' &&
|
||||
attributes.name === DELIVERY.targetDeploymentName &&
|
||||
['get', 'update'].includes(attributes.verb);
|
||||
}
|
||||
|
||||
function issuerPermission(attributes) {
|
||||
return attributes.namespace === DELIVERY.stageNamespace &&
|
||||
attributes.verb === 'create' &&
|
||||
attributes.resource === 'serviceaccounts' &&
|
||||
attributes.subresource === 'token' &&
|
||||
attributes.name === 'ql3-worker-credential-admin';
|
||||
}
|
||||
|
||||
function fixture(options = {}) {
|
||||
const value = options.token ?? jwt();
|
||||
const issuedAt = Math.floor(NOW_MS / 1_000);
|
||||
const response = {
|
||||
apiVersion: 'authentication.k8s.io/v1',
|
||||
kind: 'TokenRequest',
|
||||
status: {
|
||||
token: value,
|
||||
expirationTimestamp:
|
||||
options.expirationTimestamp ??
|
||||
new Date(
|
||||
(issuedAt + WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_SECONDS) *
|
||||
1_000,
|
||||
),
|
||||
},
|
||||
};
|
||||
const requests = [];
|
||||
const receivedTokens = [];
|
||||
let active = false;
|
||||
let disposals = 0;
|
||||
const tokenRequests = {
|
||||
async createNamespacedServiceAccountToken(request) {
|
||||
requests.push(structuredClone(request));
|
||||
if (options.requestError) throw new Error('sensitive upstream failure');
|
||||
return response;
|
||||
},
|
||||
};
|
||||
const issuerAuthorization = {
|
||||
async createSelfSubjectAccessReview(request) {
|
||||
return {
|
||||
status: {
|
||||
allowed: options.issuerOverbroad === true
|
||||
? true
|
||||
: issuerPermission(request.body.spec.resourceAttributes),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
const createRestrictedClients = (token) => {
|
||||
receivedTokens.push(token);
|
||||
active = true;
|
||||
const assertActive = () => {
|
||||
if (!active) {
|
||||
throw Object.assign(new Error('disposed credential'), { code: 401 });
|
||||
}
|
||||
};
|
||||
return {
|
||||
secrets: {
|
||||
async readNamespacedSecret() { assertActive(); throw Object.assign(new Error('absent'), { code: 404 }); },
|
||||
async createNamespacedSecret() { assertActive(); throw new Error('unused'); },
|
||||
async replaceNamespacedSecret() { assertActive(); throw new Error('unused'); },
|
||||
async deleteNamespacedSecret() { assertActive(); throw new Error('unused'); },
|
||||
async listNamespacedSecret() { assertActive(); return { items: [] }; },
|
||||
},
|
||||
deployments: {
|
||||
async readNamespacedDeployment() { assertActive(); throw new Error('unused'); },
|
||||
async replaceNamespacedDeployment() { assertActive(); throw new Error('unused'); },
|
||||
},
|
||||
authorization: {
|
||||
async createSelfSubjectAccessReview(request) {
|
||||
assertActive();
|
||||
return {
|
||||
status: {
|
||||
allowed: options.overbroad === true
|
||||
? true
|
||||
: permission(request.body.spec.resourceAttributes),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
dispose() {
|
||||
disposals += 1;
|
||||
active = false;
|
||||
if (options.disposeError) throw new Error('sensitive dispose failure');
|
||||
},
|
||||
};
|
||||
};
|
||||
const session = createWorkerCredentialKubernetesTokenRequestSession(
|
||||
tokenRequests,
|
||||
issuerAuthorization,
|
||||
createRestrictedClients,
|
||||
{
|
||||
serviceAccountName: 'ql3-worker-credential-admin',
|
||||
identitySecretName: 'worker-a-identity',
|
||||
delivery: DELIVERY,
|
||||
now: () => NOW_MS,
|
||||
},
|
||||
);
|
||||
return {
|
||||
session,
|
||||
response,
|
||||
requests,
|
||||
receivedTokens,
|
||||
get active() { return active; },
|
||||
get disposals() { return disposals; },
|
||||
};
|
||||
}
|
||||
|
||||
test('mints one bounded token, proves exact RBAC and disposes retained clients', async () => {
|
||||
const state = fixture();
|
||||
let retained;
|
||||
const result = await state.session.withDelivery(async (context) => {
|
||||
retained = context.delivery;
|
||||
assert.deepEqual(context.evidence, {
|
||||
tokenLifetimeSeconds: 600,
|
||||
issuerAllowedChecks: 1,
|
||||
issuerDeniedChecks: 8,
|
||||
allowedChecks: 8,
|
||||
deniedChecks: 20,
|
||||
});
|
||||
assert.match(context.delivery.deploymentTargetDigest, /^[0-9a-f]{64}$/);
|
||||
return 'published';
|
||||
});
|
||||
assert.equal(result, 'published');
|
||||
assert.deepEqual(state.requests, [{
|
||||
name: 'ql3-worker-credential-admin',
|
||||
namespace: DELIVERY.stageNamespace,
|
||||
body: {
|
||||
apiVersion: 'authentication.k8s.io/v1',
|
||||
kind: 'TokenRequest',
|
||||
spec: { expirationSeconds: 600 },
|
||||
},
|
||||
}]);
|
||||
assert.equal(state.receivedTokens.length, 1);
|
||||
assert.equal(state.response.status.token, '');
|
||||
assert.equal(state.disposals, 1);
|
||||
assert.equal(state.active, false);
|
||||
await assert.rejects(
|
||||
retained.inspect('123e4567-e89b-42d3-a456-426614174901'),
|
||||
WorkerCredentialDeliveryUnavailableError,
|
||||
);
|
||||
state.receivedTokens[0] = '';
|
||||
});
|
||||
|
||||
test('rejects an overbroad delivery token before invoking credential work', async () => {
|
||||
const state = fixture({ overbroad: true });
|
||||
let invoked = false;
|
||||
await assert.rejects(
|
||||
state.session.withDelivery(async () => {
|
||||
invoked = true;
|
||||
}),
|
||||
WorkerCredentialKubernetesTokenRequestUnavailableError,
|
||||
);
|
||||
assert.equal(invoked, false);
|
||||
assert.equal(state.response.status.token, '');
|
||||
assert.equal(state.disposals, 1);
|
||||
assert.equal(state.active, false);
|
||||
state.receivedTokens[0] = '';
|
||||
});
|
||||
|
||||
test('rejects an overbroad token issuer before requesting a token', async () => {
|
||||
const state = fixture({ issuerOverbroad: true });
|
||||
await assert.rejects(
|
||||
state.session.withDelivery(async () => undefined),
|
||||
WorkerCredentialKubernetesTokenRequestUnavailableError,
|
||||
);
|
||||
assert.equal(state.requests.length, 0);
|
||||
assert.equal(state.receivedTokens.length, 0);
|
||||
assert.equal(state.disposals, 0);
|
||||
});
|
||||
|
||||
test('rejects malformed, wrong-subject and overlong token responses before client creation', async () => {
|
||||
const issuedAt = Math.floor(NOW_MS / 1_000);
|
||||
const cases = [
|
||||
{ token: 'not-a-jwt' },
|
||||
{ token: jwt({ header: { alg: 'none' } }) },
|
||||
{ token: jwt({ claims: { sub: 'system:serviceaccount:other:other' } }) },
|
||||
{ token: jwt({ claims: { exp: issuedAt + 601 } }), expirationTimestamp: new Date((issuedAt + 601) * 1_000) },
|
||||
{ token: jwt(), expirationTimestamp: new Date((issuedAt + 599) * 1_000) },
|
||||
];
|
||||
for (const value of cases) {
|
||||
const state = fixture(value);
|
||||
await assert.rejects(
|
||||
state.session.withDelivery(async () => undefined),
|
||||
WorkerCredentialKubernetesTokenRequestUnavailableError,
|
||||
);
|
||||
assert.equal(state.receivedTokens.length, 0);
|
||||
assert.equal(state.response.status.token, '');
|
||||
}
|
||||
});
|
||||
|
||||
test('maps TokenRequest failures to a stable secret-free error', async () => {
|
||||
const state = fixture({ requestError: true });
|
||||
await assert.rejects(
|
||||
state.session.withDelivery(async () => undefined),
|
||||
(error) => {
|
||||
assert.ok(
|
||||
error instanceof WorkerCredentialKubernetesTokenRequestUnavailableError,
|
||||
);
|
||||
assert.doesNotMatch(error.message, /sensitive|upstream/i);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
assert.equal(state.receivedTokens.length, 0);
|
||||
});
|
||||
|
||||
test('fails closed when restricted client disposal fails', async () => {
|
||||
const state = fixture({ disposeError: true });
|
||||
await assert.rejects(
|
||||
state.session.withDelivery(async () => 'would-have-succeeded'),
|
||||
(error) => {
|
||||
assert.ok(
|
||||
error instanceof WorkerCredentialKubernetesTokenRequestUnavailableError,
|
||||
);
|
||||
assert.doesNotMatch(error.message, /sensitive|dispose/i);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
assert.equal(state.response.status.token, '');
|
||||
assert.equal(state.disposals, 1);
|
||||
assert.equal(state.active, false);
|
||||
state.receivedTokens[0] = '';
|
||||
});
|
||||
Reference in New Issue
Block a user