Files

811 lines
28 KiB
JavaScript

const assert = require('node:assert/strict');
const { createHash, randomUUID } = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const { migrateLocalSqlitePath } = require('@qinglong/local-sqlite/migration');
const {
openLocalSqliteBootstrapDatabase,
} = require('@qinglong/local-sqlite/bootstrap');
const {
createLocalOwnerBootstrapService,
LOCAL_IDENTITY_BOOTSTRAP_DEFAULT_TTL_MS,
LocalOwnerBootstrapServiceUnavailableError,
} = require('../dist/bootstrap');
const {
formatApiCredentialToken,
} = require('@qinglong/runtime-core/api-credential-token');
const {
FileLocalOwnerBootstrapSecretDelivery,
LocalOwnerConsoleConfigurationError,
LocalOwnerSecretDeliveryError,
openLocalOwnerConsole,
} = require('@qinglong/local-owner-console');
function fixture(t) {
const deploymentRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-owner-console-'),
);
fs.chmodSync(deploymentRoot, 0o700);
t.after(() => fs.rmSync(deploymentRoot, { recursive: true, force: true }));
const databasePath = path.join(deploymentRoot, 'qinglong3.sqlite');
const pepperPath = path.join(deploymentRoot, 'owner.pepper');
const secretDeliveryDirectory = path.join(deploymentRoot, 'secrets');
fs.mkdirSync(secretDeliveryDirectory, { mode: 0o700 });
fs.writeFileSync(pepperPath, Buffer.alloc(32, 73).toString('base64url'), {
mode: 0o600,
});
return {
deploymentRoot,
databasePath,
pepperPath,
secretDeliveryDirectory,
profile: 'edge',
};
}
async function ready(t) {
const options = fixture(t);
await migrateLocalSqlitePath({
databasePath: options.databasePath,
profile: options.profile,
});
const material = fs.readFileSync(options.pepperPath);
const materialDigest = createHash('sha256')
.update('qinglong.local-owner-pepper.summary.v1\0', 'utf8')
.update(material)
.digest('hex');
material.fill(0);
const database = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
await database.ownerPepper.register({
mutationId: '00000000-0000-4000-8000-000000000191',
pepperKeyId: 'legacy-v1',
materialDigest,
backupDigest: 'b'.repeat(64),
registeredAtMs: 1,
});
await database.ownerPepper.activate({
mutationId: '00000000-0000-4000-8000-000000000192',
pepperKeyId: 'legacy-v1',
expectedGeneration: 0,
activatedAtMs: 2,
});
await database.close();
return options;
}
function authorityFor(options) {
const root = fs.lstatSync(options.deploymentRoot, { bigint: true });
const uid = process.getuid();
const proofDigest = createHash('sha256')
.update('qinglong.local-owner-console.proof.v1\0', 'utf8')
.update(process.platform, 'utf8')
.update('\0', 'utf8')
.update(String(uid), 'utf8')
.update('\0', 'utf8')
.update(root.dev.toString(), 'utf8')
.update('\0', 'utf8')
.update(root.ino.toString(), 'utf8')
.digest('hex');
const authenticatedAtMs = Date.now();
return {
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: `local-console:${proofDigest}`,
authenticatedAtMs,
expiresAtMs: authenticatedAtMs + 60_000,
assurance: 'local_console',
};
}
test('proves one bounded delivery crash bridge is clear', (t) => {
const options = fixture(t);
const delivery = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const mutationId = '00000000-0000-4000-8000-000000000b01';
const evidence = delivery.inspectBridgeClear('credential', mutationId);
assert.equal(evidence.kind, 'credential');
assert.equal(evidence.acknowledgementMutationId, mutationId);
assert.match(evidence.evidenceDigest, /^[0-9a-f]{64}$/);
fs.writeFileSync(
path.join(
options.secretDeliveryDirectory,
`credential-${mutationId}.pending.json`,
),
'{}',
{ mode: 0o600 },
);
assert.throws(
() => delivery.inspectBridgeClear('credential', mutationId),
/crash bridge is not clear/,
);
});
async function directService(options, secretDelivery) {
const database = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
const service = createLocalOwnerBootstrapService(
database.ownerBootstrap,
database.apiCredentials,
fs.readFileSync(options.pepperPath, 'utf8'),
authorityFor(options),
{ secretDelivery },
);
return { database, service };
}
test('binds POSIX proof at composition time and removes issuer from requests', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
assert.deepEqual(console.recovery, {
inspectedPendingRecords: 0,
publishedRecords: 0,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const provisioned = await console.service.provision({
mutationId: '00000000-0000-4000-8000-000000000201',
requestId: 'console-provision-201',
});
assert.equal(provisioned.status, 'inserted');
assert.equal(provisioned.credentialToken, null);
const credentialPath = console.credentialDeliveryPath(
'00000000-0000-4000-8000-000000000201',
);
assert.equal(fs.statSync(credentialPath).mode & 0o777, 0o600);
const credential = JSON.parse(fs.readFileSync(credentialPath, 'utf8'));
assert.equal(credential.kind, 'credential');
assert.equal(credential.credentialId, provisioned.credentialId);
const issued = await console.service.issue({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000202',
requestId: 'console-issue-202',
});
assert.equal(issued.status, 'inserted');
assert.equal(issued.challengeToken, null);
const challengePath = console.challengeDeliveryPath(
'00000000-0000-4000-8000-000000000202',
);
assert.equal(fs.statSync(challengePath).mode & 0o777, 0o600);
const challenge = JSON.parse(fs.readFileSync(challengePath, 'utf8'));
assert.equal(challenge.kind, 'challenge');
assert.equal(challenge.challengeId, issued.challengeId);
const claimed = await console.service.claim({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000205',
requestId: 'console-claim-205',
challengeId: challenge.challengeId,
challengeToken: challenge.secret,
credentialToken: formatApiCredentialToken(
credential.credentialId,
credential.secret,
),
});
assert.equal(claimed.status, 'inserted');
const databaseBytes = fs.readFileSync(options.databasePath);
assert.equal(databaseBytes.includes(credential.secret), false);
assert.equal(databaseBytes.includes(challenge.secret), false);
await assert.rejects(
console.service.issue({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000206',
requestId: 'console-issue-206',
issuer: {
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: 'forged',
authenticatedAtMs: Date.now(),
expiresAtMs: Date.now() + 60_000,
assurance: 'local_console',
},
}),
);
});
test('claims the first Owner from staged deliveries without crossing the transport with secrets', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
const credentialMutationId = '00000000-0000-4000-8000-000000000711';
const challengeMutationId = '00000000-0000-4000-8000-000000000712';
const claimMutationId = '00000000-0000-4000-8000-000000000713';
await console.service.provision({
mutationId: credentialMutationId,
requestId: 'console-provision-711',
});
await console.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: 'console-issue-712',
});
const credentialDelivery =
console.inspectCredentialDelivery(credentialMutationId);
const challengeDelivery =
console.inspectChallengeDelivery(challengeMutationId);
const claimed = await console.claimOwnerFromDeliveries({
projectId: 'default',
mutationId: claimMutationId,
requestId: 'console-claim-713',
credentialMutationId,
challengeMutationId,
});
assert.equal(claimed.status, 'inserted');
assert.equal(claimed.binding.role, 'owner');
assert.equal(JSON.stringify(claimed).includes('secret'), false);
await console.acknowledgeCredentialDelivery(
credentialMutationId,
credentialDelivery.deliveryDigest,
);
await console.acknowledgeChallengeDelivery(
challengeMutationId,
challengeDelivery.deliveryDigest,
);
const replay = await console.claimOwnerFromDeliveries({
projectId: 'default',
mutationId: claimMutationId,
requestId: 'console-claim-713',
credentialMutationId,
challengeMutationId,
});
assert.equal(replay.status, 'existing');
await assert.rejects(
console.claimOwnerFromDeliveries({
projectId: 'default',
mutationId: '00000000-0000-4000-8000-000000000714',
requestId: 'console-claim-714',
credentialMutationId,
challengeMutationId,
challengeToken: 'forbidden',
}),
LocalOwnerSecretDeliveryError,
);
});
test('recovers one credential without revoking the old token before delivery acknowledgement', async (t) => {
const options = await ready(t);
const first = await openLocalOwnerConsole(options);
const provisionMutationId = '00000000-0000-4000-8000-000000000701';
const provisioned = await first.service.provision({
mutationId: provisionMutationId,
requestId: 'console-provision-701',
});
const provisionDelivery =
first.inspectCredentialDelivery(provisionMutationId);
await first.acknowledgeCredentialDelivery(
provisionMutationId,
provisionDelivery.deliveryDigest,
);
const issueMutationId = '00000000-0000-4000-8000-000000000702';
const issued = await first.credentialRecovery.issue({
mutationId: issueMutationId,
requestId: 'console-recover-issue-702',
previousCredentialId: provisioned.credentialId,
expectedPreviousVersion: 1,
});
assert.equal(issued.status, 'inserted');
assert.equal(issued.state, 'issued');
assert.equal(issued.replacementCredentialToken, null);
const recoveryDelivery = first.inspectCredentialDelivery(issueMutationId);
await assert.rejects(
first.credentialRecovery.complete({
issueMutationId,
mutationId: '00000000-0000-4000-8000-000000000703',
requestId: 'console-recover-complete-703',
}),
);
const beforeAcknowledgement = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
assert.equal(
(
await beforeAcknowledgement.apiCredentials.resolve(
provisioned.credentialId,
)
).state,
'active',
);
await beforeAcknowledgement.close();
await first.close();
const restarted = await openLocalOwnerConsole(options);
t.after(() => restarted.close());
assert.equal(fs.existsSync(recoveryDelivery.path), true);
await restarted.acknowledgeCredentialRecoveryDelivery(
issueMutationId,
recoveryDelivery.deliveryDigest,
);
const completed = await restarted.credentialRecovery.complete({
issueMutationId,
mutationId: '00000000-0000-4000-8000-000000000703',
requestId: 'console-recover-complete-703',
});
assert.equal(completed.state, 'completed');
assert.equal(fs.existsSync(recoveryDelivery.path), false);
const database = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
assert.equal(
(await database.apiCredentials.resolve(provisioned.credentialId)).state,
'revoked',
);
assert.equal(
(await database.apiCredentials.resolve(issued.replacementCredentialId))
.state,
'active',
);
await database.close();
const replay = await restarted.credentialRecovery.issue({
mutationId: issueMutationId,
requestId: 'console-recover-issue-702',
previousCredentialId: provisioned.credentialId,
expectedPreviousVersion: 1,
});
assert.equal(replay.status, 'existing');
assert.equal(replay.state, 'completed');
assert.equal(replay.replacementCredentialToken, null);
});
test('acknowledges exact ready records and replays without regenerating secrets', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const credentialMutationId = '00000000-0000-4000-8000-000000000207';
const provisionRequestId = 'console-provision-207';
const provisioned = await console.service.provision({
mutationId: credentialMutationId,
requestId: provisionRequestId,
});
const credentialSummary =
console.inspectCredentialDelivery(credentialMutationId);
const credentialReady = fs.readFileSync(credentialSummary.path);
await assert.rejects(
console.acknowledgeCredentialDelivery(credentialMutationId, '0'.repeat(64)),
LocalOwnerSecretDeliveryError,
);
assert.equal(fs.existsSync(credentialSummary.path), true);
const acknowledgementDatabaseA = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
const acknowledgementDatabaseB = await openLocalSqliteBootstrapDatabase({
databasePath: options.databasePath,
profile: options.profile,
});
const deliveryA = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const deliveryB = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const pepper = fs.readFileSync(options.pepperPath, 'utf8');
const concurrentAcknowledgements = await Promise.all([
deliveryA.acknowledge(
acknowledgementDatabaseA.ownerBootstrap,
pepper,
'credential',
credentialMutationId,
credentialSummary.deliveryDigest,
1,
),
deliveryB.acknowledge(
acknowledgementDatabaseB.ownerBootstrap,
pepper,
'credential',
credentialMutationId,
credentialSummary.deliveryDigest,
2,
),
]);
await Promise.all([
acknowledgementDatabaseA.close(),
acknowledgementDatabaseB.close(),
]);
assert.deepEqual(
concurrentAcknowledgements[0],
concurrentAcknowledgements[1],
);
const credentialAcknowledgement = await console.acknowledgeCredentialDelivery(
credentialMutationId,
credentialSummary.deliveryDigest,
);
assert.deepEqual(credentialAcknowledgement, {
state: 'acknowledged',
kind: 'credential',
mutationId: credentialMutationId,
requestId: provisionRequestId,
ttlMs: LOCAL_IDENTITY_BOOTSTRAP_DEFAULT_TTL_MS,
});
assert.equal(fs.existsSync(credentialSummary.path), false);
const credentialAcknowledgementPath = path.join(
options.secretDeliveryDirectory,
`credential-${credentialMutationId}.acknowledged.json`,
);
assert.equal(fs.existsSync(credentialAcknowledgementPath), false);
const ledgerDatabase = new DatabaseSync(options.databasePath);
const credentialTombstone = ledgerDatabase
.prepare(
`SELECT * FROM "QingLong3LocalOwnerDeliveryAcknowledgements"
WHERE "mutation_id" = ?`,
)
.get(credentialMutationId);
ledgerDatabase.close();
assert.equal(
credentialTombstone.delivery_digest,
credentialSummary.deliveryDigest,
);
assert.equal([1, 2].includes(credentialTombstone.acknowledged_at_ms), true);
assert.equal(Object.keys(credentialTombstone).includes('secret'), false);
const replayedProvision = await console.service.provision({
mutationId: credentialMutationId,
requestId: provisionRequestId,
});
assert.equal(replayedProvision.status, 'existing');
assert.equal(replayedProvision.subjectId, provisioned.subjectId);
assert.equal(replayedProvision.credentialId, provisioned.credentialId);
assert.equal(replayedProvision.credentialToken, null);
const challengeMutationId = '00000000-0000-4000-8000-000000000208';
const issueRequestId = 'console-issue-208';
const issued = await console.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: issueRequestId,
});
const challengeSummary =
console.inspectChallengeDelivery(challengeMutationId);
const challengeAcknowledgement = await console.acknowledgeChallengeDelivery(
challengeMutationId,
challengeSummary.deliveryDigest,
);
assert.deepEqual(challengeAcknowledgement, {
state: 'acknowledged',
kind: 'challenge',
projectId: 'default',
mutationId: challengeMutationId,
requestId: issueRequestId,
ttlMs: 600_000,
});
const replayedIssue = await console.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: issueRequestId,
});
assert.equal(replayedIssue.status, 'existing');
assert.equal(replayedIssue.challengeId, issued.challengeId);
assert.equal(replayedIssue.challengeToken, null);
fs.writeFileSync(credentialSummary.path, credentialReady, { mode: 0o600 });
await console.close();
const recovered = await openLocalOwnerConsole(options);
t.after(() => recovered.close());
assert.equal(fs.existsSync(credentialSummary.path), false);
assert.deepEqual(recovered.recovery, {
inspectedPendingRecords: 0,
publishedRecords: 0,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const recoveredReplay = await recovered.service.provision({
mutationId: credentialMutationId,
requestId: provisionRequestId,
});
assert.equal(recoveredReplay.status, 'existing');
assert.equal(recoveredReplay.credentialToken, null);
});
test('retains a pre-commit secret and publishes it after the matching commit', async (t) => {
const options = await ready(t);
const delivery = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const mutationId = '00000000-0000-4000-8000-000000000211';
const staged = await delivery.prepare({
kind: 'credential',
mutationId,
requestId: 'console-provision-211',
subjectId: `usr_${Buffer.alloc(16, 11).toString('base64url')}`,
credentialId: `own_${Buffer.alloc(16, 12).toString('base64url')}`,
secret: Buffer.alloc(32, 13).toString('base64url'),
ttlMs: LOCAL_IDENTITY_BOOTSTRAP_DEFAULT_TTL_MS,
});
const pendingPath = delivery
.readyPath('credential', mutationId)
.replace('.ready.json', '.pending.json');
assert.equal(fs.existsSync(pendingPath), true);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
assert.deepEqual(console.recovery, {
inspectedPendingRecords: 1,
publishedRecords: 0,
retainedUncommittedRecords: 1,
orphanTemporaryRecords: 0,
});
const provisioned = await console.service.provision({
mutationId,
requestId: staged.requestId,
});
assert.equal(provisioned.status, 'inserted');
assert.equal(provisioned.subjectId, staged.subjectId);
assert.equal(provisioned.credentialId, staged.credentialId);
assert.equal(provisioned.credentialToken, null);
assert.equal(fs.existsSync(pendingPath), false);
assert.equal(fs.existsSync(console.credentialDeliveryPath(mutationId)), true);
});
test('recovers database-committed credential and challenge after publish failure', async (t) => {
const options = await ready(t);
const delivery = new FileLocalOwnerBootstrapSecretDelivery(
options.secretDeliveryDirectory,
);
const failingDelivery = {
prepare(candidate) {
return delivery.prepare(candidate);
},
async publish() {
throw new Error('injected publish failure');
},
};
const provisionMutationId = '00000000-0000-4000-8000-000000000221';
const first = await directService(options, failingDelivery);
await assert.rejects(
first.service.provision({
mutationId: provisionMutationId,
requestId: 'console-provision-221',
}),
LocalOwnerBootstrapServiceUnavailableError,
);
await first.database.close();
const recoveredCredential = await openLocalOwnerConsole(options);
assert.deepEqual(recoveredCredential.recovery, {
inspectedPendingRecords: 1,
publishedRecords: 1,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const replayedProvision = await recoveredCredential.service.provision({
mutationId: provisionMutationId,
requestId: 'console-provision-221',
});
assert.equal(replayedProvision.status, 'existing');
assert.equal(replayedProvision.credentialToken, null);
await recoveredCredential.close();
const challengeMutationId = '00000000-0000-4000-8000-000000000222';
const second = await directService(options, failingDelivery);
await assert.rejects(
second.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: 'console-issue-222',
}),
LocalOwnerBootstrapServiceUnavailableError,
);
await second.database.close();
const recoveredChallenge = await openLocalOwnerConsole(options);
t.after(() => recoveredChallenge.close());
assert.deepEqual(recoveredChallenge.recovery, {
inspectedPendingRecords: 1,
publishedRecords: 1,
retainedUncommittedRecords: 0,
orphanTemporaryRecords: 0,
});
const replayedIssue = await recoveredChallenge.service.issue({
projectId: 'default',
mutationId: challengeMutationId,
requestId: 'console-issue-222',
});
assert.equal(replayedIssue.status, 'existing');
assert.equal(replayedIssue.challengeToken, null);
});
test('fails closed on tampered delivery records and bounded-directory overflow', async (t) => {
await t.test('private record mode', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000231';
await console.service.provision({
mutationId,
requestId: 'console-provision-231',
});
const recordPath = console.credentialDeliveryPath(mutationId);
await console.close();
fs.chmodSync(recordPath, 0o644);
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
await t.test('database digest mismatch', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000232';
await console.service.provision({
mutationId,
requestId: 'console-provision-232',
});
const recordPath = console.credentialDeliveryPath(mutationId);
const record = JSON.parse(fs.readFileSync(recordPath, 'utf8'));
await console.close();
record.secret = Buffer.alloc(32, 99).toString('base64url');
fs.writeFileSync(recordPath, `${JSON.stringify(record)}\n`, {
mode: 0o600,
});
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
await t.test('entry budget', async (t) => {
const options = await ready(t);
for (let index = 0; index < 65; index += 1) {
const name = `.credential-${randomUUID()}.${randomUUID()}.tmp`;
fs.writeFileSync(path.join(options.secretDeliveryDirectory, name), 'x', {
mode: 0o600,
});
}
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
await t.test('tampered acknowledgement fact', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000233';
await console.service.provision({
mutationId,
requestId: 'console-provision-233',
});
const summary = console.inspectCredentialDelivery(mutationId);
await console.acknowledgeCredentialDelivery(
mutationId,
summary.deliveryDigest,
);
await console.close();
const database = new DatabaseSync(options.databasePath);
database
.prepare(
`UPDATE "QingLong3LocalOwnerDeliveryAcknowledgements"
SET "fact_digest" = ? WHERE "mutation_id" = ?`,
)
.run('0'.repeat(64), mutationId);
database.close();
const reopened = await openLocalOwnerConsole(options);
t.after(() => reopened.close());
await assert.rejects(
reopened.service.provision({
mutationId,
requestId: 'console-provision-233',
}),
LocalOwnerBootstrapServiceUnavailableError,
);
});
await t.test('acknowledged mutation with pending record', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
const mutationId = '00000000-0000-4000-8000-000000000234';
await console.service.provision({
mutationId,
requestId: 'console-provision-234',
});
const summary = console.inspectCredentialDelivery(mutationId);
const readyMaterial = fs.readFileSync(summary.path);
await console.acknowledgeCredentialDelivery(
mutationId,
summary.deliveryDigest,
);
await console.close();
fs.writeFileSync(
summary.path.replace('.ready.json', '.pending.json'),
readyMaterial,
{ mode: 0o600 },
);
await assert.rejects(
openLocalOwnerConsole(options),
LocalOwnerSecretDeliveryError,
);
});
});
test('cleans staged files after ENOSPC and read-only delivery failures', async (t) => {
for (const [index, code] of ['ENOSPC', 'EROFS'].entries()) {
await t.test(code, async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
const originalWriteFileSync = fs.writeFileSync;
fs.writeFileSync = function injectedWriteFailure(target, ...args) {
if (typeof target === 'number') {
throw Object.assign(new Error(`injected ${code}`), { code });
}
return originalWriteFileSync.call(this, target, ...args);
};
try {
await assert.rejects(
console.service.provision({
mutationId: `00000000-0000-4000-8000-00000000024${index}`,
requestId: `console-provision-24${index}`,
}),
LocalOwnerBootstrapServiceUnavailableError,
);
} finally {
fs.writeFileSync = originalWriteFileSync;
}
assert.deepEqual(fs.readdirSync(options.secretDeliveryDirectory), []);
});
}
});
test('rejects broad deployment permissions and pepper symlinks', async (t) => {
const broad = await ready(t);
fs.chmodSync(broad.deploymentRoot, 0o755);
await assert.rejects(
openLocalOwnerConsole(broad),
LocalOwnerConsoleConfigurationError,
);
const linked = await ready(t);
const actualPepper = path.join(linked.deploymentRoot, 'actual.pepper');
fs.renameSync(linked.pepperPath, actualPepper);
fs.symlinkSync(actualPepper, linked.pepperPath);
await assert.rejects(
openLocalOwnerConsole(linked),
LocalOwnerConsoleConfigurationError,
);
});
test('rechecks database identity before every authority operation', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
t.after(() => console.close());
const moved = path.join(options.deploymentRoot, 'moved.sqlite');
fs.renameSync(options.databasePath, moved);
fs.copyFileSync(moved, options.databasePath);
fs.chmodSync(options.databasePath, 0o600);
await assert.rejects(
async () =>
console.service.provision({
mutationId: '00000000-0000-4000-8000-000000000203',
requestId: 'console-provision-203',
}),
LocalOwnerConsoleConfigurationError,
);
});
test('close is idempotent and no CLI or default-runtime authority is exported', async (t) => {
const options = await ready(t);
const console = await openLocalOwnerConsole(options);
await Promise.all([console.close(), console.close()]);
await assert.rejects(
console.service.provision({
mutationId: '00000000-0000-4000-8000-000000000204',
requestId: 'console-provision-204',
}),
);
const manifest = require('../package.json');
assert.equal('bin' in manifest, false);
const localRuntime = require('@qinglong/local-sqlite/runtime');
assert.equal('openLocalOwnerConsole' in localRuntime, false);
});