diff --git a/src/secrets/CommandEnv.ts b/src/secrets/CommandEnv.ts index 03122222..3550aa23 100644 --- a/src/secrets/CommandEnv.ts +++ b/src/secrets/CommandEnv.ts @@ -24,255 +24,262 @@ class CommandEnv extends CommandPolykey { this.addOption(binOptions.envDuplicate); this.argument( '', - 'command and arguments formatted as [envPaths...][cmd][cmdArgs...]', + 'command and arguments formatted as [envPaths...][-- cmd [cmdArgs...]]', binParsers.parseEnvArgs, ); - this.action(async (args: Array, options) => { - const { default: PolykeyClient } = await import( - 'polykey/dist/PolykeyClient' - ); - const { - envInvalid, - envDuplicate, - envFormat, - }: { - envInvalid: 'error' | 'warn' | 'ignore'; - envDuplicate: 'keep' | 'overwrite' | 'warn' | 'error'; - envFormat: 'auto' | 'unix' | 'cmd' | 'powershell' | 'json'; - } = options; - // There are a few stages here - // 1. parse the desired secrets - // 2. obtain the desired secrets - // 3. switching behaviour here based on parameters - // a. exec the command with the provided env variables from the secrets - // b. output the env variables in the desired format + this.passThroughOptions(); // Let -- pass through as-is to parse as delimiter for cmd + this.action( + async ( + args: [Array<[string, string?, string?]>, Array], + options, + ) => { + args[1].shift(); + const { default: PolykeyClient } = await import( + 'polykey/dist/PolykeyClient' + ); + const { + envInvalid, + envDuplicate, + envFormat, + }: { + envInvalid: 'error' | 'warn' | 'ignore'; + envDuplicate: 'keep' | 'overwrite' | 'warn' | 'error'; + envFormat: 'auto' | 'unix' | 'cmd' | 'powershell' | 'json'; + } = options; + // There are a few stages here + // 1. parse the desired secrets + // 2. obtain the desired secrets + // 3. switching behaviour here based on parameters + // a. exec the command with the provided env variables from the secrets + // b. output the env variables in the desired format - const [envVariables, [cmd, ...argv]] = args; - const clientOptions = await binProcessors.processClientOptions( - options.nodePath, - options.nodeId, - options.clientHost, - options.clientPort, - this.fs, - this.logger.getChild(binProcessors.processClientOptions.name), - ); - const meta = await binProcessors.processAuthentication( - options.passwordFile, - this.fs, - ); + const [envVariables, [cmd, ...argv]] = args; + const clientOptions = await binProcessors.processClientOptions( + options.nodePath, + options.nodeId, + options.clientHost, + options.clientPort, + this.fs, + this.logger.getChild(binProcessors.processClientOptions.name), + ); + const meta = await binProcessors.processAuthentication( + options.passwordFile, + this.fs, + ); - let pkClient: PolykeyClient; - this.exitHandlers.handlers.push(async () => { - if (pkClient != null) await pkClient.stop(); - }); - try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodeId: clientOptions.nodeId, - host: clientOptions.clientHost, - port: clientOptions.clientPort, - options: { - nodePath: options.nodePath, - }, - logger: this.logger.getChild(PolykeyClient.name), + let pkClient: PolykeyClient; + this.exitHandlers.handlers.push(async () => { + if (pkClient != null) await pkClient.stop(); }); + try { + pkClient = await PolykeyClient.createPolykeyClient({ + nodeId: clientOptions.nodeId, + host: clientOptions.clientHost, + port: clientOptions.clientPort, + options: { + nodePath: options.nodePath, + }, + logger: this.logger.getChild(PolykeyClient.name), + }); - // Getting envs - const [envp] = await binUtils.retryAuthentication(async (auth) => { - const responseStream = - await pkClient.rpcClient.methods.vaultsSecretsEnv(); - // Writing desired secrets - const secretRenameMap = new Map(); - const writeP = (async () => { - const writer = responseStream.writable.getWriter(); - let first = true; - for (const envVariable of envVariables) { - const [nameOrId, secretName, secretNameNew] = envVariable; - secretRenameMap.set(secretName, secretNameNew); - await writer.write({ - nameOrId, - secretName, - metadata: first ? auth : undefined, - }); - first = false; - } - await writer.close(); - })(); + // Getting envs + const [envp] = await binUtils.retryAuthentication(async (auth) => { + const responseStream = + await pkClient.rpcClient.methods.vaultsSecretsEnv(); + // Writing desired secrets + const secretRenameMap = new Map(); + const writeP = (async () => { + const writer = responseStream.writable.getWriter(); + let first = true; + for (const envVariable of envVariables) { + const [nameOrId, secretName, secretNameNew] = envVariable; + secretRenameMap.set(secretName ?? '/', secretNameNew); + await writer.write({ + nameOrId: nameOrId, + secretName: secretName ?? '/', + metadata: first ? auth : undefined, + }); + first = false; + } + await writer.close(); + })(); - const envp: Record = {}; - const envpPath: Record< - string, - { - nameOrId: string; - secretName: string; - } - > = {}; - for await (const value of responseStream.readable) { - const { nameOrId, secretName, secretContent } = value; - let newName = secretRenameMap.get(secretName); - if (newName == null) { - const secretEnvName = path.basename(secretName); - // Validating name - if (!binUtils.validEnvRegex.test(secretEnvName)) { - switch (envInvalid) { + const envp: Record = {}; + const envpPath: Record< + string, + { + nameOrId: string; + secretName: string; + } + > = {}; + for await (const value of responseStream.readable) { + const { nameOrId, secretName, secretContent } = value; + let newName = secretRenameMap.get(secretName); + if (newName == null) { + const secretEnvName = path.basename(secretName); + // Validating name + if (!binUtils.validEnvRegex.test(secretEnvName)) { + switch (envInvalid) { + case 'error': + throw new binErrors.ErrorPolykeyCLIInvalidEnvName( + `The following env variable name (${secretEnvName}) is invalid`, + ); + case 'warn': + this.logger.warn( + `The following env variable name (${secretEnvName}) is invalid and was dropped`, + ); + // Fallthrough + case 'ignore': + continue; + default: + utils.never(); + } + } + newName = secretEnvName; + } + // Handling duplicate names + if (envp[newName] != null) { + switch (envDuplicate) { + // Continue without modifying case 'error': - throw new binErrors.ErrorPolykeyCLIInvalidEnvName( - `The following env variable name (${secretEnvName}) is invalid`, + throw new binErrors.ErrorPolykeyCLIDuplicateEnvName( + `The env variable (${newName}) is duplicate`, ); + // Fallthrough + case 'keep': + continue; + // Log a warning and overwrite case 'warn': this.logger.warn( - `The following env variable name (${secretEnvName}) is invalid and was dropped`, + `The env variable (${newName}) is duplicate, overwriting`, ); // Fallthrough - case 'ignore': - continue; + case 'overwrite': + break; default: utils.never(); } } - newName = secretEnvName; - } - // Handling duplicate names - if (envp[newName] != null) { - switch (envDuplicate) { - // Continue without modifying - case 'error': - throw new binErrors.ErrorPolykeyCLIDuplicateEnvName( - `The env variable (${newName}) is duplicate`, - ); - // Fallthrough - case 'keep': - continue; - // Log a warning and overwrite - case 'warn': - this.logger.warn( - `The env variable (${newName}) is duplicate, overwriting`, - ); - // Fallthrough - case 'overwrite': - break; - default: - utils.never(); - } + envp[newName] = secretContent; + envpPath[newName] = { + nameOrId, + secretName, + }; } - envp[newName] = secretContent; - envpPath[newName] = { - nameOrId, - secretName, - }; - } - await writeP; - return [envp, envpPath]; - }, meta); - // End connection early to avoid errors on server - await pkClient.stop(); + await writeP; + return [envp, envpPath]; + }, meta); + // End connection early to avoid errors on server + await pkClient.stop(); - // Here we want to switch between the different usages - const platform = os.platform(); - if (cmd != null) { - // If a cmd is| provided then we default to exec it - switch (platform) { - case 'linux': - // Fallthrough - case 'darwin': - { - const { exec } = await import('@matrixai/exec'); - exec.execvp(cmd, argv, envp); + // Here we want to switch between the different usages + const platform = os.platform(); + if (cmd != null) { + // If a cmd is| provided then we default to exec it + switch (platform) { + case 'linux': + // Fallthrough + case 'darwin': + { + const { exec } = await import('@matrixai/exec'); + exec.execvp(cmd, argv, envp); + } + break; + default: { + const { spawnSync } = await import('child_process'); + const result = spawnSync(cmd, argv, { + env: { + ...process.env, + ...envp, + }, + shell: false, + windowsHide: true, + stdio: 'inherit', + }); + process.exit(result.status ?? 255); } - break; - default: { - const { spawnSync } = await import('child_process'); - const result = spawnSync(cmd, argv, { - env: { - ...process.env, - ...envp, - }, - shell: false, - windowsHide: true, - stdio: 'inherit', - }); - process.exit(result.status ?? 255); } - } - } else { - // Otherwise we switch between output formats - // If set to `auto` then we need to infer the format - let format = envFormat; - if (envFormat === 'auto') { - format = - { - darwin: 'unix', - linux: 'unix', - win32: 'cmd', - }[platform] ?? 'unix'; - } - switch (format) { - case 'unix': - { - // Formatting as a .env file - let data = ''; - for (const [key, value] of Object.entries(envp)) { - data += `${key}='${value}'\n`; + } else { + // Otherwise we switch between output formats + // If set to `auto` then we need to infer the format + let format = envFormat; + if (envFormat === 'auto') { + format = + { + darwin: 'unix', + linux: 'unix', + win32: 'cmd', + }[platform] ?? 'unix'; + } + switch (format) { + case 'unix': + { + // Formatting as a .env file + let data = ''; + for (const [key, value] of Object.entries(envp)) { + data += `${key}='${value}'\n`; + } + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: 'raw', - data, - }), - ); - } - break; - case 'cmd': - { - // Formatting as a .bat file for windows cmd - let data = ''; - for (const [key, value] of Object.entries(envp)) { - data += `set "${key}=${value}"\n`; + break; + case 'cmd': + { + // Formatting as a .bat file for windows cmd + let data = ''; + for (const [key, value] of Object.entries(envp)) { + data += `set "${key}=${value}"\n`; + } + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: 'raw', - data, - }), - ); - } - break; - case 'powershell': - { - // Formatting as a .bat file for windows cmd - let data = ''; - for (const [key, value] of Object.entries(envp)) { - data += `\$env:${key} = '${value}'\n`; + break; + case 'powershell': + { + // Formatting as a .bat file for windows cmd + let data = ''; + for (const [key, value] of Object.entries(envp)) { + data += `\$env:${key} = '${value}'\n`; + } + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: 'raw', - data, - }), - ); - } - break; - case 'json': - { - const data = {}; - for (const [key, value] of Object.entries(envp)) { - data[key] = value; + break; + case 'json': + { + const data = {}; + for (const [key, value] of Object.entries(envp)) { + data[key] = value; + } + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: data, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: 'json', - data: data, - }), - ); - } - break; - default: - utils.never(); + break; + default: + utils.never(); + } } + } finally { + if (pkClient! != null) await pkClient.stop(); } - } finally { - if (pkClient! != null) await pkClient.stop(); - } - }); + }, + ); } } diff --git a/src/utils/options.ts b/src/utils/options.ts index 7ed0c1b7..69fca4ce 100644 --- a/src/utils/options.ts +++ b/src/utils/options.ts @@ -214,7 +214,13 @@ const envVariables = new commander.Option('-e --env ', 'specify envs') .argParser( (value: string, previous: Array<[string, string, string?]> | undefined) => { const acc = previous ?? []; - acc.push(binParsers.parseSecretPathEnv(value)); + const [vault, secret, val] = binParsers.parseSecretPathEnv(value); + if (secret == null) { + throw new commander.InvalidArgumentError( + 'You must provide at least one secret path', + ); + } + acc.push([vault, secret, val]); return acc; }, ); diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index b0d026fe..46c52982 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -8,7 +8,9 @@ import * as gestaltsUtils from 'polykey/dist/gestalts/utils'; import * as networkUtils from 'polykey/dist/network/utils'; import * as nodesUtils from 'polykey/dist/nodes/utils'; -const secretPathRegex = /^([\w-]+)(?::([^\0\\=]+))?$/; +const vaultNameRegex = /^(?!.*[:])[ -~\t\n]*$/s; +const secretPathRegex = /^(?!.*[=])[ -~\t\n]*$/s; +const vaultNameSecretPathRegex = /^([\w\-\.]+)(?::([^\0\\=]+))?$/; const secretPathValueRegex = /^([a-zA-Z_][\w]+)?$/; const environmentVariableRegex = /^([a-zA-Z_]+[a-zA-Z0-9_]*)?$/; @@ -80,15 +82,26 @@ function parseSecretPathOptional( lastEqualIndex === -1 ? undefined : secretPath.substring(lastEqualIndex + 1); - if (!secretPathRegex.test(splitSecretPath)) { + if (!vaultNameSecretPathRegex.test(splitSecretPath)) { throw new commander.InvalidArgumentError( `${secretPath} is not of the format [:][=]`, ); } - const [, vaultName, directoryPath] = splitSecretPath.match(secretPathRegex)!; + const [, vaultName, directoryPath] = splitSecretPath.match( + vaultNameSecretPathRegex, + )!; return [vaultName, directoryPath, value]; } +function parseVaultName(vaultName: string): string { + if (!vaultNameRegex.test(vaultName)) { + throw new commander.InvalidArgumentError( + `${vaultName} is not a valid vault name`, + ); + } + return vaultName; +} + function parseSecretPath(secretPath: string): [string, string, string?] { // E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned // If 'vault1', an error is thrown @@ -111,8 +124,37 @@ function parseSecretPathValue(secretPath: string): [string, string, string?] { return [vaultName, directoryPath, value]; } -function parseSecretPathEnv(secretPath: string): [string, string, string?] { - const [vaultName, directoryPath, value] = parseSecretPath(secretPath); +function parseSecretPathEnv(secretPath: string): [string, string?, string?] { + // The colon character `:` is prohibited in vaultName, so it's first occurence + // means that this is the delimiter between vaultName and secretPath. + const colonIndex = secretPath.indexOf(':'); + // If no colon exists, treat entire string as vault name + if (colonIndex === -1) { + return [parseVaultName(secretPath), undefined, undefined]; + } + // Calculate contents before the `=` separator + const vaultNamePart = secretPath.substring(0, colonIndex); + const secretPathPart = secretPath.substring(colonIndex + 1); + // Calculate contents after the `=` separator + const equalIndex = secretPathPart.indexOf('='); + const splitSecretPath = + equalIndex === -1 + ? secretPathPart + : secretPathPart.substring(0, equalIndex); + const valueData = + equalIndex === -1 ? undefined : secretPathPart.substring(equalIndex + 1); + if (splitSecretPath != null && !secretPathRegex.test(splitSecretPath)) { + throw new commander.InvalidArgumentError( + `${secretPath} is not of the format [:][=]`, + ); + } + const parsedVaultName = parseVaultName(vaultNamePart); + const parsedSecretPath = splitSecretPath.match(secretPathRegex)?.[0] ?? '/'; + const [vaultName, directoryPath, value] = [ + parsedVaultName, + parsedSecretPath, + valueData, + ]; if (value != null && !environmentVariableRegex.test(value)) { throw new commander.InvalidArgumentError( `${value} is not a valid environment variable name`, @@ -182,25 +224,24 @@ const parseSeedNodes: (data: string) => [SeedNodes, boolean] = /** * This parses the arguments used for the env command. It should be formatted as - * [--] [cmd] [cmdArgs...] - * The cmd part of the list is separated in two ways, either the user explicitly uses `--` or the first non-secret path separates it. + * [-- cmd [cmdArgs...]] + * The cmd part of the list is separated by using `--`. */ function parseEnvArgs( value: string, - prev: [Array<[string, string, string?]>, Array] | undefined, -): [Array<[string, string, string?]>, Array] { - const current: [Array<[string, string, string?]>, Array] = prev ?? [ + prev: [Array<[string, string?, string?]>, Array] | undefined, +): [Array<[string, string?, string?]>, Array] { + const current: [Array<[string, string?, string?]>, Array] = prev ?? [ [], [], ]; if (current[1].length === 0) { // Parse a secret path - try { + if (value !== '--') { current[0].push(parseSecretPathEnv(value)); - } catch (e) { - if (!(e instanceof commander.InvalidArgumentError)) throw e; - // If we get an invalid argument error then we switch over to parsing args verbatim + } else { current[1].push(value); + return current; } } else { // Otherwise we just have the cmd args @@ -215,6 +256,7 @@ function parseEnvArgs( } export { + vaultNameRegex, secretPathRegex, secretPathValueRegex, environmentVariableRegex, @@ -222,6 +264,7 @@ export { validateParserToArgListParser, parseCoreCount, parseSecretPathOptional, + parseVaultName, parseSecretPath, parseSecretPathValue, parseSecretPathEnv, diff --git a/src/vaults/CommandClone.ts b/src/vaults/CommandClone.ts index 23508dff..0dd7a059 100644 --- a/src/vaults/CommandClone.ts +++ b/src/vaults/CommandClone.ts @@ -11,7 +11,11 @@ class CommandClone extends CommandPolykey { super(...args); this.name('clone'); this.description('Clone a Vault from Another Node'); - this.argument('', 'Name or Id of the vault to be cloned'); + this.argument( + '', + 'Name of the vault to be cloned', + binParsers.parseVaultName, + ); this.argument( '', 'Id of the node to clone the vault from', diff --git a/src/vaults/CommandCreate.ts b/src/vaults/CommandCreate.ts index 4624955b..163b37ac 100644 --- a/src/vaults/CommandCreate.ts +++ b/src/vaults/CommandCreate.ts @@ -4,6 +4,7 @@ import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandCreate extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -11,7 +12,11 @@ class CommandCreate extends CommandPolykey { this.name('create'); this.aliases(['touch']); this.description('Create a new Vault'); - this.argument('', 'Name of the new vault to be created'); + this.argument( + '', + 'Name of the new vault to be created', + binParsers.parseVaultName, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/vaults/CommandDelete.ts b/src/vaults/CommandDelete.ts index ff3e905c..8c06b6c0 100644 --- a/src/vaults/CommandDelete.ts +++ b/src/vaults/CommandDelete.ts @@ -3,13 +3,18 @@ import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandDelete extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('delete'); this.description('Delete an Existing Vault'); - this.argument('', 'Name of the vault to be deleted'); + this.argument( + '', + 'Name of the vault to be deleted', + binParsers.parseVaultName, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/vaults/CommandLog.ts b/src/vaults/CommandLog.ts index bd4ab644..7e2e5b77 100644 --- a/src/vaults/CommandLog.ts +++ b/src/vaults/CommandLog.ts @@ -4,13 +4,18 @@ import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandLog extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('log'); this.description('Get the Version History of a Vault'); - this.argument('', 'Name of the vault to obtain the log from'); + this.argument( + '', + 'Name of the vault to obtain the log from', + binParsers.parseVaultName, + ); this.addOption(binOptions.commitId); this.addOption(binOptions.depth); this.addOption(binOptions.nodeId); diff --git a/src/vaults/CommandPermissions.ts b/src/vaults/CommandPermissions.ts index bd0437b8..d69add09 100644 --- a/src/vaults/CommandPermissions.ts +++ b/src/vaults/CommandPermissions.ts @@ -3,6 +3,7 @@ import * as binProcessors from '../utils/processors'; import * as binUtils from '../utils'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; +import * as binParsers from '../utils/parsers'; class CommandPermissions extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -10,7 +11,7 @@ class CommandPermissions extends CommandPolykey { this.name('permissions'); this.alias('perms'); this.description('Sets the permissions of a vault for Node Ids'); - this.argument('', 'Name or ID of the vault'); + this.argument('', 'Name of the vault to be pulled into'); + this.argument( + '', + 'Name of the vault to be pulled into', + binParsers.parseVaultName, + ); this.argument( '[targetNodeId]', '(Optional) target node to pull from', diff --git a/src/vaults/CommandRename.ts b/src/vaults/CommandRename.ts index 34f1f2b3..63af2e7d 100644 --- a/src/vaults/CommandRename.ts +++ b/src/vaults/CommandRename.ts @@ -3,14 +3,23 @@ import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandRename extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('rename'); this.description('Rename an Existing Vault'); - this.argument('', 'Name of the vault to be renamed'); - this.argument('', 'New name of the vault'); + this.argument( + '', + 'Name of the vault to be renamed', + binParsers.parseVaultName, + ); + this.argument( + '', + 'New name of the vault', + binParsers.parseVaultName, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/vaults/CommandShare.ts b/src/vaults/CommandShare.ts index b6e86ad3..a6572567 100644 --- a/src/vaults/CommandShare.ts +++ b/src/vaults/CommandShare.ts @@ -11,7 +11,11 @@ class CommandShare extends CommandPolykey { super(...args); this.name('share'); this.description('Set the Permissions of a Vault for a Node'); - this.argument('', 'Name of the vault to be shared'); + this.argument( + '', + 'Name of the vault to be shared', + binParsers.parseVaultName, + ); this.argument( '', 'Id of the node to share to', diff --git a/src/vaults/CommandUnshare.ts b/src/vaults/CommandUnshare.ts index e791f170..65ab2d5e 100644 --- a/src/vaults/CommandUnshare.ts +++ b/src/vaults/CommandUnshare.ts @@ -11,7 +11,11 @@ class CommandUnshare extends CommandPolykey { super(...args); this.name('unshare'); this.description('Unset the Permissions of a Vault for a Node'); - this.argument('', 'Name of the vault to be unshared'); + this.argument( + '', + 'Name of the vault to be unshared', + binParsers.parseVaultName, + ); this.argument( '', 'Id of the node to unshare with', diff --git a/src/vaults/CommandVersion.ts b/src/vaults/CommandVersion.ts index 327bfd18..8eb5cb7e 100644 --- a/src/vaults/CommandVersion.ts +++ b/src/vaults/CommandVersion.ts @@ -3,13 +3,18 @@ import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandVersion extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('version'); this.description('Set a Vault to a Particular Version in its History'); - this.argument('', 'Name of the vault to change the version of'); + this.argument( + '', + 'Name of the vault to change the version of', + binParsers.parseVaultName, + ); this.argument('', 'Id of the commit that will be changed to'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); diff --git a/tests/secrets/env.test.ts b/tests/secrets/env.test.ts index 103860c5..a42f8a8f 100644 --- a/tests/secrets/env.test.ts +++ b/tests/secrets/env.test.ts @@ -60,8 +60,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -89,9 +89,9 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET1`, `${vaultName}:SECRET2`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -122,8 +122,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:dir1`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -152,8 +152,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET=SECRET_NEW`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -183,8 +183,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:dir1=SECRET_NEW`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -217,10 +217,10 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET1`, `${vaultName}:SECRET2`, `${vaultName}:dir1`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -250,8 +250,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET1`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -286,11 +286,11 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET1`, `${vaultName}:SECRET2=SECRET1`, `${vaultName}:SECRET3=SECRET4`, `${vaultName}:dir1`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -325,7 +325,7 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - `${vaultName}:.`, + `${vaultName}`, ]; const result = await testUtils.pkExec([...command], { @@ -364,8 +364,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - `${vaultName}1:.`, - `${vaultName}2:.`, + `${vaultName}1`, + `${vaultName}2`, ]; const result = await testUtils.pkExec([...command], { @@ -404,8 +404,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'cmd', - `${vaultName}1:.`, - `${vaultName}2:.`, + `${vaultName}1`, + `${vaultName}2`, ]; const result = await testUtils.pkExec([...command], { @@ -444,8 +444,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'powershell', - `${vaultName}1:.`, - `${vaultName}2:.`, + `${vaultName}1`, + `${vaultName}2`, ]; const result = await testUtils.pkExec([...command], { @@ -475,7 +475,7 @@ describe('commandEnv', () => { dataDir, '--env-format', 'json', - `${vaultName}:.`, + `${vaultName}`, ]; const result = await testUtils.pkExec([...command], { @@ -560,7 +560,7 @@ describe('commandEnv', () => { 'unix', '-ei', 'error', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -584,7 +584,7 @@ describe('commandEnv', () => { 'unix', '-ei', 'warn', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -612,7 +612,7 @@ describe('commandEnv', () => { 'unix', '-ei', 'ignore', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -642,7 +642,7 @@ describe('commandEnv', () => { 'unix', '-ed', 'error', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -669,7 +669,7 @@ describe('commandEnv', () => { 'unix', '-ed', 'warn', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -698,7 +698,7 @@ describe('commandEnv', () => { 'unix', '-ed', 'keep', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -725,7 +725,7 @@ describe('commandEnv', () => { 'unix', '-ed', 'overwrite', - `${vaultName}:.`, + `${vaultName}`, ], { env: { PK_PASSWORD: password } }, ); @@ -750,8 +750,8 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', - '--', `${vaultName}:SECRET`, + '--', 'node', '-e', 'console.log(JSON.stringify(process.env))', @@ -771,12 +771,15 @@ describe('commandEnv', () => { ])( 'parse secrets env arguments', async (secretPathEnvArray, cmd, cmdArgsArray) => { - // If we don't use the optional `--` delimiter then we can't include `:` in vault names - fc.pre(!cmd.includes(':')); let output: - | [Array<[string, string, string?]>, Array] + | [Array<[string, string?, string?]>, Array] | undefined = undefined; - const args: Array = [...secretPathEnvArray, cmd, ...cmdArgsArray]; + const args: Array = [ + ...secretPathEnvArray, + '--', + cmd, + ...cmdArgsArray, + ]; for (const arg of args) { output = binParsers.parseEnvArgs(arg, output); } @@ -785,7 +788,7 @@ describe('commandEnv', () => { return binParsers.parseSecretPath(v); }); expect(parsedEnvs).toMatchObject(expectedSecretPathArray); - expect(parsedArgs).toMatchObject([cmd, ...cmdArgsArray]); + expect(parsedArgs).toMatchObject(['--', cmd, ...cmdArgsArray]); }, ); test('handles no arguments', async () => { @@ -796,7 +799,7 @@ describe('commandEnv', () => { }); expect(result1.exitCode).toBe(64); }); - test('Handles providing no secret paths', async () => { + test('handles providing no secret paths', async () => { command = [ 'secrets', 'env', @@ -804,6 +807,7 @@ describe('commandEnv', () => { dataDir, '--env-format', 'unix', + '--', 'someCommand', ]; @@ -812,4 +816,44 @@ describe('commandEnv', () => { }); expect(result1.exitCode).toBe(64); }); + test('should output all secrets without explicit secret path', async () => { + const vaultId1 = await polykeyAgent.vaultManager.createVault( + `${vaultName}1`, + ); + const vaultId2 = await polykeyAgent.vaultManager.createVault( + `${vaultName}2`, + ); + + await polykeyAgent.vaultManager.withVaults( + [vaultId1, vaultId2], + async (vault1, vault2) => { + await vaultOps.addSecret(vault1, 'SECRET1', 'this is the secret1'); + await vaultOps.addSecret(vault2, 'SECRET2', 'this is the secret2'); + await vaultOps.mkdir(vault1, 'dir1'); + await vaultOps.mkdir(vault2, 'dir1'); + await vaultOps.addSecret(vault1, 'dir1/SECRET3', 'this is the secret3'); + await vaultOps.addSecret(vault2, 'dir1/SECRET4', 'this is the secret4'); + }, + ); + + command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + `${vaultName}1`, + `${vaultName}2`, + ]; + + const result = await testUtils.pkExec([...command], { + env: { PK_PASSWORD: password }, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("SECRET1='this is the secret1'"); + expect(result.stdout).toContain("SECRET2='this is the secret2'"); + expect(result.stdout).toContain("SECRET3='this is the secret3'"); + expect(result.stdout).toContain("SECRET4='this is the secret4'"); + }); });