mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const { generateKeyPairSync } = require('node:crypto');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
ClusterPluginPackageRecoveryProcessConfigError,
|
||||
loadClusterPluginPackageRegistryCredentialFile,
|
||||
loadClusterPluginPackagePublisherTrustFile,
|
||||
loadClusterPluginPackageRecoveryProcessConfig,
|
||||
runClusterPluginPackageRecoveryProcess,
|
||||
} = require('@qinglong/cluster-admin/plugin-package-recovery-process');
|
||||
|
||||
function environment(overrides = {}) {
|
||||
return {
|
||||
QL3_CLUSTER_IDENTITY: 'cluster-production-001',
|
||||
QL3_KUBERNETES_NAMESPACE: 'qinglong3-system',
|
||||
QL3_PLUGIN_PACKAGE_OCI_REGISTRIES: 'ghcr.io,registry.example.com:5443',
|
||||
QL3_PLUGIN_PACKAGE_PUBLISHER_TRUST_FILE: '/trust/publishers.json',
|
||||
QL3_PLUGIN_PACKAGE_OCI_TIMEOUT_MS: '12000',
|
||||
QL3_PLUGIN_PACKAGE_RECOVERY_PAGE_SIZE: '8',
|
||||
QL3_PLUGIN_PACKAGE_RECOVERY_MAX_PAGES: '4',
|
||||
QL3_POSTGRES_PACKAGE_EXECUTOR_URL:
|
||||
'postgresql://ql3_package_executor:secret@postgres/qinglong',
|
||||
QL3_POSTGRES_TLS_MODE: 'disable',
|
||||
QL3_POSTGRES_ALLOW_INSECURE: 'true',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('loads one explicit Package-executor-only recovery process configuration', () => {
|
||||
const config = loadClusterPluginPackageRecoveryProcessConfig(environment());
|
||||
assert.equal(config.clusterIdentity, 'cluster-production-001');
|
||||
assert.equal(config.namespace, 'qinglong3-system');
|
||||
assert.equal(config.publisherTrustAuthorityId, 'cluster');
|
||||
assert.deepEqual(config.allowedRegistries, [
|
||||
'ghcr.io',
|
||||
'registry.example.com:5443',
|
||||
]);
|
||||
assert.equal(config.requestTimeoutMs, 12_000);
|
||||
assert.equal(config.pageSize, 8);
|
||||
assert.equal(config.maxPages, 4);
|
||||
assert.equal(config.database.pool.maxConnections, 1);
|
||||
assert.equal(config.database.connection.tls.mode, 'disable');
|
||||
assert.equal('QL3_POSTGRES_RUNTIME_URL' in config.database.connection, false);
|
||||
});
|
||||
|
||||
test('rejects runtime credentials, duplicate registries and implicit insecure PostgreSQL', async () => {
|
||||
for (const invalid of [
|
||||
environment({
|
||||
QL3_POSTGRES_PACKAGE_EXECUTOR_URL: undefined,
|
||||
QL3_POSTGRES_RUNTIME_URL:
|
||||
'postgresql://ql3_runtime:secret@postgres/qinglong',
|
||||
}),
|
||||
environment({
|
||||
QL3_PLUGIN_PACKAGE_OCI_REGISTRIES: 'ghcr.io,ghcr.io',
|
||||
}),
|
||||
environment({
|
||||
QL3_POSTGRES_ALLOW_INSECURE: undefined,
|
||||
}),
|
||||
]) {
|
||||
assert.throws(
|
||||
() => loadClusterPluginPackageRecoveryProcessConfig(invalid),
|
||||
ClusterPluginPackageRecoveryProcessConfigError,
|
||||
);
|
||||
}
|
||||
|
||||
let touched = false;
|
||||
await assert.rejects(
|
||||
runClusterPluginPackageRecoveryProcess({
|
||||
environment: environment({
|
||||
QL3_PLUGIN_PACKAGE_RECOVERY_MAX_PAGES: '65',
|
||||
}),
|
||||
openDatabase: async () => {
|
||||
touched = true;
|
||||
throw new Error('must not open');
|
||||
},
|
||||
api: {},
|
||||
stageAuthority: {},
|
||||
}),
|
||||
ClusterPluginPackageRecoveryProcessConfigError,
|
||||
);
|
||||
assert.equal(touched, false);
|
||||
});
|
||||
|
||||
test('loads a bounded read-only publisher trust file', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-publisher-trust-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const filePath = path.join(root, 'publishers.json');
|
||||
const { publicKey } = generateKeyPairSync('ed25519');
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
JSON.stringify({
|
||||
schema: 'qinglong/plugin-package-publisher-trust@v1',
|
||||
keys: [
|
||||
{
|
||||
publisher: 'packages.example.com',
|
||||
keyId: 'release-2026',
|
||||
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }),
|
||||
notBeforeMs: 100,
|
||||
notAfterMs: 10_000,
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ mode: 0o444 },
|
||||
);
|
||||
fs.chmodSync(filePath, 0o444);
|
||||
const trust = loadClusterPluginPackagePublisherTrustFile(filePath);
|
||||
assert.equal(trust.size, 1);
|
||||
|
||||
fs.chmodSync(filePath, 0o666);
|
||||
assert.throws(
|
||||
() => loadClusterPluginPackagePublisherTrustFile(filePath),
|
||||
/read-only regular file/,
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps process authority off the cluster-admin root', () => {
|
||||
const root = require('@qinglong/cluster-admin');
|
||||
const manifest = require('../package.json');
|
||||
assert.equal(root.runClusterPluginPackageRecoveryProcess, undefined);
|
||||
assert.equal(
|
||||
manifest.bin['ql3-plugin-package-recover'],
|
||||
'dist/plugin-package/recovery/pluginPackageRecoveryCli.js',
|
||||
);
|
||||
});
|
||||
|
||||
test('binds an optional registry credential file without enabling ambient credentials', () => {
|
||||
const publicConfig = loadClusterPluginPackageRecoveryProcessConfig(
|
||||
environment(),
|
||||
);
|
||||
assert.equal(publicConfig.registryCredentialFile, undefined);
|
||||
const privateConfig = loadClusterPluginPackageRecoveryProcessConfig(
|
||||
environment({
|
||||
QL3_PLUGIN_PACKAGE_REGISTRY_CREDENTIAL_FILE:
|
||||
'/var/run/secrets/qinglong3/registry/credentials.json',
|
||||
}),
|
||||
);
|
||||
assert.equal(
|
||||
privateConfig.registryCredentialFile,
|
||||
'/var/run/secrets/qinglong3/registry/credentials.json',
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
loadClusterPluginPackageRecoveryProcessConfig(
|
||||
environment({
|
||||
QL3_PLUGIN_PACKAGE_REGISTRY_CREDENTIAL_FILE: 'credentials.json',
|
||||
}),
|
||||
),
|
||||
ClusterPluginPackageRecoveryProcessConfigError,
|
||||
);
|
||||
});
|
||||
|
||||
test('loads exact basic and bearer registry credentials and disposes retained bytes', (t) => {
|
||||
const root = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'ql3-registry-credentials-'),
|
||||
);
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const filePath = path.join(root, 'credentials.json');
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
JSON.stringify({
|
||||
schema: 'qinglong/plugin-package-registry-credentials@v1',
|
||||
credentials: [
|
||||
{
|
||||
registry: 'ghcr.io',
|
||||
scheme: 'bearer',
|
||||
token: 'token.value-1',
|
||||
},
|
||||
{
|
||||
registry: 'registry.example.com:5443',
|
||||
scheme: 'basic',
|
||||
username: 'ql3-admin',
|
||||
password: 'private-password',
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ mode: 0o440 },
|
||||
);
|
||||
fs.chmodSync(filePath, 0o440);
|
||||
const credentials = loadClusterPluginPackageRegistryCredentialFile(filePath, [
|
||||
'ghcr.io',
|
||||
'registry.example.com:5443',
|
||||
]);
|
||||
assert.equal(credentials.authorizationFor('ghcr.io'), 'Bearer token.value-1');
|
||||
assert.equal(
|
||||
credentials.authorizationFor('registry.example.com:5443'),
|
||||
`Basic ${Buffer.from('ql3-admin:private-password').toString('base64')}`,
|
||||
);
|
||||
assert.equal(credentials.authorizationFor('registry.example.com'), undefined);
|
||||
credentials.dispose();
|
||||
assert.equal(credentials.authorizationFor('ghcr.io'), undefined);
|
||||
credentials.dispose();
|
||||
});
|
||||
|
||||
test('rejects overbroad, duplicate and publicly readable registry credentials', (t) => {
|
||||
const root = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'ql3-invalid-registry-credentials-'),
|
||||
);
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const filePath = path.join(root, 'credentials.json');
|
||||
const write = (credentials, mode = 0o440) => {
|
||||
if (fs.existsSync(filePath)) fs.chmodSync(filePath, 0o600);
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
JSON.stringify({
|
||||
schema: 'qinglong/plugin-package-registry-credentials@v1',
|
||||
credentials,
|
||||
}),
|
||||
{ mode },
|
||||
);
|
||||
fs.chmodSync(filePath, mode);
|
||||
};
|
||||
write([
|
||||
{
|
||||
registry: 'not-allowed.example.com',
|
||||
scheme: 'bearer',
|
||||
token: 'private-token',
|
||||
},
|
||||
]);
|
||||
assert.throws(
|
||||
() => loadClusterPluginPackageRegistryCredentialFile(filePath, ['ghcr.io']),
|
||||
/binding is invalid/,
|
||||
);
|
||||
|
||||
write([
|
||||
{
|
||||
registry: 'ghcr.io',
|
||||
scheme: 'bearer',
|
||||
token: 'private-token',
|
||||
},
|
||||
{
|
||||
registry: 'ghcr.io',
|
||||
scheme: 'basic',
|
||||
username: 'owner',
|
||||
password: 'private-password',
|
||||
},
|
||||
]);
|
||||
assert.throws(
|
||||
() => loadClusterPluginPackageRegistryCredentialFile(filePath, ['ghcr.io']),
|
||||
/binding is invalid/,
|
||||
);
|
||||
|
||||
write(
|
||||
[
|
||||
{
|
||||
registry: 'ghcr.io',
|
||||
scheme: 'bearer',
|
||||
token: 'private-token',
|
||||
},
|
||||
],
|
||||
0o444,
|
||||
);
|
||||
assert.throws(
|
||||
() => loadClusterPluginPackageRegistryCredentialFile(filePath, ['ghcr.io']),
|
||||
/private regular file/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user