From 354cc77fae03ad19c7789c6501e6f6a5489d84e1 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Fri, 17 Sep 2021 16:53:33 +1000 Subject: [PATCH] Vaults sharing and permissions --- src/PolykeyAgent.ts | 22 +- src/agent/agentService.ts | 63 +- src/vaults/VaultInternal.ts | 2 +- src/vaults/VaultManager.ts | 93 +- src/vaults/errors.ts | 3 + src/vaults/old/Vault.ts.old | 659 ++++++++++++ src/vaults/old/VaultManager.ts.old | 976 ++++++++++++++++++ tests/PolykeyAgent.test.ts | 9 + tests/agent/GRPCClientAgent.test.ts | 4 +- tests/agent/utils.ts | 4 + tests/bin/polykey.test.ts | 29 + tests/client/clientService.test.ts | 2 +- tests/nodes/NodeConnection.test.ts | 18 +- .../NotificationsManager.test.ts | 20 +- tests/vaults/VaultInternal.test.ts | 2 +- tests/vaults/VaultManager.test.ts | 661 +++++++----- tests/vaults/VaultOps.test.ts | 4 - 17 files changed, 2243 insertions(+), 328 deletions(-) create mode 100644 src/vaults/old/Vault.ts.old create mode 100644 src/vaults/old/VaultManager.ts.old diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index ed2bda00d7..e4b055b601 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -285,6 +285,16 @@ class Polykey { fs: fs_, logger: logger_.getChild('NodeManager'), })); + const notifications_ = + notificationsManager ?? + (await NotificationsManager.createNotificationsManager({ + acl: acl_, + db: db_, + nodeManager: nodes_, + keyManager: keys_, + logger: logger_.getChild('NotificationsManager'), + fresh, + })); const vaults_ = vaultManager ?? (await VaultManager.createVaultManager({ @@ -293,6 +303,7 @@ class Polykey { vaultsKey: keys_.vaultKey, nodeManager: nodes_, gestaltGraph: gestalts_, + notificationsManager: notifications_, acl: acl_, db: db_, fs: fs_, @@ -314,16 +325,6 @@ class Polykey { nodeManager: nodes_, logger: logger_.getChild('Discovery'), })); - const notifications_ = - notificationsManager ?? - (await NotificationsManager.createNotificationsManager({ - acl: acl_, - db: db_, - nodeManager: nodes_, - keyManager: keys_, - logger: logger_.getChild('NotificationsManager'), - fresh, - })); const sessionManager = await SessionManager.createSessionManager({ db: db_, @@ -471,6 +472,7 @@ class Polykey { nodeManager: this.nodes, sigchain: this.sigchain, notificationsManager: this.notifications, + acl: this.acl, }); // Registering providers. diff --git a/src/agent/agentService.ts b/src/agent/agentService.ts index e19c19de1b..0d5ba21280 100644 --- a/src/agent/agentService.ts +++ b/src/agent/agentService.ts @@ -27,6 +27,8 @@ import { utils as claimsUtils, errors as claimsErrors } from '../claims'; import { makeVaultId, makeVaultIdPretty } from '../vaults/utils'; import { makeNodeId } from '../nodes/utils'; import { utils as idUtils } from '@matrixai/id'; +import { ACL } from '../acl'; +import { NodeId } from '@/nodes/types'; /** * Creates the client service for use with a GRPCServer @@ -39,12 +41,15 @@ function createAgentService({ nodeManager, notificationsManager, sigchain, + acl, }: { keyManager: KeyManager; vaultManager: VaultManager; nodeManager: NodeManager; sigchain: Sigchain; notificationsManager: NotificationsManager; + acl: ACL; + }): IAgentServer { const agentService: IAgentServer = { echo: async ( @@ -61,21 +66,36 @@ function createAgentService({ const genWritable = grpcUtils.generatorWritable(call); const request = call.request; const vaultNameOrId = request.getVaultId(); - let vaultId, vaultName; + let vaultName; + let vaultId = await vaultManager.getVaultId(vaultNameOrId as VaultName); + if (!vaultId) { + try { + vaultId = makeVaultId(idUtils.fromString(vaultNameOrId)); + vaultName = await vaultManager.getVaultName(vaultId); + } catch (err) { + throw new vaultsErrors.ErrorVaultUndefined; + } + } else { + vaultName = vaultNameOrId; + } + await vaultManager.openVault(vaultId); + const metaIn = call.metadata; + const nodeId = metaIn.get('nodeId').pop()!.toString() as NodeId; + const actionType = metaIn.get('action').pop()!.toString(); + const perms = await acl.getNodePerm(nodeId); + if (!perms) { + throw new vaultsErrors.ErrorVaultPermissionDenied; + } + let vaultPerms = perms.vaults[idUtils.toString(vaultId)]; try { - vaultId = makeVaultId(idUtils.fromString(vaultNameOrId)); - await vaultManager.openVault(vaultId); - vaultName = await vaultManager.getVaultName(vaultId); + vaultPerms[actionType] } catch (err) { - if (err instanceof vaultsErrors.ErrorVaultUndefined) { - vaultId = await vaultManager.getVaultId(vaultNameOrId as VaultName); - await vaultManager.openVault(vaultId); - vaultName = vaultNameOrId; - } else { - throw err; + if (err instanceof TypeError) { + // genWritable.stream.emit('error', vaultsErrors.ErrorVaultPermissionDenied); + throw new vaultsErrors.ErrorVaultPermissionDenied; + return; } } - // TODO: Check the permissions here const meta = new grpc.Metadata(); meta.set('vaultName', vaultName); meta.set('vaultId', makeVaultIdPretty(vaultId)); @@ -107,22 +127,15 @@ function createAgentService({ const vaultNameOrId = meta.get('vaultNameOrId').pop()!.toString(); if (vaultNameOrId == null) throw new ErrorGRPC('vault-name not in metadata.'); - let vaultId; - try { - vaultId = makeVaultId(vaultNameOrId); - await vaultManager.openVault(vaultId); - } catch (err) { - if ( - err instanceof vaultsErrors.ErrorVaultUndefined || - err instanceof SyntaxError - ) { - vaultId = await vaultManager.getVaultId(vaultNameOrId as VaultName); - await vaultManager.openVault(vaultId); - } else { - throw err; + let vaultId = await vaultManager.getVaultId(vaultNameOrId as VaultName); + if (!vaultId) { + try { + vaultId = makeVaultId(vaultNameOrId); + } catch (err) { + throw new vaultsErrors.ErrorVaultUndefined; } } - // TODO: Check the permissions here + await vaultManager.openVault(vaultId); const response = new agentPB.PackChunk(); const [sideBand, progressStream] = await vaultManager.handlePackRequest( vaultId, diff --git a/src/vaults/VaultInternal.ts b/src/vaults/VaultInternal.ts index 7756fdc045..12fbc272c5 100644 --- a/src/vaults/VaultInternal.ts +++ b/src/vaults/VaultInternal.ts @@ -344,7 +344,7 @@ class VaultInternal { } @ready(new vaultsErrors.ErrorVaultDestroyed()) - public async applySchema() {} + public async applySchema(vs) {} } export default VaultInternal; diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index 211d49a6ee..a8e35aee43 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -67,6 +67,7 @@ class VaultManager { nodeManager, gestaltGraph, acl, + notificationsManager, db, fs, logger, @@ -78,6 +79,7 @@ class VaultManager { nodeManager: NodeManager; gestaltGraph: GestaltGraph; acl: ACL; + notificationsManager: NotificationsManager; db: DB; fs?: FileSystem; logger?: Logger; @@ -110,6 +112,7 @@ class VaultManager { nodeManager, gestaltGraph, acl, + notificationsManager, db, vaultsKey, vaultsDbDomain, @@ -127,6 +130,7 @@ class VaultManager { nodeManager, gestaltGraph, acl, + notificationsManager, db, vaultsKey, vaultsDbDomain, @@ -141,6 +145,7 @@ class VaultManager { nodeManager: NodeManager; gestaltGraph: GestaltGraph; acl: ACL; + notificationsManager: NotificationsManager; db: DB; vaultsKey: VaultKey; vaultsDbDomain: string; @@ -155,6 +160,7 @@ class VaultManager { this.nodeManager = nodeManager; this.gestaltGraph = gestaltGraph; this.acl = acl; + this.notificationsManager = notificationsManager; this.db = db; this.vaultsDbDomain = vaultsDbDomain; this.vaultsNamesDbDomain = vaultsNamesDbDomain; @@ -319,16 +325,9 @@ class VaultManager { if (!vaultName) throw new vaultsErrors.ErrorVaultUndefined(); return await this.gestaltGraph._transaction(async () => { return await this.acl._transaction(async () => { - const gestalt = await this.gestaltGraph.getGestaltByNode(nodeId); - if (gestalt == null) { - throw new gestaltErrors.ErrorGestaltsGraphNodeIdMissing(); - } - const nodes = gestalt.nodes; - for (const node in nodes) { - await this.acl.setNodeAction(nodeId, 'scan'); - await this.acl.setVaultAction(vaultId, nodes[node].id, 'pull'); - await this.acl.setVaultAction(vaultId, nodes[node].id, 'clone'); - } + await this.gestaltGraph.setGestaltActionByNode(nodeId, 'scan'); + await this.acl.setVaultAction(vaultId, nodeId, 'pull'); + await this.acl.setVaultAction(vaultId, nodeId, 'clone'); await this.notificationsManager.sendNotification(nodeId, { type: 'VaultShare', vaultId: idUtils.toString(vaultId), @@ -342,12 +341,26 @@ class VaultManager { }); } + @ready(new vaultsErrors.ErrorVaultManagerDestroyed()) + public async unshareVault(vaultId: VaultId, nodeId: NodeId): Promise { + const vaultName = await this.getVaultName(vaultId); + if (!vaultName) throw new vaultsErrors.ErrorVaultUndefined(); + return await this.gestaltGraph._transaction(async () => { + return await this.acl._transaction(async () => { + await this.gestaltGraph.unsetGestaltActionByNode(nodeId, 'scan'); + await this.acl.unsetVaultAction(vaultId, nodeId, 'pull'); + await this.acl.unsetVaultAction(vaultId, nodeId, 'clone'); + }); + }); + } + @ready(new vaultsErrors.ErrorVaultManagerDestroyed()) public async cloneVault( nodeId: NodeId, vaultNameOrId: VaultId | VaultName, ): Promise { let vaultName, remoteVaultId; + const thisNodeId = this.nodeManager.getNodeId(); const nodeConnection = await this.nodeManager.getConnectionToNode(nodeId); const client = nodeConnection.getClient(); const vaultId = await this.generateVaultId(); @@ -373,13 +386,19 @@ class VaultManager { const infoResponse = { async *[Symbol.iterator]() { const request = new agentPB.InfoRequest(); + const meta = new grpc.Metadata(); + meta.set('nodeId', thisNodeId); + meta.set('action', 'clone'); if (typeof vaultNameOrId === 'string') { request.setVaultId(vaultNameOrId); } else { request.setVaultId(idUtils.toString(vaultNameOrId)); } - const response = client.vaultsGitInfoGet(request); - response.stream.on('metadata', async (meta) => { + const response = client.vaultsGitInfoGet(request, meta); + response.stream.on('error', (err) => { + throw Error(); + }); + response.stream.on('metadata', (meta) => { vaultName = meta.get('vaultName').pop()!.toString(); remoteVaultId = makeVaultId( meta.get('vaultId').pop()!.toString(), @@ -477,7 +496,7 @@ class VaultManager { await this.db.put(this.vaultsNamesDbDomain, idUtils.toBuffer(vaultId), { name: vaultName, defaultPullNode: nodeId, - defaultPullVault: idUtils.toBuffer(remoteVaultId), + defaultPullVault: idUtils.toString(remoteVaultId), }); return vault; }, [vaultId]); @@ -494,30 +513,29 @@ class VaultManager { }): Promise { let metaChange = 0; let vaultMeta, remoteVaultId; + const thisNodeId = this.nodeManager.getNodeId(); return await this._transaction(async () => { - if (pullNodeId == null || pullVaultNameOrId == null) { - vaultMeta = await this.db.get( - this.vaultsNamesDbDomain, - idUtils.toBuffer(vaultId), + vaultMeta = await this.db.get( + this.vaultsNamesDbDomain, + idUtils.toBuffer(vaultId), + ); + if (!vaultMeta) throw new vaultsErrors.ErrorVaultUnlinked(); + if (pullNodeId == null) { + pullNodeId = vaultMeta.defaultPullNode; + } else { + metaChange = 1; + vaultMeta.defaultPullNode = pullNodeId; + } + if (pullVaultNameOrId == null) { + pullVaultNameOrId = makeVaultId( + idUtils.fromString(vaultMeta.defaultPullVault), ); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultUnlinked(); - if (pullNodeId == null) { - pullNodeId = vaultMeta.defaultPullNode; - } else { - metaChange = 1; - vaultMeta.defaultPullNode = pullNodeId; - } - if (pullVaultNameOrId == null) { - pullVaultNameOrId = makeVaultId( - idUtils.fromBuffer(Buffer.from(vaultMeta.defaultPullVault.data)), - ); + } else { + metaChange = 1; + if (typeof pullVaultNameOrId === 'string') { + metaChange = 2; } else { - metaChange = 1; - if (typeof pullVaultNameOrId === 'string') { - metaChange = 2; - } else { - vaultMeta.defaultPullVault = idUtils.toBuffer(pullVaultNameOrId); - } + vaultMeta.defaultPullVault = idUtils.toString(pullVaultNameOrId); } } const nodeConnection = await this.nodeManager.getConnectionToNode( @@ -539,12 +557,15 @@ class VaultManager { const infoResponse = { async *[Symbol.iterator]() { const request = new agentPB.InfoRequest(); + const meta = new grpc.Metadata(); + meta.set('nodeId', thisNodeId); + meta.set('action', 'clone'); if (typeof pullVaultNameOrId === 'string') { request.setVaultId(pullVaultNameOrId); } else { request.setVaultId(idUtils.toString(pullVaultNameOrId!)); } - const response = client.vaultsGitInfoGet(request); + const response = client.vaultsGitInfoGet(request, meta); response.stream.on('metadata', async (meta) => { remoteVaultId = makeVaultId( meta.get('vaultId').pop()!.toString(), @@ -626,7 +647,7 @@ class VaultManager { throw err; } if (metaChange !== 0) { - if (metaChange === 2) vaultMeta.defaultPullVault = remoteVaultId; + if (metaChange === 2) vaultMeta.defaultPullVault = idUtils.toString(remoteVaultId); await this.db.put( this.vaultsNamesDbDomain, idUtils.toBuffer(vaultId), diff --git a/src/vaults/errors.ts b/src/vaults/errors.ts index ab4faf10ff..1999ca1b16 100644 --- a/src/vaults/errors.ts +++ b/src/vaults/errors.ts @@ -40,6 +40,8 @@ class ErrorVaultCommitUndefined extends ErrorVaults { exitCode: number = 10; } +class ErrorVaultPermissionDenied extends ErrorVaults {} + class ErrorSecretUndefined extends ErrorSecrets {} class ErrorSecretDefined extends ErrorSecrets {} @@ -65,6 +67,7 @@ export { ErrorInvalidVaultId, ErrorVaultMergeConflict, ErrorVaultCommitUndefined, + ErrorVaultPermissionDenied, ErrorSecretUndefined, ErrorSecretDefined, ErrorReadingSecret, diff --git a/src/vaults/old/Vault.ts.old b/src/vaults/old/Vault.ts.old new file mode 100644 index 0000000000..fc29db3a02 --- /dev/null +++ b/src/vaults/old/Vault.ts.old @@ -0,0 +1,659 @@ +import type { FileSystem } from '../../types'; +import type { + FileChanges, + FileOptions, + SecretList, + SecretName, VaultId, + VaultIdRaw, + VaultKey, + VaultName +} from "../types"; +import type { NodeId } from '../../nodes/types'; +import type { WorkerManager } from '../../workers'; + +import fs from 'fs'; +import path from 'path'; +import git from 'isomorphic-git'; +import { Mutex } from 'async-mutex'; +import { EncryptedFS } from 'encryptedfs'; +import { PassThrough } from 'readable-stream'; +import Logger from '@matrixai/logger'; + +import { GitRequest } from '../../git'; + +import * as vaultsUtils from '../utils'; +import * as gitUtils from '../../git/utils'; +import * as vaultsErrors from '../errors'; +import * as gitErrors from '../../git/errors'; + +class Vault { + public readonly baseDir: string; + public readonly vaultId: VaultId; + + public vaultName: VaultName; + protected fs: FileSystem; + protected efs: EncryptedFS; + protected lock: Mutex = new Mutex(); + protected logger: Logger; + protected workerManager?: WorkerManager; + protected _started: boolean; + + constructor({ + vaultId, + vaultName, + baseDir, + fs, + logger, + }: { + vaultId: VaultId; + vaultName: VaultName; + baseDir: string; + fs: FileSystem; + logger?: Logger; + }) { + this.vaultId = vaultId; + this.vaultName = vaultName; + this.baseDir = baseDir; + this.fs = fs; + this.logger = logger ?? new Logger(this.constructor.name); + this._started = false; + } + + get started(): boolean { + return this._started; + } + + public setWorkerManager(workerManager: WorkerManager): void { + this.workerManager = workerManager; + this.efs.setWorkerManager(workerManager); + } + + public unsetWorkerManager(): void { + delete this.workerManager; + this.efs.unsetWorkerManager(); + } + + public async start({ key }: { key: VaultKey }): Promise { + this.efs = await EncryptedFS.createEncryptedFS({ + dbKey: key, + dbPath: this.baseDir, + logger: this.logger.getChild('EncryptedFS'), + }); + const release = await this.lock.acquire(); + try { + if (!(await this.efs.exists('.git'))) { + await git.init({ + fs: this.efs, + dir: '', + }); + + await git.commit({ + fs: this.efs, + dir: '', + author: { + name: this.vaultId, + }, + message: 'Initial Commit', + }); + + // Pack-refs not auto-generated by isomorphic git but needed + await this.efs.writeFile( + path.join('.git', 'packed-refs'), + '# pack-refs with: peeled fully-peeled sorted', + ); + this.logger.info(`Initialising vault at '${this.baseDir}'`); + } else { + // Const files = vaultsUtils.readdirRecursivelyEFS2(this.efs, '', true); + // for await (const filepath of files) { + // await git.add({ + // fs: this.efs, + // dir: '', + // filepath: filepath, + // }); + // } + // await git.checkout({ + // fs: this.efs, + // dir: '', + // force: true, + // }); + } + } finally { + release(); + } + } + + public async stop(): Promise { + const release = await this.lock.acquire(); + try { + await fs.promises.rmdir(this.baseDir, { recursive: true }); + this.logger.info(`Destroyed vault directory at ${this.baseDir}`); + } finally { + release(); + } + } + + /** + * Renames the vault, does not effect the underlying fs name + */ + public async renameVault(newVaultName: VaultName): Promise { + this.vaultName = newVaultName; + } + + /** + * Retreives stats for a vault + */ + public async stats(): Promise { + return await this.fs.promises.stat(this.baseDir); + } + + /** + * Adds a secret to the vault + */ + public async addSecret( + secretName: SecretName, + content: string, + ): Promise { + const release = await this.lock.acquire(); + try { + // Throw an error if the vault is not initialised + if (!(await this.efs.exists('.git'))) { + throw new vaultsErrors.ErrorVaultUninitialised( + `${this.vaultName} has not been initialised\nVaultId: ${this.vaultId}`, + ); + // Throw an error if the secret exists + } else if (await this.efs.exists(secretName)) { + throw new vaultsErrors.ErrorSecretDefined( + `${secretName} already exists, try updating instead`, + ); + // Throw an error if the secret contains a '.git' dir + } else if (path.basename(secretName) === '.git') { + throw new vaultsErrors.ErrorGitFile( + '.git files cannot be added to a vault', + ); + } + + // Create the directory to the secret if it doesn't exist + await this.efs.mkdir(path.dirname(secretName), {recursive: true}); + + // Write the secret into the vault + await this.efs.writeFile(secretName, content, {}); + this.logger.info(`Wrote secret to directory at '${secretName}'`); + + // Commit the changes + await this.commitChanges( + // FIXME: this is failing for some reason. + [ + { + fileName: secretName, + action: 'added', + }, + ], + `Add secret: ${secretName}`, + ); + return !!(await this.efs.exists(secretName)); + + } finally { + release(); + } + } + + /** + * Changes the contents of a secret + */ + public async updateSecret( + secretName: SecretName, + content: string, + ): Promise { + const release = await this.lock.acquire(); + try { + // Throw error if secret does not exist + if (!(await this.efs.exists(secretName))) { + throw new vaultsErrors.ErrorSecretUndefined( + 'Secret does not exist, try adding it instead.', + ); + } + + // Write secret into vault + await this.efs.writeFile(secretName, content); + this.logger.info(`Updated secret at directory '${secretName}'`); + + // Commit changes + await this.commitChanges( + [{ fileName: secretName, action: 'modified' }], + `Update secret: ${secretName}`, + ); + } finally { + release(); + } + } + + /** + * Changes the name of a secret in a vault + */ + public async renameSecret( + currSecretName: SecretName, + newSecretName: SecretName, + ): Promise { + const release = await this.lock.acquire(); + try { + // Throw error if trying to rename a '.git' file + if ( + path.basename(currSecretName) === '.git' || + path.basename(newSecretName) === '.git' + ) { + throw new vaultsErrors.ErrorGitFile( + 'Cannot rename a file to or from .git', + ); + } + + // Throw an error if the old secret does not exist + if (!(await this.efs.exists(currSecretName))) { + throw new vaultsErrors.ErrorSecretUndefined( + `${currSecretName} does not exist`, + ); + } + + // Throw an error if the new name already exists + if (await this.efs.exists(newSecretName)) { + throw new vaultsErrors.ErrorSecretDefined( + `${newSecretName} already exists`, + ); + } + + // Renames the secret in the vault + await this.efs.rename(currSecretName, newSecretName); + this.logger.info( + `Renamed secret at ${currSecretName} to ${newSecretName}`, + ); + // Commit changes + await this.commitChanges( + [ + { + fileName: currSecretName, + action: 'removed', + }, + { + fileName: newSecretName, + action: 'added', + }, + ], + `Renamed secret: ${currSecretName}`, + ); + if ( + (await this.efs.exists(currSecretName)) || + !(await this.efs.exists(newSecretName)) + ) { + return false; + } + return true; + } finally { + release(); + } + } + + /** + * Returns the contents of a secret + */ + public async getSecret(secretName: SecretName): Promise { + const release = await this.lock.acquire(); + try { + return (await this.efs.readFile(secretName)).toString(); + } catch (err) { + // Throw an error if the secret does not exist + if (err.code === 'ENOENT') { + throw new vaultsErrors.ErrorSecretUndefined( + `Secret with name: ${secretName} does not exist`, + ); + } + throw err; + } finally { + release(); + } + } + + /** + * Removes a secret from a vault + */ + public async deleteSecret( + secretName: SecretName, + fileOptions?: FileOptions, + ): Promise { + const release = await this.lock.acquire(); + try { + // Throw error if trying to remove '.git' file + if (path.basename(secretName) === '.git') { + throw new vaultsErrors.ErrorGitFile('Cannot remove .git'); + } + // Handle if secret is a directory + if ((await this.efs.stat(secretName)).isDirectory()) { + if (fileOptions?.recursive) { + // Remove the specified directory + await this.efs.rmdir(secretName); // TODO: Double check that this works as recursive. + this.logger.info(`Deleted directory at '${secretName}'`); + await this.commitChanges( + [ + { + fileName: secretName, + action: 'removed', + }, + ], + `Remove directory: ${secretName}`, + ); + // Throw error if not recursively deleting a directory + } else { + throw new vaultsErrors.ErrorRecursive( + 'delete a vault directory must be recursive', + ); + } + } else if (await this.efs.exists(secretName)) { + // Remove the specified file + await this.efs.unlink(secretName); + this.logger.info(`Deleted secret at '${secretName}'`); + + // Commit changes + await this.commitChanges( + [ + { + fileName: secretName, + action: 'removed', + }, + ], + `Remove secret: ${secretName}`, + ); + // Throw error if secret doesn't exist + } else { + throw new vaultsErrors.ErrorSecretUndefined( + `path '${secretName}' does not exist in vault`, + ); + } + if (await this.efs.exists(secretName)) { + return false; + } + return true; + } finally { + release(); + } + } + + /** + * Adds an empty directory to the root of the vault. + * i.e. mkdir("folder", { recursive = false }) creates the "/folder" directory + */ + public async mkdir( + dirPath: SecretName, + fileOptions?: FileOptions, + ): Promise { + const release = await this.lock.acquire(); + try { + // Create the specified directory + const recursive = fileOptions?.recursive ?? false; + try { + await this.efs.mkdirp(dirPath); + } catch (err) { + if (err.code === 'ENOENT' && !recursive) { + throw new vaultsErrors.ErrorRecursive( + `Could not create directory '${dirPath}' without recursive option`, + ); + } + } + this.logger.info(`Created secret directory at '${dirPath}'`); + if (await this.efs.exists(dirPath)) { + return true; + } + return false; + } finally { + release(); + } + } + + /** + * Adds a secret directory to the vault + */ + public async addSecretDirectory(secretDirectory: SecretName): Promise { + const release = await this.lock.acquire(); + const commitList: FileChanges = []; + const absoluteDirPath = path.resolve(secretDirectory); + try { + // Obtain the path to each secret in the provided directory + for await (const secretPath of vaultsUtils.readdirRecursively( + absoluteDirPath, + )) { + // Determine the path to the secret + const relPath = path.relative( + path.dirname(absoluteDirPath), + secretPath, + ); + // Obtain the content of the secret + const secretName = path.basename(secretPath); + const content = await fs.promises.readFile(secretPath); + // Throw error if the '.git' file is nonexistent + if (!(await this.efs.exists('.git'))) { + throw new vaultsErrors.ErrorVaultUninitialised( + `${this.vaultName} has not been initialised\nVaultId: ${this.vaultId}`, + ); + // Throw error if trying to add '.git' file + } else if (secretName === '.git') { + throw new vaultsErrors.ErrorGitFile( + '`.git files cannot be added to a vault', + ); + // If existing path exists, write secret + } else if (await this.efs.exists(relPath)) { + try { + // Write secret into vault + await this.efs.writeFile(relPath, content, {}); + this.logger.info(`Added secret at directory '${relPath}'`); + commitList.push({ + fileName: relPath, + action: 'modified', + }); + } catch (err) { + // Warn of a failed addition but continue operation + this.logger.warn(`Adding secret ${relPath} failed`); + } + } else { + try { + // Create directory if it doesn't exist + await this.efs.mkdirp(path.dirname(relPath)); + // Write secret into vault + await this.efs.writeFile(relPath, content, {}); + this.logger.info(`Added secret to directory at '${relPath}'`); + commitList.push({ + fileName: relPath, + action: 'added', + }); + } catch (err) { + // Warn of a failed addition but continue operation + this.logger.warn(`Adding secret ${relPath} failed`); + } + } + } + + // Commit changes + await this.commitChanges( + commitList, + `Add/Modify secrets: ${commitList[0].fileName} and ${ + commitList.length - 1 + } more`, + ); + } finally { + release(); + } + } + + /** + * Retrieves a list of the secrets in a vault + */ + public async listSecrets(): Promise { + const secrets: SecretList = []; + for await (const secret of vaultsUtils.readdirRecursivelyEFS( + this.efs, + '', + )) { + secrets.push(secret); + } + return secrets; + } + + /** + * Clones secrets from a remote vault into this vault + * TODO: Once EFS is updated, pass `this.fs` into EFS constructor + */ + public async cloneVault( + gitHandler: GitRequest, + vaultKey: VaultKey, + nodeId: NodeId, + ): Promise { + this.efs = await EncryptedFS.createEncryptedFS({ + dbKey: vaultKey, + dbPath: this.baseDir, + }); + + // Construct the target vault id + const vaultId = `${vaultsUtils.splitVaultId(this.vaultId)}:${nodeId}`; + + // Clone desired vault + await git.clone({ + fs: this.efs, + http: gitHandler, + dir: '', + url: `http://0.0.0.0/${vaultId}`, + ref: 'master', + singleBranch: true, + }); + // Write pack-refs that are not auto-generated by isomorphic git but needed + await this.efs.writeFile( + path.join('.git', 'packed-refs'), + '# pack-refs with: peeled fully-peeled sorted', + ); + this.logger.info(`Cloned vault at '${this.vaultId}'`); + } + + /** + * Pulls secrets from a remote vault into this vault + */ + public async pullVault( + gitHandler: GitRequest, + nodeId: NodeId, + ): Promise { + // Construct the target vault id + const vaultId = `${vaultsUtils.splitVaultId(this.vaultId)}:${nodeId}`; + + try { + // Pull from the target vault Id + await git.pull({ + fs: this.efs, + http: gitHandler, + dir: '', + url: `http://0.0.0.0/${vaultId}`, + ref: 'HEAD', + singleBranch: true, + author: { + name: this.vaultId, + }, + }); + } catch (err) { + // Throw an error if merge conflicts occur + if (err instanceof git.Errors.MergeNotSupportedError) { + throw new vaultsErrors.ErrorVaultMergeConflict( + 'Merge Conflicts are not supported yet', + ); + } + } + } + + /** + * Returns an async generator that yields buffers representing the git info response + */ + public async *handleInfoRequest(): AsyncGenerator { + // Define the service for uploading packets + const service = 'upload-pack'; + yield Buffer.from( + gitUtils.createGitPacketLine('# service=git-' + service + '\n'), + ); + + // Separator for message + yield Buffer.from('0000'); + + // Reading the relevant files to contruct the information reply + for (const buffer of (await gitUtils.uploadPack(this.efs, '.git', true)) ?? + []) { + yield buffer; + } + } + + /** + * Takes vaultName and a pack request and returns two streams used to handle the pack + * response + */ + public async handlePackRequest(body: Buffer): Promise { + if (body.toString().slice(4, 8) === 'want') { + // Determine the oid of the requested object + const wantedObjectId = body.toString().slice(9, 49); + + // Pack the requested object into a message + const packResult = await gitUtils.packObjects({ + fs: this.efs, + gitdir: '.git', + refs: [wantedObjectId], + }); + + // Construct and return readable streams to send the message + const readable = new PassThrough(); + const progressStream = new PassThrough(); + const sideBand = gitUtils.mux( + 'side-band-64', + readable, + packResult.packstream, + progressStream, + ); + return [sideBand, progressStream]; + } else { + // Throw error if the request is not for pack information + throw new gitErrors.ErrorGitUnimplementedMethod( + `Request of type '${body + .toString() + .slice(4, 8)}' not valid, expected 'want'`, + ); + } + } + + /* === Helpers === */ + + /** + * Commits the changes made to a vault repository + */ + protected async commitChanges( + fileChanges: FileChanges, + message: string, + ): Promise { + // Obtain each file change + for (const fileChange of fileChanges) { + // Use isomorphic git to add or remove the file/dir + if (fileChange.action === 'removed') { + await git.remove({ + fs: this.efs, + dir: '', + filepath: fileChange.fileName, + }); + } else { + await git.add({ + fs: this.efs, + dir: '', + filepath: fileChange.fileName, + }); + } + } + + // Commit the changes made + await git.commit({ + fs: this.efs, + dir: '', + author: { + name: this.vaultId, + }, + message: message, + }); + } +} + +export default Vault; diff --git a/src/vaults/old/VaultManager.ts.old b/src/vaults/old/VaultManager.ts.old new file mode 100644 index 0000000000..d48442b9f0 --- /dev/null +++ b/src/vaults/old/VaultManager.ts.old @@ -0,0 +1,976 @@ +import type { DB, DBLevel, DBOp } from '@matrixai/db'; +import type { + VaultIdRaw, + VaultName, + VaultMap, + VaultPermissions, + VaultKey, +} from '../types'; +import type { FileSystem } from '../../types'; +import type { WorkerManager } from '../../workers'; +import type { NodeId } from '../../nodes/types'; + +import fs from 'fs'; +import path from 'path'; +import Logger from '@matrixai/logger'; +import { Mutex } from 'async-mutex'; +import Vault from './Vault'; + +import { KeyManager } from '../../keys'; +import { NodeManager } from '../../nodes'; +import { GestaltGraph } from '../../gestalts'; +import { ACL } from '../../acl'; +import { GitRequest } from '../../git'; +import { agentPB } from '../../agent'; + +import * as utils from '../../utils'; +import * as vaultsUtils from '../utils'; +import * as vaultsErrors from '../errors'; +import * as keysErrors from '../../keys/errors'; +import * as gitErrors from '../../git/errors'; +import * as nodesErrors from '../../nodes/errors'; +import * as aclErrors from '../../acl/errors'; +import * as gestaltErrors from '../../gestalts/errors'; +import { errors as dbErrors } from '@matrixai/db'; + +type Vaults = { + [vaultId: string]: Vault; +}; + +class VaultManager { + public readonly vaultsPath: string; + public readonly vaultsDbPath: string; + + protected fs: FileSystem; + + protected keyManager: KeyManager; + protected nodeManager: NodeManager; + protected db: DB; + protected acl: ACL; + protected gestaltGraph: GestaltGraph; + + protected vaultsDbDomain: string = this.constructor.name; + protected vaultsKeysDbDomain: Array = [this.vaultsDbDomain, 'keys']; + protected vaultsNamesDbDomain: Array = [this.vaultsDbDomain, 'names']; + protected vaultsNodesDbDomain: Array = [this.vaultsDbDomain, 'nodes']; + protected vaultsDb: DBLevel; + protected vaultsKeysDb: DBLevel; + protected vaultsNamesDb: DBLevel; + protected vaultsNodesDb: DBLevel; + protected lock: Mutex = new Mutex(); + + protected vaults: Vaults; + protected logger: Logger; + protected workerManager?: WorkerManager; + + protected _started: boolean; + + /** + * Construct a VaultManager object + * @param vaultsPath path to store vault and vault data in. should be /vaults + * @param keyManager Key Manager object + * @param fs fs object + * @param logger Logger + */ + constructor({ + vaultsPath, + keyManager, + nodeManager, + db, + acl, + gestaltGraph, + fs, + logger, + }: { + vaultsPath: string; + keyManager: KeyManager; + nodeManager: NodeManager; + db: DB; + acl: ACL; + gestaltGraph: GestaltGraph; + fs?: FileSystem; + logger?: Logger; + }) { + this.vaultsPath = vaultsPath; + this.vaultsDbPath = path.join(this.vaultsPath, 'vaultsDb'); + + this.keyManager = keyManager; + this.db = db; + this.nodeManager = nodeManager; + this.acl = acl; + this.gestaltGraph = gestaltGraph; + + this.fs = fs ?? require('fs'); + + this.vaults = {}; + this.logger = logger ?? new Logger(this.constructor.name); + this._started = false; + } + + // TODO: Add in node manager started in here (currently is a function not a getter) + get started(): boolean { + if ( + this._started && + !this.keyManager.destroyed && + this.db.running && + this.acl.destroyed && + this.gestaltGraph.destroyed + ) { + return true; + } + return false; + } + + get locked(): boolean { + return this.lock.isLocked(); + } + + public setWorkerManager(workerManager: WorkerManager): void { + this.workerManager = workerManager; + for (const vaultId in this.vaults) { + this.vaults[vaultId].setWorkerManager(workerManager); + } + } + + public unsetWorkerManager(): void { + delete this.workerManager; + for (const vaultId in this.vaults) { + this.vaults[vaultId].unsetWorkerManager(); + } + } + + public async start({ fresh = false }: { fresh?: boolean }): Promise { + if (this.keyManager.destroyed) { + throw new keysErrors.ErrorKeyManagerDestroyed(); + } else if (!this.db.running) { + throw new dbErrors.ErrorDBNotRunning(); + } else if (!this.nodeManager.running) { + throw new nodesErrors.ErrorNodeManagerNotStarted(); + } else if (this.acl.destroyed) { + throw new aclErrors.ErrorACLDestroyed(); + } else if (this.gestaltGraph.destroyed) { + throw new gestaltErrors.ErrorGestaltsGraphDestroyed(); + } + this.logger.info('Starting Vault Manager'); + if (fresh) { + await this.fs.promises.rm(this.vaultsPath, { + force: true, + recursive: true, + }); + this.logger.info(`Removing vaults directory at '${this.vaultsPath}'`); + } + await utils.mkdirExists(this.fs, this.vaultsPath, { recursive: true }); + this.vaultsDb = await this.db.level(this.vaultsDbDomain); + // Stores VaultId -> VaultKey + this.vaultsKeysDb = await this.db.level( + this.vaultsKeysDbDomain[1], + this.vaultsDb, + ); + // Stores VaultName -> VaultId + this.vaultsNamesDb = await this.db.level( + this.vaultsNamesDbDomain[1], + this.vaultsDb, + ); + // Stores VaultId -> NodeId + this.vaultsNodesDb = await this.db.level( + this.vaultsNodesDbDomain[1], + this.vaultsDb, + ); + if (fresh) { + await this.vaultsDb.clear(); + } + this._started = true; + this.logger.info('Started Vault Manager'); + } + + public async stop(): Promise { + this.logger.info('Stopping Vault Manager'); + this._started = false; + this.logger.info('Stopped Vault Manager'); + } + + /** + * Run several operations within the same lock + * This does not ensure atomicity of the underlying database + * Database atomicity still depends on the underlying operation + */ + public async transaction( + f: (vaultManager: VaultManager) => Promise, + ): Promise { + const release = await this.lock.acquire(); + try { + return await f(this); + } finally { + release(); + } + } + + /** + * Transaction wrapper that will not lock if the operation was executed + * within a transaction context + */ + public async _transaction(f: () => Promise): Promise { + if (this.lock.isLocked()) { + return await f(); + } else { + return await this.transaction(f); + } + } + + /** + * Adds a new vault, given a vault name. Also generates a new vault key + * and writes encrypted vault metadata to disk. + * + * @throws ErrorVaultDefined if vault with the same name already exists + * @param vaultName Name of the new vault + * @returns The newly created vault object + */ + public async createVault(vaultName: VaultName): Promise { + // Generate a unique vault Id + const vaultId = await this.generateVaultId(); + + // Create the Vault instance and path + await this.fs.promises.mkdir(path.join(this.vaultsPath, vaultId)); + this.logger.info( + `Creating new vault directory at '${path.join(this.vaultsPath, vaultId)}`, + ); + const vault = new Vault({ + vaultId: vaultId, + vaultName: vaultName, + baseDir: path.join(this.vaultsPath, vaultId), + fs: fs, + logger: this.logger, + }); + + // Generate the key and store the vault in memory and on disk + const key = await vaultsUtils.generateVaultKey(); + await this.createVaultOps(vaultName, vaultId, key); + await vault.start({ key: key }); + this.vaults[vaultId] = vault; + return vault; + } + + /** + * Retreieves the Vault instance + * + * @throws ErrorVaultUndefined if vaultId does not exist + * @param vaultId Id of vault + * @returns a vault instance. + */ + public async getVault(vaultId: VaultIdRaw): Promise { + // If the vault does not already exist in the memory map, set up the vault + if (!this.vaults[vaultId]) { + await this.setupVault(vaultId); + } + return this.vaults[vaultId]; + } + + /** + * Rename an existing vault. Updates references to vault keys and + * writes new encrypted vault metadata to disk. + * + * @throws ErrorVaultUndefined if vault currVaultName does not exist + * @throws ErrorVaultDefined if newVaultName already exists + * @param vaultId Id of vault to be renamed + * @param newVaultName New name of vault + * @returns true if success + */ + public async renameVault( + vaultId: VaultIdRaw, + newVaultName: VaultName, + ): Promise { + // If the vault does not already exist in the memory map, set up the vault + if (!this.vaults[vaultId]) { + await this.setupVault(vaultId); + } + const vault = this.vaults[vaultId]; + await this.renameVaultOps(vault.vaultName, newVaultName); + await vault.renameVault(newVaultName); + return true; + } + + /** + * Retreives stats for a vault + * + * @returns the stats of the vault directory + */ + public async vaultStats(vaultId: VaultIdRaw): Promise { + // If the vault does not already exist in the memory map, set up the vault + if (!this.vaults[vaultId]) { + await this.setupVault(vaultId); + } + const vault = this.vaults[vaultId]; + return await vault.stats(); + } + + /** + * Delete an existing vault. Deletes file from filesystem and + * updates mappings to vaults and vaultKeys. If it fails to delete + * from the filesystem, it will not modify any mappings and reutrn false + * + * @throws ErrorVaultUndefined if vault name does not exist + * @param vaultId Id of vault to be deleted + * @returns true if successful delete, false if vault path still exists + */ + public async deleteVault(vaultId: VaultIdRaw): Promise { + return await this._transaction(async () => { + return await this.acl._transaction(async () => { + // If the vault does not already exist in the memory map, set up the vault + if (!this.vaults[vaultId]) { + await this.setupVault(vaultId); + } + // Delete the vault state + await this.vaults[vaultId].stop(); + const vaultPath = this.vaults[vaultId].baseDir; + this.logger.info(`Removed vault directory at '${vaultPath}'`); + + // Return if the vault was not successfully deleted + if (await vaultsUtils.fileExists(this.fs, vaultPath)) { + return false; + } + // Remove vault from the database + const name = this.vaults[vaultId].vaultName; + await this.deleteVaultOps(name); + + // Remove vault permissions from the database + await this.acl.unsetVaultPerms(vaultId); + + // Remove vault from in memory map + delete this.vaults[vaultId]; + return true; + }); + }); + } + + /** + * Retrieve all the vaults for current node + * + * @returns Array of VaultName and VaultIds managed currently by the vault manager + */ + public async listVaults(): Promise { + const vaults: VaultMap = new Map(); + // Read all vault objects from the database + for await (const o of this.vaultsNamesDb.createReadStream({})) { + const id = (o as any).value; + const name = (o as any).key.toString() as string; + const vaultId = await this.db.deserializeDecrypt(id, false); + // vaults.push({ + // name: name, + // id: vaultId, + // }); + } + return vaults; + } + + /** + * Gives vault id given the vault name + * @param vaultName The Vault name + * @returns the id that matches the given vault name. undefined if nothing is found + */ + public async getVaultId(vaultName: VaultName): Promise { + return await this.getVaultIdByVaultName(vaultName); + } + + /** + * Scans all the vaults for current node which a node Id has permissions for + * + * @returns Array of VaultName and VaultIds managed currently by the vault manager + */ + public async scanVaults(nodeId: NodeId): Promise { + return await this.acl._transaction(async () => { + // List all vaults + const vaults = await this.listVaults(); + const scan: VaultMap = new Map(); + for (const vault of vaults) { + // // Check if the vault has valid permissions + // const list = await this.acl.getVaultPerm(vault.id); + // if (list[nodeId]) { + // if (list[nodeId].vaults[vault.id]['pull'] !== undefined) { + // scan.push(vault); + // } + // } + } + return scan; + }); + } + + /** + * Sets the default pull node of a vault + * + * @throws ErrorVaultUndefined if vaultId does not exist + * @param vaultId Id of vault + * @param linkVault Id of the cloned vault + */ + public async setDefaultNode(vaultId: VaultIdRaw, nodeId: NodeId): Promise { + // If the vault does not already exist in the memory map, set up the vault + if (!this.vaults[vaultId]) { + await this.setupVault(vaultId); + } + await this.setVaultNodebyVaultId(vaultId, nodeId); + } + + /** + * Gets the Vault that is associated with a cloned Vault ID + * + * @throws ErrorVaultUndefined if vaultId does not exist + * @param vaultId Id of vault that has been cloned + * @returns instance of the vault that is linked to the cloned vault + */ + public async getDefaultNode(vaultId: VaultIdRaw): Promise { + return await this.getVaultNodeByVaultId(vaultId); + } + + /** + * Sets the permissions of a gestalt using a provided nodeId + * This should take in a nodeId representing a gestalt, and remove + * all permissions for all nodeIds that are associated in the gestalt graph + * + * @param nodeId Identifier for gestalt as NodeId + * @param vaultId Id of the vault to set permissions for + */ + public async setVaultPermissions( + nodeId: NodeId, + vaultId: VaultIdRaw, + ): Promise { + return await this.gestaltGraph._transaction(async () => { + return await this.acl._transaction(async () => { + // Obtain the gestalt from the provided node + const gestalt = await this.gestaltGraph.getGestaltByNode(nodeId); + if (gestalt == null) { + // Throw an error if the gestalt does not exist + throw new gestaltErrors.ErrorGestaltsGraphNodeIdMissing(); + } + // Set the vault permissions for each node in the gestalt + const nodes = gestalt?.nodes; + for (const node in nodes) { + await this.setVaultAction([nodes[node].id], vaultId); + } + }); + }); + } + + /** + * Unsets the permissions of a gestalt using a provided nodeId + * This should take in a nodeId representing a gestalt, and remove + * all permissions for all nodeIds that are associated in the gestalt graph + * + * @param nodeId Identifier for gestalt as NodeId + * @param vaultId Id of the vault to unset permissions for + */ + public async unsetVaultPermissions( + nodeId: NodeId, + vaultId: VaultIdRaw, + ): Promise { + return await this.gestaltGraph._transaction(async () => { + return await this.acl._transaction(async () => { + // Obtain the gestalt from the provided node + const gestalt = await this.gestaltGraph.getGestaltByNode(nodeId); + if (gestalt == null) { + // If no gestalt for the given node is found, return + return; + } + // Unset the vault permissions for each node in the gestalt + const nodes = gestalt?.nodes; + for (const node in nodes) { + await this.unsetVaultAction([nodes[node].id], vaultId); + } + }); + }); + } + + /** + * Gets the permissions of a vault for a single or all nodes + * + * @param nodeId Id of the specific node to look up permissions for + * @param vaultId Id of the vault to look up permissions for + * @returns a record of the permissions for the vault + */ + public async getVaultPermissions( + vaultId: VaultIdRaw, + nodeId?: NodeId, + ): Promise { + return await this.acl._transaction(async () => { + const record: VaultPermissions = {}; + // Get the permissions for the provided vault + const perms = await this.acl.getVaultPerm(vaultId); + + // Set the return message based on the permissions for a node + for (const node in perms) { + if (nodeId && nodeId === node) { + record[node] = perms[node].vaults[vaultId]; + } else if (nodeId == null) { + record[node] = perms[node].vaults[vaultId]; + } + } + return record; + }); + } + + /** + * Clones a vault from another node + * + * @throws ErrorRemoteVaultUndefined if vaultName does not exist on + * connected node + * @throws ErrorNodeConnectionNotExist if the address of the node to connect to + * does not exist + * @throws ErrorRGitPermissionDenied if the node cannot access the desired vault + * @param vaultId Id of remote vault + * @param nodeId identifier of node to clone from + */ + public async cloneVault(vaultId: VaultIdRaw, nodeId: NodeId): Promise { + // Create a connection to the specified node + await this.nodeManager.getConnectionToNode(nodeId); + const client = await this.nodeManager.getClient(nodeId); + + // Compile the vault Id + const id = `${vaultsUtils.splitVaultId(vaultId)}:${nodeId}`; + + // Send a message to the connected agent to see if the clone can occur + const vaultPermMessage = new agentPB.VaultPermMessage(); + vaultPermMessage.setNodeId(this.nodeManager.getNodeId()); + vaultPermMessage.setVaultId(id); + const permission = await client.vaultsPermisssionsCheck(vaultPermMessage); + if (permission.getPermission() === false) { + // Throw an error if the permissions for the node are not valid + throw new gitErrors.ErrorGitPermissionDenied(); + } + + // Create the handler for git to clone from + const gitRequest = await vaultsUtils.constructGitHandler( + client, + this.nodeManager.getNodeId(), + ); + + // Search for the given vault Id and return the name + const list = await gitRequest.scanVaults(); + let vaultName = '' as VaultName; + // let vaultName = vaultsUtils.searchVaultName(list, vaultId); + + // Add ' copy' until the vault name is unique + let valid = false; + while (!valid) { + if (await this.getVaultId(vaultName)) { + this.logger.warn( + `'${vaultName}' already exists, cloned into '${vaultName} copy'`, + ); + // Add an extra string to avoid conflicts + // vaultName += ' copy'; + } else { + valid = true; + } + } + + // Clone the vault + await this.cloneVaultOps(gitRequest, vaultName, vaultId, nodeId); + + // Set the default pulling node to the specified node Id + await this.setDefaultNode( + `${vaultsUtils.splitVaultId( + vaultId, + )}':'${this.nodeManager.getNodeId()}` as VaultIdRaw, + nodeId, + ); + } + + /** + * Pulls a vault from another node + * + * @throws ErrorVaultUnlinked if the vault does not have an already cloned repo + * @throws ErrorVaultModified if changes have been made to the local repo + * @throws ErrorNodeConnectionNotExist if the address of the node to connect to + * does not exist + * @throws ErrorRGitPermissionDenied if the node cannot access the desired vault + * @param vaultId Id of vault + * @param nodeId identifier of node to clone from + */ + public async pullVault(vaultId: VaultIdRaw, nodeId?: NodeId): Promise { + let node = nodeId; + // If no nodeId was provided, obtain the default node + if (nodeId == null) { + node = await this.getDefaultNode(vaultId); + } + if (node == null) { + // Throw an error if a linked vault cannot be found for the vault + throw new vaultsErrors.ErrorVaultUnlinked( + 'Vault Id has not been cloned from remote repository', + ); + } + + // Create a connection to the specified node + await this.nodeManager.getConnectionToNode(node); + const client = await this.nodeManager.getClient(node); + + // Compile the vault Id + const id = `${vaultsUtils.splitVaultId(vaultId)}:${node}` as VaultIdRaw; + + // Send a message to the connected agent to see if the pull can occur + const vaultPermMessage = new agentPB.VaultPermMessage(); + vaultPermMessage.setNodeId(this.nodeManager.getNodeId()); + vaultPermMessage.setVaultId(id); + const permission = await client.vaultsPermisssionsCheck(vaultPermMessage); + if (permission.getPermission() === false) { + // Throw an error if the permissions for the node are not valid + throw new gitErrors.ErrorGitPermissionDenied(); + } + + // Create the handler for git to pull from + const gitRequest = await vaultsUtils.constructGitHandler( + client, + this.nodeManager.getNodeId(), + ); + + // Obtain a list of the remote vaults and throw an error if the desired vault is undefined + const list = await gitRequest.scanVaults(); + // vaultsUtils.searchVaultName(list, id); + + // Pull the vault + const vault = await this.getVault(vaultId); + await vault.pullVault(gitRequest, node); + + // Set the default pulling node to the specified node Id + await this.setDefaultNode(vaultId, node); + } + + /** + * Returns a generator that yields the names of the vaults + */ + public async *handleVaultNamesRequest( + nodeId: NodeId, + ): AsyncGenerator { + // Obtain a list of all vaults + const vaults = await this.scanVaults(nodeId); + for (const vault in vaults) { + // Yield each vault Id and name + yield Buffer.from(`${vaults[vault].name}\t${vaults[vault].id}`); + } + } + + /* === Helpers === */ + /** + * Generates a vault Id that is unique + * @throws If a unique Id cannot be made after 50 attempts + */ + protected async generateVaultId(): Promise { + // While the vault Id is not unique, generate a new Id + let vaultId = vaultsUtils.generateVaultId(this.nodeManager.getNodeId()); + let i = 0; + while (1) { + try { + await this.getVault(vaultId); + } catch (e) { + if (e instanceof vaultsErrors.ErrorVaultUndefined) { + break; + } + } + i++; + if (i > 50) { + // Throw an error if a unique id cannot be generated after 50 attempts + throw new vaultsErrors.ErrorCreateVaultId( + 'Could not create a unique vaultId after 50 attempts', + ); + } + vaultId = vaultsUtils.generateVaultId(this.nodeManager.getNodeId()); + } + return vaultId; + } + + /** + * Creates an empty vault that can be cloned into + * + * @throws ErrorVaultDefined if vault with the same name already exists + * @param vaultName Name of the new vault + * @returns The newly created vault object + */ + protected async cloneVaultOps( + gitHandler: GitRequest, + vaultName: VaultName, + vaultId: VaultIdRaw, + nodeId: NodeId, + ): Promise { + // Compile the new vaultId with the current node Id appended + const newVaultId = `${vaultsUtils.splitVaultId( + vaultId, + )}:${this.nodeManager.getNodeId()}` as VaultIdRaw; + + // Create the Vault instance and path + await this.fs.promises.mkdir(path.join(this.vaultsPath, newVaultId)); + const vault = new Vault({ + vaultId: newVaultId, + vaultName: vaultName, + baseDir: path.join(this.vaultsPath, newVaultId), + fs: fs, + logger: this.logger, + }); + this.logger.info(`Created empty vault at '${vault.baseDir}'`); + + // Generate the key and store the vault in memory and on disk + const key = await vaultsUtils.generateVaultKey(); + await this.createVaultOps(vaultName, newVaultId, key); + this.vaults[newVaultId] = vault; + + // Clone into the vault + await vault.cloneVault(gitHandler, key, nodeId); + } + + /** + * Renames an existing vault name to a new vault name + * If the existing vault name doesn't exist, nothing will change + */ + protected async renameVaultOps( + vaultName: VaultName, + newVaultName: VaultName, + ): Promise { + await this._transaction(async () => { + const vaultId = await this.db.get( + this.vaultsNamesDbDomain, + vaultName, + ); + if (vaultId == null) { + return; + } + const ops: Array = [ + { + type: 'del', + domain: this.vaultsNamesDbDomain, + key: vaultName, + }, + { + type: 'put', + domain: this.vaultsNamesDbDomain, + key: newVaultName, + value: vaultId, + }, + ]; + await this.db.batch(ops); + }); + } + + /** + * Puts a new vault and the vault Id into the db + */ + protected async createVaultOps( + vaultName: VaultName, + vaultId: VaultIdRaw, + vaultKey: VaultKey, + ): Promise { + await this._transaction(async () => { + const existingId = await this.db.get( + this.vaultsNamesDbDomain, + vaultName, + ); + if (existingId != null) { + // Throw an error if the vault name already exists + throw new vaultsErrors.ErrorVaultDefined( + 'Vault Name already exists in Polykey, specify a new Vault Name', + ); + } + const ops: Array = [ + { + type: 'put', + domain: this.vaultsNamesDbDomain, + key: vaultName, + value: vaultId, + }, + { + type: 'put', + domain: this.vaultsKeysDbDomain, + key: vaultId, + value: vaultKey, + }, + ]; + await this.db.batch(ops); + }); + } + + /** + * Deletes a vault using an existing vault name + * If the existing vault name doesn't exist, nothing will change + */ + protected async deleteVaultOps(vaultName: VaultName): Promise { + await this._transaction(async () => { + const vaultId = await this.db.get( + this.vaultsNamesDbDomain, + vaultName, + ); + if (vaultId == null) { + return; + } + const ops: Array = [ + { + type: 'del', + domain: this.vaultsNamesDbDomain, + key: vaultName, + }, + { + type: 'del', + domain: this.vaultsKeysDbDomain, + key: vaultId, + }, + { + type: 'del', + domain: this.vaultsNodesDbDomain, + key: vaultId, + }, + ]; + await this.db.batch(ops); + }); + } + + protected async setupVault(vaultId: VaultIdRaw) { + return await this._transaction(async () => { + let vaultName = '' as VaultName; + + // Obtain the vault information form the database + for await (const o of this.vaultsNamesDb.createReadStream({})) { + const vId = (o as any).value; + const name = (o as any).key as VaultName; + const id = await this.db.deserializeDecrypt(vId, false); + if (vaultId === id) { + vaultName = name; + break; + } + } + if (vaultName === '') { + // Throw an error if the vault name does not exist + throw new vaultsErrors.ErrorVaultUndefined(); + } + + // Obtain the vault key from the database + const vaultKey = await this.getVaultKeyByVaultId(vaultId); + if (vaultKey == null) { + // Throw an error if the vault key does not exist + throw new vaultsErrors.ErrorVaultUndefined(); + } + + // Construct and start the vault + this.vaults[vaultId] = new Vault({ + vaultId: vaultId, + vaultName: vaultName, + baseDir: path.join(this.vaultsPath, vaultId), + fs: fs, + logger: this.logger, + }); + this.vaults[vaultId].start({ key: vaultKey }); + }); + } + + /** + * Gets the vault id for a given vault name + */ + protected async getVaultIdByVaultName( + vaultName: VaultName, + ): Promise { + return await this._transaction(async () => { + const vaultId = await this.db.get( + this.vaultsNamesDbDomain, + vaultName, + ); + if (vaultId == null) { + return; + } + return vaultId; + }); + } + + /** + * Gets the vault key for a given vault id + */ + protected async getVaultKeyByVaultId( + vaultId: VaultIdRaw, + ): Promise { + return await this._transaction(async () => { + const vaultKey = await this.db.get( + this.vaultsKeysDbDomain, + vaultId, + ); + if (vaultKey == null) { + return; + } + return vaultKey; + }); + } + + /** + * Gets the vault link for a given vault id + */ + protected async getVaultNodeByVaultId( + vaultId: VaultIdRaw, + ): Promise { + return await this._transaction(async () => { + const vaultLink = await this.db.get( + this.vaultsNodesDbDomain, + vaultId, + ); + if (vaultLink == null) { + return; + } + return vaultLink; + }); + } + + /** + * Sets the default node Id to pull from for a vault Id + */ + protected async setVaultNodebyVaultId( + vaultId: VaultIdRaw, + vaultNode: NodeId, + ): Promise { + await this._transaction(async () => { + await this.db.put(this.vaultsNodesDbDomain, vaultId, vaultNode); + }); + } + + /** + * Gives pulling permissions for a vault to one or more nodes + * + * @param nodeIds Id(s) of the nodes to share with + * @param vaultId Id of the vault that the permissions are for + */ + protected async setVaultAction( + nodeIds: NodeId[], + vaultId: VaultIdRaw, + ): Promise { + return await this.acl._transaction(async () => { + for (const nodeId of nodeIds) { + try { + await this.acl.setVaultAction(vaultId, nodeId, 'pull'); + } catch (err) { + if (err instanceof aclErrors.ErrorACLNodeIdMissing) { + await this.acl.setNodePerm(nodeId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + await this.acl.setVaultAction(vaultId, nodeId, 'pull'); + } + } + } + }); + } + + /** + * Removes pulling permissions for a vault for one or more nodes + * + * @param nodeIds Id(s) of the nodes to remove permissions from + * @param vaultId Id of the vault that the permissions are for + */ + protected async unsetVaultAction( + nodeIds: NodeId[], + vaultId: VaultIdRaw, + ): Promise { + return await this.acl._transaction(async () => { + for (const nodeId of nodeIds) { + try { + await this.acl.unsetVaultAction(vaultId, nodeId, 'pull'); + } catch (err) { + if (err instanceof aclErrors.ErrorACLNodeIdMissing) { + return; + } + } + } + }); + } +} + +export default VaultManager; diff --git a/tests/PolykeyAgent.test.ts b/tests/PolykeyAgent.test.ts index 3e9f65f556..e5e1e4251f 100644 --- a/tests/PolykeyAgent.test.ts +++ b/tests/PolykeyAgent.test.ts @@ -44,6 +44,15 @@ describe('Polykey', () => { }, global.polykeyStartupTimeout, ); + // Test.skip( + // 'construction has no side effects', + // async () => { + // const nodePath = `${dataDir}/polykey`; + // await PolykeyAgent.createPolykey({ password, nodePath, logger }); + // await expect(() => fs.promises.stat(nodePath)).rejects.toThrow(/ENOENT/); // Construction has side effects now. + // }, + // global.polykeyStartupTimeout, + // ); test( 'async start constructs node path', async () => { diff --git a/tests/agent/GRPCClientAgent.test.ts b/tests/agent/GRPCClientAgent.test.ts index b412edd1fb..621a341618 100644 --- a/tests/agent/GRPCClientAgent.test.ts +++ b/tests/agent/GRPCClientAgent.test.ts @@ -1,4 +1,4 @@ -import type { NodeAddress, NodeInfo } from '@/nodes/types'; +import type { NodeAddress, NodeId, NodeInfo } from '@/nodes/types'; import type { ClaimIdString, ClaimIntermediary } from '@/claims/types'; import type { Host, Port, TLSConfig } from '@/network/types'; import type { VaultName } from '@/vaults/types'; @@ -134,6 +134,7 @@ describe('GRPC agent', () => { db: db, acl: acl, gestaltGraph: gestaltGraph, + notificationsManager: notificationsManager, fs: fs, logger: logger, }); @@ -151,6 +152,7 @@ describe('GRPC agent', () => { nodeManager, sigchain, notificationsManager, + acl, }); client = await testUtils.openTestAgentClient(port); }, global.polykeyStartupTimeout); diff --git a/tests/agent/utils.ts b/tests/agent/utils.ts index 4310715d00..1bd815ff60 100644 --- a/tests/agent/utils.ts +++ b/tests/agent/utils.ts @@ -11,6 +11,7 @@ import { NodeManager } from '@/nodes'; import { promisify } from '@/utils'; import { Sigchain } from '@/sigchain'; import { NotificationsManager } from '@/notifications'; +import { ACL } from '@/acl'; async function openTestAgentServer({ keyManager, @@ -18,12 +19,14 @@ async function openTestAgentServer({ nodeManager, sigchain, notificationsManager, + acl, }: { keyManager: KeyManager; vaultManager: VaultManager; nodeManager: NodeManager; sigchain: Sigchain; notificationsManager: NotificationsManager; + acl: ACL; }) { const agentService: IAgentServer = createAgentService({ keyManager, @@ -31,6 +34,7 @@ async function openTestAgentServer({ nodeManager, sigchain: sigchain, notificationsManager: notificationsManager, + acl: acl, }); const server = new grpc.Server(); diff --git a/tests/bin/polykey.test.ts b/tests/bin/polykey.test.ts index 0d9785be10..b992dd1a82 100644 --- a/tests/bin/polykey.test.ts +++ b/tests/bin/polykey.test.ts @@ -8,3 +8,32 @@ describe('polykey', () => { expect(result.stderr.length > 0).toBe(true); }); }); + +// Describe('CLI echoes', () => { +// beforeEach(async () => { +// dataDir = await fs.promises.mkdtemp( +// path.join(os.tmpdir(), 'polykey-test-'), +// ); +// polykeyAgent = await PolykeyAgent.createPolykey({ +// nodePath: dataDir, +// logger: logger, +// }); +// await polykeyAgent.start({ +// password: 'password', +// }); +// }); + +// afterEach(async () => { +// await polykeyAgent.stop(); +// }); + +// test('should echo', async () => { +// const result = await pk(['echoes', 'echo', '-np', dataDir, 'HelloWorld']); +// expect(result).toBe(0); +// }); + +// test('should cause error', async () => { +// const result = await pk(['echoes', 'echo', '-np', dataDir, 'ThrowAnError']); +// expect(result).toBe(1); +// }); +// }); diff --git a/tests/client/clientService.test.ts b/tests/client/clientService.test.ts index 40471c9032..046c2e2d87 100644 --- a/tests/client/clientService.test.ts +++ b/tests/client/clientService.test.ts @@ -33,7 +33,7 @@ import { ErrorSessionTokenInvalid } from '@/errors'; import { checkAgentRunning } from '@/agent/utils'; import { NotificationData } from '@/notifications/types'; import { makeNodeId } from '@/nodes/utils'; -import { Vault, VaultName } from '@/vaults/types'; +import { Vault, VaultId, VaultName } from '@/vaults/types'; import { vaultOps } from '@/vaults'; import { makeVaultId, makeVaultIdPretty } from '@/vaults/utils'; import { utils as idUtils } from '@matrixai/id'; diff --git a/tests/nodes/NodeConnection.test.ts b/tests/nodes/NodeConnection.test.ts index ba4450206f..9d616b9f66 100644 --- a/tests/nodes/NodeConnection.test.ts +++ b/tests/nodes/NodeConnection.test.ts @@ -163,6 +163,14 @@ describe('NodeConnection', () => { fs: fs, logger: logger, }); + serverNotificationsManager = + await NotificationsManager.createNotificationsManager({ + acl: serverACL, + db: serverDb, + nodeManager: serverNodeManager, + keyManager: serverKeyManager, + logger: logger, + }); serverVaultManager = await VaultManager.createVaultManager({ keyManager: serverKeyManager, vaultsPath: serverVaultsPath, @@ -171,17 +179,10 @@ describe('NodeConnection', () => { db: serverDb, acl: serverACL, gestaltGraph: serverGestaltGraph, + notificationsManager: serverNotificationsManager, fs: fs, logger: logger, }); - serverNotificationsManager = - await NotificationsManager.createNotificationsManager({ - acl: serverACL, - db: serverDb, - nodeManager: serverNodeManager, - keyManager: serverKeyManager, - logger: logger, - }); await serverDb.start(); await serverGestaltGraph.setNode(node); await serverNodeManager.start(); @@ -191,6 +192,7 @@ describe('NodeConnection', () => { nodeManager: serverNodeManager, sigchain: serverSigchain, notificationsManager: serverNotificationsManager, + acl: serverACL, }); server = await GRPCServer.createGRPCServer({ logger: logger, diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index b3c0e1cdb9..0073cc2933 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -127,6 +127,15 @@ describe('NotificationsManager', () => { fs: fs, logger: logger, }); + receiverNotificationsManager = + await NotificationsManager.createNotificationsManager({ + acl: receiverACL, + db: receiverDb, + nodeManager: receiverNodeManager, + keyManager: receiverKeyManager, + messageCap: 5, + logger: logger, + }); receiverVaultManager = await VaultManager.createVaultManager({ keyManager: receiverKeyManager, vaultsPath: receiverVaultsPath, @@ -135,18 +144,10 @@ describe('NotificationsManager', () => { db: receiverDb, acl: receiverACL, gestaltGraph: receiverGestaltGraph, + notificationsManager: receiverNotificationsManager, fs: fs, logger: logger, }); - receiverNotificationsManager = - await NotificationsManager.createNotificationsManager({ - acl: receiverACL, - db: receiverDb, - nodeManager: receiverNodeManager, - keyManager: receiverKeyManager, - messageCap: 5, - logger: logger, - }); receiverKeyPairPem = receiverKeyManager.getRootKeyPairPem(); receiverCertPem = receiverKeyManager.getRootCertPem(); receiverNodeId = networkUtils.certNodeId(receiverKeyManager.getRootCert()); @@ -164,6 +165,7 @@ describe('NotificationsManager', () => { nodeManager: receiverNodeManager, sigchain: receiverSigchain, notificationsManager: receiverNotificationsManager, + acl: receiverACL, }); server = await GRPCServer.createGRPCServer({ logger: logger, diff --git a/tests/vaults/VaultInternal.test.ts b/tests/vaults/VaultInternal.test.ts index bbc74dde90..a29d8e70ec 100644 --- a/tests/vaults/VaultInternal.test.ts +++ b/tests/vaults/VaultInternal.test.ts @@ -41,7 +41,7 @@ describe('VaultInternal', () => { keyManager = await KeyManager.createKeyManager({ keysPath, password: 'password', - logger: logger, + logger, }); vault = await VaultInternal.create({ vaultId, diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index d50d04d56c..6ae8bbb032 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -43,21 +43,10 @@ describe('VaultManager', () => { let nodeManager: NodeManager; let vaultManager: VaultManager; let sigchain: Sigchain; - - // FIXME, try not to do this, they can all have the localhost, - // but use the generated port when the server is started. - const sourceHost = '127.0.0.1' as Host; - const sourcePort = 11112 as Port; - const targetHost = '127.0.0.2' as Host; - const targetPort = 11113 as Port; - const altHost = '127.0.0.3' as Host; - const altPort = 11114 as Port; - const altHostIn = '127.0.0.4' as Host; - const altPortIn = 11115 as Port; + let notificationsManager: NotificationsManager; let fwdProxy: ForwardProxy; let revProxy: ReverseProxy; - let altRevProxy: ReverseProxy; const vaultName = 'TestVault' as VaultName; const secondVaultName = 'SecondTestVault' as VaultName; @@ -71,9 +60,6 @@ describe('VaultManager', () => { revProxy = await ReverseProxy.createReverseProxy({ logger: logger, }); - altRevProxy = await ReverseProxy.createReverseProxy({ - logger: logger, - }); }); beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -89,15 +75,6 @@ describe('VaultManager', () => { logger: logger, }); - await fwdProxy.start({ - tlsConfig: { - keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, - certChainPem: await keyManager.getRootCertChainPem(), - }, - egressHost: sourceHost, - egressPort: sourcePort, - }); - db = await DB.createDB({ dbPath: dbPath, logger: logger, @@ -127,6 +104,16 @@ describe('VaultManager', () => { logger: logger, }); + notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl: acl, + db: db, + nodeManager: nodeManager, + keyManager: keyManager, + messageCap: 5, + logger: logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ db: db, acl: acl, @@ -141,6 +128,7 @@ describe('VaultManager', () => { db, acl: acl, gestaltGraph: gestaltGraph, + notificationsManager: notificationsManager, fs, logger: logger, fresh: true, @@ -153,15 +141,13 @@ describe('VaultManager', () => { await db.stop(); await nodeManager.stop(); await keyManager.destroy(); + await fwdProxy.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); }); - afterAll(async () => { - await fwdProxy.stop(); - }); test('is type correct', () => { expect(vaultManager).toBeInstanceOf(VaultManager); }); @@ -256,52 +242,49 @@ describe('VaultManager', () => { [firstVault.vaultId.toString(), secondVault.vaultId.toString()].sort(), ); }); - test( - 'able to read and load existing metadata', - async () => { - const vaultNames = [ - 'Vault1', - 'Vault2', - 'Vault3', - 'Vault4', - 'Vault5', - 'Vault6', - 'Vault7', - 'Vault8', - 'Vault9', - 'Vault10', - ]; - for (const vaultName of vaultNames) { - await vaultManager.createVault(vaultName as VaultName); - } - const vaults = await vaultManager.listVaults(); - const vaultId = vaults.get('Vault1' as VaultName) as VaultId; - expect(vaultId).not.toBeUndefined(); - const vault = await vaultManager.openVault(vaultId); - expect(vault).toBeTruthy(); - await vaultManager.destroy(); - await db.stop(); - await db.start(); - vaultManager = await VaultManager.createVaultManager({ - keyManager: keyManager, - vaultsPath, - vaultsKey, - nodeManager, - gestaltGraph, - acl, - db, - logger, - }); - const restartedVaultNames: Array = []; - const vaultList = await vaultManager.listVaults(); - vaultList.forEach((_, vaultName) => { - restartedVaultNames.push(vaultName); - }); - expect(restartedVaultNames.sort()).toEqual(vaultNames.sort()); - }, - global.defaultTimeout * 2, - ); - test.skip('cannot concurrently create the same vault', async () => { + test('able to read and load existing metadata', async () => { + const vaultNames = [ + 'Vault1', + 'Vault2', + 'Vault3', + 'Vault4', + 'Vault5', + 'Vault6', + 'Vault7', + 'Vault8', + 'Vault9', + 'Vault10', + ]; + for (const vaultName of vaultNames) { + await vaultManager.createVault(vaultName as VaultName); + } + const vaults = await vaultManager.listVaults(); + const vaultId = vaults.get('Vault1' as VaultName) as VaultId; + expect(vaultId).not.toBeUndefined(); + const vault = await vaultManager.openVault(vaultId); + expect(vault).toBeTruthy(); + await vaultManager.destroy(); + await db.stop(); + await db.start(); + vaultManager = await VaultManager.createVaultManager({ + keyManager: keyManager, + vaultsPath, + vaultsKey, + nodeManager, + gestaltGraph, + notificationsManager, + acl, + db, + logger, + }); + const restartedVaultNames: Array = []; + const vaultList = await vaultManager.listVaults(); + vaultList.forEach((_, vaultName) => { + restartedVaultNames.push(vaultName); + }); + expect(restartedVaultNames.sort()).toEqual(vaultNames.sort()); + }, global.defaultTimeout * 2); + test.skip('cannot concurrently create vaults with the same name', async () => { const vaults = Promise.all([ vaultManager.createVault(vaultName), vaultManager.createVault(vaultName), @@ -343,6 +326,7 @@ describe('VaultManager', () => { db, acl: acl, gestaltGraph: gestaltGraph, + notificationsManager, fs, logger, }); @@ -391,7 +375,7 @@ describe('VaultManager', () => { const v9 = await vaultManager.getVaultId('Vault9' as VaultName); expect(v9).toBeTruthy(); await vaultManager.renameVault(v9!, 'Vault10' as VaultName); - await vaultManager.createVault('ThirdImpact' as VaultName); + const beforeVault = await vaultManager.createVault('ThirdImpact' as VaultName); await vaultManager.createVault('Cake' as VaultName); const vn: Array = []; (await vaultManager.listVaults()).forEach((_, vaultName) => @@ -399,9 +383,6 @@ describe('VaultManager', () => { ); expect(vn.sort()).toEqual(alteredVaultNames.sort()); await vaultManager.destroy(); - await db.stop(); - - await db.start(); const vaultManagerReloaded = await VaultManager.createVaultManager({ keyManager: keyManager, vaultsPath, @@ -410,6 +391,7 @@ describe('VaultManager', () => { db, acl: acl, gestaltGraph: gestaltGraph, + notificationsManager, fs, logger, }); @@ -427,97 +409,22 @@ describe('VaultManager', () => { vnAltered.push(vaultName), ); expect(vnAltered.sort()).toEqual(alteredVaultNames.sort()); + const reloadedVault = await vaultManagerReloaded.openVault(beforeVault.vaultId); + await reloadedVault.commit(async (efs) => { + await efs.writeFile('reloaded', 'reload'); + }); + const file = await reloadedVault.access(async (efs) => { + return await efs.readFile('reloaded', { encoding: 'utf8' }); + }); + expect(file).toBe('reload'); await vaultManagerReloaded.destroy(); }, global.defaultTimeout * 2, ); - // Test('able to update the default node repo to pull from', async () => { - // await vaultManager.start({}); - // const vault1 = await vaultManager.createVault('MyTestVault'); - // const vault2 = await vaultManager.createVault('MyOtherTestVault'); - // const noNode = await vaultManager.getDefaultNode(vault1.vaultId); - // expect(noNode).toBeUndefined(); - // await vaultManager.setDefaultNode(vault1.vaultId, 'abc' as NodeId); - // const node = await vaultManager.getDefaultNode(vault1.vaultId); - // const noNode2 = await vaultManager.getDefaultNode(vault2.vaultId); - // expect(node).toBe('abc'); - // expect(noNode2).toBeUndefined(); - // await vaultManager.stop(); - // }); - // test('checking gestalt permissions for vaults', async () => { - // const node1: NodeInfo = { - // id: '123' as NodeId, - // chain: { nodes: {}, identities: {} } as ChainData, - // }; - // const node2: NodeInfo = { - // id: '345' as NodeId, - // chain: { nodes: {}, identities: {} } as ChainData, - // }; - // const node3: NodeInfo = { - // id: '678' as NodeId, - // chain: { nodes: {}, identities: {} } as ChainData, - // }; - // const node4: NodeInfo = { - // id: '890' as NodeId, - // chain: { nodes: {}, identities: {} } as ChainData, - // }; - // const id1: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: { - // nodes: {}, - // } as ChainData, - // }; - // const id2: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'def' as IdentityId, - // claims: { - // nodes: {}, - // } as ChainData, - // }; - - // await gestaltGraph.setNode(node1); - // await gestaltGraph.setNode(node2); - // await gestaltGraph.setNode(node3); - // await gestaltGraph.setNode(node4); - // await gestaltGraph.setIdentity(id1); - // await gestaltGraph.setIdentity(id2); - // await gestaltGraph.linkNodeAndNode(node1, node2); - // await gestaltGraph.linkNodeAndIdentity(node1, id1); - // await gestaltGraph.linkNodeAndIdentity(node4, id2); - - // await vaultManager.start({}); - // const vault = await vaultManager.createVault('Test'); - // await vaultManager.setVaultPermissions('123' as NodeId, vault.vaultId); - // let record = await vaultManager.getVaultPermissions(vault.vaultId); - // expect(record).not.toBeUndefined(); - // expect(record['123']['pull']).toBeNull(); - // expect(record['345']['pull']).toBeNull(); - // expect(record['678']).toBeUndefined(); - // expect(record['890']).toBeUndefined(); - - // await vaultManager.unsetVaultPermissions('345' as NodeId, vault.vaultId); - // record = await vaultManager.getVaultPermissions(vault.vaultId); - // expect(record).not.toBeUndefined(); - // expect(record['123']['pull']).toBeUndefined(); - // expect(record['345']['pull']).toBeUndefined(); - - // await gestaltGraph.unlinkNodeAndNode(node1.id, node2.id); - // await vaultManager.setVaultPermissions('345' as NodeId, vault.vaultId); - // record = await vaultManager.getVaultPermissions(vault.vaultId); - // expect(record).not.toBeUndefined(); - // expect(record['123']['pull']).toBeUndefined(); - // expect(record['345']['pull']).toBeNull(); - - // await vaultManager.stop(); - // }); - // /* TESTING TODO: - // * Changing the default node to pull from - // */ describe('interacting with another node to', () => { let targetDataDir: string, altDataDir: string; let targetKeyManager: KeyManager, altKeyManager: KeyManager; - let targetFwdProxy: ForwardProxy; + let targetFwdProxy: ForwardProxy, altFwdProxy: ForwardProxy; let targetDb: DB, altDb: DB; let targetACL: ACL, altACL: ACL; let targetGestaltGraph: GestaltGraph, altGestaltGraph: GestaltGraph; @@ -528,23 +435,39 @@ describe('VaultManager', () => { altNotificationsManager: NotificationsManager; let targetNodeId: NodeId, altNodeId: NodeId; - let revTLSConfig: TLSConfig, altRevTLSConfig: TLSConfig; + let revTLSConfig: TLSConfig, targetRevTLSConfig: TLSConfig, altRevTLSConfig: TLSConfig; - let targetAgentService: IAgentServer, altAgentService: IAgentServer; - let targetServer: GRPCServer, altServer: GRPCServer; + let agentService: IAgentServer, targetAgentService: IAgentServer, altAgentService: IAgentServer; + let agentServer: GRPCServer, targetServer: GRPCServer, altServer: GRPCServer; - let node: NodeInfo; + let targetRevProxy: ReverseProxy, altRevProxy: ReverseProxy; - let altFwdProxy: ForwardProxy; + let node: NodeInfo; beforeAll(async () => { altFwdProxy = await ForwardProxy.createForwardProxy({ authToken: 'abc', logger: logger, }); + targetFwdProxy = await ForwardProxy.createForwardProxy({ + authToken: 'def', + logger: logger, + }); + altRevProxy = await ReverseProxy.createReverseProxy({ + logger: logger, + }); + targetRevProxy = await ReverseProxy.createReverseProxy({ + logger: logger, + }); }); beforeEach(async () => { + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + } + }); node = { id: nodeManager.getNodeId(), chain: { nodes: {}, identities: {} } as ChainData, @@ -560,12 +483,18 @@ describe('VaultManager', () => { }); targetNodeId = targetKeyManager.getNodeId(); revTLSConfig = { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }; + targetRevTLSConfig = { keyPrivatePem: targetKeyManager.getRootKeyPairPem().privateKey, certChainPem: await targetKeyManager.getRootCertChainPem(), }; - targetFwdProxy = await ForwardProxy.createForwardProxy({ - authToken: '', - logger: logger, + await targetFwdProxy.start({ + tlsConfig: { + keyPrivatePem: targetKeyManager.getRootKeyPairPem().privateKey, + certChainPem: await targetKeyManager.getRootCertChainPem(), + } }); targetDb = await DB.createDB({ dbPath: path.join(targetDataDir, 'db'), @@ -583,7 +512,7 @@ describe('VaultManager', () => { sigchain: targetSigchain, keyManager: targetKeyManager, fwdProxy: targetFwdProxy, - revProxy: revProxy, + revProxy: targetRevProxy, fs: fs, logger: logger, }); @@ -616,6 +545,7 @@ describe('VaultManager', () => { db: targetDb, acl: targetACL, gestaltGraph: targetGestaltGraph, + notificationsManager: targetNotificationsManager, logger: logger, fresh: true, }); @@ -625,13 +555,13 @@ describe('VaultManager', () => { nodeManager: targetNodeManager, sigchain: targetSigchain, notificationsManager: targetNotificationsManager, + acl: targetACL, }); targetServer = await GRPCServer.createGRPCServer({ logger: logger, }); await targetServer.start({ services: [[AgentService, targetAgentService]], - host: targetHost, }); altDataDir = await fs.promises.mkdtemp( @@ -656,9 +586,7 @@ describe('VaultManager', () => { tlsConfig: { keyPrivatePem: altKeyManager.getRootKeyPairPem().privateKey, certChainPem: await altKeyManager.getRootCertChainPem(), - }, - egressHost: altHost, - egressPort: altPort, + } }); altDb = await DB.createDB({ dbPath: path.join(altDataDir, 'db'), @@ -708,6 +636,7 @@ describe('VaultManager', () => { nodeManager: altNodeManager, db: altDb, acl: altACL, + notificationsManager: altNotificationsManager, gestaltGraph: altGestaltGraph, logger: logger, }); @@ -717,46 +646,79 @@ describe('VaultManager', () => { nodeManager: altNodeManager, sigchain: altSigchain, notificationsManager: altNotificationsManager, + acl: altACL, }); altServer = await GRPCServer.createGRPCServer({ logger: logger, }); + await altServer.start({ services: [[AgentService, altAgentService]], - host: altHostIn, + }); + + agentService = createAgentService({ + keyManager: keyManager, + vaultManager: vaultManager, + nodeManager: nodeManager, + sigchain: sigchain, + notificationsManager: notificationsManager, + acl: acl, + }); + + agentServer = await GRPCServer.createGRPCServer({ + logger: logger, + }); + + await agentServer.start({ + services: [[AgentService, agentService]], }); await revProxy.start({ - ingressHost: targetHost, - ingressPort: targetPort, - serverHost: targetHost, - serverPort: targetServer.getPort(), + serverHost: agentServer.getHost(), + serverPort: agentServer.getPort(), tlsConfig: revTLSConfig, }); + await targetRevProxy.start({ + serverHost: targetServer.getHost(), + serverPort: targetServer.getPort(), + tlsConfig: targetRevTLSConfig, + }); + await altRevProxy.start({ - ingressHost: altHostIn, - ingressPort: altPortIn, - serverHost: altHostIn, + serverHost: altServer.getHost(), serverPort: altServer.getPort(), tlsConfig: altRevTLSConfig, }); + + await acl.setNodePerm(targetNodeId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + + await acl.setNodePerm(altNodeId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + + await altACL.setNodePerm(targetNodeId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + }, global.polykeyStartupTimeout * 2); afterEach(async () => { - await revProxy.closeConnection(altHost, altPort); - await revProxy.closeConnection(sourceHost, sourcePort); - await altRevProxy.closeConnection(sourceHost, sourcePort); - await fwdProxy.closeConnection( - fwdProxy.getEgressHost(), - fwdProxy.getEgressPort(), - ); - await altFwdProxy.closeConnection( - altFwdProxy.getEgressHost(), - altFwdProxy.getEgressPort(), - ); await revProxy.stop(); + await targetRevProxy.stop(); await altRevProxy.stop(); + await agentServer.stop(); await targetServer.stop(); await targetVaultManager.destroy(); await targetGestaltGraph.destroy(); @@ -777,74 +739,309 @@ describe('VaultManager', () => { await altDb.stop(); await altNodeManager.stop(); await altKeyManager.destroy(); + await targetFwdProxy.stop(); + await altFwdProxy.stop(); await fs.promises.rm(altDataDir, { force: true, recursive: true, }); }); - afterAll(async () => { - await altFwdProxy.stop(); - }); - test( - 'clone and pull vaults', + 'clone vaults using a vault name', async () => { - // Await vaultManager.createVault(vaultName); - // await vaultManager.createVault('MyFirstVault copy'); - const vault = await targetVaultManager.createVault(vaultName); - // Await targetVaultManager.setVaultPermissions( - // nodeManager.getNodeId(), - // vault.vaultId, - // ); + const firstVault = await targetVaultManager.createVault(vaultName); const names: string[] = []; - for (let i = 0; i < 1; i++) { + for (let i = 0; i < 5; i++) { const name = 'secret ' + i.toString(); names.push(name); const content = 'Success?'; - await vaultOps.addSecret(vault, name, content); + await vaultOps.addSecret(firstVault, name, content); } await nodeManager.setNode(targetNodeId, { - ip: targetHost, - port: targetPort, + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), } as NodeAddress); - await nodeManager.getConnectionToNode(targetNodeId); - await revProxy.openConnection(sourceHost, sourcePort); - await vaultManager.cloneVault(targetNodeId, vault.vaultId); + await targetVaultManager.shareVault( + firstVault.vaultId, + nodeManager.getNodeId(), + ); + await vaultManager.cloneVault(targetNodeId, vaultName); const vaultId = await vaultManager.getVaultId(vaultName); const vaultClone = await vaultManager.openVault(vaultId!); let file = await vaultClone.access(async (efs) => { return await efs.readFile('secret 0', { encoding: 'utf8' }); }); expect(file).toBe('Success?'); - // Expect(vaultsList[2].name).toStrictEqual('MyFirstVault copy copy'); - // await expect( - // vaultManager.getDefaultNode(vaultsList[2].id), - // ).resolves.toBe(targetNodeId); - // const clonedVault = await vaultManager.getVault(vaultsList[2].id); - // expect(await clonedVault.getSecret('secret 9')).toStrictEqual( - // 'Success?', - // ); - // expect((await clonedVault.listSecrets()).sort()).toStrictEqual( - // names.sort(), - // ); - for (let i = 1; i < 2; i++) { + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); + }, + global.defaultTimeout * 2, + ); + test( + 'clone and pull vaults using a vault id', + async () => { + const firstVault = await targetVaultManager.createVault(vaultName); + await nodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await targetVaultManager.shareVault( + firstVault.vaultId, + nodeManager.getNodeId(), + ); + const names: string[] = []; + for (let i = 0; i < 5; i++) { + const name = 'secret ' + i.toString(); + names.push(name); + const content = 'Success?'; + await vaultOps.addSecret(firstVault, name, content); + } + await vaultManager.cloneVault(targetNodeId, firstVault.vaultId); + const vaultId = await vaultManager.getVaultId(vaultName); + const vaultClone = await vaultManager.openVault(vaultId!); + let file = await vaultClone.access(async (efs) => { + return await efs.readFile('secret 0', { encoding: 'utf8' }); + }); + expect(file).toBe('Success?'); + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); + for (let i = 5; i < 10; i++) { const name = 'secret ' + i.toString(); names.push(name); const content = 'Second Success?'; - await vaultOps.addSecret(vault, name, content); + await vaultOps.addSecret(firstVault, name, content); } await vaultManager.pullVault({ vaultId: vaultClone.vaultId }); file = await vaultClone.access(async (efs) => { - return await efs.readFile('secret 1', { encoding: 'utf8' }); + return await efs.readFile('secret 5', { encoding: 'utf8' }); }); expect(file).toBe('Second Success?'); - // Expect((await clonedVault.listSecrets()).sort()).toStrictEqual( - // names.sort(), - // ); - // expect(await clonedVault.getSecret('secret 19')).toStrictEqual( - // 'Second Success?', - // ); + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); + }, + global.defaultTimeout * 2, + ); + // FIXME: Errors aren't being handled over GRPC + test.skip( + 'reject cloning and pulling when permissions are not set', + async () => { + const vault = await targetVaultManager.createVault(vaultName); + await nodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await vaultOps.addSecret(vault, 'MyFirstSecret', 'Success?'); + await expect(() => + vaultManager.cloneVault(targetNodeId, vault.vaultId), + ).rejects.toThrow(vaultErrors.ErrorVaultPermissionDenied); + await expect(vaultManager.listVaults()).resolves.toStrictEqual([]); + await targetVaultManager.shareVault( + vault.vaultId, + nodeManager.getNodeId() + ); + const clonedVault = await vaultManager.cloneVault(targetNodeId, vault.vaultId); + const file = await clonedVault.access(async (efs) => { + return await efs.readFile('MyFirstSecret', { encoding: 'utf8' }); + }) + expect(file).toBe('Success?'); + await targetVaultManager.unshareVault( + vault.vaultId, + nodeManager.getNodeId() + ); + vaultOps.addSecret(vault, 'MySecondSecret', 'SecondSuccess?'); + await expect(() => + vaultManager.pullVault({ vaultId: clonedVault.vaultId }), + ).rejects.toThrow(vaultErrors.ErrorVaultPermissionDenied); + await expect(vaultOps.listSecrets(clonedVault)).resolves.toStrictEqual( + ['MyFirstSecret'], + ); + }, + global.defaultTimeout * 2, + ); + test( + 'throw when encountering merge conflicts', + async () => { + const vault = await targetVaultManager.createVault(vaultName); + await nodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await targetVaultManager.shareVault( + vault.vaultId, + nodeManager.getNodeId() + ); + const names: string[] = []; + for (let i = 0; i < 5; i++) { + const name = 'secret ' + i.toString(); + names.push(name); + const content = 'Success?'; + await vaultOps.addSecret(vault, name, content); + } + await vaultOps.mkdir(vault, 'dir', { recursive: true }); + const cloneVault = await vaultManager.cloneVault(targetNodeId, vault.vaultId); + await vaultOps.renameSecret(cloneVault, 'secret 4', 'secret 5'); + await vaultOps.renameSecret(vault, 'secret 4', 'causing merge conflict'); + await expect(() => + vaultManager.pullVault({ vaultId: cloneVault.vaultId }), + ).rejects.toThrow(vaultErrors.ErrorVaultMergeConflict); + }, + global.defaultTimeout * 2, + ); + test( + 'clone and pull from other cloned vaults', + async () => { + const vault = await targetVaultManager.createVault(vaultName); + await nodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await nodeManager.setNode(altNodeId, { + ip: altRevProxy.getIngressHost(), + port: altRevProxy.getIngressPort(), + } as NodeAddress); + await altNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await altNodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(altNodeId, { + ip: altRevProxy.getIngressHost(), + port: altRevProxy.getIngressPort(), + } as NodeAddress); + await targetVaultManager.shareVault( + vault.vaultId, + altNodeManager.getNodeId(), + ); + await targetVaultManager.shareVault( + vault.vaultId, + nodeManager.getNodeId() + ); + const names: string[] = []; + for (let i = 0; i < 5; i++) { + const name = 'secret ' + i.toString(); + names.push(name); + const content = 'Success?'; + await vaultOps.addSecret(vault, name, content); + } + const clonedVaultAlt = await altVaultManager.cloneVault(targetNodeId, vault.vaultId); + await altVaultManager.shareVault( + clonedVaultAlt.vaultId, + nodeManager.getNodeId() + ); + await vaultManager.cloneVault(altNodeId, clonedVaultAlt.vaultId); + const vaultIdClone = await vaultManager.getVaultId(vaultName); + const vaultClone = await vaultManager.openVault(vaultIdClone!); + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); + for (let i = 5; i < 10; i++) { + const name = 'secret ' + i.toString(); + names.push(name); + const content = 'Success?'; + await vaultOps.addSecret(vault, name, content); + } + await vaultManager.pullVault({ vaultId: vaultClone.vaultId, pullNodeId: targetNodeId, pullVaultNameOrId: vault.vaultId }); + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); + }, + global.defaultTimeout * 2, + ); + test( + 'manage pulling from different remotes', + async () => { + const vault = await targetVaultManager.createVault(vaultName); + await nodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await nodeManager.setNode(altNodeId, { + ip: altRevProxy.getIngressHost(), + port: altRevProxy.getIngressPort(), + } as NodeAddress); + await altNodeManager.setNode(nodeManager.getNodeId(), { + ip: revProxy.getIngressHost(), + port: revProxy.getIngressPort(), + } as NodeAddress); + await altNodeManager.setNode(targetNodeId, { + ip: targetRevProxy.getIngressHost(), + port: targetRevProxy.getIngressPort(), + } as NodeAddress); + await targetNodeManager.setNode(altNodeId, { + ip: altRevProxy.getIngressHost(), + port: altRevProxy.getIngressPort(), + } as NodeAddress); + await targetVaultManager.shareVault( + vault.vaultId, + altNodeManager.getNodeId(), + ); + await targetVaultManager.shareVault( + vault.vaultId, + nodeManager.getNodeId() + ); + const names: string[] = []; + for (let i = 0; i < 2; i++) { + const name = 'secret ' + i.toString(); + names.push(name); + const content = 'Success?'; + await vaultOps.addSecret(vault, name, content); + } + const clonedVaultAlt = await altVaultManager.cloneVault(targetNodeId, vault.vaultId); + await altVaultManager.shareVault( + clonedVaultAlt.vaultId, + nodeManager.getNodeId() + ); + const vaultClone = await vaultManager.cloneVault(altNodeId, clonedVaultAlt.vaultId); + for (let i = 2; i < 4; i++) { + const name = 'secret ' + i.toString(); + names.push(name); + const content = 'Success?'; + await vaultOps.addSecret(vault, name, content); + } + await vaultManager.pullVault({ vaultId: vaultClone.vaultId, pullNodeId: targetNodeId, pullVaultNameOrId: vaultName }); + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); + for (let i = 4; i < 6; i++) { + const name = 'secret ' + i.toString(); + const content = 'Success?'; + await vaultOps.addSecret(clonedVaultAlt, name, content); + } + await vaultManager.pullVault({ vaultId: vaultClone.vaultId }); + expect((await vaultOps.listSecrets(vaultClone)).sort()).toStrictEqual( + names.sort(), + ); }, global.defaultTimeout * 2, ); diff --git a/tests/vaults/VaultOps.test.ts b/tests/vaults/VaultOps.test.ts index ba06f58c62..e98a45c609 100644 --- a/tests/vaults/VaultOps.test.ts +++ b/tests/vaults/VaultOps.test.ts @@ -15,9 +15,6 @@ import { NodeId } from '@/nodes/types'; describe('VaultOps', () => { const password = 'password'; const logger = new Logger('VaultOps', LogLevel.WARN, [new StreamHandler()]); - const probeLogger = new Logger('vaultOpsProbe', LogLevel.INFO, [ - new StreamHandler(), - ]); let dataDir: string; @@ -215,7 +212,6 @@ describe('VaultOps', () => { { recursive: true, }, - probeLogger, ); await expect( vault.access((efs) => efs.readdir('dir-1')),