feat(secrets): add a 0600 file secret backend so production runs on Linux
The production composition hardwired the macOS Keychain (/usr/bin/security), which made Linux deployment impossible. Now: - SecretStore gains a stable provider identity persisted in secret_references.provider; services stop hardcoding 'macos-keychain' - shared reference codec (secret-reference.ts) understands both keychain:// and secret-file:// references - FileSecretStore: single 0600 JSON map under the data root, atomic temp+rename writes, serialized in-process, same validation envelope - production composition picks the backend via MULTI_SIMADMIN_SECRET_BACKEND (default: Keychain on darwin, file store elsewhere) and readiness probes the matching backend
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
} from './application/system/console-update-service.js';
|
||||
import { openDatabase } from './infrastructure/database/database.js';
|
||||
import { migrateDatabase } from './infrastructure/database/migrations.js';
|
||||
import { MacOSKeychainSecretStore } from './infrastructure/secrets/keychain-secret-store.js';
|
||||
import { createDefaultSecretStore, resolveSecretBackend, defaultSecretFileMetadataCheck } from './infrastructure/secrets/secret-backend.js';
|
||||
import type { SecretStore } from './infrastructure/secrets/secret-store.js';
|
||||
import { createProductionUpstream } from './infrastructure/transport/production-upstream.js';
|
||||
import { createProductionReadiness, defaultKeychainMetadataCheck } from './production-readiness.js';
|
||||
@@ -21,6 +21,7 @@ export interface ProductionControlPlaneOptions {
|
||||
readonly upstream?: SafeControlPlaneUpstream;
|
||||
readonly gatewayToken?: string;
|
||||
readonly keychainMetadataCheck?: () => boolean | Promise<boolean>;
|
||||
readonly secretFileMetadataCheck?: () => boolean | Promise<boolean>;
|
||||
readonly assetsCheck?: () => boolean | Promise<boolean>;
|
||||
readonly runtimeVersion?: string;
|
||||
readonly recoveryMarkersCheck?: () => number | Promise<number>;
|
||||
@@ -44,8 +45,10 @@ export function buildProductionControlPlane(
|
||||
const db = openDatabase(options.databasePath);
|
||||
try {
|
||||
migrateDatabase(db);
|
||||
const store = options.store ?? new MacOSKeychainSecretStore();
|
||||
const env = options.env ?? process.env;
|
||||
const secretFilePath = join(dirname(options.databasePath), 'secrets.json');
|
||||
const store = options.store ?? createDefaultSecretStore({ env, secretFilePath });
|
||||
const secretBackend = resolveSecretBackend(env);
|
||||
const updateRoot = env.MULTI_SIMADMIN_UPDATE_ROOT ?? process.cwd();
|
||||
const updateRunner = createCommandRunner({ cwd: updateRoot, timeoutMs: 120_000 });
|
||||
const app = buildControlPlaneApp({
|
||||
@@ -71,7 +74,16 @@ export function buildProductionControlPlane(
|
||||
readiness: createProductionReadiness({
|
||||
db,
|
||||
gatewayToken: options.gatewayToken ?? '',
|
||||
keychainMetadataCheck: options.keychainMetadataCheck ?? defaultKeychainMetadataCheck,
|
||||
...(secretBackend === 'macos-keychain'
|
||||
? {
|
||||
keychainMetadataCheck:
|
||||
options.keychainMetadataCheck ?? defaultKeychainMetadataCheck,
|
||||
}
|
||||
: {
|
||||
secretFileMetadataCheck:
|
||||
options.secretFileMetadataCheck ??
|
||||
(async () => defaultSecretFileMetadataCheck(secretFilePath)),
|
||||
}),
|
||||
...(options.assetsCheck ? { assetsCheck: options.assetsCheck } : {}),
|
||||
...(options.recoveryMarkersCheck
|
||||
? { recoveryMarkersCheck: options.recoveryMarkersCheck }
|
||||
|
||||
Reference in New Issue
Block a user