Skip to content

Commit

Permalink
feat: progress on CLI vault MVP
Browse files Browse the repository at this point in the history
  • Loading branch information
dallen4 committed Aug 27, 2024
1 parent 8043793 commit 12e3ce8
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 98 deletions.
4 changes: 2 additions & 2 deletions cli/actions/secret/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export async function secretAdd(name: string, value: string) {

const { vaults, active_vault } = config;

const { createSecret } = createSecretsHelpers(vaults[active_vault]);
const { addSecrets } = createSecretsHelpers(vaults[active_vault]);

await createSecret(name, value);
await addSecrets([{ name, value }]);

logInfo('secret added successfully!');
}
20 changes: 14 additions & 6 deletions cli/actions/vault/export.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { createSecretsHelpers } from 'db/secrets';
import { loadConfig } from 'lib/config';
import { syncEnv } from 'lib/env';
import { logInfo } from 'lib/log';
import { resolve } from 'path';
import { cwd } from 'process';

export async function vaultExport(vaultNameInput: string) {
// TODO format support (.env, JSON files)
export async function vaultExport(
vaultNameInput: string,
envDestinationPath: string,
) {
const { config } = await loadConfig();

const { vaults, active_vault } = config;
Expand All @@ -16,13 +23,14 @@ export async function vaultExport(vaultNameInput: string) {

const secrets = await getAllSecrets();
const secretsMap = secrets.reduce(
(prev, { name, value }) => ({
...prev,
[name]: value,
}),
{} as Record<string, string>,
(prev, { name, value }) => (prev += `${name}="${value}"\n`),
``,
);

logInfo(`Secrets retrieved for '${vaultNameInput}' vault!`);
console.log(secretsMap);

const fullEnvPath = resolve(cwd(), envDestinationPath);

console.log(fullEnvPath);
}
26 changes: 26 additions & 0 deletions cli/actions/vault/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { vaultExists } from 'db/vaults';
import { loadConfig } from 'lib/config';
import { addEnvToVault } from 'lib/env';
import { logError, logInfo } from 'lib/log';
import { exit } from 'process';

export async function vaultImport(envPath: string) {
const { config } = await loadConfig();

const { vaults, active_vault } = config;

const { key } = vaults[active_vault];

const location = vaultExists(vaults, active_vault);

if (!location) {
logError('Default vault could not be found!');
return exit(1);
}

const newSecrets = await addEnvToVault(envPath, { key, location });

logInfo(
`${newSecrets.rowsAffected} secrets added to vault from '${envPath}'!`,
);
}
6 changes: 6 additions & 0 deletions cli/actions/vault/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './create';
export * from './delete';
export * from './export';
export * from './import';
export * from './sync';
export * from './use';
36 changes: 36 additions & 0 deletions cli/actions/vault/sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createSecretsHelpers } from 'db/secrets';
import { loadConfig } from 'lib/config';
import { syncEnv } from 'lib/env';
import { logInfo } from 'lib/log';
import { resolve } from 'path';
import { cwd } from 'process';

export async function vaultSync(
vaultNameInput: string,
envDestinationPath: string,
) {
const { config } = await loadConfig();

const { vaults, active_vault } = config;

const { location, key } = vaults[active_vault];

const { getAllSecrets } = createSecretsHelpers({
location,
key,
});

const secrets = await getAllSecrets();
const secretsMap = secrets.reduce(
(prev, { name, value }) => ({
...prev,
[name]: value,
}),
{},
);

logInfo(`Secrets synced to ./.env for '${active_vault}' vault!`);
console.log(secretsMap);

await syncEnv('./.env', secretsMap);
}
5 changes: 3 additions & 2 deletions cli/actions/vault/use.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { vaultExists } from 'db/vaults';
import { existsSync } from 'fs';
import { loadConfig, saveConfig } from 'lib/config';
import { logError, logInfo } from 'lib/log';
import { cwd, exit } from 'process';
import { DeadropConfig } from 'types/config';

export async function vaultUse(vaultNameInput: string) {
const { config } = await loadConfig();
Expand All @@ -24,7 +24,8 @@ export async function vaultUse(vaultNameInput: string) {
return exit(0);
}

const updatedConfig = {
const updatedConfig: DeadropConfig = {
...config,
active_vault: vaultNameInput,
vaults,
};
Expand Down
102 changes: 102 additions & 0 deletions cli/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Command } from 'commander';
import { description, version } from '../package.json';
import init from 'actions/init';
import { drop } from 'actions/drop';
import { grab } from 'actions/grab';
import { secretAdd } from 'actions/secret/add';
import { secretRemove } from 'actions/secret/remove';
import {
vaultCreate,
vaultDelete,
vaultExport,
vaultImport,
vaultSync,
vaultUse,
} from 'actions/vault';

const deadrop = new Command();

deadrop.name('deadrop').description(description).version(version);

deadrop.command('init').action(init);

deadrop
.command('drop')
.description('drop a secret from a vault or in raw format')
.argument('[input]', 'secret to drop')
.option('-i, --input [input]', 'secret to drop')
.option('-f, --file', 'secret to drop is a file')
.action(drop);

deadrop
.command('grab')
.description('grab a secret with a drop ID')
.argument('<id>', 'drop session ID')
.action(grab);

// vault commands

const vaultRoot = deadrop
.command('vault')
.description('manage your vaults');

vaultRoot
.command('create')
.description(
'create a new vault, optionally specify its parent folder',
)
.argument('<name>', 'name of the vault')
.argument('[location]', 'folder location of the vault')
.action(vaultCreate);

vaultRoot
.command('use')
.description('change the current active vault deadrop is using')
.argument('<name>', 'name of the vault to switch to as active')
.action(vaultUse);

vaultRoot
.command('sync')
.description('sync the current active vault with .env file')
.action(vaultSync);

vaultRoot
.command('export')
.description('export all the secrets of the specified vault')
.argument('<name>', 'name of the vault to export')
.action(vaultExport);

vaultRoot
.command('import')
.description(
'import all the secrets of a given .env file to active vault',
)
.argument('<path>', 'path to the .env file')
.action(vaultImport);

vaultRoot
.command('delete')
.description(
`delete the specified vault's database and remove it from config`,
)
.argument('<name>', 'name of the vault to delete')
.action(vaultDelete);

// secrets commands

const secretRoot = deadrop
.command('secret')
.description('manage your secrets in active vault');

secretRoot
.command('add')
.argument('[name]', 'name of the secret')
.argument('[value]', 'value of the secret')
.action(secretAdd);

secretRoot
.command('remove')
.argument('[name]', 'name of the secret to remove')
.action(secretRemove);

export { deadrop };
90 changes: 2 additions & 88 deletions cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,11 @@
#!/usr/bin/env node
import { drop } from 'actions/drop';
import { grab } from 'actions/grab';
import init from 'actions/init';
import { vaultCreate } from 'actions/vault/create';
import { vaultDelete } from 'actions/vault/delete';
import { vaultExport } from 'actions/vault/export';
import { vaultUse } from 'actions/vault/use';
import { Command } from 'commander';
import 'dotenv/config';
import { deadrop } from 'core';
import { checkNodeVersion } from 'lib/util';
import { description, version } from './package.json';
import { secretAdd } from 'actions/secret/add';

checkNodeVersion();

const program = new Command();

program.name('deadrop').description(description).version(version);

program.command('init').action(init);

program
.command('drop')
.description('drop a secret from a vault or in raw format')
.argument('[input]', 'secret to drop')
.option('-i, --input [input]', 'secret to drop')
.option('-f, --file', 'secret to drop is a file')
.action(drop);

program
.command('grab')
.description('grab a secret with a drop ID')
.argument('<id>', 'drop session ID')
.action(grab);

// vault commands

const vaultRoot = program
.command('vault')
.description('manage your vaults');

vaultRoot
.command('create')
.description(
'create a new vault, optionally specify its parent folder',
)
.argument('<name>', 'name of the vault')
.argument('[location]', 'folder location of the vault')
.action(vaultCreate);

vaultRoot
.command('use')
.description('change the current active vault deadrop is using')
.argument('<name>', 'name of the vault to switch to as active')
.action(vaultUse);

vaultRoot
.command('export')
.description('export all the secrets of the specified vault')
.argument('<name>', 'name of the vault to export')
.action(vaultExport);

vaultRoot
.command('delete')
.description(
`delete the specified vault's database and remove it from config`,
)
.argument('<name>', 'name of the vault to delete')
.action(vaultDelete);

// secrets commands

const secretRoot = program
.command('secret')
.description('manage your secrets (in current vault');

secretRoot
.command('add')
.argument('[name]', 'name of the secret')
.argument('[value]', 'value of the secret')
.action(secretAdd);

secretRoot
.command('drop')
.argument('[name]', 'name of the secret to drop')
.action(vaultCreate);

secretRoot
.command('remove')
.argument('[name]', 'name of the secret to remove')
.action(vaultCreate);

program.parse();
deadrop.parse();

const exitSignals: NodeJS.Signals[] = [
'SIGINT',
Expand Down
54 changes: 54 additions & 0 deletions cli/lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { initDB } from 'db/init';
import { secretsTable } from 'db/schema';
import { stringify, parse } from 'envfile';
import { appendFile, readFile, writeFile } from 'fs/promises';
import { resolve } from 'path';
import { cwd } from 'process';
import { VaultDBConfig } from 'types/config';

type Env = Record<string, string>;

const encoding: BufferEncoding = 'utf-8';

export async function syncEnv(
filePath: string,
envVars: Env,
append = false,
) {
const fullPath = resolve(cwd(), filePath);

const envAsString = stringify(envVars);

const envContent = `# generated by deadrop\n\n${envAsString}\n`;

if (append) await appendFile(fullPath, `\n${envContent}`, encoding);
else await writeFile(fullPath, envContent, encoding);
}

export async function loadEnvFromFile(filePath: string) {
const fullPath = resolve(cwd(), filePath);
console.log(fullPath);
const envContent = await readFile(fullPath, encoding);

const parsedEnv = parse(envContent);

return parsedEnv;
}

export async function addEnvToVault(
envPath: string,
vault: VaultDBConfig,
) {
const envVars = await loadEnvFromFile(envPath);

const db = initDB(vault.location, vault.key);

const secretsToAdd = Object.entries(envVars).map(
([key, value]) => ({
name: key,
value,
}),
);
console.log(secretsToAdd);
return db.insert(secretsTable).values(secretsToAdd).run();
}
Loading

0 comments on commit 12e3ce8

Please sign in to comment.