@p0security/cli 0.11.0 → 0.11.2

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 (41) hide show
  1. package/dist/commands/__tests__/login.test.js +17 -0
  2. package/dist/commands/__tests__/login.test.js.map +1 -1
  3. package/dist/commands/__tests__/ls.test.js +4 -3
  4. package/dist/commands/__tests__/ls.test.js.map +1 -1
  5. package/dist/commands/__tests__/ssh.test.js +10 -5
  6. package/dist/commands/__tests__/ssh.test.js.map +1 -1
  7. package/dist/commands/kubeconfig.js +3 -2
  8. package/dist/commands/kubeconfig.js.map +1 -1
  9. package/dist/commands/login.js +11 -0
  10. package/dist/commands/login.js.map +1 -1
  11. package/dist/commands/ls.js +4 -6
  12. package/dist/commands/ls.js.map +1 -1
  13. package/dist/commands/scp.js +3 -2
  14. package/dist/commands/scp.js.map +1 -1
  15. package/dist/commands/shared/request.js +2 -2
  16. package/dist/commands/shared/request.js.map +1 -1
  17. package/dist/commands/ssh.js +3 -2
  18. package/dist/commands/ssh.js.map +1 -1
  19. package/dist/drivers/__mocks__/stdio.d.ts +14 -0
  20. package/dist/drivers/__mocks__/stdio.js +26 -0
  21. package/dist/drivers/__mocks__/stdio.js.map +1 -0
  22. package/dist/drivers/ansi.d.ts +8 -0
  23. package/dist/drivers/ansi.js +25 -0
  24. package/dist/drivers/ansi.js.map +1 -0
  25. package/dist/drivers/auth.d.ts +1 -0
  26. package/dist/drivers/auth.js +8 -4
  27. package/dist/drivers/auth.js.map +1 -1
  28. package/dist/drivers/stdio.d.ts +6 -5
  29. package/dist/drivers/stdio.js +50 -7
  30. package/dist/drivers/stdio.js.map +1 -1
  31. package/dist/plugins/aws/ssh.js +45 -23
  32. package/dist/plugins/aws/ssh.js.map +1 -1
  33. package/dist/plugins/aws/types.d.ts +6 -4
  34. package/dist/plugins/google/ssh-key.js +9 -1
  35. package/dist/plugins/google/ssh-key.js.map +1 -1
  36. package/dist/plugins/google/ssh.js +61 -28
  37. package/dist/plugins/google/ssh.js.map +1 -1
  38. package/dist/plugins/ssh/index.js +62 -88
  39. package/dist/plugins/ssh/index.js.map +1 -1
  40. package/dist/types/ssh.d.ts +28 -13
  41. package/package.json +3 -3
@@ -9,15 +9,25 @@ This file is part of @p0security/cli
9
9
 
10
10
  You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
11
11
  **/
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
12
21
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.Ansi = exports.print2 = exports.print1 = void 0;
22
+ exports.spinUntil = exports.clear2 = exports.reset2 = exports.print2 = exports.print1 = void 0;
14
23
  /** Functions to handle stdio
15
24
  *
16
25
  * These are essentially wrappers around console.foo, but allow for
17
26
  * - Better testing
18
27
  * - Later redirection / duplication
19
28
  */
20
- const lodash_1 = require("lodash");
29
+ const util_1 = require("../util");
30
+ const ansi_1 = require("./ansi");
21
31
  /** Used to output machine-readable text to stdout
22
32
  *
23
33
  * In general this should not be used for text meant to be consumed
@@ -37,10 +47,43 @@ function print2(message) {
37
47
  console.error(message);
38
48
  }
39
49
  exports.print2 = print2;
40
- const AnsiCodes = {
41
- Reset: "00",
42
- Dim: "02",
43
- Yellow: "33",
50
+ /** Resets the terminal cursor to the beginning of the line */
51
+ function reset2() {
52
+ process.stderr.write((0, ansi_1.Ansi)("0G"));
53
+ }
54
+ exports.reset2 = reset2;
55
+ /** Clears the current terminal line */
56
+ function clear2() {
57
+ // Replaces text with spaces
58
+ process.stderr.write((0, ansi_1.Ansi)("2K"));
59
+ reset2();
60
+ }
61
+ exports.clear2 = clear2;
62
+ const Spin = {
63
+ items: ["⠇", "⠋", "⠙", "⠸", "⠴", "⠦"],
64
+ delayMs: 200,
44
65
  };
