@p0security/cli 0.8.2 → 0.9.0

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/dist/commands/__tests__/grant.test.d.ts +1 -0
  2. package/dist/commands/__tests__/grant.test.js +55 -0
  3. package/dist/commands/aws/files.d.ts +41 -0
  4. package/dist/commands/aws/files.js +108 -0
  5. package/dist/commands/grant.d.ts +4 -0
  6. package/dist/commands/grant.js +17 -0
  7. package/dist/commands/index.js +4 -0
  8. package/dist/commands/kubeconfig.d.ts +9 -0
  9. package/dist/commands/kubeconfig.js +190 -0
  10. package/dist/commands/request.d.ts +0 -8
  11. package/dist/commands/request.js +3 -102
  12. package/dist/commands/shared/index.d.ts +2 -2
  13. package/dist/commands/shared/index.js +1 -1
  14. package/dist/commands/shared/request.d.ts +14 -0
  15. package/dist/commands/shared/request.js +115 -0
  16. package/dist/commands/shared/ssh.d.ts +7 -1
  17. package/dist/commands/shared/ssh.js +9 -7
  18. package/dist/common/install.d.ts +11 -0
  19. package/dist/common/install.js +120 -0
  20. package/dist/drivers/stdio.d.ts +1 -0
  21. package/dist/drivers/stdio.js +1 -0
  22. package/dist/plugins/aws/ssh.d.ts +2 -2
  23. package/dist/plugins/aws/ssh.js +54 -0
  24. package/dist/plugins/aws/ssm/install.js +7 -86
  25. package/dist/plugins/google/ssh.d.ts +0 -10
  26. package/dist/plugins/google/ssh.js +45 -0
  27. package/dist/plugins/kubeconfig/index.d.ts +23 -0
  28. package/dist/plugins/kubeconfig/index.js +98 -0
  29. package/dist/plugins/kubeconfig/install.d.ts +1 -0
  30. package/dist/plugins/kubeconfig/install.js +65 -0
  31. package/dist/plugins/kubeconfig/types.d.ts +57 -0
  32. package/dist/plugins/kubeconfig/types.js +2 -0
  33. package/dist/plugins/ssh/index.d.ts +2 -2
  34. package/dist/plugins/ssh/index.js +65 -93
  35. package/dist/types/request.d.ts +2 -1
  36. package/dist/types/ssh.d.ts +21 -1
  37. package/dist/util.d.ts +11 -0
  38. package/dist/util.js +13 -1
  39. package/package.json +7 -3
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.request = exports.requestArgs = void 0;
13
+ /** Copyright © 2024-present P0 Security
14
+
15
+ This file is part of @p0security/cli
16
+
17
+ @p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
18
+
19
+ @p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
20
+
21
+ You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
22
+ **/
23
+ const api_1 = require("../../drivers/api");
24
+ const auth_1 = require("../../drivers/auth");
25
+ const firestore_1 = require("../../drivers/firestore");
26
+ const stdio_1 = require("../../drivers/stdio");
27
+ const firestore_2 = require("firebase/firestore");
28
+ const typescript_1 = require("typescript");
29
+ const WAIT_TIMEOUT = 300e3;
30
+ const APPROVED = { message: "Your request was approved", code: 0 };
31
+ const DENIED = { message: "Your request was denied", code: 2 };
32
+ const ERRORED = { message: "Your request encountered an error", code: 1 };
33
+ const COMPLETED_REQUEST_STATUSES = {
34
+ APPROVED,
35
+ APPROVED_NOTIFIED: APPROVED,
36
+ DONE: APPROVED,
37
+ DONE_NOTIFIED: APPROVED,
38
+ DENIED,
39
+ ERRORED,
40
+ };
41
+ const isCompletedStatus = (status) => status in COMPLETED_REQUEST_STATUSES;
42
+ const requestArgs = (yargs) => yargs
43
+ .parserConfiguration({ "unknown-options-as-args": true })
44
+ .help(false) // Turn off help in order to forward the --help command to the backend so P0 can provide the available requestable resources
45
+ .option("wait", {
46
+ alias: "w",
47
+ boolean: true,
48
+ default: false,
49
+ describe: "Block until the command is completed",
50
+ })
51
+ .option("arguments", {
52
+ array: true,
53
+ string: true,
54
+ default: [],
55
+ });
56
+ exports.requestArgs = requestArgs;
57
+ const waitForRequest = (tenantId, requestId, logMessage) => __awaiter(void 0, void 0, void 0, function* () {
58
+ return yield new Promise((resolve) => {
59
+ if (logMessage)
60
+ (0, stdio_1.print2)("Will wait up to 5 minutes for this request to complete...");
61
+ let cancel = undefined;
62
+ const unsubscribe = (0, firestore_2.onSnapshot)((0, firestore_1.doc)(`o/${tenantId}/permission-requests/${requestId}`), (snap) => {
63
+ const data = snap.data();
64
+ if (!data)
65
+ return;
66
+ const { status } = data;
67
+ if (isCompletedStatus(status)) {
68
+ if (cancel)
69
+ clearTimeout(cancel);
70
+ unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe();
71
+ const { message, code } = COMPLETED_REQUEST_STATUSES[status];
72
+ if (code !== 0 || logMessage)
73
+ (0, stdio_1.print2)(message);
74
+ resolve(code);
75
+ }
76
+ });
77
+ cancel = setTimeout(() => {
78
+ unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe();
79
+ (0, stdio_1.print2)("Your request did not complete within 5 minutes.");
80
+ resolve(4);
81
+ }, WAIT_TIMEOUT);
82
+ });
83
+ });
84
+ const request = (command) => (args, authn, options) => __awaiter(void 0, void 0, void 0, function* () {
85
+ const resolvedAuthn = authn !== null && authn !== void 0 ? authn : (yield (0, auth_1.authenticate)());
86
+ const { userCredential } = resolvedAuthn;
87
+ const data = yield (0, api_1.fetchCommand)(resolvedAuthn, args, [
88
+ command,
89
+ ...args.arguments,
90
+ ]);
91
+ if (data && "ok" in data && "message" in data && data.ok) {
92
+ const logMessage = !(options === null || options === void 0 ? void 0 : options.message) ||
93
+ (options === null || options === void 0 ? void 0 : options.message) === "all" ||
94
+ ((options === null || options === void 0 ? void 0 : options.message) === "approval-required" &&
95
+ !data.isPreexisting &&
96
+ !data.isPersistent);
97
+ if (logMessage)
98
+ (0, stdio_1.print2)(data.message);
99
+ const { id } = data;
100
+ if (args.wait && id && userCredential.user.tenantId) {
101
+ const code = yield waitForRequest(userCredential.user.tenantId, id, logMessage);
102
+ if (code) {
103
+ typescript_1.sys.exit(code);
104
+ return undefined;
105
+ }
106
+ return data;
107
+ }
108
+ else
109
+ return undefined;
110
+ }
111
+ else {
112
+ throw data;
113
+ }
114
+ });
115
+ exports.request = request;
@@ -1,6 +1,6 @@
1
1
  import { Authn } from "../../types/identity";
