@p0security/cli 0.6.2 → 0.8.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__/ssh.test.js +35 -9
- package/dist/commands/allow.d.ts +10 -0
- package/dist/commands/allow.js +57 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/scp.js +9 -24
- package/dist/commands/shared.d.ts +14 -11
- package/dist/commands/shared.js +20 -3
- package/dist/commands/ssh.js +15 -21
- package/dist/common/__mocks__/keys.d.ts +13 -0
- package/dist/common/__mocks__/keys.js +18 -0
- package/dist/common/keys.d.ts +9 -0
- package/dist/common/keys.js +84 -0
- package/dist/drivers/api.d.ts +0 -16
- package/dist/drivers/api.js +11 -6
- package/dist/plugins/aws/assumeRole.js +3 -2
- package/dist/plugins/aws/ssm/index.d.ts +2 -4
- package/dist/plugins/aws/ssm/index.js +76 -244
- package/dist/plugins/aws/types.d.ts +8 -6
- package/dist/plugins/login.js +2 -0
- package/dist/plugins/oidc/login.d.ts +5 -0
- package/dist/plugins/oidc/login.js +133 -0
- package/dist/plugins/okta/login.js +4 -88
- package/dist/plugins/ping/login.d.ts +13 -0
- package/dist/plugins/ping/login.js +16 -0
- package/dist/plugins/ssh-agent/index.d.ts +4 -0
- package/dist/plugins/ssh-agent/index.js +136 -0
- package/dist/plugins/ssh-agent/types.d.ts +13 -0
- package/dist/plugins/ssh-agent/types.js +2 -0
- package/dist/types/allow.d.ts +14 -0
- package/dist/types/allow.js +2 -0
- package/dist/types/org.d.ts +12 -4
- package/dist/types/org.js +10 -0
- package/dist/util.d.ts +3 -0
- package/dist/util.js +11 -1
- package/package.json +1 -3
|
@@ -22,6 +22,7 @@ This file is part of @p0security/cli
|
|
|
22
22
|
|
|
23
23
|
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
|
|
24
24
|
**/
|
|
25
|
+
const keys_1 = require("../../common/__mocks__/keys");
|
|
25
26
|
const api_1 = require("../../drivers/api");
|
|
26
27
|
const stdio_1 = require("../../drivers/stdio");
|
|
27
28
|
const ssm_1 = require("../../plugins/aws/ssm");
|
|
@@ -35,10 +36,28 @@ jest.mock("../../drivers/api");
|
|
|
35
36
|
jest.mock("../../drivers/auth");
|
|
36
37
|
jest.mock("../../drivers/stdio");
|
|
37
38
|
jest.mock("../../plugins/aws/ssm");
|
|
39
|
+
jest.mock("../../common/keys");
|
|
38
40
|
const mockFetchCommand = api_1.fetchCommand;
|
|
39
|
-
const
|
|
41
|
+
const mockSshOrScp = ssm_1.sshOrScp;
|
|
40
42
|
const mockPrint1 = stdio_1.print1;
|
|
41
43
|
const mockPrint2 = stdio_1.print2;
|
|
44
|
+
const MOCK_REQUEST = {
|
|
45
|
+
status: "DONE",
|
|
46
|
+
generated: {
|
|
47
|
+
name: "name",
|
|
48
|
+
ssh: {
|
|
49
|
+
linuxUserName: "linuxUserName",
|
|
50
|
+
publicKey: keys_1.TEST_PUBLIC_KEY,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
permission: {
|
|
54
|
+
spec: {
|
|
55
|
+
instanceId: "instanceId",
|
|
56
|
+
accountId: "accountId",
|
|
57
|
+
region: "region",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
42
61
|
(0, firestore_1.mockGetDoc)({
|
|
43
62
|
"iam-write": {
|
|
44
63
|
["aws:test-account"]: {
|
|
@@ -46,7 +65,6 @@ const mockPrint2 = stdio_1.print2;
|
|
|
46
65
|
},
|
|
47
66
|
},
|
|
48
67
|
});
|
|
49
|
-
mockSsm.mockResolvedValue({});
|
|
50
68
|
describe("ssh", () => {
|
|
51
69
|
describe.each([
|
|
52
70
|
["persistent", true],
|
|
@@ -69,9 +87,6 @@ describe("ssh", () => {
|
|
|
69
87
|
},
|
|
70
88
|
},
|
|
71
89
|
},
|
|
72
|
-
generated: {
|
|
73
|
-
documentName: "documentName",
|
|
74
|
-
},
|
|
75
90
|
},
|
|
76
91
|
});
|
|
77
92
|
});
|
|
@@ -97,20 +112,31 @@ describe("ssh", () => {
|
|
|
97
112
|
yield Promise.race([wait, promise]);
|
|
98
113
|
yield expect(wait).resolves.toBeUndefined();
|
|
99
114
|
}));
|
|
100
|
-
it("should call
|
|
101
|
-
const promise = (0, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
115
|
+
it("should call sshOrScp with non-interactive command", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
const promise = (0, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance do something`);
|
|
102
117
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
103
118
|
firestore_2.onSnapshot.trigger({
|
|
104
119
|
status: "APPROVED",
|
|
105
120
|
});
|
|
106
121
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
122
|
+
firestore_2.onSnapshot.trigger(MOCK_REQUEST);
|
|
123
|
+
yield expect(promise).resolves.toBeDefined();
|
|
124
|
+
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
|
|
125
|
+
expect(mockPrint1).not.toHaveBeenCalled();
|
|
126
|
+
expect(mockSshOrScp).toHaveBeenCalled();
|
|
127
|
+
}));
|
|
128
|
+
it("should call sshOrScp with interactive session", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
129
|
+
const promise = (0, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
130
|
+
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
107
131
|
firestore_2.onSnapshot.trigger({
|
|
108
|
-
status: "
|
|
132
|
+
status: "APPROVED",
|
|
109
133
|
});
|
|
134
|
+
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
135
|
+
firestore_2.onSnapshot.trigger(MOCK_REQUEST);
|
|
110
136
|
yield expect(promise).resolves.toBeDefined();
|
|
111
137
|
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
|
|
112
138
|
expect(mockPrint1).not.toHaveBeenCalled();
|
|
113
|
-
expect(
|
|
139
|
+
expect(mockSshOrScp).toHaveBeenCalled();
|
|
114
140
|
}));
|
|
115
141
|
});
|
|
116
142
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AllowResponse } from "../types/allow";
|
|
2
|
+
import { Authn } from "../types/identity";
|
|
3
|
+
import yargs from "yargs";
|
|
4
|
+
export declare const allowCommand: (yargs: yargs.Argv<{}>) => yargs.Argv<{
|
|
5
|
+
arguments: string[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare const allow: (args: yargs.ArgumentsCamelCase<{
|
|
8
|
+
arguments: string[];
|
|
9
|
+
wait?: boolean;
|
|
10
|
+
}>, authn?: Authn) => Promise<AllowResponse | undefined>;
|
|
@@ -0,0 +1,57 @@
|
|
|
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.allow = exports.allowCommand = 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 allowArgs = (yargs) => yargs
|
|
28
|
+
.parserConfiguration({ "unknown-options-as-args": true })
|
|
29
|
+
.help(false) // Turn off help in order to forward the --help command to the backend so P0 can provide the available requestable resources
|
|
30
|
+
.option("wait", {
|
|
31
|
+
alias: "w",
|
|
32
|
+
boolean: true,
|
|
33
|
+
default: false,
|
|
34
|
+
describe: "Block until the command is completed",
|
|
35
|
+
})
|
|
36
|
+
.option("arguments", {
|
|
37
|
+
array: true,
|
|
38
|
+
string: true,
|
|
39
|
+
default: [],
|
|
40
|
+
});
|
|
41
|
+
const allowCommand = (yargs) => yargs.command("allow [arguments..]", "Create standing access for a resource", allowArgs, (0, firestore_1.guard)(exports.allow));
|
|
42
|
+
exports.allowCommand = allowCommand;
|
|
43
|
+
const allow = (args, authn) => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
+
const resolvedAuthn = authn !== null && authn !== void 0 ? authn : (yield (0, auth_1.authenticate)());
|
|
45
|
+
const data = yield (0, api_1.fetchCommand)(resolvedAuthn, args, [
|
|
46
|
+
"allow",
|
|
47
|
+
...args.arguments,
|
|
48
|
+
]);
|
|
49
|
+
if (data && "ok" in data && "message" in data && data.ok) {
|
|
50
|
+
(0, stdio_1.print2)(data.message);
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw data;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
exports.allow = allow;
|
package/dist/commands/index.js
CHANGED
|
@@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with @p0
|
|
|
16
16
|
**/
|
|
17
17
|
const stdio_1 = require("../drivers/stdio");
|
|
18
18
|
const version_1 = require("../middlewares/version");
|
|
19
|
+
const allow_1 = require("./allow");
|
|
19
20
|
const aws_1 = require("./aws");
|
|
20
21
|
const login_1 = require("./login");
|
|
21
22
|
const ls_1 = require("./ls");
|
|
@@ -30,6 +31,7 @@ const commands = [
|
|
|
30
31
|
login_1.loginCommand,
|
|
31
32
|
ls_1.lsCommand,
|
|
32
33
|
request_1.requestCommand,
|
|
34
|
+
allow_1.allowCommand,
|
|
33
35
|
ssh_1.sshCommand,
|
|
34
36
|
scp_1.scpCommand,
|
|
35
37
|
];
|
package/dist/commands/scp.js
CHANGED
|
@@ -8,9 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
exports.scpCommand = void 0;
|
|
16
13
|
/** Copyright © 2024-present P0 Security
|
|
@@ -23,12 +20,10 @@ This file is part of @p0security/cli
|
|
|
23
20
|
|
|
24
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/>.
|
|
25
22
|
**/
|
|
26
|
-
const api_1 = require("../drivers/api");
|
|
27
23
|
const auth_1 = require("../drivers/auth");
|
|
28
24
|
const firestore_1 = require("../drivers/firestore");
|
|
29
25
|
const ssm_1 = require("../plugins/aws/ssm");
|
|
30
26
|
const shared_1 = require("./shared");
|
|
31
|
-
const node_forge_1 = __importDefault(require("node-forge"));
|
|
32
27
|
const scpCommand = (yargs) => yargs.command("scp <source> <destination>",
|
|
33
28
|
// TODO (ENG-1930): support scp across multiple remote hosts.
|
|
34
29
|
"SCP copies files between a local and remote host.", (yargs) => yargs
|
|
@@ -61,7 +56,7 @@ const scpCommand = (yargs) => yargs.command("scp <source> <destination>",
|
|
|
61
56
|
})
|
|
62
57
|
.option("debug", {
|
|
63
58
|
type: "boolean",
|
|
64
|
-
describe: "Print debug information
|
|
59
|
+
describe: "Print debug information. The ssh-agent subprocess is not terminated automatically.",
|
|
65
60
|
}), (0, firestore_1.guard)(scpAction));
|
|
66
61
|
exports.scpCommand = scpCommand;
|
|
67
62
|
/** Transfers files between a local and remote hosts using SSH.
|
|
@@ -74,19 +69,15 @@ const scpAction = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
74
69
|
if (!host) {
|
|
75
70
|
throw "Could not determine host identifier from source or destination";
|
|
76
71
|
}
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
72
|
+
const result = yield (0, shared_1.provisionRequest)(authn, args, host);
|
|
73
|
+
if (!result) {
|
|
79
74
|
throw "Server did not return a request id. Please contact support@p0.dev for assistance.";
|
|
80
75
|
}
|
|
81
|
-
const {
|
|
82
|
-
const
|
|
83
|
-
requestId,
|
|
84
|
-
destination: host,
|
|
85
|
-
publicKey,
|
|
86
|
-
});
|
|
76
|
+
const { request, privateKey } = result;
|
|
77
|
+
const data = (0, shared_1.requestToSsh)(request);
|
|
87
78
|
// replace the host with the linuxUserName@instanceId
|
|
88
|
-
const { source, destination } = replaceHostWithInstance(
|
|
89
|
-
yield (0, ssm_1.
|
|
79
|
+
const { source, destination } = replaceHostWithInstance(data, args);
|
|
80
|
+
yield (0, ssm_1.sshOrScp)(authn, data, Object.assign(Object.assign({}, args), { source,
|
|
90
81
|
destination }), privateKey);
|
|
91
82
|
});
|
|
92
83
|
/** If a path is not explicitly local, use this pattern to determine if it's remote */
|
|
@@ -110,16 +101,10 @@ const replaceHostWithInstance = (result, args) => {
|
|
|
110
101
|
let source = args.source;
|
|
111
102
|
let destination = args.destination;
|
|
112
103
|
if (isExplicitlyRemote(source)) {
|
|
113
|
-
source = `${result.linuxUserName}@${result.
|
|
104
|
+
source = `${result.linuxUserName}@${result.id}:${source.split(":")[1]}`;
|
|
114
105
|
}
|
|
115
106
|
if (isExplicitlyRemote(destination)) {
|
|
116
|
-
destination = `${result.linuxUserName}@${result.
|
|
107
|
+
destination = `${result.linuxUserName}@${result.id}:${destination.split(":")[1]}`;
|
|
117
108
|
}
|
|
118
109
|
return { source, destination };
|
|
119
110
|
};
|
|
120
|
-
const createKeyPair = () => {
|
|
121
|
-
const rsaKeyPair = node_forge_1.default.pki.rsa.generateKeyPair({ bits: 2048 });
|
|
122
|
-
const privateKey = node_forge_1.default.pki.privateKeyToPem(rsaKeyPair.privateKey);
|
|
123
|
-
const publicKey = node_forge_1.default.ssh.publicKeyToOpenSSH(rsaKeyPair.publicKey);
|
|
124
|
-
return { publicKey, privateKey };
|
|
125
|
-
};
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
import { AwsSsh } from "../plugins/aws/types";
|
|
1
2
|
import { Authn } from "../types/identity";
|
|
3
|
+
import { Request } from "../types/request";
|
|
2
4
|
import yargs from "yargs";
|
|
3
|
-
export declare type
|
|
4
|
-
documentName: string;
|
|
5
|
+
export declare type SshRequest = {
|
|
5
6
|
linuxUserName: string;
|
|
6
|
-
ok: true;
|
|
7
7
|
role: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
region: string;
|
|
12
|
-
id: string;
|
|
13
|
-
name?: string;
|
|
14
|
-
};
|
|
8
|
+
accountId: string;
|
|
9
|
+
region: string;
|
|
10
|
+
id: string;
|
|
15
11
|
};
|
|
16
12
|
export declare type BaseSshCommandArgs = {
|
|
17
13
|
sudo?: boolean;
|
|
@@ -29,7 +25,14 @@ export declare type SshCommandArgs = BaseSshCommandArgs & {
|
|
|
29
25
|
destination: string;
|
|
30
26
|
L?: string;
|
|
31
27
|
N?: boolean;
|
|
28
|
+
A?: boolean;
|
|
32
29
|
arguments: string[];
|
|
33
30
|
command?: string;
|
|
31
|
+
debug?: boolean;
|
|
34
32
|
};
|
|
35
|
-
export declare const provisionRequest: (authn: Authn, args: yargs.ArgumentsCamelCase<BaseSshCommandArgs>, destination: string) => Promise<
|
|
33
|
+
export declare const provisionRequest: (authn: Authn, args: yargs.ArgumentsCamelCase<BaseSshCommandArgs>, destination: string) => Promise<{
|
|
34
|
+
request: Request<AwsSsh>;
|
|
35
|
+
publicKey: string;
|
|
36
|
+
privateKey: string;
|
|
37
|
+
} | undefined>;
|
|
38
|
+
export declare const requestToSsh: (request: AwsSsh) => SshRequest;
|
package/dist/commands/shared.js
CHANGED
|
@@ -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.provisionRequest = void 0;
|
|
12
|
+
exports.requestToSsh = exports.provisionRequest = void 0;
|
|
13
13
|
/** Copyright © 2024-present P0 Security
|
|
14
14
|
|
|
15
15
|
This file is part of @p0security/cli
|
|
@@ -20,6 +20,7 @@ This file is part of @p0security/cli
|
|
|
20
20
|
|
|
21
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
22
|
**/
|
|
23
|
+
const keys_1 = require("../common/keys");
|
|
23
24
|
const firestore_1 = require("../drivers/firestore");
|
|
24
25
|
const stdio_1 = require("../drivers/stdio");
|
|
25
26
|
const request_1 = require("../types/request");
|
|
@@ -78,10 +79,13 @@ const waitForProvisioning = (authn, requestId) => __awaiter(void 0, void 0, void
|
|
|
78
79
|
});
|
|
79
80
|
const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0, void 0, function* () {
|
|
80
81
|
yield validateSshInstall(authn);
|
|
82
|
+
const { publicKey, privateKey } = yield (0, keys_1.createKeyPair)();
|
|
81
83
|
const response = yield (0, request_2.request)(Object.assign(Object.assign({}, (0, lodash_1.pick)(args, "$0", "_")), { arguments: [
|
|
82
84
|
"ssh",
|
|
83
85
|
"session",
|
|
84
86
|
destination,
|
|
87
|
+
"--public-key",
|
|
88
|
+
publicKey,
|
|
85
89
|
// Prefix is required because the backend uses it to determine that this is an AWS request
|
|
86
90
|
"--provider",
|
|
87
91
|
"aws",
|
|
@@ -96,7 +100,20 @@ const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0,
|
|
|
96
100
|
const { id, isPreexisting } = response;
|
|
97
101
|
if (!isPreexisting)
|
|
98
102
|
(0, stdio_1.print2)("Waiting for access to be provisioned");
|
|
99
|
-
yield waitForProvisioning(authn, id);
|
|
100
|
-
|
|
103
|
+
const provisionedRequest = yield waitForProvisioning(authn, id);
|
|
104
|
+
if (provisionedRequest.generated.ssh.publicKey !== publicKey) {
|
|
105
|
+
throw "Public key mismatch. Please revoke the request and try again.";
|
|
106
|
+
}
|
|
107
|
+
return { request: provisionedRequest, publicKey, privateKey };
|
|
101
108
|
});
|
|
102
109
|
exports.provisionRequest = provisionRequest;
|
|
110
|
+
const requestToSsh = (request) => {
|
|
111
|
+
return {
|
|
112
|
+
id: request.permission.spec.instanceId,
|
|
113
|
+
accountId: request.permission.spec.accountId,
|
|
114
|
+
region: request.permission.spec.region,
|
|
115
|
+
role: request.generated.name,
|
|
116
|
+
linuxUserName: request.generated.ssh.linuxUserName,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
exports.requestToSsh = requestToSsh;
|
package/dist/commands/ssh.js
CHANGED
|
@@ -20,13 +20,10 @@ This file is part of @p0security/cli
|
|
|
20
20
|
|
|
21
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
22
|
**/
|
|
23
|
-
const api_1 = require("../drivers/api");
|
|
24
23
|
const auth_1 = require("../drivers/auth");
|
|
25
24
|
const firestore_1 = require("../drivers/firestore");
|
|
26
25
|
const ssm_1 = require("../plugins/aws/ssm");
|
|
27
26
|
const shared_1 = require("./shared");
|
|
28
|
-
// Matches strings with the pattern "digits:digits" (e.g. 1234:5678)
|
|
29
|
-
const LOCAL_PORT_FORWARD_PATTERN = /^\d+:\d+$/;
|
|
30
27
|
const sshCommand = (yargs) => yargs.command("ssh <destination> [command [arguments..]]", "SSH into a virtual machine", (yargs) => yargs
|
|
31
28
|
.positional("destination", {
|
|
32
29
|
type: "string",
|
|
@@ -45,22 +42,19 @@ const sshCommand = (yargs) => yargs.command("ssh <destination> [command [argumen
|
|
|
45
42
|
array: true,
|
|
46
43
|
string: true,
|
|
47
44
|
default: [],
|
|
48
|
-
})
|
|
49
|
-
.check((argv) => {
|
|
50
|
-
if (argv.L == null)
|
|
51
|
-
return true;
|
|
52
|
-
return (argv.L.match(LOCAL_PORT_FORWARD_PATTERN) ||
|
|
53
|
-
"Local port forward should be in the format `local_port:remote_port`");
|
|
54
45
|
})
|
|
55
46
|
.option("L", {
|
|
56
47
|
type: "string",
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"Forward a local port to the remote host; `local_socket:remote_socket`",
|
|
48
|
+
// Copied from `man ssh`
|
|
49
|
+
describe: "Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be forwarded to the given host and port, or Unix socket, on the remote side.",
|
|
60
50
|
})
|
|
61
51
|
.option("N", {
|
|
62
52
|
type: "boolean",
|
|
63
53
|
describe: "Do not execute a remote command. Useful for forwarding ports.",
|
|
54
|
+
})
|
|
55
|
+
.option("A", {
|
|
56
|
+
type: "boolean",
|
|
57
|
+
describe: "Enables forwarding of connections from an authentication agent such as ssh-agent",
|
|
64
58
|
})
|
|
65
59
|
// Match `p0 request --reason`
|
|
66
60
|
.option("reason", {
|
|
@@ -70,7 +64,11 @@ const sshCommand = (yargs) => yargs.command("ssh <destination> [command [argumen
|
|
|
70
64
|
.option("account", {
|
|
71
65
|
type: "string",
|
|
72
66
|
describe: "The account on which the instance is located",
|
|
73
|
-
})
|
|
67
|
+
})
|
|
68
|
+
.option("debug", {
|
|
69
|
+
type: "boolean",
|
|
70
|
+
describe: "Print debug information. The ssh-agent subprocess is not terminated automatically.",
|
|
71
|
+
}), (0, firestore_1.guard)(sshAction));
|
|
74
72
|
exports.sshCommand = sshCommand;
|
|
75
73
|
/** Connect to an SSH backend
|
|
76
74
|
*
|
|
@@ -79,17 +77,13 @@ exports.sshCommand = sshCommand;
|
|
|
79
77
|
* Supported SSH mechanisms:
|
|
80
78
|
* - AWS EC2 via SSM with Okta SAML
|
|
81
79
|
*/
|
|
82
|
-
const
|
|
80
|
+
const sshAction = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
81
|
// Prefix is required because the backend uses it to determine that this is an AWS request
|
|
84
82
|
const authn = yield (0, auth_1.authenticate)();
|
|
85
83
|
const destination = args.destination;
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
84
|
+
const result = yield (0, shared_1.provisionRequest)(authn, args, destination);
|
|
85
|
+
if (!result) {
|
|
88
86
|
throw "Server did not return a request id. Please contact support@p0.dev for assistance.";
|
|
89
87
|
}
|
|
90
|
-
|
|
91
|
-
requestId,
|
|
92
|
-
destination,
|
|
93
|
-
});
|
|
94
|
-
yield (0, ssm_1.ssm)(authn, result, args);
|
|
88
|
+
yield (0, ssm_1.sshOrScp)(authn, (0, shared_1.requestToSsh)(result.request), Object.assign(Object.assign({}, args), { destination }), result.privateKey);
|
|
95
89
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
/** Copyright © 2024-present P0 Security
|
|
3
|
+
|
|
4
|
+
This file is part of @p0security/cli
|
|
5
|
+
|
|
6
|
+
@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.
|
|
7
|
+
|
|
8
|
+
@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.
|
|
9
|
+
|
|
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
|
+
**/
|
|
12
|
+
export declare const TEST_PUBLIC_KEY = "test-public-key";
|
|
13
|
+
export declare const createKeyPair: jest.Mock<any, any, any>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createKeyPair = exports.TEST_PUBLIC_KEY = void 0;
|
|
4
|
+
/** Copyright © 2024-present P0 Security
|
|
5
|
+
|
|
6
|
+
This file is part of @p0security/cli
|
|
7
|
+
|
|
8
|
+
@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.
|
|
9
|
+
|
|
10
|
+
@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.
|
|
11
|
+
|
|
12
|
+
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
|
|
13
|
+
**/
|
|
14
|
+
exports.TEST_PUBLIC_KEY = "test-public-key";
|
|
15
|
+
exports.createKeyPair = jest.fn().mockImplementation(() => ({
|
|
16
|
+
publicKey: "test-public-key",
|
|
17
|
+
privateKey: "test-private-key",
|
|
18
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const PUBLIC_KEY_PATH: string;
|
|
2
|
+
export declare const PRIVATE_KEY_PATH: string;
|
|
3
|
+
/**
|
|
4
|
+
* Search for a cached key pair, or create a new one if not found
|
|
5
|
+
*/
|
|
6
|
+
export declare const createKeyPair: () => Promise<{
|
|
7
|
+
publicKey: string;
|
|
8
|
+
privateKey: string;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.createKeyPair = exports.PRIVATE_KEY_PATH = exports.PUBLIC_KEY_PATH = void 0;
|
|
39
|
+
/** Copyright © 2024-present P0 Security
|
|
40
|
+
|
|
41
|
+
This file is part of @p0security/cli
|
|
42
|
+
|
|
43
|
+
@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.
|
|
44
|
+
|
|
45
|
+
@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.
|
|
46
|
+
|
|
47
|
+
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
|
|
48
|
+
**/
|
|
49
|
+
const util_1 = require("../util");
|
|
50
|
+
const fs = __importStar(require("fs/promises"));
|
|
51
|
+
const node_forge_1 = __importDefault(require("node-forge"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
exports.PUBLIC_KEY_PATH = path.join(util_1.P0_PATH, "ssh", "id_rsa.pub");
|
|
54
|
+
exports.PRIVATE_KEY_PATH = path.join(util_1.P0_PATH, "ssh", "id_rsa");
|
|
55
|
+
/**
|
|
56
|
+
* Search for a cached key pair, or create a new one if not found
|
|
57
|
+
*/
|
|
58
|
+
const createKeyPair = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
if ((yield fileExists(exports.PUBLIC_KEY_PATH)) &&
|
|
60
|
+
(yield fileExists(exports.PRIVATE_KEY_PATH))) {
|
|
61
|
+
const publicKey = yield fs.readFile(exports.PUBLIC_KEY_PATH, "utf8");
|
|
62
|
+
const privateKey = yield fs.readFile(exports.PRIVATE_KEY_PATH, "utf8");
|
|
63
|
+
return { publicKey, privateKey };
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const rsaKeyPair = node_forge_1.default.pki.rsa.generateKeyPair({ bits: 2048 });
|
|
67
|
+
const privateKey = node_forge_1.default.pki.privateKeyToPem(rsaKeyPair.privateKey);
|
|
68
|
+
const publicKey = node_forge_1.default.ssh.publicKeyToOpenSSH(rsaKeyPair.publicKey);
|
|
69
|
+
yield fs.mkdir(path.dirname(exports.PUBLIC_KEY_PATH), { recursive: true });
|
|
70
|
+
yield fs.writeFile(exports.PUBLIC_KEY_PATH, publicKey, { mode: 0o600 });
|
|
71
|
+
yield fs.writeFile(exports.PRIVATE_KEY_PATH, privateKey, { mode: 0o600 });
|
|
72
|
+
return { publicKey, privateKey };
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
exports.createKeyPair = createKeyPair;
|
|
76
|
+
const fileExists = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
77
|
+
try {
|
|
78
|
+
yield fs.access(path);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
});
|
package/dist/drivers/api.d.ts
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
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 { ExerciseGrantResponse } from "../commands/shared";
|
|
12
1
|
import { Authn } from "../types/identity";
|
|
13
2
|
import yargs from "yargs";
|
|
14
3
|
export declare const fetchCommand: <T>(authn: Authn, args: yargs.ArgumentsCamelCase, argv: string[]) => Promise<T>;
|
|
15
|
-
export declare const fetchExerciseGrant: (authn: Authn, args: {
|
|
16
|
-
requestId: string;
|
|
17
|
-
destination: string;
|
|
18
|
-
publicKey?: string;
|
|
19
|
-
}) => Promise<ExerciseGrantResponse>;
|
|
20
4
|
export declare const baseFetch: <T>(authn: Authn, url: string, method: string, body: string) => Promise<T>;
|
package/dist/drivers/api.js
CHANGED
|
@@ -32,12 +32,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.baseFetch = exports.
|
|
35
|
+
exports.baseFetch = exports.fetchCommand = void 0;
|
|
36
|
+
/** Copyright © 2024-present P0 Security
|
|
37
|
+
|
|
38
|
+
This file is part of @p0security/cli
|
|
39
|
+
|
|
40
|
+
@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.
|
|
41
|
+
|
|
42
|
+
@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.
|
|
43
|
+
|
|
44
|
+
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
|
|
45
|
+
**/
|
|
36
46
|
const env_1 = require("../drivers/env");
|
|
37
47
|
const path = __importStar(require("node:path"));
|
|
38
48
|
const tenantUrl = (tenant) => `${env_1.config.appUrl}/o/${tenant}`;
|
|
39
49
|
const commandUrl = (tenant) => `${tenantUrl(tenant)}/command/`;
|
|
40
|
-
const exerciseGrantUrl = (tenant) => `${tenantUrl(tenant)}/exercise-grant/`;
|
|
41
50
|
const fetchCommand = (authn, args, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
51
|
return (0, exports.baseFetch)(authn, commandUrl(authn.identity.org.slug), "POST", JSON.stringify({
|
|
43
52
|
argv,
|
|
@@ -45,10 +54,6 @@ const fetchCommand = (authn, args, argv) => __awaiter(void 0, void 0, void 0, fu
|
|
|
45
54
|
}));
|
|
46
55
|
});
|
|
47
56
|
exports.fetchCommand = fetchCommand;
|
|
48
|
-
const fetchExerciseGrant = (authn, args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
49
|
-
return (0, exports.baseFetch)(authn, exerciseGrantUrl(authn.identity.org.slug), "POST", JSON.stringify(args));
|
|
50
|
-
});
|
|
51
|
-
exports.fetchExerciseGrant = fetchExerciseGrant;
|
|
52
57
|
const baseFetch = (authn, url, method, body) => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
58
|
const token = yield authn.userCredential.user.getIdToken();
|
|
54
59
|
const response = yield fetch(url, {
|
|
@@ -26,9 +26,10 @@ const api_1 = require("./api");
|
|
|
26
26
|
const api_2 = require("./api");
|
|
27
27
|
const roleArn = (args) => `${(0, api_1.arnPrefix)(args.account)}:role/${args.role}`;
|
|
28
28
|
const stsAssume = (params) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
-
const url = `https://sts.amazonaws.com
|
|
29
|
+
const url = `https://sts.amazonaws.com`;
|
|
30
30
|
const response = yield fetch(url, {
|
|
31
|
-
method: "
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: new URLSearchParams(params),
|
|
32
33
|
});
|
|
33
34
|
yield (0, fetch_1.validateResponse)(response);
|
|
34
35
|
const stsXml = yield response.text();
|
|
@@ -8,8 +8,6 @@ This file is part of @p0security/cli
|
|
|
8
8
|
|
|
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
|
-
import {
|
|
11
|
+
import { ScpCommandArgs, SshCommandArgs, SshRequest } from "../../../commands/shared";
|
|
12
12
|
import { Authn } from "../../../types/identity";
|
|
13
|
-
|
|
14
|
-
export declare const ssm: (authn: Authn, request: ExerciseGrantResponse, args: SshCommandArgs) => Promise<void>;
|
|
15
|
-
export declare const scp: (authn: Authn, data: ExerciseGrantResponse, args: ScpCommandArgs, privateKey: string) => Promise<number | null>;
|
|
13
|
+
export declare const sshOrScp: (authn: Authn, data: SshRequest, cmdArgs: ScpCommandArgs | SshCommandArgs, privateKey: string) => Promise<number | null>;
|