@govuk-pay/cli 0.0.52 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +2 -1
  2. package/readme.md +4 -3
  3. package/src/commands/secrets/config/config.types.js +50 -0
  4. package/src/commands/secrets/config/secrets/bitwarden_cli.js +4 -0
  5. package/src/commands/secrets/config/secrets/pay_low_pass/deploy-7.js +9 -0
  6. package/src/commands/secrets/config/secrets/pay_low_pass/deploy-tooling.js +18 -0
  7. package/src/commands/secrets/config/secrets/pay_low_pass/deploy.js +64 -0
  8. package/src/commands/secrets/config/secrets/pay_low_pass/dev.js +13 -0
  9. package/src/commands/secrets/config/secrets/pay_low_pass/production-2.js +104 -0
  10. package/src/commands/secrets/config/secrets/pay_low_pass/production.js +8 -0
  11. package/src/commands/secrets/config/secrets/pay_low_pass/staging-2.js +98 -0
  12. package/src/commands/secrets/config/secrets/pay_low_pass/staging.js +8 -0
  13. package/src/commands/secrets/config/secrets/pay_low_pass/test-12.js +101 -0
  14. package/src/commands/secrets/config/secrets/pay_low_pass/test-perf-1.js +98 -0
  15. package/src/commands/secrets/config/secrets/pay_low_pass/test.js +13 -0
  16. package/src/commands/secrets/config/secrets/pay_low_pass.js +27 -0
  17. package/src/commands/secrets/config/secrets/ssm.js +4 -0
  18. package/src/commands/secrets/config/secrets/value/deploy-tooling.js +10 -0
  19. package/src/commands/secrets/config/secrets/value/deploy.js +20 -0
  20. package/src/commands/secrets/config/secrets/value/production-2.js +45 -0
  21. package/src/commands/secrets/config/secrets/value/staging-2.js +47 -0
  22. package/src/commands/secrets/config/secrets/value/test-12.js +47 -0
  23. package/src/commands/secrets/config/secrets/value/test-perf-1.js +49 -0
  24. package/src/commands/secrets/config/secrets/value.js +17 -0
  25. package/src/commands/secrets/config/secrets.js +86 -0
  26. package/src/commands/secrets/config/service_secrets.js +238 -0
  27. package/src/commands/secrets/providers/bitwarden_cli.js +98 -0
  28. package/src/commands/secrets/providers/factory.js +42 -0
  29. package/src/commands/secrets/providers/pass_repo.js +65 -0
  30. package/src/commands/secrets/providers/providers.types.js +21 -0
  31. package/src/commands/secrets/providers/ssm.js +155 -0
  32. package/src/commands/secrets/providers/value.js +10 -0
  33. package/src/commands/secrets/subcommands/audit.js +41 -9
  34. package/src/commands/secrets/subcommands/fetch.js +36 -15
  35. package/src/commands/secrets/subcommands/provision.js +99 -7
  36. package/src/commands/secrets.js +1 -1
  37. package/src/core/standardContent.js +5 -1
  38. package/src/util/configs.js +7 -1
  39. package/src/commands/secrets/subcommands/copy.js +0 -35
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BitwardenCLIProvider = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const providers_types_1 = require("./providers.types");
6
+ class BitwardenCLIProvider extends providers_types_1.AbstractSecretProvider {
7
+ bitwardenUnlockToken;
8
+ constructor(env, secretSource) {
9
+ super(env, secretSource);
10
+ this.bitwardenUnlockToken = checkBitwardenStatus();
11
+ }
12
+ async get(secretConfig) {
13
+ const env = {
14
+ ...process.env
15
+ };
16
+ if (this.bitwardenUnlockToken !== undefined) {
17
+ env.BW_SESSION = this.bitwardenUnlockToken;
18
+ }
19
+ const bwGetPasswordResult = (0, child_process_1.spawnSync)('bw', ['get', 'password', secretConfig.secretSourceValue], {
20
+ env
21
+ });
22
+ if (bwGetPasswordResult.status !== 0) {
23
+ if (bwGetPasswordResult.stderr.toString().includes('Not found.')) {
24
+ console.error(`Secret ${secretConfig.secretSourceValue} not found in bitwarden. This can either be because it doesn't exist, or you don't have access`);
25
+ return undefined;
26
+ }
27
+ console.error('Unknown error when trying to get the password from bitwarden, output from the get follows:');
28
+ console.error(bwGetPasswordResult.output.toString());
29
+ process.exit(1);
30
+ }
31
+ return bwGetPasswordResult.stdout.toString();
32
+ }
33
+ }
34
+ exports.BitwardenCLIProvider = BitwardenCLIProvider;
35
+ function checkBitwardenStatus() {
36
+ const bwAvailableResult = (0, child_process_1.spawnSync)('command', ['-v', 'bw'], { shell: true });
37
+ if (bwAvailableResult.status !== 0) {
38
+ console.error('You need to install the bitwarden CLI before you can source secrets from it: `brew install bitwarden-cli`');
39
+ process.exit(1);
40
+ }
41
+ const bwStatusResult = (0, child_process_1.spawnSync)('bw', ['status']);
42
+ if (bwStatusResult.status !== 0) {
43
+ console.error('Could not check the login status of the bitwarden cli with command `bw status`. Output from the command follows:');
44
+ console.error(bwStatusResult.output.toString());
45
+ process.exit(1);
46
+ }
47
+ let bwStatusJson;
48
+ try {
49
+ bwStatusJson = JSON.parse(bwStatusResult.stdout.toString());
50
+ }
51
+ catch (e) {
52
+ console.error('An error occured when trying to parse the JSON output of `bw status`. The output of that command follows:');
53
+ console.error(bwStatusResult.output.toString());
54
+ process.exit(1);
55
+ }
56
+ if (!('status' in bwStatusJson)) {
57
+ console.error('The JSON output of `bw status` did not contain a \'status\' key. The output of that command follows:');
58
+ console.error(bwStatusResult.output.toString());
59
+ process.exit(1);
60
+ }
61
+ if (bwStatusJson.status === 'unauthenticated') {
62
+ console.error('Error: You are not authenticated in the bitwarden cli, you need to run `bw login` and login');
63
+ process.exit(1);
64
+ }
65
+ else if (bwStatusJson.status === 'locked') {
66
+ console.warn('Your bitwarden cli is logged in, but locked. Executing `bw unlock`, please enter your master password when prompted');
67
+ return unlockBitWarden();
68
+ }
69
+ else if (bwStatusJson.status === 'unlocked') {
70
+ return undefined;
71
+ }
72
+ }
73
+ function unlockBitWarden() {
74
+ const bwUnlockResult = (0, child_process_1.spawnSync)('bw', ['unlock'], {
75
+ shell: true,
76
+ stdio: ['inherit', 'pipe', 'inherit']
77
+ });
78
+ if (bwUnlockResult.status !== 0) {
79
+ console.error('Failed to unlock your bitwarden vault.');
80
+ process.exit(1);
81
+ }
82
+ const lineWithToken = bwUnlockResult.stdout.toString().split('\n').find((line) => {
83
+ return line.startsWith('$ export BW_SESSION=');
84
+ });
85
+ if (lineWithToken === undefined) {
86
+ console.error('Could not find the BW_SESSION token in the `bw unlock` output. Looking for a line that starts with `$ export BW_SESSION=`. Got:');
87
+ console.error(bwUnlockResult.output.toString());
88
+ process.exit(1);
89
+ }
90
+ const sessionToken = lineWithToken.substring(lineWithToken.indexOf('"') + 1, lineWithToken.lastIndexOf('"'));
91
+ if (sessionToken.length === 0 || sessionToken.startsWith('$ export BW_SESSION=') || sessionToken.includes('"')) {
92
+ console.error('The session token we read from `bw unlock` does not look correct. Output from the command follows');
93
+ console.error(bwUnlockResult.output.toString());
94
+ process.exit(1);
95
+ }
96
+ console.warn('Bitwarden unlocked');
97
+ return sessionToken;
98
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.providerFor = void 0;
4
+ const bitwarden_cli_1 = require("./bitwarden_cli");
5
+ const pass_repo_1 = require("./pass_repo");
6
+ const ssm_1 = require("./ssm");
7
+ const value_1 = require("./value");
8
+ const providers = {
9
+ 'bitwarden-cli': {},
10
+ 'pay-low-pass': {},
11
+ ssm: {},
12
+ value: {}
13
+ };
14
+ function providerFor(secretConfig) {
15
+ let memoisedProvider = providers[secretConfig.source][secretConfig.environment];
16
+ if (memoisedProvider === undefined) {
17
+ switch (secretConfig.source) {
18
+ case 'pay-low-pass': {
19
+ memoisedProvider = new pass_repo_1.PassRepoProvider(secretConfig.environment, secretConfig.source);
20
+ break;
21
+ }
22
+ case 'value': {
23
+ memoisedProvider = new value_1.ValueProvider(secretConfig.environment, secretConfig.source);
24
+ break;
25
+ }
26
+ case 'ssm': {
27
+ memoisedProvider = new ssm_1.SSMProvider(secretConfig.environment, secretConfig.source);
28
+ break;
29
+ }
30
+ case 'bitwarden-cli': {
31
+ memoisedProvider = new bitwarden_cli_1.BitwardenCLIProvider(secretConfig.environment, secretConfig.source);
32
+ break;
33
+ }
34
+ }
35
+ providers[secretConfig.source][secretConfig.environment] = memoisedProvider;
36
+ }
37
+ if (memoisedProvider === undefined) {
38
+ throw new Error(`Failed to retrieve, or create a provider for the secret ${secretConfig.secretSourceValue} in ${secretConfig.environment} from the ${secretConfig.source} provider`);
39
+ }
40
+ return memoisedProvider;
41
+ }
42
+ exports.providerFor = providerFor;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PassRepoProvider = void 0;
7
+ const node_child_process_1 = __importDefault(require("node:child_process"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const providers_types_1 = require("./providers.types");
11
+ const configs_1 = require("../../../util/configs");
12
+ class PassRepoProvider extends providers_types_1.AbstractSecretProvider {
13
+ passRepoDirectory;
14
+ constructor(environment, secretSource) {
15
+ super(environment, secretSource);
16
+ this.passRepoDirectory = this.getPassDirectory();
17
+ }
18
+ async get(secretConfig) {
19
+ const passCommandResult = node_child_process_1.default.spawnSync('pass', [secretConfig.secretSourceValue], {
20
+ env: {
21
+ ...process.env,
22
+ PASSWORD_STORE_DIR: this.passRepoDirectory
23
+ },
24
+ // This sets stdin, to be inherited by the spawned process, this is neccessary so that
25
+ // the pass command can prompt the user for a PIN.
26
+ // However we need to capture the stdout and stderr (which pass writes the secret to) so we set
27
+ // that to be pipe which will store it in the results .output Buffer[], .stdout Buffer, and .stderr Buffer
28
+ stdio: [
29
+ 'inherit', // stdin
30
+ 'pipe', // stdout
31
+ 'pipe' // stderr
32
+ ]
33
+ });
34
+ if (passCommandResult.status === null) {
35
+ if (passCommandResult.signal === null) {
36
+ console.error(`The pass command to load the secret ${secretConfig.secretSourceValue} failed due to an unknown reason. It's output follows:`);
37
+ }
38
+ else {
39
+ console.error(`The pass command to load the secret ${secretConfig.secretSourceValue} was terminated by signal ${passCommandResult.signal}. It's output follows`);
40
+ }
41
+ console.error(passCommandResult.output.map((buffer) => { return buffer === null ? '' : buffer.toString('utf-8'); }).join('\n'));
42
+ process.exit(1);
43
+ }
44
+ if (passCommandResult.status !== 0) {
45
+ const stdErrString = passCommandResult.output.map((buffer) => { return buffer === null ? '' : buffer.toString('utf-8'); }).join('\n');
46
+ if (stdErrString.includes('is not in the password store')) {
47
+ return undefined;
48
+ }
49
+ console.error(`The pass command to load the secret ${secretConfig.secretSourceValue} failed with exit code ${passCommandResult.status}. It's output follows:`);
50
+ console.error(stdErrString);
51
+ process.exit(1);
52
+ }
53
+ return passCommandResult.stdout.toString('utf-8').trim();
54
+ }
55
+ getPassDirectory() {
56
+ const worksspaceDir = (0, configs_1.workspaceEnvVar)();
57
+ const passRepoDirectory = node_path_1.default.resolve(node_path_1.default.join(worksspaceDir, this.secretSource));
58
+ const pathStat = node_fs_1.default.lstatSync(passRepoDirectory);
59
+ if (!pathStat.isDirectory()) {
60
+ throw new Error(`The pass repo directory specified ${passRepoDirectory} is not a directory`);
61
+ }
62
+ return passRepoDirectory;
63
+ }
64
+ }
65
+ exports.PassRepoProvider = PassRepoProvider;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbstractSecretProvider = void 0;
4
+ class AbstractSecretProvider {
5
+ environment;
6
+ secretSource;
7
+ constructor(environment, secretSource) {
8
+ this.environment = environment;
9
+ this.secretSource = secretSource;
10
+ }
11
+ async get(_secretConfig) {
12
+ throw new Error('Not implemented');
13
+ }
14
+ async set(_secretConfig, _secretValue) {
15
+ throw new Error('Not implemented');
16
+ }
17
+ async list(_serviceName) {
18
+ throw new Error('Not implemented');
19
+ }
20
+ }
21
+ exports.AbstractSecretProvider = AbstractSecretProvider;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SSMProvider = void 0;
4
+ const client_ssm_1 = require("@aws-sdk/client-ssm");
5
+ const providers_types_1 = require("./providers.types");
6
+ const service_secrets_1 = require("../config/service_secrets");
7
+ const CONCOURSE_SECRETS = [
8
+ 'cd-pay-dev',
9
+ 'cd-pay-deploy',
10
+ 'cd-main'
11
+ ];
12
+ const CONCOURSE_PARAMETER_NAME_PREFIX = '/pay-cd/concourse/pipelines/';
13
+ const LOWERCASE_SECRET_NAMES_TO_SECRET_NAMES = service_secrets_1.SECRET_NAMES.reduce((result, secretName) => {
14
+ return { ...result, [secretName.toLowerCase()]: secretName };
15
+ }, {});
16
+ class SSMProvider extends providers_types_1.AbstractSecretProvider {
17
+ ssmClient;
18
+ constructor(env, secretSource) {
19
+ super(env, secretSource);
20
+ this.ssmClient = new client_ssm_1.SSMClient({ region: 'eu-west-1' });
21
+ }
22
+ async get(secretConfig) {
23
+ const ssmParameterName = ssmParameterNameForSecret(secretConfig);
24
+ const getParameterCommand = new client_ssm_1.GetParameterCommand({
25
+ Name: ssmParameterName,
26
+ WithDecryption: true
27
+ });
28
+ let response;
29
+ try {
30
+ response = await this.ssmClient.send(getParameterCommand);
31
+ }
32
+ catch (e) {
33
+ if (e instanceof client_ssm_1.ParameterNotFound) {
34
+ return undefined;
35
+ }
36
+ else if (e instanceof Error && e.name === 'CredentialsProviderError') {
37
+ console.error('AWS Could not load any credentials, perhaps you need to run this with aws-vault: `aws-vault exec <account> -- pay secrets ...`?');
38
+ process.exit(1);
39
+ }
40
+ console.error(`An error occured while trying to get the parameter ${ssmParameterName}.`);
41
+ throw e;
42
+ }
43
+ if (response.Parameter?.Value === undefined) {
44
+ let errorMessage = `When calling SSM to get the parameter ${ssmParameterName} there was no parameter or value returned, and no error raised! `;
45
+ if (response.$metadata.httpStatusCode === undefined) {
46
+ errorMessage += 'There was also no http status code in the response metadata!';
47
+ }
48
+ else {
49
+ errorMessage += `The last http status code of the request to get the parameter was ${response.$metadata.httpStatusCode}`;
50
+ }
51
+ throw new Error(errorMessage);
52
+ }
53
+ return response.Parameter.Value;
54
+ }
55
+ async set(secretConfig, value) {
56
+ const ssmParameterName = ssmParameterNameForSecret(secretConfig);
57
+ const putParameterCommand = new client_ssm_1.PutParameterCommand({
58
+ Name: ssmParameterName,
59
+ Type: 'SecureString',
60
+ Value: value,
61
+ Overwrite: true
62
+ });
63
+ try {
64
+ await this.ssmClient.send(putParameterCommand);
65
+ }
66
+ catch (e) {
67
+ if (e instanceof client_ssm_1.InvalidAllowedPatternException || e instanceof client_ssm_1.ParameterPatternMismatchException) {
68
+ console.error(`When trying to set secret ${secretConfig.name} in environment ${secretConfig.environment} for service ${secretConfig.service}`);
69
+ console.error(`AWS returned an error that the parameter ${ssmParameterName} is not a valid parameter name.`);
70
+ console.error('See https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html#sysman-parameter-name-constraints for constraints on parameter names');
71
+ process.exit(1);
72
+ }
73
+ else if (e instanceof Error && e.name === 'CredentialsProviderError') {
74
+ console.error('AWS Could not load any credentials, perhaps you need to run this with aws-vault: `aws-vault exec <account> -- pay secrets ...`?');
75
+ process.exit(1);
76
+ }
77
+ else {
78
+ console.error(`When trying to set secret ${secretConfig.name} in environment ${secretConfig.environment} for service ${secretConfig.service} AWS returned an unhandled (by us) excpetion, re-throwing error`);
79
+ throw e;
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+ async list(serviceName) {
85
+ const filters = [
86
+ {
87
+ Key: 'Name',
88
+ Option: 'BeginsWith',
89
+ Values: [
90
+ ssmParameterNamePrefix(this.environment, serviceName)
91
+ ]
92
+ }
93
+ ];
94
+ const paginator = (0, client_ssm_1.paginateDescribeParameters)({ client: this.ssmClient }, { ParameterFilters: filters });
95
+ const parameters = [];
96
+ for await (const page of paginator) {
97
+ if (page.Parameters === undefined) {
98
+ continue;
99
+ }
100
+ parameters.push(...page.Parameters);
101
+ }
102
+ const secretConfigs = [];
103
+ for (const parameter of parameters) {
104
+ if (parameter.Name === undefined) {
105
+ continue;
106
+ }
107
+ const secretConfig = this.ssmParameterNameToLowercaseSecret(parameter.Name, serviceName);
108
+ secretConfigs.push(secretConfig);
109
+ }
110
+ return secretConfigs;
111
+ }
112
+ ssmParameterNameToLowercaseSecret(parameterName, serviceName) {
113
+ if (parameterName.startsWith(CONCOURSE_PARAMETER_NAME_PREFIX)) {
114
+ return this.concourseParameterNameToLowercaseSecret(parameterName, serviceName);
115
+ }
116
+ const lowercaseSecretName = parameterName.substring(parameterName.indexOf('.') + 1);
117
+ return {
118
+ environment: this.environment,
119
+ name: lowercaseSecretName in LOWERCASE_SECRET_NAMES_TO_SECRET_NAMES ? LOWERCASE_SECRET_NAMES_TO_SECRET_NAMES[lowercaseSecretName] : lowercaseSecretName,
120
+ secretSourceValue: parameterName,
121
+ service: serviceName,
122
+ source: 'ssm'
123
+ };
124
+ }
125
+ concourseParameterNameToLowercaseSecret(parameterName, serviceName) {
126
+ const paramNameWithoutPrefix = parameterName.substring(CONCOURSE_PARAMETER_NAME_PREFIX.length);
127
+ const lowercaseSecretName = paramNameWithoutPrefix.substring(paramNameWithoutPrefix.indexOf('/') + 1);
128
+ return {
129
+ environment: this.environment,
130
+ name: lowercaseSecretName in LOWERCASE_SECRET_NAMES_TO_SECRET_NAMES ? LOWERCASE_SECRET_NAMES_TO_SECRET_NAMES[lowercaseSecretName] : lowercaseSecretName,
131
+ secretSourceValue: parameterName,
132
+ service: serviceName,
133
+ source: 'ssm'
134
+ };
135
+ }
136
+ }
137
+ exports.SSMProvider = SSMProvider;
138
+ function ssmParameterNameForSecret(secretConfig) {
139
+ if (CONCOURSE_SECRETS.includes(secretConfig.service)) {
140
+ return concourseSecretName(secretConfig);
141
+ }
142
+ return `${secretConfig.environment}_${secretConfig.service}.${secretConfig.name}`.toLowerCase();
143
+ }
144
+ function concourseSecretName(secretConfig) {
145
+ // Service name is `cd-pay-dev` but the ssm name is only `pay-dev` so remove the cd-
146
+ const serviceName = secretConfig.service.substring(3);
147
+ return `${CONCOURSE_PARAMETER_NAME_PREFIX}${serviceName.toLowerCase()}/${secretConfig.name.toLowerCase()}`;
148
+ }
149
+ function ssmParameterNamePrefix(environment, serviceName) {
150
+ if (CONCOURSE_SECRETS.includes(serviceName)) {
151
+ // Service name is `cd-pay-dev` but the ssm name is only `pay-dev` so remove the cd-
152
+ return `${CONCOURSE_PARAMETER_NAME_PREFIX}${serviceName.toLowerCase().substring(3)}/`;
153
+ }
154
+ return `${environment}_${serviceName}.`;
155
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValueProvider = void 0;
4
+ const providers_types_1 = require("../providers/providers.types");
5
+ class ValueProvider extends providers_types_1.AbstractSecretProvider {
6
+ async get(secretConfig) {
7
+ return secretConfig.secretSourceValue;
8
+ }
9
+ }
10
+ exports.ValueProvider = ValueProvider;
@@ -4,27 +4,59 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.handler = exports.builder = exports.desc = exports.command = void 0;
7
- const node_child_process_1 = require("node:child_process");
8
- const preflight_js_1 = __importDefault(require("../utils/preflight.js"));
9
- exports.command = 'audit <service> <env..>';
10
- exports.desc = 'Audits secrets for <service> for the given <envs..>';
7
+ const cli_table3_1 = __importDefault(require("cli-table3"));
8
+ const config_types_1 = require("../config/config.types");
9
+ const service_secrets_js_1 = require("../config/service_secrets.js");
10
+ const ssm_1 = require("../providers/ssm");
11
+ const configs_1 = require("../../../util/configs");
12
+ const standardContent_js_1 = require("../../../core/standardContent.js");
13
+ exports.command = 'audit <service> <env>';
14
+ exports.desc = 'Check whether the configured secrets for <service> in <env> are provisioned. Also lists secrets which are provisioned but which we do not have config for. Note: Does not check the value of the secret, only existance';
11
15
  const builder = (yargs) => {
12
16
  return yargs
13
17
  .positional('service', {
14
18
  type: 'string',
15
- description: 'The service (e.g. connector) to audit the secret for'
19
+ description: 'The service (e.g. connector) to audit the secret for',
20
+ choices: config_types_1.SERVICE_NAMES
16
21
  })
17
22
  .positional('env', {
18
23
  type: 'string',
19
- description: 'The environment (e.g. test-12) to audit the secret for'
24
+ description: 'The environment (e.g. test-12) to audit the secret for',
25
+ choices: config_types_1.ENVIRONMENT_NAMES
20
26
  });
21
27
  };
22
28
  exports.builder = builder;
23
29
  exports.handler = auditHandler;
24
30
  async function auditHandler(argv) {
25
31
  const service = argv.service;
26
- const envs = argv.env;
27
- const preflightInfo = (0, preflight_js_1.default)();
28
- (0, node_child_process_1.spawnSync)(preflightInfo.rbenvCommand, ['exec', 'bundle', 'exec', 'bin/pay', 'secrets', 'audit', service, ...envs], { shell: true, stdio: 'inherit', cwd: preflightInfo.pathToLegacyRubyCli });
32
+ const env = argv.env;
33
+ await (0, standardContent_js_1.showHeader)();
34
+ await (0, configs_1.checkAwsCredentials)((0, configs_1.payEnvironmentToAWSAccountName)(env));
35
+ const secretsToAudit = service_secrets_js_1.SERVICE_SECRETS[service].sort();
36
+ const ssmProvider = new ssm_1.SSMProvider(env, 'ssm');
37
+ const secretsInSSM = await ssmProvider.list(service);
38
+ const checkedSecretsInSSM = new Map();
39
+ for (const secretInSSM of secretsInSSM) {
40
+ checkedSecretsInSSM.set(secretInSSM.name, secretInSSM);
41
+ }
42
+ const table = new cli_table3_1.default({
43
+ head: [`Secrets for ${service}`, env]
44
+ });
45
+ for (const secretToAudit of secretsToAudit) {
46
+ if (checkedSecretsInSSM.has(secretToAudit)) {
47
+ table.push([secretToAudit, '✅']);
48
+ checkedSecretsInSSM.delete(secretToAudit);
49
+ }
50
+ else {
51
+ table.push([secretToAudit, '❌']);
52
+ }
53
+ }
54
+ if (checkedSecretsInSSM.size > 0) {
55
+ table.push([
56
+ 'Provisioned Secrets which are not configured in pay secrets',
57
+ Array.from(checkedSecretsInSSM.keys()).sort().join('\n')
58
+ ]);
59
+ }
60
+ console.log(table.toString());
29
61
  }
30
62
  exports.default = auditHandler;
@@ -1,26 +1,30 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.handler = exports.builder = exports.desc = exports.command = void 0;
7
- const node_child_process_1 = require("node:child_process");
8
- const preflight_js_1 = __importDefault(require("../utils/preflight.js"));
4
+ const config_types_1 = require("../config/config.types");
5
+ const service_secrets_1 = require("../config/service_secrets");
6
+ const secrets_1 = require("../config/secrets");
7
+ const configs_1 = require("../../../util/configs");
8
+ const factory_1 = require("../providers/factory");
9
+ const standardContent_1 = require("../../../core/standardContent");
9
10
  exports.command = 'fetch <env> <service> <secret_name> [--use-ssm]';
10
11
  exports.desc = 'Fetches a <named secret> for <service> for <env>, if use-ssm is set then get the secret from ssm';
11
12
  const builder = (yargs) => {
12
13
  return yargs
13
14
  .positional('env', {
14
15
  type: 'string',
15
- description: 'The environment (e.g. test-12) to fetch the secret for'
16
+ description: 'The environment (e.g. test-12) to fetch the secret for',
17
+ choices: config_types_1.ENVIRONMENT_NAMES
16
18
  })
17
19
  .positional('service', {
18
20
  type: 'string',
19
- description: 'The service (e.g. connector) to fetch the secret for'
21
+ description: 'The service (e.g. connector) to fetch the secret for',
22
+ choices: config_types_1.SERVICE_NAMES
20
23
  })
21
24
  .positional('secret_name', {
22
25
  type: 'string',
23
- description: 'The name of the secret to get'
26
+ description: 'The name of the secret to get',
27
+ choices: service_secrets_1.SECRET_NAMES
24
28
  })
25
29
  .option('use-ssm', {
26
30
  type: 'boolean',
@@ -31,17 +35,34 @@ const builder = (yargs) => {
31
35
  exports.builder = builder;
32
36
  exports.handler = fetchHandler;
33
37
  async function fetchHandler(argv) {
34
- const service = argv.service;
35
38
  const env = argv.env;
39
+ const service = argv.service;
36
40
  const secretName = argv.secret_name;
37
41
  const useSSM = argv.useSsm;
38
- const preflightInfo = (0, preflight_js_1.default)();
39
- const rbenvArgs = ['exec', 'bundle', 'exec', 'bin/pay', 'secrets', 'fetch', env, service, secretName];
42
+ // Printing the header to stdout so you can use this command piped to another process
43
+ // and the other process will only recevie the printed secret. E.g | pbcopy to copy the secret
44
+ // to your copy paste buffer without the header etc
45
+ await (0, standardContent_1.showHeaderStdErr)();
46
+ const secretConfig = (0, secrets_1.getSecretConfig)(env, service, secretName);
40
47
  if (useSSM) {
41
- rbenvArgs.push('--use-ssm');
48
+ secretConfig.source = 'ssm';
49
+ }
50
+ if (secretConfig.source === 'ssm') {
51
+ await (0, configs_1.checkAwsCredentials)((0, configs_1.payEnvironmentToAWSAccountName)(env));
52
+ }
53
+ const secretProvider = (0, factory_1.providerFor)(secretConfig);
54
+ const secretValue = await secretProvider.get(secretConfig);
55
+ if (secretValue === undefined) {
56
+ console.error(`The secret ${secretName} was not found in ${secretConfig.source}`);
57
+ process.exit(1);
58
+ }
59
+ if (!process.stdout.isTTY) {
60
+ // If stdout of the process isn't connected to a terminal (e.g. if it's piped to another process, like pbcopy for instance)
61
+ // Print to stderr a message to make it clear to the user the value is being hidden from display, but has been printed to stdout
62
+ process.stderr.write('HIDDEN - Actual Value written to stdout');
42
63
  }
43
- (0, node_child_process_1.spawnSync)(preflightInfo.rbenvCommand, rbenvArgs, { shell: true, stdio: 'inherit', cwd: preflightInfo.pathToLegacyRubyCli });
44
- // The fetch command doesn't output a newline which is really annoying, so add one
45
- console.log();
64
+ process.stdout.write(secretValue);
65
+ // Since the secret is written with no trailing newline we should print one (to stderr) to improve the UX
66
+ console.warn();
46
67
  }
47
68
  exports.default = fetchHandler;