45
- exports.Ansi = (0, lodash_1.mapValues)(AnsiCodes, (v) => `\u001b[${v}m`);
66
+ /** Prints a Unicode spinner until a promise resolves */
67
+ const spinUntil = (message, promise) => __awaiter(void 0, void 0, void 0, function* () {
68
+ let isDone = false;
69
+ let ix = 0;
70
+ // 'catch' here just prevents UncaughtExceptionError; errors are sent to caller
71
+ // on function return
72
+ void promise.finally(() => (isDone = true)).catch(() => { });
73
+ while (!isDone) {
74
+ yield (0, util_1.sleep)(Spin.delayMs);
75
+ if (isDone)
76
+ break;
77
+ clear2();
78
+ process.stderr.write(ansi_1.AnsiSgr.Green +
79
+ Spin.items[ix % Spin.items.length] +
80
+ " " +
81
+ message +
82
+ ansi_1.AnsiSgr.Reset);
83
+ ix++;
84
+ }
85
+ clear2();
86
+ return yield promise;
87
+ });
88
+ exports.spinUntil = spinUntil;
46
89
  //# sourceMappingURL=stdio.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/drivers/stdio.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEH;;;;;GAKG;AACH,mCAAmC;AAEnC;;;;GAIG;AACH,SAAgB,MAAM,CAAC,OAAY;IACjC,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC;AAHD,wBAGC;AAED;;;GAGG;AACH,SAAgB,MAAM,CAAC,OAAY;IACjC,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAHD,wBAGC;AAED,MAAM,SAAS,GAAG;IAChB,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;IACT,MAAM,EAAE,IAAI;CACJ,CAAC;AAEE,QAAA,IAAI,GAAG,IAAA,kBAAS,EAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/drivers/stdio.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;;;;;;;AAEH;;;;;GAKG;AACH,kCAAgC;AAChC,iCAAuC;AAEvC;;;;GAIG;AACH,SAAgB,MAAM,CAAC,OAAY;IACjC,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC;AAHD,wBAGC;AAED;;;GAGG;AACH,SAAgB,MAAM,CAAC,OAAY;IACjC,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAHD,wBAGC;AAED,8DAA8D;AAC9D,SAAgB,MAAM;IACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAA,WAAI,EAAC,IAAI,CAAC,CAAC,CAAC;AACnC,CAAC;AAFD,wBAEC;AAED,uCAAuC;AACvC,SAAgB,MAAM;IACpB,4BAA4B;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAA,WAAI,EAAC,IAAI,CAAC,CAAC,CAAC;IACjC,MAAM,EAAE,CAAC;AACX,CAAC;AAJD,wBAIC;AAED,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IACrC,OAAO,EAAE,GAAG;CACb,CAAC;AAEF,wDAAwD;AACjD,MAAM,SAAS,GAAG,CAAU,OAAe,EAAE,OAAmB,EAAE,EAAE;IACzE,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,+EAA+E;IAC/E,qBAAqB;IACrB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,MAAM,EAAE;QACd,MAAM,IAAA,YAAK,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,IAAI,MAAM;YAAE,MAAM;QAClB,MAAM,EAAE,CAAC;QACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,cAAO,CAAC,KAAK;YACX,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YAClC,GAAG;YACH,OAAO;YACP,cAAO,CAAC,KAAK,CAChB,CAAC;QACF,EAAE,EAAE,CAAC;KACN;IACD,MAAM,EAAE,CAAC;IACT,OAAO,MAAM,OAAO,CAAC;AACvB,CAAC,CAAA,CAAC;AArBW,QAAA,SAAS,aAqBpB"}
@@ -15,29 +15,34 @@ const aws_1 = require("../okta/aws");
15
15
  const config_1 = require("./config");
16
16
  const idc_1 = require("./idc");
17
17
  const install_1 = require("./ssm/install");
18
- /** Maximum number of attempts to start an SSH session
19
- *
20
- * Each attempt consumes ~ 1 s.
21
- */
22
- const MAX_SSH_RETRIES = 30;
18
+ const PROPAGATION_TIMEOUT_LIMIT_MS = 30 * 1000;
23
19
  /** The name of the SessionManager port forwarding document. This document is managed by AWS. */
24
20
  const START_SSH_SESSION_DOCUMENT_NAME = "AWS-StartSSHSession";