2
2
  import { Request } from "../../types/request";
3
- import { CliSshRequest, SshRequest, SupportedSshProvider } from "../../types/ssh";
3
+ import { CliSshRequest, SshProvider, SshRequest, SupportedSshProvider } from "../../types/ssh";
4
4
  import yargs from "yargs";
5
5
  export declare type BaseSshCommandArgs = {
6
6
  sudo?: boolean;
@@ -23,6 +23,12 @@ export declare type SshCommandArgs = BaseSshCommandArgs & {
23
23
  arguments: string[];
24
24
  command?: string;
25
25
  };
26
+ export declare type CommandArgs = ScpCommandArgs | SshCommandArgs;
27
+ export declare const SSH_PROVIDERS: Record<SupportedSshProvider, SshProvider<any, any, any, any>>;
28
+ export declare const isSudoCommand: (args: {
29
+ sudo?: boolean;
30
+ command?: string;
31
+ }) => boolean;
26
32
  export declare const provisionRequest: (authn: Authn, args: yargs.ArgumentsCamelCase<BaseSshCommandArgs>, destination: string) => Promise<{
27
33
  request: Request<CliSshRequest>;
28
34
  publicKey: string;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.requestToSsh = exports.provisionRequest = void 0;
12
+ exports.requestToSsh = exports.provisionRequest = exports.isSudoCommand = exports.SSH_PROVIDERS = void 0;
13
13
  /** Copyright © 2024-present P0 Security
14
14
 
15
15
  This file is part of @p0security/cli
@@ -27,10 +27,10 @@ const stdio_1 = require("../../drivers/stdio");
27
27
  const ssh_1 = require("../../plugins/aws/ssh");
28
28
  const ssh_2 = require("../../plugins/google/ssh");
29
29
  const ssh_3 = require("../../types/ssh");
30
- const request_1 = require("../request");
30
+ const request_1 = require("./request");
31
31
  const firestore_2 = require("firebase/firestore");
32
32
  const lodash_1 = require("lodash");
33
- const SSH_PROVIDERS = {
33
+ exports.SSH_PROVIDERS = {
34
34
  aws: ssh_1.awsSshProvider,
35
35
  gcloud: ssh_2.gcpSshProvider,
36
36
  };
@@ -48,19 +48,21 @@ const validateSshInstall = (authn, args) => __awaiter(void 0, void 0, void 0, fu
48
48
  }
49
49
  });
50
50
  const pluginToCliRequest = (request, options) => __awaiter(void 0, void 0, void 0, function* () {
51
- return yield SSH_PROVIDERS[request.permission.spec.type].toCliRequest(request, options);
51
+ return yield exports.SSH_PROVIDERS[request.permission.spec.type].toCliRequest(request, options);
52
52
  });
53
+ const isSudoCommand = (args) => args.sudo || args.command === "sudo";
54
+ exports.isSudoCommand = isSudoCommand;
53
55
  const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0, void 0, function* () {
54
56
  yield validateSshInstall(authn, args);
55
57
  const { publicKey, privateKey } = yield (0, keys_1.createKeyPair)();
56
- const response = yield (0, request_1.request)(Object.assign(Object.assign({}, (0, lodash_1.pick)(args, "$0", "_")), { arguments: [
58
+ const response = yield (0, request_1.request)("request")(Object.assign(Object.assign({}, (0, lodash_1.pick)(args, "$0", "_")), { arguments: [
57
59
  "ssh",
58
60
  "session",
59
61
  destination,
60
62
  "--public-key",
61
63
  publicKey,
62
64
  ...(args.provider ? ["--provider", args.provider] : []),
63
- ...(args.sudo || args.command === "sudo" ? ["--sudo"] : []),
65
+ ...((0, exports.isSudoCommand)(args) ? ["--sudo"] : []),
64
66
  ...(args.reason ? ["--reason", args.reason] : []),
65
67
  ...(args.account ? ["--account", args.account] : []),
66
68
  ], wait: true }), authn, { message: "approval-required" });
@@ -81,5 +83,5 @@ const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0,
81
83
  return { request: cliRequest, publicKey, privateKey };
82
84
  });
83
85
  exports.provisionRequest = provisionRequest;
84
- const requestToSsh = (request) => SSH_PROVIDERS[request.permission.spec.type].requestToSsh(request);
86
+ const requestToSsh = (request) => exports.SSH_PROVIDERS[request.permission.spec.type].requestToSsh(request);
85
87
  exports.requestToSsh = requestToSsh;
@@ -0,0 +1,11 @@
1
+ export declare const SupportedPlatforms: readonly ["darwin"];
2
+ export declare type SupportedPlatform = (typeof SupportedPlatforms)[number];
3
+ export declare const AwsItems: readonly ["aws"];
4
+ export declare type AwsItem = (typeof AwsItems)[number];
5
+ export declare type InstallMetadata = {
6
+ label: string;
7
+ commands: Record<SupportedPlatform, Readonly<string[]>>;
8
+ };
9
+ export declare const AwsInstall: Readonly<Record<AwsItem, InstallMetadata>>;
10
+ export declare const guidedInstall: <T extends string, U extends Readonly<Record<T, InstallMetadata>>>(platform: SupportedPlatform, item: T, installData: U) => Promise<void>;
11
+ export declare const ensureInstall: <T extends string, U extends Readonly<Record<T, InstallMetadata>>>(installItems: readonly T[], installData: U) => Promise<boolean>;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ensureInstall = exports.guidedInstall = exports.AwsInstall = exports.AwsItems = exports.SupportedPlatforms = void 0;
16
+ /** Copyright © 2024-present P0 Security
17
+
18
+ This file is part of @p0security/cli
19
+
20
+ @p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
21
+
22
+ @p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
23
+
24
+ You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
25
+ **/
26
+ const stdio_1 = require("../drivers/stdio");
27
+ const types_1 = require("../types");
28
+ const lodash_1 = require("lodash");
29
+ const node_child_process_1 = require("node:child_process");
30
+ const node_os_1 = __importDefault(require("node:os"));
31
+ const typescript_1 = require("typescript");
32
+ const which_1 = __importDefault(require("which"));
33
+ exports.SupportedPlatforms = ["darwin"];
34
+ exports.AwsItems = ["aws"];
35
+ exports.AwsInstall = {
36
+ aws: {
37
+ label: "AWS CLI v2",
38
+ commands: {
39
+ darwin: [
40
+ 'curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"',
41
+ "sudo installer -pkg AWSCLIV2.pkg -target /",
42
+ 'rm "AWSCLIV2.pkg"',
43
+ ],
44
+ },
45
+ },
46
+ };
47
+ const printToInstall = (toInstall, installMetadata) => {
48
+ (0, stdio_1.print2)("The following items must be installed on your system to continue:");
49
+ for (const item of toInstall) {
50
+ (0, stdio_1.print2)(` - ${installMetadata[item].label} (${item})`);
51
+ }
52
+ (0, stdio_1.print2)("");
53
+ };
54
+ const queryInteractive = () => __awaiter(void 0, void 0, void 0, function* () {
55
+ const inquirer = (yield import("inquirer")).default;
56
+ const { isGuided } = yield inquirer.prompt([
57
+ {
58
+ type: "confirm",
59
+ name: "isGuided",
60
+ message: "Do you want P0 to install these for you (sudo access required)?",
61
+ },
62
+ ]);
63
+ (0, stdio_1.print2)("");
64
+ return isGuided;
65
+ });
66
+ const requiredInstalls = (installItems) => __awaiter(void 0, void 0, void 0, function* () {
67
+ return (0, lodash_1.compact)(yield Promise.all(installItems.map((item) => __awaiter(void 0, void 0, void 0, function* () { return (yield (0, which_1.default)(item, { nothrow: true })) === null ? item : undefined; }))));
68
+ });
69
+ const printInstallCommands = (platform, item, installData) => {
70
+ const { label, commands } = installData[item];
71
+ (0, stdio_1.print2)(`To install ${label}, run the following commands:\n`);
72
+ for (const command of commands[platform]) {
73
+ (0, stdio_1.print1)(` ${command}`);
74
+ }
75
+ (0, stdio_1.print1)(""); // Newline is useful for reading command output in a script, so send to /fd/1
76
+ };
77
+ const guidedInstall = (platform, item, installData) => __awaiter(void 0, void 0, void 0, function* () {
78
+ const commands = installData[item].commands[platform];
79
+ const combined = commands.join(" && \\\n");
80
+ (0, stdio_1.print2)(`Executing:\n${combined}`);
81
+ (0, stdio_1.print2)("");
82
+ yield new Promise((resolve, reject) => {
83
+ const child = (0, node_child_process_1.spawn)("bash", ["-c", combined], { stdio: "inherit" });
84
+ child.on("exit", (code) => {
85
+ if (code === 0)
86
+ resolve();
87
+ else
88
+ reject(`Shell exited with code ${code}`);
89
+ });
90
+ });
91
+ (0, stdio_1.print2)("");
92
+ });
93
+ exports.guidedInstall = guidedInstall;
94
+ const ensureInstall = (installItems, installData) => __awaiter(void 0, void 0, void 0, function* () {
95
+ var _a;
96
+ const toInstall = yield requiredInstalls(installItems);
97
+ if (toInstall.length === 0) {
98
+ return true;
99
+ }
100
+ const platform = node_os_1.default.platform();
101
+ printToInstall(toInstall, installData);
102
+ if (!(0, types_1.isa)(exports.SupportedPlatforms)(platform)) {
103
+ throw (`Guided dependency installation is not available on platform ${platform}\n` +
104
+ "Please install the above dependencies manually, or ensure they are on your PATH.");
105
+ }
106
+ const interactive = !!((_a = typescript_1.sys.writeOutputIsTTY) === null || _a === void 0 ? void 0 : _a.call(typescript_1.sys)) && (yield queryInteractive());
107
+ for (const item of toInstall) {
108
+ if (interactive)
109
+ yield (0, exports.guidedInstall)(platform, item, installData);
110
+ else
111
+ printInstallCommands(platform, item, installData);
112
+ }
113
+ const remaining = yield requiredInstalls(installItems);
114
+ if (remaining.length === 0) {
115
+ (0, stdio_1.print2)("All packages successfully installed");
116
+ return true;
117
+ }
118
+ return false;
119
+ });
120
+ exports.ensureInstall = ensureInstall;
@@ -22,4 +22,5 @@ export declare function print2(message: any): void;
22
22
  export declare const Ansi: {
23
23
  readonly Reset: string;
24
24
  readonly Dim: string;
25
+ readonly Yellow: string;
25
26
  };
@@ -40,5 +40,6 @@ exports.print2 = print2;
40
40
  const AnsiCodes = {
41
41
  Reset: "00",
42
42
  Dim: "02",
43
+ Yellow: "33",
43
44
  };
44
45
  exports.Ansi = (0, lodash_1.mapValues)(AnsiCodes, (v) => `\u001b[${v}m`);
@@ -9,5 +9,5 @@ This file is part of @p0security/cli
9
9
  You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
10
10
  **/
11
11
  import { SshProvider } from "../../types/ssh";
12
- import { AwsSshPermissionSpec, AwsSshRequest } from "./types";
13
- export declare const awsSshProvider: SshProvider<AwsSshPermissionSpec, undefined, AwsSshRequest>;
12
+ import { AwsCredentials, AwsSshPermissionSpec, AwsSshRequest } from "./types";
13
+ export declare const awsSshProvider: SshProvider<AwsSshPermissionSpec, undefined, AwsSshRequest, AwsCredentials>;
@@ -10,6 +10,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.awsSshProvider = void 0;
13
+ const util_1 = require("../../util");
14
+ const aws_1 = require("../okta/aws");
15
+ const config_1 = require("./config");
16
+ const idc_1 = require("./idc");
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;
23
+ /** The name of the SessionManager port forwarding document. This document is managed by AWS. */
24
+ const START_SSH_SESSION_DOCUMENT_NAME = "AWS-StartSSHSession";
13
25
  exports.awsSshProvider = {
14
26
  requestToSsh: (request) => {
15
27
  const { permission, generated } = request;
@@ -21,4 +33,46 @@ exports.awsSshProvider = {
21
33
  ? Object.assign(Object.assign({}, common), { role: name, type: "aws", access: "role" }) : Object.assign(Object.assign({}, common), { idc, permissionSet: name, type: "aws", access: "idc" });
22
34
  },
23
35
  toCliRequest: (request) => __awaiter(void 0, void 0, void 0, function* () { return (Object.assign(Object.assign({}, request), { cliLocalData: undefined })); }),
36
+ cloudProviderLogin: (authn, request) => __awaiter(void 0, void 0, void 0, function* () {
37
+ var _a, _b, _c, _d;
38
+ if (!(yield (0, install_1.ensureSsmInstall)())) {
39
+ throw "Please try again after installing the required AWS utilities";
40
+ }
41
+ const { config } = yield (0, config_1.getAwsConfig)(authn, request.accountId);
42
+ if (!((_a = config.login) === null || _a === void 0 ? void 0 : _a.type) || ((_b = config.login) === null || _b === void 0 ? void 0 : _b.type) === "iam") {
43
+ throw "This account is not configured for SSH access via the P0 CLI";
44
+ }
45
+ return ((_c = config.login) === null || _c === void 0 ? void 0 : _c.type) === "idc"
46
+ ? yield (0, idc_1.assumeRoleWithIdc)(request)
47
+ : ((_d = config.login) === null || _d === void 0 ? void 0 : _d.type) === "federated"
48
+ ? yield (0, aws_1.assumeRoleWithOktaSaml)(authn, request)
49
+ : (0, util_1.throwAssertNever)(config.login);
50
+ }),
51
+ proxyCommand: (request) => {
52
+ return [
53
+ "aws",
54
+ "ssm",
55
+ "start-session",
56
+ "--region",
57
+ request.region,
58
+ "--target",
59
+ "%h",
60
+ "--document-name",
61
+ START_SSH_SESSION_DOCUMENT_NAME,
62
+ "--parameters",
63
+ '"portNumber=%p"',
64
+ ];
65
+ },
66
+ reproCommands: (request) => {
67
+ // TODO: Add manual commands for IDC login
68
+ if (request.access !== "idc") {
69
+ return [
70
+ `eval $(p0 aws role assume ${request.role} --account ${request.accountId})`,
71
+ ];
72
+ }
73
+ return undefined;
74
+ },
75
+ preTestAccessPropagationArgs: () => undefined,
76
+ maxRetries: MAX_SSH_RETRIES,
77
+ friendlyName: "AWS",
24
78
  };
@@ -23,27 +23,11 @@ This file is part of @p0security/cli
23
23
 
24
24
  You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
25
25
  **/
26
- const stdio_1 = require("../../../drivers/stdio");
26
+ const install_1 = require("../../../common/install");
27
27
  const types_1 = require("../../../types");
28
- const lodash_1 = require("lodash");
29
- const node_child_process_1 = require("node:child_process");
30
28
  const node_os_1 = __importDefault(require("node:os"));
31
- const typescript_1 = require("typescript");
32
- const which_1 = __importDefault(require("which"));
33
- const SupportedPlatforms = ["darwin"];
34
- const AwsItems = ["aws", "session-manager-plugin"];
35
- const AwsInstall = {
36
- aws: {
37
- label: "AWS CLI v2",
38
- commands: {
39
- darwin: [
40
- 'curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"',
41
- "sudo installer -pkg AWSCLIV2.pkg -target /",
42
- 'rm "AWSCLIV2.pkg"',
43
- ],
44
- },
45
- },
46
- "session-manager-plugin": {
29
+ const SsmItems = [...install_1.AwsItems, "session-manager-plugin"];
30
+ const SsmInstall = Object.assign(Object.assign({}, install_1.AwsInstall), { "session-manager-plugin": {
47
31
  label: "the AWS CLI Session Manager plugin",
48
32
  commands: {
49
33
  darwin: [
@@ -53,54 +37,7 @@ const AwsInstall = {
53
37
  'rm "session-manager-plugin.pkg"',
54
38
  ],
55
39
  },
56
- },
57
- };
58
- const printToInstall = (toInstall) => {
59
- (0, stdio_1.print2)("The following items must be installed on your system to continue:");
60
- for (const item of toInstall) {
61
- (0, stdio_1.print2)(` - ${AwsInstall[item].label}`);
62
- }
63
- (0, stdio_1.print2)("");
64
- };
65
- const queryInteractive = () => __awaiter(void 0, void 0, void 0, function* () {
66
- const inquirer = (yield import("inquirer")).default;
67
- const { isGuided } = yield inquirer.prompt([
68
- {
69
- type: "confirm",
70
- name: "isGuided",
71
- message: "Do you want P0 to install these for you (sudo access required)?",
72
- },
73
- ]);
74
- (0, stdio_1.print2)("");
75
- return isGuided;
76
- });
77
- const requiredInstalls = () => __awaiter(void 0, void 0, void 0, function* () {
78
- return (0, lodash_1.compact)(yield Promise.all(AwsItems.map((item) => __awaiter(void 0, void 0, void 0, function* () { return (yield (0, which_1.default)(item, { nothrow: true })) === null ? item : undefined; }))));
79
- });
80
- const printInstallCommands = (platform, item) => {
81
- const { label, commands } = AwsInstall[item];
82
- (0, stdio_1.print2)(`To install ${label}, run the following commands:\n`);
83
- for (const command of commands[platform]) {
84
- (0, stdio_1.print1)(` ${command}`);
85
- }
86
- (0, stdio_1.print1)(""); // Newline is useful for reading command output in a script, so send to /fd/1
87
- };
88
- const guidedInstall = (platform, item) => __awaiter(void 0, void 0, void 0, function* () {
89
- const commands = AwsInstall[item].commands[platform];
90
- const combined = commands.join(" && \\\n");
91
- (0, stdio_1.print2)(`Executing:\n${combined}`);
92
- (0, stdio_1.print2)("");
93
- yield new Promise((resolve, reject) => {
94
- const child = (0, node_child_process_1.spawn)("bash", ["-c", combined], { stdio: "inherit" });
95
- child.on("exit", (code) => {
96
- if (code === 0)
97
- resolve();
98
- else
99
- reject(`Shell exited with code ${code}`);
100
- });
101
- });
102
- (0, stdio_1.print2)("");
103
- });
40
+ } });
104
41
  /** Ensures that AWS CLI and SSM plugin are installed on the user environment
105
42
  *
106
43
  * If they are not, and the session is a TTY, prompt the user to auto-install. If
@@ -108,26 +45,10 @@ const guidedInstall = (platform, item) => __awaiter(void 0, void 0, void 0, func
108
45
  * stdout.
109
46
  */
110
47
  const ensureSsmInstall = () => __awaiter(void 0, void 0, void 0, function* () {
111
- var _a;
112
48
  const platform = node_os_1.default.platform();
113
- if (!(0, types_1.isa)(SupportedPlatforms)(platform))
49
+ // Preserve existing behavior of a hard error on unsupported platforms
50
+ if (!(0, types_1.isa)(install_1.SupportedPlatforms)(platform))
114
51
  throw "SSH to AWS managed instances is only available on MacOS";
115
- const toInstall = yield requiredInstalls();
116
- if (toInstall.length === 0)
117
- return true;
118
- printToInstall(toInstall);
119
- const interactive = !!((_a = typescript_1.sys.writeOutputIsTTY) === null || _a === void 0 ? void 0 : _a.call(typescript_1.sys)) && (yield queryInteractive());
120
- for (const item of toInstall) {
121
- if (interactive)
122
- yield guidedInstall(platform, item);
123
- else
124
- printInstallCommands(platform, item);
125
- }
126
- const remaining = yield requiredInstalls();
127
- if (remaining.length === 0) {
128
- (0, stdio_1.print2)("All packages successfully installed");
129
- return true;
130
- }
131
- return false;
52
+ return yield (0, install_1.ensureInstall)(SsmItems, SsmInstall);
132
53
  });
133
54
  exports.ensureSsmInstall = ensureSsmInstall;
@@ -1,13 +1,3 @@
1
- /** Copyright © 2024-present P0 Security
2
-
3
- This file is part of @p0security/cli
4
-
5
- @p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
6
-
7
- @p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
-
9
- You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
10
- **/
11
1
  import { SshProvider } from "../../types/ssh";
12
2
  import { GcpSshPermissionSpec, GcpSshRequest } from "./types";
13
3
  export declare const gcpSshProvider: SshProvider<GcpSshPermissionSpec, {
@@ -10,7 +10,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.gcpSshProvider = void 0;
13
+ /** Copyright © 2024-present P0 Security
14
+
15
+ This file is part of @p0security/cli
16
+
17
+ @p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
18
+
19
+ @p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
20
+
21
+ You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
22
+ **/
23
+ const ssh_1 = require("../../commands/shared/ssh");
13
24
  const ssh_key_1 = require("./ssh-key");
25
+ /** Maximum number of attempts to start an SSH session
26
+ *
27
+ * The length of each attempt varies based on the type of error from a few seconds to < 1s
28
+ */
29
+ const MAX_SSH_RETRIES = 120;
14
30
  exports.gcpSshProvider = {
15
31
  requestToSsh: (request) => {
16
32
  return {
@@ -26,4 +42,33 @@ exports.gcpSshProvider = {
26
42
  linuxUserName: yield (0, ssh_key_1.importSshKey)(request.permission.spec.publicKey, options),
27
43
  } }));
28
44
  }),
45
+ cloudProviderLogin: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
46
+ proxyCommand: (request) => {
47
+ return [
48
+ "gcloud",
49
+ "compute",
50
+ "start-iap-tunnel",
51
+ request.id,
52
+ "%p",
53
+ // --listen-on-stdin flag is required for interactive SSH session.
54
+ // It is undocumented on page https://cloud.google.com/sdk/gcloud/reference/compute/start-iap-tunnel
55
+ // but mention on page https://cloud.google.com/iap/docs/tcp-by-host
56
+ // and also found in `gcloud ssh --dry-run` output
57
+ "--listen-on-stdin",
58
+ `--zone=${request.zone}`,
59
+ `--project=${request.projectId}`,
60
+ ];
61
+ },
62
+ reproCommands: () => undefined,
63
+ preTestAccessPropagationArgs: (cmdArgs) => {
64
+ if ((0, ssh_1.isSudoCommand)(cmdArgs)) {
65
+ return Object.assign(Object.assign({}, cmdArgs), {
66
+ // `sudo -v` prints `Sorry, user <user> may not run sudo on <hostname>.` to stderr when user is not a sudoer.
67
+ // It prints nothing to stdout when user is a sudoer - which is important because we don't want any output from the pre-test.
68
+ command: "sudo", arguments: ["-v"] });
69
+ }
70
+ return undefined;
71
+ },
72
+ maxRetries: MAX_SSH_RETRIES,
73
+ friendlyName: "Google Cloud",
29
74
  };
@@ -0,0 +1,23 @@
1
+ /** Copyright © 2024-present P0 Security
2
+
3
+ This file is part of @p0security/cli
4
+
5
+ @p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
6
+
7
+ @p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
+
9
+ You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
10
+ **/
11
+ import { KubeconfigCommandArgs } from "../../commands/kubeconfig";
12
+ import { Authn } from "../../types/identity";
13
+ import { Request } from "../../types/request";
14
+ import { AwsCredentials } from "../aws/types";
15
+ import { EksClusterConfig, K8sGenerated, K8sPermissionSpec } from "./types";
16
+ import yargs from "yargs";
17
+ export declare const getAndValidateK8sIntegration: (authn: Authn, clusterId: string) => Promise<{
18
+ clusterConfig: EksClusterConfig;
19
+ awsLoginType: "federated" | "idc";
20
+ }>;
21
+ export declare const requestAccessToCluster: (authn: Authn, args: yargs.ArgumentsCamelCase<KubeconfigCommandArgs>, clusterId: string, role: string) => Promise<Request<K8sPermissionSpec>>;
22
+ export declare const profileName: (eksCluterName: string) => string;
23
+ export declare const awsCloudAuth: (authn: Authn, awsAccountId: string, generated: K8sGenerated, loginType: "federated" | "idc") => Promise<AwsCredentials>;