@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.
- package/dist/commands/__tests__/grant.test.d.ts +1 -0
- package/dist/commands/__tests__/grant.test.js +55 -0
- package/dist/commands/aws/files.d.ts +41 -0
- package/dist/commands/aws/files.js +108 -0
- package/dist/commands/grant.d.ts +4 -0
- package/dist/commands/grant.js +17 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/kubeconfig.d.ts +9 -0
- package/dist/commands/kubeconfig.js +190 -0
- package/dist/commands/request.d.ts +0 -8
- package/dist/commands/request.js +3 -102
- package/dist/commands/shared/index.d.ts +2 -2
- package/dist/commands/shared/index.js +1 -1
- package/dist/commands/shared/request.d.ts +14 -0
- package/dist/commands/shared/request.js +115 -0
- package/dist/commands/shared/ssh.d.ts +7 -1
- package/dist/commands/shared/ssh.js +9 -7
- package/dist/common/install.d.ts +11 -0
- package/dist/common/install.js +120 -0
- package/dist/drivers/stdio.d.ts +1 -0
- package/dist/drivers/stdio.js +1 -0
- package/dist/plugins/aws/ssh.d.ts +2 -2
- package/dist/plugins/aws/ssh.js +54 -0
- package/dist/plugins/aws/ssm/install.js +7 -86
- package/dist/plugins/google/ssh.d.ts +0 -10
- package/dist/plugins/google/ssh.js +45 -0
- package/dist/plugins/kubeconfig/index.d.ts +23 -0
- package/dist/plugins/kubeconfig/index.js +98 -0
- package/dist/plugins/kubeconfig/install.d.ts +1 -0
- package/dist/plugins/kubeconfig/install.js +65 -0
- package/dist/plugins/kubeconfig/types.d.ts +57 -0
- package/dist/plugins/kubeconfig/types.js +2 -0
- package/dist/plugins/ssh/index.d.ts +2 -2
- package/dist/plugins/ssh/index.js +65 -93
- package/dist/types/request.d.ts +2 -1
- package/dist/types/ssh.d.ts +21 -1
- package/dist/util.d.ts +11 -0
- package/dist/util.js +13 -1
- 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("
|
|
30
|
+
const request_1 = require("./request");
|
|
31
31
|
const firestore_2 = require("firebase/firestore");
|
|
32
32
|
const lodash_1 = require("lodash");
|
|
33
|
-
|
|
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
|
-
...(
|
|
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;
|
package/dist/drivers/stdio.d.ts
CHANGED
package/dist/drivers/stdio.js
CHANGED
|
@@ -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>;
|
package/dist/plugins/aws/ssh.js
CHANGED
|
@@ -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
|
|
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
|
|
32
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|