Files
qinglong/packages/ql3-cluster-admin/test/pluginPackageManagementClient.test.cjs
T

1291 lines
37 KiB
JavaScript

const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const {
chmodSync,
mkdtempSync,
readFileSync,
realpathSync,
rmSync,
symlinkSync,
writeFileSync,
} = require('node:fs');
const { createServer } = require('node:https');
const { tmpdir } = require('node:os');
const { join, resolve } = require('node:path');
const { test } = require('node:test');
const {
ClusterPluginPackageManagementClientConfigurationError,
ClusterPluginPackageManagementClientRemoteError,
ClusterPluginPackageManagementClientRequestError,
executeClusterPluginPackageManagementClient,
} = require('@qinglong/cluster-admin/plugin-package-management-client');
const publicClientModule = require('@qinglong/cluster-admin/plugin-package-management-client');
const {
probeClusterAuthenticatedManagementClientReadiness,
} = require('../dist/management-support/managementReadinessProbe.js');
const CA_CERT = resolve(
__dirname,
'../../ql3-cluster-control/test/fixtures/mtls/ca-cert.pem',
);
const SERVER_KEY = resolve(
__dirname,
'../../ql3-cluster-control/test/fixtures/mtls/server-key.pem',
);
const SERVER_CERT = resolve(
__dirname,
'../../ql3-cluster-control/test/fixtures/mtls/server-cert.pem',
);
const CLIENT_KEY = resolve(
__dirname,
'../../ql3-cluster-control/test/fixtures/mtls/client-key.pem',
);
const CLIENT_CERT = resolve(
__dirname,
'../../ql3-cluster-control/test/fixtures/mtls/client-cert.pem',
);
const CLIENT_CLI = resolve(
__dirname,
'../dist/plugin-package/management/pluginPackageManagementClientCli.js',
);
const ASSERTION = 'eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJvcGVyYXRvciJ9.c2ln';
test('keeps owned TLS preparation out of the public client subpath', () => {
assert.equal(
publicClientModule.prepareClusterAuthenticatedManagementClientConfiguration,
undefined,
);
assert.equal(
publicClientModule.prepareClusterAuthenticatedManagementClientKindConfiguration,
undefined,
);
assert.equal(
publicClientModule.probeClusterAuthenticatedManagementClientReadiness,
undefined,
);
});
function inspectCommand(operation = 'plugin-package.inspect') {
return {
schemaVersion: 1,
operation,
request: {
actionRef: 'package:cluster-monitor:1',
approvalRequestId: 'approval-cluster-monitor-1',
inspectionId: 'inspection-cluster-monitor-1',
},
};
}
function commands() {
const decision = {
actionRef: 'package:cluster-monitor:1',
approvalRequestId: 'approval-cluster-monitor-1',
expectedVersion: 1,
decisionId: 'decision-cluster-monitor-1',
auditEventId: 'audit-cluster-monitor-decision-1',
decision: 'approved',
reasonCode: 'reviewed',
};
const inspection = inspectCommand().request;
return [
{
schemaVersion: 1,
operation: 'plugin-package.propose',
request: {
actionRef: 'package:cluster-monitor:1',
approvalRequestId: 'approval-cluster-monitor-1',
proposalAuditEventId: 'audit-cluster-monitor-proposal-1',
approvalAuditEventId: 'audit-cluster-monitor-approval-1',
actionInput: {},
},
},
{
schemaVersion: 1,
operation: 'plugin-package.decide',
request: decision,
},
inspectCommand(),
{
schemaVersion: 1,
operation: 'plugin-package.lifecycle.propose',
request: {
actionRef: 'lifecycle:cluster-monitor:disable:1',
approvalRequestId: 'approval-lifecycle-cluster-monitor-1',
approvalAuditEventId: 'audit-lifecycle-approval-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.lifecycle.decide',
request: {
...decision,
actionRef: 'lifecycle:cluster-monitor:disable:1',
approvalRequestId: 'approval-lifecycle-cluster-monitor-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.lifecycle.inspect',
request: {
actionRef: 'lifecycle:cluster-monitor:disable:1',
approvalRequestId: 'approval-lifecycle-cluster-monitor-1',
inspectionId: 'inspection-lifecycle-cluster-monitor-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.installation.inspect',
request: {
projectId: 'project-1',
packageName: 'cluster-monitor',
inspectionId: 'inspection-installation-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.installation.list',
request: {
projectId: 'project-1',
limit: 8,
inspectionId: 'inspection-installation-list-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.publisher-revocation.propose',
request: {
actionRef: 'publisher:example:revocation:1',
approvalRequestId: 'approval-publisher-revocation-1',
proposalAuditEventId: 'audit-publisher-revocation-proposal-1',
approvalAuditEventId: 'audit-publisher-revocation-approval-1',
publisher: 'example',
keyId: 'publisher-key-1',
authorizationMode: 'dual_control',
reasonCode: 'suspected_key_compromise',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.publisher-revocation.decide',
request: decision,
},
{
schemaVersion: 1,
operation: 'plugin-package.publisher-revocation.inspect',
request: inspection,
},
{
schemaVersion: 1,
operation: 'plugin-package.publisher-trust-transition.propose',
request: {
actionRef: 'publisher:example:transition:1',
approvalRequestId: 'approval-publisher-transition-1',
proposalAuditEventId: 'audit-publisher-transition-proposal-1',
approvalAuditEventId: 'audit-publisher-transition-approval-1',
mode: 'overlap_add',
publisher: 'example',
keyId: 'publisher-key-2',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.publisher-trust-transition.decide',
request: decision,
},
{
schemaVersion: 1,
operation: 'plugin-package.publisher-trust-transition.inspect',
request: inspection,
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.plan',
request: {
actionRef: 'secret-binding:cluster-monitor:1',
projectId: 'project-1',
packageName: 'cluster-monitor',
assignments: [{
name: 'TOKEN',
secretRef:
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJwcm9qZWN0LTEiLCJuYW1lIjoicnVudGltZS10b2tlbiIsInZlcnNpb24iOjJ9',
}],
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.propose',
request: {
actionRef: 'secret-binding:cluster-monitor:1',
approvalRequestId: 'approval-secret-binding-1',
approvalAuditEventId: 'audit-secret-binding-approval-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.decide',
request: {
...decision,
actionRef: 'secret-binding:cluster-monitor:1',
approvalRequestId: 'approval-secret-binding-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.inspect',
request: {
actionRef: 'secret-binding:cluster-monitor:1',
approvalRequestId: 'approval-secret-binding-1',
inspectionId: 'inspection-secret-binding-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.transition.plan',
request: {
actionRef: 'secret-transition:cluster-monitor:2',
projectId: 'project-1',
packageName: 'cluster-monitor',
assignments: [{
name: 'TOKEN',
secretRef:
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJwcm9qZWN0LTEiLCJuYW1lIjoicnVudGltZS10b2tlbiIsInZlcnNpb24iOjJ9',
}],
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.transition.propose',
request: {
actionRef: 'secret-transition:cluster-monitor:2',
approvalRequestId: 'approval-secret-transition-1',
approvalAuditEventId: 'audit-secret-transition-approval-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.transition.decide',
request: {
...decision,
actionRef: 'secret-transition:cluster-monitor:2',
approvalRequestId: 'approval-secret-transition-1',
},
},
{
schemaVersion: 1,
operation: 'plugin-package.secret-binding.transition.inspect',
request: {
actionRef: 'secret-transition:cluster-monitor:2',
approvalRequestId: 'approval-secret-transition-1',
inspectionId: 'inspection-secret-transition-1',
},
},
];
}
function approvalSummary() {
return {
id: 'approval-cluster-monitor-1',
projectId: 'project-1',
version: 2,
state: 'approved',
risk: 'high',
decisionMode: 'separation_of_duty',
requestedAtMs: 1_000,
expiresAtMs: 10_000,
decision: 'approved',
decisionReasonCode: 'reviewed',
decidedAtMs: 2_000,
dispatchId: null,
consumedAtMs: null,
actionDigest: 'action-digest-1',
previewDigest: 'preview-digest-1',
};
}
function proposalSummary(operation) {
const common = {
actionRef: 'package:cluster-monitor:1',
projectId: 'project-1',
actionDigest: 'action-digest-1',
previewDigest: 'preview-digest-1',
proposalDigest: 'proposal-digest-1',
createdAtMs: 1_000,
};
if (operation.startsWith('plugin-package.publisher-trust-transition.')) {
return {
...common,
trustAuthorityId: 'cluster',
trustGeneration: 2,
mode: 'overlap_add',
publisher: 'example',
keyId: 'publisher-key-2',
previousTrustDigest: 'trust-digest-1',
currentTrustDigest: 'trust-digest-2',
};
}
if (operation.startsWith('plugin-package.publisher-revocation.')) {
return {
...common,
trustAuthorityId: 'cluster',
trustGeneration: 2,
publisher: 'example',
keyId: 'publisher-key-1',
previousTrustDigest: 'trust-digest-1',
currentTrustDigest: 'trust-digest-2',
authorizationMode: 'dual_control',
reasonCode: 'suspected_key_compromise',
};
}
return {
...common,
packageName: '@example/cluster-monitor',
packageVersion: '1.0.0',
operation: 'install',
sourceKind: 'oci',
architecture: 'arm64',
deploymentProfile: 'cluster',
targetGeneration: 1,
};
}
function installationSummary() {
return {
installationId: 'install-cluster-monitor-1',
projectId: 'project-1',
packageName: 'cluster-monitor',
packageVersion: '1.0.0',
operation: 'install',
state: 'active',
targetGeneration: 1,
activeLockDigest: 'a'.repeat(64),
previousActiveLockDigest: null,
recoveryAction: 'none',
availability: 'active',
quarantineReason: null,
quarantineAuthorizationMode: null,
quarantineEventDigest: null,
quarantinedAtMs: null,
withdrawalStatus: null,
withdrawalReceiptDigest: null,
withdrawalCommittedAtMs: null,
failureReason: null,
failedFrom: null,
failedAtMs: null,
version: 4,
createdAtMs: 1_000,
updatedAtMs: 2_000,
recordDigest: 'b'.repeat(64),
};
}
function lifecyclePlanSummary() {
return {
actionRef: 'lifecycle:cluster-monitor:disable:1',
planDigest: '1'.repeat(64),
plannedAtMs: 1_000,
expiresAtMs: 10_000,
action: 'disable',
projectId: 'project-1',
packageName: 'cluster-monitor',
installationId: 'install-cluster-monitor-1',
lockDigest: '2'.repeat(64),
installVersion: 4,
installRecordDigest: '3'.repeat(64),
expected: {
version: 0,
disposition: 'active',
eventDigest: null,
},
generationDigest: '4'.repeat(64),
materializedRevisionDigest: '5'.repeat(64),
currentToolSnapshotDigest: '6'.repeat(64),
taskIds: ['collect'],
resourceCounts: { tasks: 1, tools: 0, workflows: 0, prompts: 0 },
referenceGraphDigest: '7'.repeat(64),
blockingReferences: [],
impactDigest: '8'.repeat(64),
};
}
function secretBindingPlanSummary() {
return {
actionRef: 'secret-binding:cluster-monitor:1',
projectId: 'project-1',
packageName: 'cluster-monitor',
installationId: 'install-cluster-monitor-1',
generation: 1,
generationDigest: '9'.repeat(64),
lockDigest: 'a'.repeat(64),
manifestDigest: 'b'.repeat(64),
entries: [{
name: 'TOKEN',
required: true,
secretRef:
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJwcm9qZWN0LTEiLCJuYW1lIjoicnVudGltZS10b2tlbiIsInZlcnNpb24iOjJ9',
}],
plannedAtMs: 1_000,
expiresAtMs: 10_000,
planDigest: 'c'.repeat(64),
approvalPlanDigest: 'd'.repeat(64),
};
}
function secretBindingTransitionPlanSummary() {
const secretRef =
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJwcm9qZWN0LTEiLCJuYW1lIjoicnVudGltZS10b2tlbiIsInZlcnNpb24iOjJ9';
return {
actionRef: 'secret-transition:cluster-monitor:2',
approvalPlanDigest: '1'.repeat(64),
plannedAtMs: 1_000,
expiresAtMs: 10_000,
kind: 'rotate',
transitionDigest: '2'.repeat(64),
projectId: 'project-1',
packageName: 'cluster-monitor',
previousInstallationId: 'install-cluster-monitor-1',
previousGeneration: 1,
previousGenerationDigest: '3'.repeat(64),
previousActiveLockDigest: '4'.repeat(64),
previousAttemptGeneration: 1,
nextInstallationId: 'install-cluster-monitor-2',
nextGeneration: 2,
nextGenerationDigest: '5'.repeat(64),
nextLockDigest: '6'.repeat(64),
nextManifestDigest: '7'.repeat(64),
changes: [{
name: 'TOKEN',
requirement: 'unchanged',
reference: 'rotated',
previous: {
required: true,
secretRef:
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJwcm9qZWN0LTEiLCJuYW1lIjoicnVudGltZS10b2tlbiIsInZlcnNpb24iOjF9',
},
next: { required: true, secretRef },
}],
};
}
function successfulResult(operation) {
const secretApproval = {
...approvalSummary(),
id: 'approval-secret-binding-1',
actionDigest: 'd'.repeat(64),
previewDigest: 'c'.repeat(64),
};
const transitionApproval = {
...approvalSummary(),
id: 'approval-secret-transition-1',
projectId: 'project-1',
actionDigest: '1'.repeat(64),
previewDigest: '2'.repeat(64),
};
if (operation === 'plugin-package.secret-binding.transition.plan') {
return {
schemaVersion: 1,
operation,
status: 'created',
plan: secretBindingTransitionPlanSummary(),
};
}
if (operation === 'plugin-package.secret-binding.transition.propose') {
return {
schemaVersion: 1,
operation,
approvalStatus: 'created',
plan: secretBindingTransitionPlanSummary(),
approval: transitionApproval,
};
}
if (operation === 'plugin-package.secret-binding.transition.inspect') {
return {
schemaVersion: 1,
operation,
plan: secretBindingTransitionPlanSummary(),
approval: transitionApproval,
stale: false,
};
}
if (operation === 'plugin-package.secret-binding.transition.decide') {
return {
schemaVersion: 1,
operation,
status: 'decided',
approval: transitionApproval,
};
}
if (operation === 'plugin-package.secret-binding.plan') {
return {
schemaVersion: 1,
operation,
status: 'created',
plan: secretBindingPlanSummary(),
};
}
if (operation === 'plugin-package.secret-binding.propose') {
return {
schemaVersion: 1,
operation,
approvalStatus: 'created',
plan: secretBindingPlanSummary(),
approval: secretApproval,
};
}
if (operation === 'plugin-package.secret-binding.inspect') {
return {
schemaVersion: 1,
operation,
plan: secretBindingPlanSummary(),
approval: secretApproval,
stale: false,
};
}
if (operation === 'plugin-package.secret-binding.decide') {
return {
schemaVersion: 1,
operation,
status: 'decided',
approval: secretApproval,
};
}
if (operation === 'plugin-package.installation.inspect') {
return {
schemaVersion: 1,
operation,
installation: installationSummary(),
};
}
if (operation === 'plugin-package.installation.list') {
return {
schemaVersion: 1,
operation,
installations: [installationSummary()],
truncated: false,
next: null,
};
}
if (operation === 'plugin-package.lifecycle.propose') {
return {
schemaVersion: 1,
operation,
approvalStatus: 'created',
plan: lifecyclePlanSummary(),
approval: approvalSummary(),
};
}
if (operation === 'plugin-package.lifecycle.inspect') {
return {
schemaVersion: 1,
operation,
plan: lifecyclePlanSummary(),
approval: approvalSummary(),
stale: false,
};
}
if (operation.endsWith('.propose')) {
return {
schemaVersion: 1,
operation,
proposalStatus: 'created',
approvalStatus: 'created',
proposal: proposalSummary(operation),
approval: approvalSummary(),
};
}
if (operation.endsWith('.decide')) {
return {
schemaVersion: 1,
operation,
status: 'decided',
approval: approvalSummary(),
};
}
return {
schemaVersion: 1,
operation,
proposal: null,
approval: null,
};
}
function privateWrite(path, value) {
writeFileSync(
path,
typeof value === 'string' ? value : JSON.stringify(value),
{ mode: 0o600 },
);
chmodSync(path, 0o600);
}
function createClientFiles(port, command = inspectCommand()) {
const directory = realpathSync(
mkdtempSync(join(tmpdir(), 'ql3-management-client-')),
);
const configFile = join(directory, 'client.json');
const commandFile = join(directory, 'command.json');
const assertionFile = join(directory, 'assertion.jwt');
privateWrite(configFile, {
schemaVersion: 1,
endpoint: `https://localhost:${port}/api/v3/plugin-packages/management`,
servername: 'localhost',
caFile: CA_CERT,
requestTimeoutMs: 1_000,
});
privateWrite(commandFile, command);
privateWrite(assertionFile, ASSERTION);
return {
directory,
paths: { configFile, commandFile, assertionFile },
};
}
async function startServer(handler, options = {}) {
const server = createServer(
{
key: readFileSync(SERVER_KEY),
cert: readFileSync(SERVER_CERT),
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
...options,
},
handler,
);
await new Promise((resolvePromise, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', resolvePromise);
});
return {
server,
port: server.address().port,
close: () =>
new Promise((resolvePromise, reject) => {
server.close((error) => (error ? reject(error) : resolvePromise()));
}),
};
}
function sendJson(response, statusCode, value, headers = {}) {
const body = Buffer.from(JSON.stringify(value));
response.writeHead(statusCode, {
'content-type': 'application/json; charset=utf-8',
'content-length': String(body.length),
...headers,
});
response.end(body);
}
test('sends one TLS 1.3 management command and validates the low-sensitive result', async () => {
const received = [];
const fixture = await startServer((request, response) => {
const chunks = [];
request.on('data', (chunk) => chunks.push(chunk));
request.once('end', () => {
received.push({
method: request.method,
path: request.url,
authorization: request.headers.authorization,
acceptEncoding: request.headers['accept-encoding'],
protocol: request.socket.getProtocol(),
command: JSON.parse(Buffer.concat(chunks).toString('utf8')),
});
sendJson(response, 200, {
schemaVersion: 1,
requestId: 'request-client-1',
result: {
schemaVersion: 1,
operation: 'plugin-package.inspect',
proposal: null,
approval: null,
},
});
});
});
const files = createClientFiles(fixture.port);
try {
const result = await executeClusterPluginPackageManagementClient(
files.paths,
);
assert.equal(result.requestId, 'request-client-1');
assert.equal(result.result.operation, 'plugin-package.inspect');
assert.deepEqual(received, [
{
method: 'POST',
path: '/api/v3/plugin-packages/management',
authorization: `Bearer ${ASSERTION}`,
acceptEncoding: 'identity',
protocol: 'TLSv1.3',
command: inspectCommand(),
},
]);
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('probes only the fixed TLS readiness endpoint without management authority', async () => {
const received = [];
let ready = true;
const fixture = await startServer((request, response) => {
const chunks = [];
request.on('data', (chunk) => chunks.push(chunk));
request.once('end', () => {
received.push({
method: request.method,
path: request.url,
authorization: request.headers.authorization,
contentType: request.headers['content-type'],
bodyBytes: Buffer.concat(chunks).length,
protocol: request.socket.getProtocol(),
});
sendJson(response, ready ? 200 : 503, {
schemaVersion: 1,
status: ready ? 'ready' : 'not_ready',
});
});
});
const files = createClientFiles(fixture.port);
try {
assert.deepEqual(
await probeClusterAuthenticatedManagementClientReadiness(
files.paths.configFile,
'package',
),
{ schemaVersion: 1, transport: 'https', ready: true },
);
ready = false;
assert.deepEqual(
await probeClusterAuthenticatedManagementClientReadiness(
files.paths.configFile,
'package',
),
{ schemaVersion: 1, transport: 'https', ready: false },
);
assert.deepEqual(received, [
{
method: 'GET',
path: '/readyz',
authorization: undefined,
contentType: undefined,
bodyBytes: 0,
protocol: 'TLSv1.3',
},
{
method: 'GET',
path: '/readyz',
authorization: undefined,
contentType: undefined,
bodyBytes: 0,
protocol: 'TLSv1.3',
},
]);
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('presents the reviewed client certificate for mTLS readiness', async () => {
let authorized = false;
const fixture = await startServer(
(request, response) => {
authorized = request.socket.authorized;
sendJson(response, 200, { schemaVersion: 1, status: 'ready' });
},
{
ca: readFileSync(CA_CERT),
requestCert: true,
rejectUnauthorized: true,
},
);
const files = createClientFiles(fixture.port);
const clientKeyFile = join(files.directory, 'client-key.pem');
privateWrite(clientKeyFile, readFileSync(CLIENT_KEY, 'utf8'));
privateWrite(files.paths.configFile, {
schemaVersion: 1,
endpoint: `https://localhost:${fixture.port}/api/v3/runs/management`,
servername: 'localhost',
caFile: CA_CERT,
clientCertificateFile: CLIENT_CERT,
clientPrivateKeyFile: clientKeyFile,
requestTimeoutMs: 1_000,
});
try {
assert.deepEqual(
await probeClusterAuthenticatedManagementClientReadiness(
files.paths.configFile,
'run',
),
{ schemaVersion: 1, transport: 'https', ready: true },
);
assert.equal(authorized, true);
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('readiness probe rejects unreviewed status and bounded response drift', async () => {
let behavior = 'wrong-status';
const fixture = await startServer((_request, response) => {
if (behavior === 'wrong-status') {
sendJson(response, 200, { schemaVersion: 1, status: 'live' });
return;
}
if (behavior === 'redirect') {
sendJson(response, 302, { schemaVersion: 1, status: 'ready' });
return;
}
response.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
});
response.end(Buffer.alloc(1_025, 0x61));
});
const files = createClientFiles(fixture.port);
try {
for (const next of ['wrong-status', 'redirect', 'oversized']) {
behavior = next;
await assert.rejects(
probeClusterAuthenticatedManagementClientReadiness(
files.paths.configFile,
'package',
),
ClusterPluginPackageManagementClientRequestError,
);
}
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('permits and validates exactly the twenty-two public management operations', async () => {
const received = [];
const fixture = await startServer((request, response) => {
const chunks = [];
request.on('data', (chunk) => chunks.push(chunk));
request.once('end', () => {
const operation = JSON.parse(
Buffer.concat(chunks).toString('utf8'),
).operation;
received.push(operation);
sendJson(response, 200, {
schemaVersion: 1,
requestId: `request-operation-${received.length}`,
result: successfulResult(operation),
});
});
});
const files = createClientFiles(fixture.port);
try {
for (const command of commands()) {
privateWrite(files.paths.commandFile, command);
const result = await executeClusterPluginPackageManagementClient(
files.paths,
);
assert.equal(result.result.operation, command.operation);
}
assert.deepEqual(
received,
commands().map((command) => command.operation),
);
privateWrite(files.paths.commandFile, {
schemaVersion: 1,
operation: 'plugin-package.execute',
request: {},
});
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
(error) => {
assert.equal(
error.code,
'CLUSTER_PLUGIN_PACKAGE_TRANSPORT_REQUEST_INVALID',
);
return true;
},
);
assert.equal(received.length, 22);
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('rejects installation inventory responses outside the requested project and keyset', async () => {
const responses = [
{
installations: [
{ ...installationSummary(), projectId: 'another-project' },
],
truncated: false,
next: null,
},
{
installations: [installationSummary(), installationSummary()],
truncated: false,
next: null,
},
{
installations: [installationSummary()],
truncated: true,
next: { packageName: 'another-package' },
},
{
installations: [
{
...installationSummary(),
availability: 'quarantined',
quarantineReason: 'confirmed_key_compromise',
},
],
truncated: false,
next: null,
},
];
const fixture = await startServer((_request, response) => {
sendJson(response, 200, {
schemaVersion: 1,
requestId: `request-invalid-inventory-${responses.length}`,
result: {
schemaVersion: 1,
operation: 'plugin-package.installation.list',
...responses.shift(),
},
});
});
const command = {
schemaVersion: 1,
operation: 'plugin-package.installation.list',
request: {
projectId: 'project-1',
limit: 2,
inspectionId: 'inspection-invalid-inventory-1',
},
};
const files = createClientFiles(fixture.port, command);
try {
for (let index = 0; index < 4; index += 1) {
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
}
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('rejects Secret binding response drift before reporting success', async () => {
const command = commands().find(
({ operation }) => operation === 'plugin-package.secret-binding.propose',
);
const invalidResults = [
{
...successfulResult(command.operation),
plan: {
...secretBindingPlanSummary(),
actionRef: 'secret-binding:another-package:1',
},
},
{
...successfulResult(command.operation),
plan: {
...secretBindingPlanSummary(),
entries: [
{
...secretBindingPlanSummary().entries[0],
secretRef:
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJhbm90aGVyLXByb2plY3QiLCJuYW1lIjoicnVudGltZS10b2tlbiIsInZlcnNpb24iOjJ9',
},
],
},
},
{
...successfulResult(command.operation),
plan: {
...secretBindingPlanSummary(),
entries: [
...secretBindingPlanSummary().entries,
...secretBindingPlanSummary().entries,
],
},
},
{
...successfulResult(command.operation),
approval: {
...successfulResult(command.operation).approval,
actionDigest: 'e'.repeat(64),
},
},
];
const fixture = await startServer((_request, response) => {
sendJson(response, 200, {
schemaVersion: 1,
requestId: `request-invalid-secret-binding-${invalidResults.length}`,
result: invalidResults.shift(),
});
});
const files = createClientFiles(fixture.port, command);
try {
for (let index = 0; index < 4; index += 1) {
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
}
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('binds Secret binding plan and plan-less inspection to the exact request', async () => {
const planCommand = commands().find(
({ operation }) => operation === 'plugin-package.secret-binding.plan',
);
const inspectCommand = commands().find(
({ operation }) => operation === 'plugin-package.secret-binding.inspect',
);
const invalidResponses = [
{
command: planCommand,
result: {
...successfulResult(planCommand.operation),
plan: {
...secretBindingPlanSummary(),
entries: [{
...secretBindingPlanSummary().entries[0],
secretRef:
'qlsecret:v1:eyJwcm9qZWN0SWQiOiJwcm9qZWN0LTEiLCJuYW1lIjoiYW5vdGhlci10b2tlbiIsInZlcnNpb24iOjJ9',
}],
},
},
},
{
command: inspectCommand,
result: {
...successfulResult(inspectCommand.operation),
plan: null,
approval: {
...successfulResult(inspectCommand.operation).approval,
id: 'approval-secret-binding-another',
},
},
},
{
command: inspectCommand,
result: {
...successfulResult(inspectCommand.operation),
plan: null,
approval: null,
},
},
];
const fixture = await startServer((_request, response) => {
sendJson(response, 200, {
schemaVersion: 1,
requestId: `request-exact-secret-binding-${invalidResponses.length}`,
result: invalidResponses[0].result,
});
});
try {
for (const invalid of invalidResponses) {
invalidResponses[0] = invalid;
const files = createClientFiles(fixture.port, invalid.command);
try {
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
} finally {
rmSync(files.directory, { recursive: true, force: true });
}
}
} finally {
await fixture.close();
}
});
test('rejects non-private, symlinked, and non-exact input files before transport', async () => {
const files = createClientFiles(443);
try {
chmodSync(files.paths.assertionFile, 0o644);
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientConfigurationError,
);
chmodSync(files.paths.assertionFile, 0o600);
const commandLink = join(files.directory, 'command-link.json');
symlinkSync(files.paths.commandFile, commandLink);
await assert.rejects(
executeClusterPluginPackageManagementClient({
...files.paths,
commandFile: commandLink,
}),
ClusterPluginPackageManagementClientConfigurationError,
);
privateWrite(files.paths.configFile, {
schemaVersion: 1,
endpoint: 'https://localhost/api/v3/plugin-packages/management',
servername: 'localhost',
caFile: CA_CERT,
requestTimeoutMs: 1_000,
proxy: 'https://proxy.invalid',
});
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientConfigurationError,
);
} finally {
rmSync(files.directory, { recursive: true, force: true });
}
});
test('does not follow redirects and rejects malformed, oversized, and timed-out responses', async () => {
let behavior = 'remote-error';
let hits = 0;
const fixture = await startServer((_request, response) => {
hits += 1;
if (behavior === 'remote-error') {
sendJson(
response,
409,
{
schemaVersion: 1,
requestId: 'request-rejected-1',
error: { code: 'test_rejection' },
},
{ 'retry-after': '7' },
);
return;
}
if (behavior === 'redirect') {
sendJson(
response,
302,
{
schemaVersion: 1,
requestId: 'request-redirect-1',
error: { code: 'redirected' },
},
{ location: 'https://example.invalid/' },
);
return;
}
if (behavior === 'wrong-content-type') {
response.writeHead(200, { 'content-type': 'text/plain' });
response.end('no');
return;
}
if (behavior === 'oversized') {
response.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
});
response.end(Buffer.alloc(128 * 1024 + 1, 0x61));
return;
}
if (behavior === 'truncated') {
response.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
'content-length': '100',
});
response.write('{"');
response.destroy();
}
});
const files = createClientFiles(fixture.port);
try {
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
(error) => {
assert.equal(
error instanceof ClusterPluginPackageManagementClientRemoteError,
true,
);
assert.equal(error.statusCode, 409);
assert.equal(error.responseCode, 'test_rejection');
assert.equal(error.requestId, 'request-rejected-1');
assert.equal(error.retryAfterSeconds, 7);
return true;
},
);
behavior = 'redirect';
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
assert.equal(hits, 2);
behavior = 'wrong-content-type';
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
behavior = 'oversized';
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
behavior = 'truncated';
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
behavior = 'timeout';
await assert.rejects(
executeClusterPluginPackageManagementClient(files.paths),
ClusterPluginPackageManagementClientRequestError,
);
assert.equal(hits, 6);
} finally {
await fixture.close();
rmSync(files.directory, { recursive: true, force: true });
}
});
test('CLI accepts path-only arguments and never reports secret content or paths', () => {
const files = createClientFiles(443);
try {
privateWrite(files.paths.assertionFile, 'top-secret-assertion');
const result = spawnSync(
process.execPath,
[
CLIENT_CLI,
`--config=${files.paths.configFile}`,
`--command=${files.paths.commandFile}`,
`--assertion=${files.paths.assertionFile}`,
],
{ encoding: 'utf8' },
);
assert.equal(result.status, 1);
assert.equal(result.stdout, '');
const fact = JSON.parse(result.stderr);
assert.deepEqual(fact, {
schemaVersion: 1,
component: 'qinglong3-plugin-package-management-client',
event: 'command_failed',
code: 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_CONFIG_INVALID',
});
assert.equal(result.stderr.includes('top-secret-assertion'), false);
assert.equal(result.stderr.includes(files.directory), false);
const help = spawnSync(process.execPath, [CLIENT_CLI, '--help'], {
encoding: 'utf8',
});
assert.equal(help.status, 0);
assert.match(help.stdout, /^Usage: ql3-plugin-package-client /);
assert.equal(help.stderr, '');
} finally {
rmSync(files.directory, { recursive: true, force: true });
}
});