@govuk-pay/cli 0.0.26 → 0.0.28

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@govuk-pay/cli",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "GOV.UK Pay Command Line Interface",
5
5
  "bin": {
6
6
  "pay": "bin/cli.js",
@@ -1,10 +1,8 @@
1
1
  Commands:
2
2
  pay browse # Opens web browser link to useful links
3
- pay deployment_status <env> # Describe what's deployed
4
3
  pay doctor # Attempts to initialise or fix the Pay CLI
5
4
  pay help [COMMAND] # Describe available commands or one specific ...
6
5
  pay local # Sets up local Pay development environment
7
6
  pay schema # Generates web based database diagrams and me...
8
7
  pay secrets # Manage secrets in and between environments
9
- pay ssm # Start an SSM session to boxes in environments
10
8
  pay tunnel # Open tunnel to application database
@@ -10,6 +10,7 @@ const client_ecs_1 = require("@aws-sdk/client-ecs");
10
10
  const client_rds_1 = require("@aws-sdk/client-rds");
11
11
  const client_ssm_1 = require("@aws-sdk/client-ssm");
12
12
  const readline_1 = __importDefault(require("readline"));
13
+ const promises_1 = __importDefault(require("node:readline/promises"));
13
14
  const child_process_1 = require("child_process");
14
15
  const constants_js_1 = require("../core/constants.js");
15
16
  let ec2;
@@ -29,6 +30,30 @@ function logTunnelCommands() {
29
30
  pay tunnel help # Describe tunnel command`);
30
31
  }
31
32
  exports.logTunnelCommands = logTunnelCommands;
33
+ async function readInputForReadOrWriteDBAccess() {
34
+ const rl = promises_1.default.createInterface({
35
+ input: process.stdin,
36
+ output: process.stdout
37
+ });
38
+ let answer = await rl.question('Do you require read-only access, or write-access to the database? [R/w]: ');
39
+ rl.close();
40
+ if (answer === undefined || answer === '') {
41
+ answer = 'R';
42
+ console.log('No option entered. Defaulting to read-only');
43
+ }
44
+ if (answer === 'R' || answer === 'r') {
45
+ printGreen('Database read-only access requested');
46
+ return 'read-only';
47
+ }
48
+ else if (answer === 'w') {
49
+ printGreen('Database write access requested');
50
+ return 'write';
51
+ }
52
+ else {
53
+ printError('Invalid option entered. Exiting..');
54
+ process.exit(1);
55
+ }
56
+ }
32
57
  async function tunnelHandler(options) {
33
58
  await (0, standardContent_js_1.showHeader)();
34
59
  const { environment, application } = parseArguments(options);
@@ -39,10 +64,11 @@ async function tunnelHandler(options) {
39
64
  let bastionTask = null;
40
65
  try {
41
66
  printWarningToUser();
67
+ const dbAccessType = await readInputForReadOrWriteDBAccess();
42
68
  const database = await getDatabaseDetails(environment, application);
43
69
  bastionTask = await startBastion(environment);
44
70
  openTunnel(bastionTask, database, environment);
45
- printHowToTunnelText(application, environment, database.EngineVersion);
71
+ await printHowToTunnelText(application, environment, database.EngineVersion, dbAccessType);
46
72
  await waitForExit();
47
73
  await shutdown(environment, bastionTask);
48
74
  }
@@ -345,10 +371,10 @@ async function terminateSession(task, environment) {
345
371
  }
346
372
  }
347
373
  }
348
- function printHowToTunnelText(application, environment, dbEngineVersion) {
349
- const dbUser = getDbUser(application);
350
- const payLowPassDbSecretName = getPayLowPassDbSecretname(environment, dbUser);
351
- const paySecretsPasswordName = getPaySecretsPasswordName();
374
+ async function printHowToTunnelText(application, environment, dbEngineVersion, dbAccessType) {
375
+ const dbUser = await getDbUser(environment, application, dbAccessType);
376
+ const payLowPassDbSecretName = getPayLowPassDbSecretName(environment, dbUser, dbAccessType);
377
+ const paySecretsPasswordName = getPaySecretsPasswordName(dbAccessType);
352
378
  printGreen(`\nConnected tunnel to ${application} RDS database in ${environment} on port 65432\n`);
353
379
  printGreen('Copy DB credentials to clipboard (in another window) using pay-low-pass:');
354
380
  printGreen(` pay-low-pass ${payLowPassDbSecretName} | pbcopy`);
@@ -361,15 +387,42 @@ function printHowToTunnelText(application, environment, dbEngineVersion) {
361
387
  printGreen('Or even more conveniently connect using a docker container and set the password automatically using pay-low-pass:');
362
388
  printGreen(` docker run -e "PGPASSWORD=$(pay-low-pass ${payLowPassDbSecretName})" --rm -ti postgres:${dbEngineVersion}-alpine psql --host docker.for.mac.localhost --port 65432 --user ${dbUser} --dbname ${application}\n`);
363
389
  }
364
- // TODO: add a write flag. Default to readonly.
365
- function getDbUser(application) {
366
- return `${application}_support_readonly`;
390
+ async function getDbUser(environment, application, dbAccessType) {
391
+ let paramName;
392
+ if (dbAccessType === 'write') {
393
+ paramName = `${environment}_${application}.db_user`;
394
+ }
395
+ else {
396
+ paramName = `${environment}_${application}.db_support_user_readonly`;
397
+ }
398
+ const ssmClient = new client_ssm_1.SSMClient();
399
+ const input = { Names: [paramName], WithDecryption: true };
400
+ const command = new client_ssm_1.GetParametersCommand(input);
401
+ const response = await ssmClient.send(command);
402
+ if (response?.Parameters?.length !== undefined && response?.Parameters?.length > 0 &&
403
+ response?.Parameters[0]?.Value !== undefined) {
404
+ return response.Parameters[0].Value;
405
+ }
406
+ else {
407
+ printError('Unable to get database username from Parameter Store');
408
+ return '';
409
+ }
367
410
  }
368
- function getPayLowPassDbSecretname(environment, user) {
369
- return `aws/rds/support_readonly_users/${environment.split('-')[0]}/${user}`;
411
+ function getPayLowPassDbSecretName(environment, user, dbAccessType) {
412
+ if (dbAccessType === 'write') {
413
+ return `aws/rds/application_users/${environment.split('-')[0]}/${user}`;
414
+ }
415
+ else {
416
+ return `aws/rds/support_readonly_users/${environment.split('-')[0]}/${user}`;
417
+ }
370
418
  }
371
- function getPaySecretsPasswordName() {
372
- return 'DB_SUPPORT_PASSWORD_READONLY';
419
+ function getPaySecretsPasswordName(dbAccessType) {
420
+ if (dbAccessType === 'write') {
421
+ return 'DB_PASSWORD';
422
+ }
423
+ else {
424
+ return 'DB_SUPPORT_PASSWORD_READONLY';
425
+ }
373
426
  }
374
427
  function printWarningToUser() {
375
428
  console.log(FORMAT.yellow, '⚠️ WARNING: When using SSM, any and all activity you perform may be getting logged for security auditing purposes (think PCI).', FORMAT.reset);
@@ -21,9 +21,6 @@ handlers.set('tunnel', {
21
21
  handlers.set('legacy', {
22
22
  handler: legacy_1.default
23
23
  });
24
- handlers.set('deployment_status', {
25
- handler: legacy_1.default
26
- });
27
24
  handlers.set('doctor', {
28
25
  handler: legacy_1.default
29
26
  });
@@ -39,9 +36,6 @@ handlers.set('schema', {
39
36
  handlers.set('secrets', {
40
37
  handler: legacy_1.default
41
38
  });
42
- handlers.set('ssm', {
43
- handler: legacy_1.default
44
- });
45
39
  handlers.set('demo', {
46
40
  handler: demo_js_1.default
47
41
  });
@@ -1,58 +0,0 @@
1
- require 'aws-sdk-ec2'
2
- require 'aws-sdk-autoscaling'
3
- require 'aws-sdk-elasticloadbalancing'
4
-
5
- module PayCLI::Commands::Ssm
6
-
7
- def self.usage!
8
- STDERR.puts <<~USAGE
9
- pay ssm <environment> <instance_id>
10
-
11
- example:
12
- pay ssm test-12 i-1c472d8c2ffd826d7
13
-
14
- USAGE
15
- exit
16
- end
17
-
18
- def self.start!
19
- args = ARGV[1..-1] || []
20
- usage! if args.length != 2
21
-
22
- PayCLI::StopYubicoAuthenticator.stop_yubico_authenticator!
23
-
24
- env, instance_id = args
25
-
26
- usage! unless instance_id.match(/^i-/)
27
-
28
- PayCLI::Environment.setup! env
29
-
30
- warn <<~WARN
31
- \e[33m
32
- ⚠️ WARNING: When using SSM, any and all activity you perform may be getting logged for security auditing purposes (think PCI).
33
- Avoid sending or accessing \e[4manything\e[24m that could cause a security breach, such as:
34
-
35
- • Secret API Keys or Tokens
36
- • Credentials or Passwords
37
- • Cardholder Data or Personally-Identifiable Information (PII)
38
- • Anything else that may be protected by GDPR or PCI-DSS
39
- • Anything classified as GSC 'Secret' or above
40
-
41
- If you have a problem with this or aren't sure, use Ctrl-C \e[4mright now\e[24m and discontinue your SSM session.
42
- \e[0m
43
- WARN
44
- sleep(5)
45
- start_ssm_session(instance_id, env)
46
- end
47
-
48
- def self.start_ssm_session(instance_id, env)
49
- STDERR.puts "SSM to instance #{instance_id} in #{env}"
50
-
51
- pid = spawn(
52
- "aws ssm start-session --target \"#{instance_id}\"",
53
- in: STDIN, out: STDOUT, err: STDERR
54
- )
55
- Process.wait pid
56
- exit $CHILD_STATUS.exitstatus
57
- end
58
- end