Files
qinglong/packages/ql3-local-owner-console/test/pepperFile.test.cjs
T

200 lines
5.9 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
LocalOwnerPepperConfigurationError,
LocalOwnerPepperConflictError,
LocalOwnerPepperUnavailableError,
backupLocalOwnerPepper,
inspectLocalOwnerPepper,
provisionLocalOwnerPepper,
restoreLocalOwnerPepper,
} = require('../dist/pepper-custody');
function fixture(t) {
const deploymentRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-owner-pepper-'),
);
fs.chmodSync(deploymentRoot, 0o700);
const backupRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-owner-pepper-backup-'),
);
fs.chmodSync(backupRoot, 0o700);
t.after(() => {
fs.rmSync(deploymentRoot, { recursive: true, force: true });
fs.rmSync(backupRoot, { recursive: true, force: true });
});
return {
deploymentRoot,
backupRoot,
pepperPath: path.join(deploymentRoot, 'owner.pepper'),
backupPath: path.join(backupRoot, 'owner.pepper.backup'),
};
}
test('provisions one canonical private pepper without replacement', (t) => {
const value = fixture(t);
const entropy = Buffer.alloc(32, 41);
const result = provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes() {
return entropy;
},
});
assert.equal(result.version, 1);
assert.equal(result.byteLength, 43);
assert.match(result.digest, /^[0-9a-f]{64}$/);
assert.equal(fs.statSync(value.pepperPath).mode & 0o777, 0o600);
assert.equal(
fs.readFileSync(value.pepperPath, 'utf8'),
Buffer.alloc(32, 41).toString('base64url'),
);
assert.equal(entropy.equals(Buffer.alloc(32)), true);
assert.deepEqual(
inspectLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
}),
result,
);
const before = fs.readFileSync(value.pepperPath);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
}),
LocalOwnerPepperConflictError,
);
assert.deepEqual(fs.readFileSync(value.pepperPath), before);
});
test('creates an independent no-replace backup and restores only to absence', (t) => {
const value = fixture(t);
const provisioned = provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes: () => Buffer.alloc(32, 42),
});
const backedUp = backupLocalOwnerPepper(value);
assert.deepEqual(backedUp, provisioned);
const pepperStat = fs.statSync(value.pepperPath, { bigint: true });
const backupStat = fs.statSync(value.backupPath, { bigint: true });
assert.equal(backupStat.mode & 0o777n, 0o600n);
assert.notEqual(backupStat.ino, pepperStat.ino);
assert.deepEqual(
fs.readFileSync(value.backupPath),
fs.readFileSync(value.pepperPath),
);
fs.unlinkSync(value.pepperPath);
const restored = restoreLocalOwnerPepper(value);
assert.deepEqual(restored, provisioned);
assert.deepEqual(
fs.readFileSync(value.pepperPath),
fs.readFileSync(value.backupPath),
);
assert.throws(
() => restoreLocalOwnerPepper(value),
LocalOwnerPepperConflictError,
);
});
test('never overwrites a pre-existing backup', (t) => {
const value = fixture(t);
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes: () => Buffer.alloc(32, 43),
});
fs.writeFileSync(value.backupPath, 'reserved', { mode: 0o600 });
assert.throws(
() => backupLocalOwnerPepper(value),
LocalOwnerPepperConflictError,
);
assert.equal(fs.readFileSync(value.backupPath, 'utf8'), 'reserved');
});
test('fails closed for broad roots, symlink parents and tampered pepper files', (t) => {
const broad = fixture(t);
fs.chmodSync(broad.deploymentRoot, 0o755);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: broad.deploymentRoot,
pepperPath: broad.pepperPath,
}),
LocalOwnerPepperUnavailableError,
);
const linked = fixture(t);
const actual = path.join(linked.deploymentRoot, 'actual');
fs.mkdirSync(actual, { mode: 0o700 });
const alias = path.join(linked.deploymentRoot, 'alias');
fs.symlinkSync(actual, alias);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: linked.deploymentRoot,
pepperPath: path.join(alias, 'owner.pepper'),
}),
LocalOwnerPepperUnavailableError,
);
const tampered = fixture(t);
provisionLocalOwnerPepper({
deploymentRoot: tampered.deploymentRoot,
pepperPath: tampered.pepperPath,
});
fs.chmodSync(tampered.pepperPath, 0o644);
assert.throws(
() =>
inspectLocalOwnerPepper({
deploymentRoot: tampered.deploymentRoot,
pepperPath: tampered.pepperPath,
}),
LocalOwnerPepperUnavailableError,
);
});
test('rejects invalid entropy and widened options before publishing', (t) => {
const value = fixture(t);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes: () => Buffer.alloc(31),
}),
LocalOwnerPepperConfigurationError,
);
assert.equal(fs.existsSync(value.pepperPath), false);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
extra: true,
}),
LocalOwnerPepperConfigurationError,
);
assert.throws(
() =>
provisionLocalOwnerPepper({
deploymentRoot: value.deploymentRoot,
pepperPath: value.pepperPath,
randomBytes() {
throw new Error('sensitive entropy provider detail');
},
}),
(error) =>
error instanceof LocalOwnerPepperUnavailableError &&
error.message === 'Local Owner pepper operation is unavailable',
);
assert.equal(fs.existsSync(value.pepperPath), false);
});