feat(enterprise): add auth TLS and recovery hardening

This commit is contained in:
2026-04-29 22:01:43 +00:00
parent 2f3031cfc7
commit ed2c02bcf9
27 changed files with 2369 additions and 55 deletions
+173
View File
@@ -0,0 +1,173 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
import { MongoClient } from 'mongodb';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
let server: smartdb.SmartdbServer;
let authedClient: MongoClient;
let openClient: MongoClient;
let readerClient: MongoClient;
let tmpDir: string;
let usersPath: string;
function makeTmpDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'smartdb-auth-test-'));
}
function cleanTmpDir(dir: string): void {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
tap.test('auth: should start server with SCRAM-SHA-256 auth enabled', async () => {
tmpDir = makeTmpDir();
usersPath = path.join(tmpDir, 'users.json');
server = new smartdb.SmartdbServer({
port: 27118,
auth: {
enabled: true,
usersPath,
scramIterations: 4096,
users: [
{
username: 'root',
password: 'secret',
database: 'admin',
roles: ['root'],
},
],
},
});
await server.start();
expect(server.running).toBeTrue();
});
tap.test('auth: should reject protected commands before authentication', async () => {
openClient = new MongoClient('mongodb://127.0.0.1:27118', {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await openClient.connect();
let threw = false;
try {
await openClient.db('admin').command({ ping: 1 });
} catch (err: any) {
threw = true;
expect(err.code).toEqual(13);
}
expect(threw).toBeTrue();
});
tap.test('auth: should reject invalid credentials', async () => {
const badClient = new MongoClient('mongodb://root:wrong@127.0.0.1:27118/admin?authSource=admin', {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
let threw = false;
try {
await badClient.connect();
await badClient.db('admin').command({ ping: 1 });
} catch {
threw = true;
} finally {
await badClient.close().catch(() => undefined);
}
expect(threw).toBeTrue();
});
tap.test('auth: should authenticate valid credentials', async () => {
authedClient = new MongoClient('mongodb://root:secret@127.0.0.1:27118/admin?authSource=admin', {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await authedClient.connect();
const result = await authedClient.db('admin').command({ ping: 1 });
expect(result.ok).toEqual(1);
});
tap.test('auth: should allow CRUD after authentication', async () => {
const coll = authedClient.db('securedb').collection('notes');
const inserted = await coll.insertOne({ title: 'enterprise auth' });
expect(inserted.acknowledged).toBeTrue();
const doc = await coll.findOne({ _id: inserted.insertedId });
expect(doc).toBeTruthy();
expect(doc!.title).toEqual('enterprise auth');
});
tap.test('auth: root should create a read-only user', async () => {
const result = await authedClient.db('admin').command({
createUser: 'reader',
pwd: 'readpass',
roles: [{ role: 'read', db: 'securedb' }],
});
expect(result.ok).toEqual(1);
const usersInfo = await authedClient.db('admin').command({ usersInfo: 'reader' });
expect(usersInfo.ok).toEqual(1);
expect(usersInfo.users.length).toEqual(1);
expect(usersInfo.users[0].user).toEqual('reader');
});
tap.test('auth: read-only user should read but not write', async () => {
readerClient = new MongoClient('mongodb://reader:readpass@127.0.0.1:27118/admin?authSource=admin', {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await readerClient.connect();
const doc = await readerClient.db('securedb').collection('notes').findOne({ title: 'enterprise auth' });
expect(doc).toBeTruthy();
let threw = false;
try {
await readerClient.db('securedb').collection('notes').insertOne({ title: 'denied write' });
} catch (err: any) {
threw = true;
expect(err.code).toEqual(13);
}
expect(threw).toBeTrue();
});
tap.test('auth: persisted users should survive server restart', async () => {
await readerClient.close();
await authedClient.close();
await server.stop();
// Simulates a crash after writing the temporary auth metadata file but before rename.
fs.writeFileSync(path.join(tmpDir, 'users.tmp'), '{ invalid json');
server = new smartdb.SmartdbServer({
port: 27118,
auth: {
enabled: true,
usersPath,
users: [],
scramIterations: 4096,
},
});
await server.start();
readerClient = new MongoClient('mongodb://reader:readpass@127.0.0.1:27118/admin?authSource=admin', {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await readerClient.connect();
const result = await readerClient.db('admin').command({ ping: 1 });
expect(result.ok).toEqual(1);
});
tap.test('auth: cleanup', async () => {
await openClient.close();
await readerClient.close();
await server.stop();
expect(server.running).toBeFalse();
cleanTmpDir(tmpDir);
});
export default tap.start();
+91
View File
@@ -0,0 +1,91 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
import { MongoClient, Db } from 'mongodb';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
let tmpDir: string;
let localDb: smartdb.LocalSmartDb;
let client: MongoClient;
let db: Db;
let dataPath: string;
let corruptedSize: number;
function makeTmpDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'smartdb-crash-test-'));
}
function cleanTmpDir(dir: string): void {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
async function startAndConnect(): Promise<void> {
localDb = new smartdb.LocalSmartDb({ folderPath: tmpDir });
const info = await localDb.start();
client = new MongoClient(info.connectionUri, {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await client.connect();
db = client.db('crashtest');
}
tap.test('crash-recovery: create baseline data', async () => {
tmpDir = makeTmpDir();
await startAndConnect();
await db.collection('docs').insertMany([
{ key: 'a', value: 1 },
{ key: 'b', value: 2 },
{ key: 'c', value: 3 },
]);
await client.close();
await localDb.stop();
dataPath = path.join(tmpDir, 'crashtest', 'docs', 'data.rdb');
expect(fs.existsSync(dataPath)).toBeTrue();
});
tap.test('crash-recovery: append a torn final record', async () => {
const data = fs.readFileSync(dataPath);
const partialRecord = data.subarray(64, 94);
expect(partialRecord.length).toEqual(30);
fs.appendFileSync(dataPath, partialRecord);
corruptedSize = fs.statSync(dataPath).size;
expect(corruptedSize).toEqual(data.length + partialRecord.length);
});
tap.test('crash-recovery: restart truncates invalid tail and preserves valid records', async () => {
await startAndConnect();
const repairedSize = fs.statSync(dataPath).size;
expect(repairedSize < corruptedSize).toBeTrue();
const docs = await db.collection('docs').find({}).sort({ key: 1 }).toArray();
expect(docs.map(doc => doc.key)).toEqual(['a', 'b', 'c']);
});
tap.test('crash-recovery: future writes remain durable after tail repair', async () => {
await db.collection('docs').insertOne({ key: 'd', value: 4 });
expect(await db.collection('docs').countDocuments()).toEqual(4);
await client.close();
await localDb.stop();
await startAndConnect();
const docs = await db.collection('docs').find({}).sort({ key: 1 }).toArray();
expect(docs.map(doc => doc.key)).toEqual(['a', 'b', 'c', 'd']);
});
tap.test('crash-recovery: cleanup', async () => {
await client.close();
await localDb.stop();
cleanTmpDir(tmpDir);
});
export default tap.start();
+171
View File
@@ -0,0 +1,171 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
import { MongoClient } from 'mongodb';
import * as fs from 'fs';
import * as net from 'net';
import * as os from 'os';
import * as path from 'path';
// Static test-only CA and server certificate. The private key is intentionally
// non-secret test fixture material and must not be reused outside tests.
const CA_PEM = `-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAwIBAgIUXQlk6FLuWELDKLw9KXi0UIYmU50wDQYJKoZIhvcNAQEL
BQAwGjEYMBYGA1UEAwwPU21hcnREQiBUZXN0IENBMB4XDTI2MDQyOTIxMjYxNFoX
DTM2MDQyNjIxMjYxNFowGjEYMBYGA1UEAwwPU21hcnREQiBUZXN0IENBMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApnRgZvodreKEKkSodwgDe2JKsA3N
GC4c7dmqmOBRQst0OYRoW0kjHnzCVHoGlMTAnjJWXRayPeJCroSA0WhEZIjgHAjW
FuWIr+MUYdCG7czdbDEqZYGsrBDUwv+ydgsDNhLKtbfVfcJckdmFp+TT+Po3sf8o
u5AfOlcjhM22reBLhZJ2FfM2IbqygRbBxNvU3tH5E1kgu2CpYieXQsmqBwkOPM0S
fgkCjlqFeeqV7Jjdq1P6srIItzg6n8/5KGBTxc7VB11WxVAZMIxnOtwpOCpSjbiy
jymBLKvyZxklWGpG9HT6RzUTdp0WpwnO7FlbYqD97jrbwA7PfhbJVUkTeQIDAQAB
o1MwUTAdBgNVHQ4EFgQUaqFWiFvibBYpJjluNW4XlocmqOQwHwYDVR0jBBgwFoAU
aqFWiFvibBYpJjluNW4XlocmqOQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
AQsFAAOCAQEAdbmRCxeHwfq6Mw0BRXWYM81xrzDMDBwLkIyaVkBJXCEX4Ybj8QHv
tplNqgQae1Hr1qYyNzkivDI/hPnvv/wDsAnT8Wz0/udPpcASTXC03xhRtFXwBSGq
2GtLa53cZHJLoGu1S2ntM6Xo3gropXSx/+LIfefsQvqRO/5WxRrEE10OiFr19rA7
md0nD6zXdwrMRghu6ACuxX6Ext6QJbTL4r1UGbHg2a9UbdBjcb8sfFPLyEjiLpBK
DYvRjddKOwbOpFPoLwmed59Pa6bcqT9NnkRHL+aXUm3M3HfVhNKae7JJShUmCzdx
rbKNJQAUp/mMHnBOSxYS7aqgwBKCiKtP4A==
-----END CERTIFICATE-----
`;
const SERVER_CERT_PEM = `-----BEGIN CERTIFICATE-----
MIIDPTCCAiWgAwIBAgIUMfuX4VHvVJ8Vo6o1U2+f7MHU7dowDQYJKoZIhvcNAQEL
BQAwGjEYMBYGA1UEAwwPU21hcnREQiBUZXN0IENBMB4XDTI2MDQyOTIxMjYxNFoX
DTM2MDQyNjIxMjYxNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5eFz1q4juQsEE7cPN5eFrLvRJW/zOMGBmiet
VTQSqVZ/3j3NBWsgxK2xQnNbEXGMlTEE11ih0cCQacc/JnbuvwOt3QX8X6oy4pmb
LMGQJEk2FgdpP6OtGqqYbt/fT7QBY39nt6z/RzxYZI7t5g/nkHnlzmzD+ila6k9b
TzBSfSmtHHKW/c6az/Dh/xe50zDgrzlBA7e5zoleKqRJFRZlDnDoLyx0EOUbbTbQ
vipMynP5bq8l6Fc0N9DAWmXvV4o2x0ZQjfEx5LTvbxNkVWtv8w9w4t4vAZqXwrXd
5OZETMWdy7ezxL0E9Snwc6sSfatlVenD/8P5hWJ/C0vCiw21RwIDAQABo4GAMH4w
GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAsGA1UdDwQEAwIFoDATBgNVHSUE
DDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUK2nSXereMZek6gxLweY1AVt9OaswHwYD
VR0jBBgwFoAUaqFWiFvibBYpJjluNW4XlocmqOQwDQYJKoZIhvcNAQELBQADggEB
AAkC6suxamn+OEmJLMqgaGCvEtFbob5pMijYC32vJNPev+bUHMOB4Oo0FyO59sX3
zfLLwk7jagbWJi37T714aSjyJwUHd4XA7McSabP4+1hOOL0NqfiE4yRnxPhlvf3E
9otoStAAJ86067DwIs5id7jYm+qrxn6bL+P1h+P1tYxnPOoD0v1cHVbtUNV2tH2E
eBhdtTbF+NHrj+oXFGI3jiI7qcwpJ9DFUo/w0sC0POY0T5aWl4ptSXVgEc7nkE91
bbPOPyoMjjZ4WhKAW5UzfOafB0bO7+4E0GHcAkBJmS4V8g5qt56nftr+d58R/odY
0hQjpoIwzl9RCEW0h8xkqMQ=
-----END CERTIFICATE-----
`;
const SERVER_KEY_PEM = `-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDl4XPWriO5CwQT
tw83l4Wsu9Elb/M4wYGaJ61VNBKpVn/ePc0FayDErbFCc1sRcYyVMQTXWKHRwJBp
xz8mdu6/A63dBfxfqjLimZsswZAkSTYWB2k/o60aqphu399PtAFjf2e3rP9HPFhk
ju3mD+eQeeXObMP6KVrqT1tPMFJ9Ka0ccpb9zprP8OH/F7nTMOCvOUEDt7nOiV4q
pEkVFmUOcOgvLHQQ5RttNtC+KkzKc/luryXoVzQ30MBaZe9XijbHRlCN8THktO9v
E2RVa2/zD3Di3i8BmpfCtd3k5kRMxZ3Lt7PEvQT1KfBzqxJ9q2VV6cP/w/mFYn8L
S8KLDbVHAgMBAAECggEAAInWJR8US1cow8kOepFayUxJUZ6hAbWGUa+dGtF757Sh
qQoZBFW7ZmqHu0Gc6X4MF79dJQn6mwyp6e2DCtqFdaITEqz0ad7yrpAwilrLtSIM
w+FxkCoYejMDF2Nj2QJxbGO8gPQhRu/vvxCMoxjPcImwjZq4nMnjAiB8dMOGte9V
av/RoWUOFXqeiJHqAXiE372I4BupwYhGrSUQyuVj3SugDRbzvPepTQNRxaBJQPgy
4ZtZ8FjJdPFvlyxv6fmLFULHwPNcS6PLWPuwpj7oEQzG4/Q9ojYj4EPdpoOW7qoH
h1Y6ag1vk5A/m9DjvMhIDzmUJmq8mlldxqbCBpH0+QKBgQD3Eh7F0ZXdLQe/aG5t
ul9hTv68NZa5M0JzJinB6WjXl2s0bUgIvAE9ZmfUYHs8AMvTu4YwJqsrpMuzFOT9
Ct5wBSyFbPzVOt9MYE1Gipxx8RfEMSq7Sp0MjarX3h0Va8ry83NWzrN1CvyP8BQq
CuXo/IislCDgPg0uXhLD/7GsWQKBgQDuMEptldCKtpW6CdLdYih6xh0j1mdGU4Kb
7mTzo3OU3nDnGXGhqvJt/xpksPl7GPRHYQ1dqRzvLKHDtTJqhkedZBnE6A94LkVl
uNJnR8v4PkR9nKKg0uK2ug9VcfSiXUpl2yyYiDc123WjHdwH2U6BV3smb/7KwEvv
FWaP7PO6nwKBgAE2w5PxPa1ChWE5YCGF4uYVf0bpdH4gdFkgfOAJB4zXn504VDxG
wDLPB/+RIcnfryCxMS2XYwvp2V5d4eokXYdrXxagvHVHvsUfTAHmuHIO3zEFlNIq
wa7IG2jIHJh4WRzseUqZ5WPT0/3ZDiBOwWZtpzZB3A99/o6Vw73WycaxAoGAHTeR
OaYB4bIJ5bskwYEz4/N/SZEYM/k0cTop6fTnzaAHi2GEncchW7rKGwXWZHIoLMVL
5WxEH1aDNUV5vLVh/X1058FrfFt4qcSlEoQtEfNZZWscS8vygWWLUfjbgDsfUCU1
cDRtSU71PCACiHfweE8pzQo539b8uYQPg6IWN5MCgYA6z/kvGiBB9xFBUAJPsj+w
XW/UGbn7svZaCob+N5RA9Rs/0idv/bO2nAauZyHG/nn6HXII6U5pmRyVqWKhI22q
K3J0LCP42Zb6/eYzQPbP1jWHCMaL2QJQGsl4NMZixlnNJV0aG/5CButqzSC/cMbG
DX0n+YqqWmCgHWU2csnlAA==
-----END PRIVATE KEY-----
`;
let server: smartdb.SmartdbServer;
let client: MongoClient;
let tmpDir: string;
let caPath: string;
let certPath: string;
let keyPath: string;
let port: number;
function makeTmpDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'smartdb-tls-test-'));
}
function cleanTmpDir(dir: string): void {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
async function getFreePort(): Promise<number> {
return await new Promise((resolve, reject) => {
const probe = net.createServer();
probe.once('error', reject);
probe.listen(0, '127.0.0.1', () => {
const address = probe.address();
if (!address || typeof address === 'string') {
probe.close(() => reject(new Error('Failed to allocate TCP port')));
return;
}
probe.close(() => resolve(address.port));
});
});
}
tap.test('tls: should start server with TLS enabled', async () => {
tmpDir = makeTmpDir();
port = await getFreePort();
caPath = path.join(tmpDir, 'ca.pem');
certPath = path.join(tmpDir, 'server.pem');
keyPath = path.join(tmpDir, 'server.key');
fs.writeFileSync(caPath, CA_PEM);
fs.writeFileSync(certPath, SERVER_CERT_PEM);
fs.writeFileSync(keyPath, SERVER_KEY_PEM, { mode: 0o600 });
server = new smartdb.SmartdbServer({
port,
tls: {
enabled: true,
certPath,
keyPath,
},
});
await server.start();
expect(server.running).toBeTrue();
expect(server.getConnectionUri()).toEqual(`mongodb://127.0.0.1:${port}/?tls=true`);
});
tap.test('tls: should connect with official MongoClient and CA validation', async () => {
client = new MongoClient(server.getConnectionUri(), {
directConnection: true,
serverSelectionTimeoutMS: 5000,
tlsCAFile: caPath,
});
await client.connect();
const ping = await client.db('admin').command({ ping: 1 });
expect(ping.ok).toEqual(1);
});
tap.test('tls: should support CRUD over encrypted transport', async () => {
const collection = client.db('tlsdb').collection('notes');
const inserted = await collection.insertOne({ title: 'encrypted transport' });
expect(inserted.acknowledged).toBeTrue();
const doc = await collection.findOne({ _id: inserted.insertedId });
expect(doc).toBeTruthy();
expect(doc!.title).toEqual('encrypted transport');
});
tap.test('tls: cleanup', async () => {
await client.close();
await server.stop();
expect(server.running).toBeFalse();
cleanTmpDir(tmpDir);
});
export default tap.start();
+115
View File
@@ -0,0 +1,115 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
import { MongoClient } from 'mongodb';
import * as net from 'net';
let server: smartdb.SmartdbServer;
let client: MongoClient;
let port: number;
async function getFreePort(): Promise<number> {
return await new Promise((resolve, reject) => {
const probe = net.createServer();
probe.once('error', reject);
probe.listen(0, '127.0.0.1', () => {
const address = probe.address();
if (!address || typeof address === 'string') {
probe.close(() => reject(new Error('Failed to allocate TCP port')));
return;
}
probe.close(() => resolve(address.port));
});
});
}
tap.test('transactions: should start server and connect', async () => {
port = await getFreePort();
server = new smartdb.SmartdbServer({ port });
await server.start();
client = new MongoClient(`mongodb://127.0.0.1:${port}`, {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await client.connect();
expect(server.running).toBeTrue();
});
tap.test('transactions: should still support explicit sessions', async () => {
const result = await client.db('admin').command({ startSession: 1 });
expect(result.ok).toEqual(1);
expect(result.id).toBeTruthy();
const end = await client.db('admin').command({ endSessions: [result.id] });
expect(end.ok).toEqual(1);
});
tap.test('transactions: should reject raw transaction-scoped writes before mutation', async () => {
const db = client.db('txntest');
const coll = db.collection('docs');
await coll.insertOne({ key: 'outside', value: 1 });
let threw = false;
try {
await db.command({
insert: 'docs',
documents: [{ key: 'inside-raw', value: 2 }],
startTransaction: true,
autocommit: false,
});
} catch (err: any) {
threw = true;
expect(err.code).toEqual(20);
expect(err.codeName).toEqual('IllegalOperation');
}
expect(threw).toBeTrue();
expect(await coll.countDocuments({ key: 'inside-raw' })).toEqual(0);
expect(await coll.countDocuments({ key: 'outside' })).toEqual(1);
});
tap.test('transactions: official driver transaction should fail without committing writes', async () => {
const coll = client.db('txntest').collection('driverdocs');
await coll.insertOne({ key: 'outside-driver', value: 0 });
const session = client.startSession();
let threw = false;
try {
session.startTransaction();
await coll.insertOne({ key: 'inside-driver', value: 1 }, { session });
await session.commitTransaction();
} catch (err: any) {
threw = true;
expect(err.code).toEqual(20);
expect(err.codeName).toEqual('IllegalOperation');
await session.abortTransaction().catch(() => undefined);
} finally {
await session.endSession();
}
expect(threw).toBeTrue();
expect(await coll.countDocuments({ key: 'inside-driver' })).toEqual(0);
expect(await coll.countDocuments({ key: 'outside-driver' })).toEqual(1);
});
tap.test('transactions: commit and abort commands should be explicit unsupported errors', async () => {
for (const command of [{ commitTransaction: 1 }, { abortTransaction: 1 }]) {
let threw = false;
try {
await client.db('admin').command(command);
} catch (err: any) {
threw = true;
expect(err.code).toEqual(20);
expect(err.codeName).toEqual('IllegalOperation');
}
expect(threw).toBeTrue();
}
});
tap.test('transactions: cleanup', async () => {
await client.close();
await server.stop();
expect(server.running).toBeFalse();
});
export default tap.start();