25
- exports.awsSshProvider = {
26
- requestToSsh: (request) => {
27
- const { permission, generated } = request;
28
- const { instanceId, accountId, region } = permission.spec;
29
- const { idc, ssh, name } = generated;
30
- const { linuxUserName } = ssh;
31
- const common = { linuxUserName, accountId, region, id: instanceId };
32
- return !idc
33
- ? Object.assign(Object.assign({}, common), { role: name, type: "aws", access: "role" }) : Object.assign(Object.assign({}, common), { idc, permissionSet: name, type: "aws", access: "idc" });
21
+ /**There are 2 cases of unprovisioned access in AWS
22
+ * 1. SSM:StartSession action is missing either on the SSM document (AWS-StartSSHSession) or the EC2 instance
23
+ * 2. Temporary error when issuing an SCP command
24
+ *
25
+ * 1: results in UNAUTHORIZED_START_SESSION_MESSAGE
26
+ * 2: results in CONNECTION_CLOSED_MESSAGE
27
+ */
28
+ const unprovisionedAccessPatterns = [
29
+ /** Matches the error message that AWS SSM prints when access is not propagated */
30
+ // Note that the resource will randomly be either the SSM document or the EC2 instance
31
+ {
32
+ pattern: /An error occurred \(AccessDeniedException\) when calling the StartSession operation: User: arn:aws:sts::.*:assumed-role\/P0GrantsRole.* is not authorized to perform: ssm:StartSession on resource: arn:aws:.*:.*:.* because no identity-based policy allows the ssm:StartSession action/,
34
33
  },
35
- toCliRequest: (request) => __awaiter(void 0, void 0, void 0, function* () { return (Object.assign(Object.assign({}, request), { cliLocalData: undefined })); }),
36
- ensureInstall: () => __awaiter(void 0, void 0, void 0, function* () {
37
- if (!(yield (0, install_1.ensureSsmInstall)())) {
38
- throw "Please try again after installing the required AWS utilities";
39
- }
40
- }),
34
+ /**
35
+ * Matches the following error messages that AWS SSM pints when ssh authorized
36
+ * key access hasn't propagated to the instance yet.
37
+ * - Connection closed by UNKNOWN port 65535
38
+ * - scp: Connection closed
39
+ * - kex_exchange_identification: Connection closed by remote host
40
+ */
41
+ {
42
+ pattern: /\bConnection closed\b.*\b(?:by UNKNOWN port \d+|by remote host)?/,
43
+ },
44
+ ];
45
+ exports.awsSshProvider = {
41
46
  cloudProviderLogin: (authn, request) => __awaiter(void 0, void 0, void 0, function* () {
42
47
  var _a, _b, _c, _d;
43
48
  const { config } = yield (0, config_1.getAwsConfig)(authn, request.accountId);
@@ -50,6 +55,14 @@ exports.awsSshProvider = {
50
55
  ? yield (0, aws_1.assumeRoleWithOktaSaml)(authn, request)
51
56
  : (0, util_1.throwAssertNever)(config.login);
52
57
  }),
58
+ ensureInstall: () => __awaiter(void 0, void 0, void 0, function* () {
59
+ if (!(yield (0, install_1.ensureSsmInstall)())) {
60
+ throw "Please try again after installing the required AWS utilities";
61
+ }
62
+ }),
63
+ friendlyName: "AWS",
64
+ propagationTimeoutMs: PROPAGATION_TIMEOUT_LIMIT_MS,
65
+ preTestAccessPropagationArgs: () => undefined,
53
66
  proxyCommand: (request) => {
54
67
  return [
55
68
  "aws",
@@ -74,8 +87,17 @@ exports.awsSshProvider = {
74
87
  }
75
88
  return undefined;
76
89
  },
77
- preTestAccessPropagationArgs: () => undefined,
78
- maxRetries: MAX_SSH_RETRIES,
79
- friendlyName: "AWS",
90
+ requestToSsh: (request) => {
91
+ const { permission, generated } = request;
92
+ const { awsResourcePermission, instanceId, accountId, region } = permission.spec;
93
+ const { idcId, idcRegion } = awsResourcePermission.permission;
94
+ const { ssh, name } = generated;
95
+ const { linuxUserName } = ssh;
96
+ const common = { linuxUserName, accountId, region, id: instanceId };
97
+ return !idcId || !idcRegion
98
+ ? Object.assign(Object.assign({}, common), { role: name, type: "aws", access: "role" }) : Object.assign(Object.assign({}, common), { idc: { id: idcId, region: idcRegion }, permissionSet: name, type: "aws", access: "idc" });
99
+ },
100
+ toCliRequest: (request) => __awaiter(void 0, void 0, void 0, function* () { return (Object.assign(Object.assign({}, request), { cliLocalData: undefined })); }),
101
+ unprovisionedAccessPatterns,
80
102
  };
81
103
  //# sourceMappingURL=ssh.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../../src/plugins/aws/ssh.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../../src/plugins/aws/ssh.ts"],"names":[],"mappings":"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"}
@@ -59,6 +59,12 @@ export type AwsSshPermission = {
59
59
  accountId: string;
60
60
  region: string;
61
61
  type: "aws";
62
+ awsResourcePermission: {
63
+ permission: {
64
+ idcId?: string;
65
+ idcRegion?: string;
66
+ };
67
+ };
62
68
  };
63
69
  type: "session";
64
70
  };
@@ -67,10 +73,6 @@ export type AwsSshGenerated = {
67
73
  ssh: {
68
74
  linuxUserName: string;
69
75
  };
70
- idc?: {
71
- region: string;
72
- id: string;
73
- };
74
76
  };
75
77
  export type AwsSshPermissionSpec = PermissionSpec<"ssh", AwsSshPermission, AwsSshGenerated>;
76
78
  export type AwsSsh = CliPermissionSpec<AwsSshPermissionSpec, undefined>;
@@ -61,7 +61,15 @@ const importSshKey = (publicKey, options) => __awaiter(void 0, void 0, void 0, f
61
61
  },
62
62
  });
63
63
  if (!response.ok) {
64
- throw `Import of SSH public key failed. HTTP error ${response.status}: ${yield response.text()}`;
64
+ if (debug) {
65
+ (0, stdio_1.print2)(`HTTP error ${response.status}: ${yield response.text()}`);
66
+ }
67
+ if (response.status === 401) {
68
+ throw `Authentication failed. Please login to Google Cloud CLI with 'gcloud auth login'`;
69
+ }
70
+ else {
71
+ throw `Import of SSH public key failed.`;
72
+ }
65
73
  }
66
74
  const data = yield response.json();
67
75
  if (debug) {
@@ -1 +1 @@
1
- {"version":3,"file":"ssh-key.js","sourceRoot":"","sources":["../../../src/plugins/google/ssh-key.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;GASG;AACH,wDAAqD;AACrD,+CAA6C;AAG7C;;;;;;;;;;GAUG;AACI,MAAM,YAAY,GAAG,CAC1B,SAAiB,EACjB,OAA6B,EAC7B,EAAE;;IACF,MAAM,KAAK,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,KAAK,mCAAI,KAAK,CAAC;IACtC,yDAAyD;IACzD,MAAM,WAAW,GAAG,MAAM,IAAA,uBAAU,EAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;QAC/D,MAAM;QACN,oBAAoB;KACrB,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,IAAA,uBAAU,EAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;QACpD,QAAQ;QACR,WAAW;QACX,SAAS;KACV,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE;QACT,IAAA,cAAM,EACJ,0BAA0B,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,OAAO,EAAE,CAC/E,CAAC;KACH;IAED,MAAM,GAAG,GAAG,2CAA2C,OAAO,qBAAqB,CAAC;IACpF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,GAAG,EAAE,SAAS;SACf,CAAC;QACF,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,WAAW,EAAE;YACtC,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;QAChB,MAAM,+CAA+C,QAAQ,CAAC,MAAM,KAAK,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;KAClG;IAED,MAAM,IAAI,GAA+B,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,KAAK,EAAE;QACT,IAAA,cAAM,EACJ,sDAAsD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAC7E,CAAC;KACH;IAED,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE9B,yEAAyE;IACzE,MAAM,aAAa,GAAG,YAAY,CAAC,aAAa,CAAC,MAAM,CACrD,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,KAAK,OAAO,CACrD,CAAC;IAEF,MAAM,YAAY,GAChB,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;QAChD,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAEhC,IAAI,KAAK,EAAE;QACT,IAAA,cAAM,EAAC,2BAA2B,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,QAAQ,EAAE,CAAC,CAAC;KAC7D;IAED,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,2HAA2H,CAAC;KACnI;IAED,OAAO,YAAY,CAAC,QAAQ,CAAC;AAC/B,CAAC,CAAA,CAAC;AAlEW,QAAA,YAAY,gBAkEvB"}
1
+ {"version":3,"file":"ssh-key.js","sourceRoot":"","sources":["../../../src/plugins/google/ssh-key.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;GASG;AACH,wDAAqD;AACrD,+CAA6C;AAG7C;;;;;;;;;;GAUG;AACI,MAAM,YAAY,GAAG,CAC1B,SAAiB,EACjB,OAA6B,EAC7B,EAAE;;IACF,MAAM,KAAK,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,KAAK,mCAAI,KAAK,CAAC;IACtC,yDAAyD;IACzD,MAAM,WAAW,GAAG,MAAM,IAAA,uBAAU,EAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;QAC/D,MAAM;QACN,oBAAoB;KACrB,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,IAAA,uBAAU,EAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;QACpD,QAAQ;QACR,WAAW;QACX,SAAS;KACV,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE;QACT,IAAA,cAAM,EACJ,0BAA0B,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,OAAO,EAAE,CAC/E,CAAC;KACH;IAED,MAAM,GAAG,GAAG,2CAA2C,OAAO,qBAAqB,CAAC;IACpF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,GAAG,EAAE,SAAS;SACf,CAAC;QACF,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,WAAW,EAAE;YACtC,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;QAChB,IAAI,KAAK,EAAE;YACT,IAAA,cAAM,EAAC,cAAc,QAAQ,CAAC,MAAM,KAAK,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;SACnE;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE;YAC3B,MAAM,kFAAkF,CAAC;SAC1F;aAAM;YACL,MAAM,kCAAkC,CAAC;SAC1C;KACF;IAED,MAAM,IAAI,GAA+B,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,KAAK,EAAE;QACT,IAAA,cAAM,EACJ,sDAAsD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAC7E,CAAC;KACH;IAED,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE9B,yEAAyE;IACzE,MAAM,aAAa,GAAG,YAAY,CAAC,aAAa,CAAC,MAAM,CACrD,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,KAAK,OAAO,CACrD,CAAC;IAEF,MAAM,YAAY,GAChB,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;QAChD,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAEhC,IAAI,KAAK,EAAE;QACT,IAAA,cAAM,EAAC,2BAA2B,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,QAAQ,EAAE,CAAC,CAAC;KAC7D;IAED,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,2HAA2H,CAAC;KACnI;IAED,OAAO,YAAY,CAAC,QAAQ,CAAC;AAC/B,CAAC,CAAA,CAAC;AA1EW,QAAA,YAAY,gBA0EvB"}
@@ -23,32 +23,61 @@ You should have received a copy of the GNU General Public License along with @p0
23
23
  const ssh_1 = require("../../commands/shared/ssh");
24
24
  const install_1 = require("./install");
25
25
  const ssh_key_1 = require("./ssh-key");
26
- /** Maximum number of attempts to start an SSH session
26
+ // It typically takes < 1 minute for access to propagate on GCP, so set the time limit to 2 minutes.
27
+ const PROPAGATION_TIMEOUT_LIMIT_MS = 2 * 60 * 1000;
28
+ /**
29
+ * There are 7 cases of unprovisioned access in Google Cloud.
30
+ * These are all potentially subject to propagation delays.
31
+ * 1. The linux user name is not present in the user's Google Workspace profile `posixAccounts` attribute
32
+ * 2. The public key is not present in the user's Google Workspace profile `sshPublicKeys` attribute
33
+ * 3. The user cannot act as the service account of the compute instance
34
+ * 4. The user cannot tunnel through the IAP tunnel to the instance
35
+ * 5. The user doesn't have osLogin or osAdminLogin role to the instance
36
+ * 5.a. compute.instances.get permission is missing
37
+ * 5.b. compute.instances.osLogin permission is missing
38
+ * 6. compute.instances.osAdminLogin is not provisioned but compute.instances.osLogin is - happens when a user upgrades existing access to sudo
39
+ * 7: Rare occurrence, the exact conditions so far undetermined (together with CONNECTION_CLOSED_MESSAGE)
27
40
  *
28
- * The length of each attempt varies based on the type of error from a few seconds to < 1s
41
+ * 1, 2, 3 (yes!), 5b: result in PUBLIC_KEY_DENIED_MESSAGE
42
+ * 4: results in UNAUTHORIZED_TUNNEL_USER_MESSAGE and also CONNECTION_CLOSED_MESSAGE
43
+ * 5a: results in UNAUTHORIZED_INSTANCES_GET_MESSAGE
44
+ * 6: results in SUDO_MESSAGE
45
+ * 7: results in DESTINATION_READ_ERROR and also CONNECTION_CLOSED_MESSAGE
29
46
  */
30
- const MAX_SSH_RETRIES = 120;
31
- exports.gcpSshProvider = {
32
- requestToSsh: (request) => {
33
- return {
34
- id: request.permission.spec.instanceName,
35
- projectId: request.permission.spec.projectId,
36
- zone: request.permission.spec.zone,
37
- linuxUserName: request.cliLocalData.linuxUserName,
38
- type: "gcloud",
39
- };
47
+ const unprovisionedAccessPatterns = [
48
+ { pattern: /Permission denied \(publickey\)/ },
49
+ {
50
+ // The output of `sudo -v` when the user is not allowed to run sudo
51
+ pattern: /Sorry, user .+ may not run sudo on .+/,
40
52
  },
41
- toCliRequest: (request, options) => __awaiter(void 0, void 0, void 0, function* () {
42
- return (Object.assign(Object.assign({}, request), { cliLocalData: {
43
- linuxUserName: yield (0, ssh_key_1.importSshKey)(request.permission.spec.publicKey, options),
44
- } }));
45
- }),
53
+ { pattern: /Error while connecting \[4033: 'not authorized'\]/ },
54
+ {
55
+ pattern: /Required 'compute\.instances\.get' permission/,
56
+ validationWindowMs: 30e3,
57
+ },
58
+ { pattern: /Error while connecting \[4010: 'destination read failed'\]/ },
59
+ ];
60
+ exports.gcpSshProvider = {
61
+ // TODO support login with Google Cloud
62
+ cloudProviderLogin: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
46
63
  ensureInstall: () => __awaiter(void 0, void 0, void 0, function* () {
47
64
  if (!(yield (0, install_1.ensureGcpSshInstall)())) {
48
65
  throw "Please try again after installing the required GCP utilities";
49
66
  }
50
67
  }),
51
- cloudProviderLogin: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
68
+ friendlyName: "Google Cloud",
69
+ loginRequiredMessage: "Please login to Google Cloud CLI with 'gcloud auth login'",
70
+ loginRequiredPattern: /You do not currently have an active account selected/,
71
+ propagationTimeoutMs: PROPAGATION_TIMEOUT_LIMIT_MS,
72
+ preTestAccessPropagationArgs: (cmdArgs) => {
73
+ if ((0, ssh_1.isSudoCommand)(cmdArgs)) {
74
+ return Object.assign(Object.assign({}, cmdArgs), {
75
+ // `sudo -v` prints `Sorry, user <user> may not run sudo on <hostname>.` to stderr when user is not a sudoer.
76
+ // It prints nothing to stdout when user is a sudoer - which is important because we don't want any output from the pre-test.
77
+ command: "sudo", arguments: ["-v"] });
78
+ }
79
+ return undefined;
80
+ },
52
81
  proxyCommand: (request) => {
53
82
  return [
54
83
  "gcloud",
@@ -66,16 +95,20 @@ exports.gcpSshProvider = {
66
95
  ];
67
96
  },
68
97
  reproCommands: () => undefined,
69
- preTestAccessPropagationArgs: (cmdArgs) => {
70
- if ((0, ssh_1.isSudoCommand)(cmdArgs)) {
71
- return Object.assign(Object.assign({}, cmdArgs), {
72
- // `sudo -v` prints `Sorry, user <user> may not run sudo on <hostname>.` to stderr when user is not a sudoer.
73
- // It prints nothing to stdout when user is a sudoer - which is important because we don't want any output from the pre-test.
74
- command: "sudo", arguments: ["-v"] });
75
- }
76
- return undefined;
98
+ requestToSsh: (request) => {
99
+ return {
100
+ id: request.permission.spec.instanceName,
101
+ projectId: request.permission.spec.projectId,
102
+ zone: request.permission.spec.zone,
103
+ linuxUserName: request.cliLocalData.linuxUserName,
104
+ type: "gcloud",
105
+ };
77
106
  },
78
- maxRetries: MAX_SSH_RETRIES,
79
- friendlyName: "Google Cloud",
107
+ unprovisionedAccessPatterns,
108
+ toCliRequest: (request, options) => __awaiter(void 0, void 0, void 0, function* () {
109
+ return (Object.assign(Object.assign({}, request), { cliLocalData: {
110
+ linuxUserName: yield (0, ssh_key_1.importSshKey)(request.permission.spec.publicKey, options),
111
+ } }));
112
+ }),
80
113
  };
81
114
  //# sourceMappingURL=ssh.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../../src/plugins/google/ssh.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;GASG;AACH,mDAA0D;AAE1D,uCAAgD;AAChD,uCAAyC;AAGzC;;;GAGG;AACH,MAAM,eAAe,GAAG,GAAG,CAAC;AAEf,QAAA,cAAc,GAIvB;IACF,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE;QACxB,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY;YACxC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS;YAC5C,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI;YAClC,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC,aAAa;YACjD,IAAI,EAAE,QAAQ;SACf,CAAC;IACJ,CAAC;IACD,YAAY,EAAE,CAAO,OAAO,EAAE,OAAO,EAAE,EAAE;QAAC,OAAA,iCACrC,OAAO,KACV,YAAY,EAAE;gBACZ,aAAa,EAAE,MAAM,IAAA,sBAAY,EAC/B,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,EACjC,OAAO,CACR;aACF,IACD,CAAA;MAAA;IACF,aAAa,EAAE,GAAS,EAAE;QACxB,IAAI,CAAC,CAAC,MAAM,IAAA,6BAAmB,GAAE,CAAC,EAAE;YAClC,MAAM,8DAA8D,CAAC;SACtE;IACH,CAAC,CAAA;IACD,kBAAkB,EAAE,GAAS,EAAE,kDAAC,OAAA,SAAS,CAAA,GAAA;IACzC,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE;QACxB,OAAO;YACL,QAAQ;YACR,SAAS;YACT,kBAAkB;YAClB,OAAO,CAAC,EAAE;YACV,IAAI;YACJ,kEAAkE;YAClE,oGAAoG;YACpG,oEAAoE;YACpE,kDAAkD;YAClD,mBAAmB;YACnB,UAAU,OAAO,CAAC,IAAI,EAAE;YACxB,aAAa,OAAO,CAAC,SAAS,EAAE;SACjC,CAAC;IACJ,CAAC;IACD,aAAa,EAAE,GAAG,EAAE,CAAC,SAAS;IAC9B,4BAA4B,EAAE,CAAC,OAAO,EAAE,EAAE;QACxC,IAAI,IAAA,mBAAa,EAAC,OAAO,CAAC,EAAE;YAC1B,uCACK,OAAO;gBACV,6GAA6G;gBAC7G,6HAA6H;gBAC7H,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,CAAC,IAAI,CAAC,IACjB;SACH;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,UAAU,EAAE,eAAe;IAC3B,YAAY,EAAE,cAAc;CAC7B,CAAC"}
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../../src/plugins/google/ssh.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;GASG;AACH,mDAA0D;AAE1D,uCAAgD;AAChD,uCAAyC;AAGzC,oGAAoG;AACpG,MAAM,4BAA4B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEnD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,2BAA2B,GAAG;IAClC,EAAE,OAAO,EAAE,iCAAiC,EAAE;IAC9C;QACE,mEAAmE;QACnE,OAAO,EAAE,uCAAuC;KACjD;IACD,EAAE,OAAO,EAAE,mDAAmD,EAAE;IAChE;QACE,OAAO,EAAE,+CAA+C;QACxD,kBAAkB,EAAE,IAAI;KACzB;IACD,EAAE,OAAO,EAAE,4DAA4D,EAAE;CACjE,CAAC;AAEE,QAAA,cAAc,GAIvB;IACF,uCAAuC;IACvC,kBAAkB,EAAE,GAAS,EAAE,kDAAC,OAAA,SAAS,CAAA,GAAA;IAEzC,aAAa,EAAE,GAAS,EAAE;QACxB,IAAI,CAAC,CAAC,MAAM,IAAA,6BAAmB,GAAE,CAAC,EAAE;YAClC,MAAM,8DAA8D,CAAC;SACtE;IACH,CAAC,CAAA;IAED,YAAY,EAAE,cAAc;IAE5B,oBAAoB,EAClB,2DAA2D;IAE7D,oBAAoB,EAAE,sDAAsD;IAE5E,oBAAoB,EAAE,4BAA4B;IAElD,4BAA4B,EAAE,CAAC,OAAO,EAAE,EAAE;QACxC,IAAI,IAAA,mBAAa,EAAC,OAAO,CAAC,EAAE;YAC1B,uCACK,OAAO;gBACV,6GAA6G;gBAC7G,6HAA6H;gBAC7H,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,CAAC,IAAI,CAAC,IACjB;SACH;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE;QACxB,OAAO;YACL,QAAQ;YACR,SAAS;YACT,kBAAkB;YAClB,OAAO,CAAC,EAAE;YACV,IAAI;YACJ,kEAAkE;YAClE,oGAAoG;YACpG,oEAAoE;YACpE,kDAAkD;YAClD,mBAAmB;YACnB,UAAU,OAAO,CAAC,IAAI,EAAE;YACxB,aAAa,OAAO,CAAC,SAAS,EAAE;SACjC,CAAC;IACJ,CAAC;IAED,aAAa,EAAE,GAAG,EAAE,CAAC,SAAS;IAE9B,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE;QACxB,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY;YACxC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS;YAC5C,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI;YAClC,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC,aAAa;YACjD,IAAI,EAAE,QAAQ;SACf,CAAC;IACJ,CAAC;IAED,2BAA2B;IAE3B,YAAY,EAAE,CAAO,OAAO,EAAE,OAAO,EAAE,EAAE;QAAC,OAAA,iCACrC,OAAO,KACV,YAAY,EAAE;gBACZ,aAAa,EAAE,MAAM,IAAA,sBAAY,EAC/B,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,EACjC,OAAO,CACR;aACF,IACD,CAAA;MAAA;CACH,CAAC"}
@@ -25,65 +25,11 @@ const keys_1 = require("../../common/keys");
25
25
  const stdio_1 = require("../../drivers/stdio");
26
26
  const util_1 = require("../../util");
27
27
  const node_child_process_1 = require("node:child_process");
28
- /** Matches the error message that AWS SSM print1 when access is not propagated */
29
- // Note that the resource will randomly be either the SSM document or the EC2 instance
30
- const UNAUTHORIZED_START_SESSION_MESSAGE = /An error occurred \(AccessDeniedException\) when calling the StartSession operation: User: arn:aws:sts::.*:assumed-role\/P0GrantsRole.* is not authorized to perform: ssm:StartSession on resource: arn:aws:.*:.*:.* because no identity-based policy allows the ssm:StartSession action/;
31
- /**
32
- * Matches the following error messages that AWS SSM print1 when ssh authorized
33
- * key access hasn't propagated to the instance yet.
34
- * - Connection closed by UNKNOWN port 65535
35
- * - scp: Connection closed
36
- * - kex_exchange_identification: Connection closed by remote host
37
- */
38
- const CONNECTION_CLOSED_MESSAGE = /\bConnection closed\b.*\b(?:by UNKNOWN port \d+|by remote host)?/;
39
- const PUBLIC_KEY_DENIED_MESSAGE = /Permission denied \(publickey\)/;
40
- const UNAUTHORIZED_TUNNEL_USER_MESSAGE = /Error while connecting \[4033: 'not authorized'\]/;
41
- const UNAUTHORIZED_INSTANCES_GET_MESSAGE = /Required 'compute\.instances\.get' permission/;
42
- const DESTINATION_READ_ERROR = /Error while connecting \[4010: 'destination read failed'\]/;
43
- const GOOGLE_LOGIN_MESSAGE = /You do not currently have an active account selected/;
44
- const SUDO_MESSAGE = /Sorry, user .+ may not run sudo on .+/; // The output of `sudo -v` when the user is not allowed to run sudo
45
28
  /** Maximum amount of time after SSH subprocess starts to check for {@link UNPROVISIONED_ACCESS_MESSAGES}
46
29
  * in the process's stderr
47
30
  */
48
31
  const DEFAULT_VALIDATION_WINDOW_MS = 5e3;
49
- const RETRY_DELAY_MS = 1000;
50
- /**
51
- * AWS
52
- * There are 2 cases of unprovisioned access in AWS
53
- * 1. SSM:StartSession action is missing either on the SSM document (AWS-StartSSHSession) or the EC2 instance
54
- * 2. Temporary error when issuing an SCP command
55
- *
56
- * 1: results in UNAUTHORIZED_START_SESSION_MESSAGE
57
- * 2: results in CONNECTION_CLOSED_MESSAGE
58
- *
59
- * Google Cloud
60
- * There are 7 cases of unprovisioned access in Google Cloud.
61
- * These are all potentially subject to propagation delays.
62
- * 1. The linux user name is not present in the user's Google Workspace profile `posixAccounts` attribute
63
- * 2. The public key is not present in the user's Google Workspace profile `sshPublicKeys` attribute
64
- * 3. The user cannot act as the service account of the compute instance
65
- * 4. The user cannot tunnel through the IAP tunnel to the instance
66
- * 5. The user doesn't have osLogin or osAdminLogin role to the instance
67
- * 5.a. compute.instances.get permission is missing
68
- * 5.b. compute.instances.osLogin permission is missing
69
- * 6. compute.instances.osAdminLogin is not provisioned but compute.instances.osLogin is - happens when a user upgrades existing access to sudo
70
- * 7: Rare occurrence, the exact conditions so far undetermined (together with CONNECTION_CLOSED_MESSAGE)
71
- *
72
- * 1, 2, 3 (yes!), 5b: result in PUBLIC_KEY_DENIED_MESSAGE
73
- * 4: results in UNAUTHORIZED_TUNNEL_USER_MESSAGE and also CONNECTION_CLOSED_MESSAGE
74
- * 5a: results in UNAUTHORIZED_INSTANCES_GET_MESSAGE
75
- * 6: results in SUDO_MESSAGE
76
- * 7: results in DESTINATION_READ_ERROR and also CONNECTION_CLOSED_MESSAGE
77
- */
78
- const UNPROVISIONED_ACCESS_MESSAGES = [
79
- { pattern: UNAUTHORIZED_START_SESSION_MESSAGE },
80
- { pattern: CONNECTION_CLOSED_MESSAGE },
81
- { pattern: PUBLIC_KEY_DENIED_MESSAGE },
82
- { pattern: SUDO_MESSAGE },
83
- { pattern: UNAUTHORIZED_TUNNEL_USER_MESSAGE },
84
- { pattern: UNAUTHORIZED_INSTANCES_GET_MESSAGE, validationWindowMs: 30e3 },
85
- { pattern: DESTINATION_READ_ERROR },
86
- ];
32
+ const RETRY_DELAY_MS = 5000;
87
33
  /** Checks if access has propagated through AWS to the SSM agent
88
34
  *
89
35
  * AWS takes about 8 minutes, GCP takes under 1 minute
@@ -100,77 +46,102 @@ const UNPROVISIONED_ACCESS_MESSAGES = [
100
46
  * This works because AWS SSM wraps the session in a single-stream pty, so we
101
47
  * do not capture stderr emitted from the wrapped shell session.
102
48
  */
103
- const accessPropagationGuard = (child, debug) => {
49
+ const accessPropagationGuard = (provider, child, options) => {
104
50
  let isEphemeralAccessDeniedException = false;
105
- let isGoogleLoginException = false;
106
- const beforeStart = Date.now();
51
+ let isLoginException = false;
107
52
  child.stderr.on("data", (chunk) => {
108
53
  const chunkString = chunk.toString("utf-8");
109
- if (debug)
110
- (0, stdio_1.print2)(chunkString);
111
- const match = UNPROVISIONED_ACCESS_MESSAGES.find((message) => chunkString.match(message.pattern));
112
- if (match &&
113
- Date.now() <=
114
- beforeStart + (match.validationWindowMs || DEFAULT_VALIDATION_WINDOW_MS)) {
54
+ parseAndPrintSshOutputToStderr(chunkString, options);
55
+ const match = provider.unprovisionedAccessPatterns.find((message) => chunkString.match(message.pattern));
56
+ if (match) {
115
57
  isEphemeralAccessDeniedException = true;
116
58
  }
117
- const googleLoginMatch = chunkString.match(GOOGLE_LOGIN_MESSAGE);
118
- isGoogleLoginException = isGoogleLoginException || !!googleLoginMatch; // once true, always true
119
- if (isGoogleLoginException) {
59
+ if (provider.loginRequiredPattern) {
60
+ const loginMatch = chunkString.match(provider.loginRequiredPattern);
61
+ isLoginException = isLoginException || !!loginMatch; // once true, always true
62
+ }
63
+ if (isLoginException) {
120
64
  isEphemeralAccessDeniedException = false; // always overwrite to false so we don't retry the access
121
65
  }
122
66
  });
123
67
  return {
124
68
  isAccessPropagated: () => !isEphemeralAccessDeniedException,
125
- isGoogleLoginException: () => isGoogleLoginException,
69
+ isLoginException: () => isLoginException,
126
70
  };
127
71
  };
72
+ /**
73
+ * Parses and prints a chunk of SSH output to stderr.
74
+ *
75
+ * If debug is enabled, all output is printed. Otherwise, only selected messages are printed.
76
+ *
77
+ * @param chunkString the chunk to print
78
+ * @param options SSH spawn options
79
+ */
80
+ const parseAndPrintSshOutputToStderr = (chunkString, options) => {
81
+ const lines = chunkString.split("\n");
82
+ const isPreTest = options.isAccessPropagationPreTest;
83
+ for (const line of lines) {
84
+ if (options.debug) {
85
+ (0, stdio_1.print2)(line);
86
+ }
87
+ else {
88
+ if (!isPreTest && line.includes("Authenticated to")) {
89
+ // We want to let the user know that they successfully authenticated
90
+ (0, stdio_1.print2)(line);
91
+ }
92
+ else if (!isPreTest && line.includes("port forwarding failed")) {
93
+ // We also want to let the user know if port forwarding failed
94
+ (0, stdio_1.print2)(line);
95
+ }
96
+ }
97
+ }
98
+ };
128
99
  const spawnChildProcess = (credential, command, args, stdio) => (0, node_child_process_1.spawn)(command, args, {
129
100
  env: Object.assign(Object.assign({}, process.env), credential),
130
101
  stdio,
131
102
  shell: false,
132
103
  });
133
- /** Starts an SSM session in the terminal by spawning `aws ssm` as a subprocess
134
- *
135
- * Requires `aws ssm` to be installed on the client machine.
136
- */
137
104
  function spawnSshNode(options) {
138
105
  return __awaiter(this, void 0, void 0, function* () {
139
106
  return new Promise((resolve, reject) => {
140
107
  const provider = ssh_1.SSH_PROVIDERS[options.provider];
141
- const attemptsRemaining = options.attemptsRemaining;
142
108
  if (options.debug) {
143
109
  const gerund = options.isAccessPropagationPreTest
144
110
  ? "Pre-testing"
145
111
  : "Trying";
146
- (0, stdio_1.print2)(`Waiting for access to propagate. ${gerund} SSH session... (remaining attempts: ${attemptsRemaining})`);
112
+ const remainingSeconds = ((options.endTime - Date.now()) / 1e3).toFixed(1);
113
+ (0, stdio_1.print2)(`Waiting for access to propagate. ${gerund} SSH session... (will wait up to ${remainingSeconds} seconds)`);
147
114
  }
148
115
  const child = spawnChildProcess(options.credential, options.command, options.args, options.stdio);
149
116
  // TODO ENG-2284 support login with Google Cloud: currently return a boolean to indicate if the exception was a Google login error.
150
- const { isAccessPropagated, isGoogleLoginException } = accessPropagationGuard(child, options.debug);
117
+ const { isAccessPropagated, isLoginException } = accessPropagationGuard(provider, child, options);
151
118
  const exitListener = child.on("exit", (code) => {
152
- var _a;
119
+ var _a, _b;
153
120
  exitListener.unref();
154
121
  // In the case of ephemeral AccessDenied exceptions due to unpropagated
155
122
  // permissions, continually retry access until success
156
123
  if (!isAccessPropagated()) {
157
- if (attemptsRemaining <= 0) {
158
- reject(`Access did not propagate through ${provider.friendlyName} before max retry attempts were exceeded. Please contact support@p0.dev for assistance.`);
124
+ if (options.endTime < Date.now()) {
125
+ reject(`Access did not propagate through ${provider.friendlyName} in time. Please contact support@p0.dev for assistance.`);
159
126
  return;
160
127
  }
161
128
  (0, util_1.delay)(RETRY_DELAY_MS)
162
- .then(() => spawnSshNode(Object.assign(Object.assign({}, options), { attemptsRemaining: attemptsRemaining - 1 })))
129
+ .then(() => spawnSshNode(options))
163
130
  .then((code) => resolve(code))
164
131
  .catch(reject);
165
132
  return;
166
133
  }
167
- else if (isGoogleLoginException()) {
168
- reject(`Please login to Google Cloud CLI with 'gcloud auth login'`);
134
+ else if (isLoginException()) {
135
+ reject((_a = provider.loginRequiredMessage) !== null && _a !== void 0 ? _a : `Please log in to the ${provider.friendlyName} CLI to SSH`);
169
136
  return;
170
137
  }
171
- (_a = options.abortController) === null || _a === void 0 ? void 0 : _a.abort(code);
172
- if (!options.isAccessPropagationPreTest)
138
+ (_b = options.abortController) === null || _b === void 0 ? void 0 : _b.abort(code);
139
+ if (code && code !== 0) {
140
+ (0, stdio_1.print2)(`Failed to establish an SSH session.${!options.debug ? " Use the --debug option to see additional details." : ""}`);
141
+ }
142
+ else if (!options.isAccessPropagationPreTest) {
173
143
  (0, stdio_1.print2)(`SSH session terminated`);
144
+ }
174
145
  resolve(code);
175
146
  });
176
147
  });
@@ -227,6 +198,7 @@ const addCommonArgs = (args, proxyCommand) => {
227
198
  if (!proxyCommandExists) {
228
199
  sshOptions.push("-o", `ProxyCommand=${proxyCommand.join(" ")}`);
229
200
  }
201
+ // Force verbose output from SSH so we can parse the output
230
202
  const verboseOptionExists = sshOptions.some((opt) => opt === "-v");
231
203
  if (!verboseOptionExists) {
232
204
  sshOptions.push("-v");
@@ -261,7 +233,7 @@ const transformForShell = (args) => {
261
233
  });
262
234
  };
263
235
  /** Construct another command to use for testing access propagation prior to actually logging in the user to the ssh session */
264
- const preTestAccessPropagationIfNeeded = (sshProvider, request, cmdArgs, proxyCommand, credential) => __awaiter(void 0, void 0, void 0, function* () {
236
+ const preTestAccessPropagationIfNeeded = (sshProvider, request, cmdArgs, proxyCommand, credential, endTime) => __awaiter(void 0, void 0, void 0, function* () {
265
237
  const testCmdArgs = sshProvider.preTestAccessPropagationArgs(cmdArgs);
266
238
  // Pre-testing comes at a performance cost because we have to execute another ssh subprocess after
267
239
  // a successful test. Only do when absolutely necessary.
@@ -276,7 +248,7 @@ const preTestAccessPropagationIfNeeded = (sshProvider, request, cmdArgs, proxyCo
276
248
  stdio: ["inherit", "inherit", "pipe"],
277
249
  debug: cmdArgs.debug,
278
250
  provider: request.type,
279
- attemptsRemaining: sshProvider.maxRetries,
251
+ endTime: endTime,
280
252
  isAccessPropagationPreTest: true,
281
253
  });
282
254
  }
@@ -300,9 +272,11 @@ const sshOrScp = (args) => __awaiter(void 0, void 0, void 0, function* () {
300
272
  (0, stdio_1.print2)(`Execute the following commands to create a similar SSH/SCP session:\n*** COMMANDS BEGIN ***\n${repro}\n*** COMMANDS END ***"\n`);
301
273
  }
302
274
  }
303
- const exitCode = yield preTestAccessPropagationIfNeeded(sshProvider, request, cmdArgs, proxyCommand, credential);
275
+ const endTime = Date.now() + sshProvider.propagationTimeoutMs;
276
+ const exitCode = yield preTestAccessPropagationIfNeeded(sshProvider, request, cmdArgs, proxyCommand, credential, endTime);
277
+ // Only exit if there was an error when pre-testing
304
278
  if (exitCode && exitCode !== 0) {
305
- return exitCode; // Only exit if there was an error when pre-testing
279
+ return exitCode;
306
280
  }
307
281
  return spawnSshNode({
308
282
  credential,
@@ -312,7 +286,7 @@ const sshOrScp = (args) => __awaiter(void 0, void 0, void 0, function* () {
312
286
  stdio: ["inherit", "inherit", "pipe"],
313
287
  debug: cmdArgs.debug,
314
288
  provider: request.type,
315
- attemptsRemaining: sshProvider.maxRetries,
289
+ endTime: endTime,
316
290
  });
317
291
  });
318
292
  exports.sshOrScp = sshOrScp;