@p0security/cli 0.7.1 → 0.8.1
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 +31 -18
- package/dist/commands/scp.js +14 -14
- package/dist/commands/shared.d.ts +21 -15
- package/dist/commands/shared.js +47 -19
- package/dist/commands/ssh.js +10 -11
- 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/common/subprocess.d.ts +10 -0
- package/dist/common/subprocess.js +72 -0
- package/dist/drivers/api.d.ts +0 -16
- package/dist/drivers/api.js +11 -6
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -12
- package/dist/plugins/aws/assumeRole.js +3 -2
- package/dist/plugins/aws/ssh.d.ts +13 -0
- package/dist/plugins/aws/ssh.js +14 -0
- package/dist/plugins/aws/types.d.ts +16 -12
- package/dist/plugins/google/ssh-key.d.ts +14 -0
- package/dist/plugins/google/ssh-key.js +80 -0
- package/dist/plugins/google/ssh.d.ts +13 -0
- package/dist/plugins/google/ssh.js +13 -0
- package/dist/plugins/google/types.d.ts +49 -0
- package/dist/plugins/google/types.js +2 -0
- package/dist/plugins/{aws/ssm → ssh}/index.d.ts +3 -3
- package/dist/plugins/ssh/index.js +302 -0
- package/dist/plugins/ssh/types.d.ts +4 -0
- package/dist/plugins/ssh-agent/index.d.ts +4 -10
- package/dist/plugins/ssh-agent/index.js +71 -111
- package/dist/plugins/ssh-agent/types.d.ts +0 -4
- package/dist/types/request.d.ts +14 -11
- package/dist/types/request.js +0 -10
- package/package.json +1 -1
- package/dist/plugins/aws/ssm/index.js +0 -221
|
@@ -22,23 +22,43 @@ 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
|
-
const
|
|
28
|
+
const ssh_1 = require("../../plugins/ssh");
|
|
28
29
|
const firestore_1 = require("../../testing/firestore");
|
|
29
30
|
const util_1 = require("../../util");
|
|
30
|
-
const
|
|
31
|
+
const ssh_2 = require("../ssh");
|
|
31
32
|
const firestore_2 = require("firebase/firestore");
|
|
32
33
|
const lodash_1 = require("lodash");
|
|
33
34
|
const yargs_1 = __importDefault(require("yargs"));
|
|
34
35
|
jest.mock("../../drivers/api");
|
|
35
36
|
jest.mock("../../drivers/auth");
|
|
36
37
|
jest.mock("../../drivers/stdio");
|
|
37
|
-
jest.mock("../../plugins/
|
|
38
|
+
jest.mock("../../plugins/ssh");
|
|
39
|
+
jest.mock("../../common/keys");
|
|
38
40
|
const mockFetchCommand = api_1.fetchCommand;
|
|
39
|
-
const mockSshOrScp =
|
|
41
|
+
const mockSshOrScp = ssh_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
|
+
},
|
|
51
|
+
},
|
|
52
|
+
permission: {
|
|
53
|
+
spec: {
|
|
54
|
+
instanceId: "instanceId",
|
|
55
|
+
accountId: "accountId",
|
|
56
|
+
region: "region",
|
|
57
|
+
publicKey: keys_1.TEST_PUBLIC_KEY,
|
|
58
|
+
type: "aws",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
42
62
|
(0, firestore_1.mockGetDoc)({
|
|
43
63
|
"iam-write": {
|
|
44
64
|
["aws:test-account"]: {
|
|
@@ -68,26 +88,23 @@ describe("ssh", () => {
|
|
|
68
88
|
},
|
|
69
89
|
},
|
|
70
90
|
},
|
|
71
|
-
generated: {
|
|
72
|
-
documentName: "documentName",
|
|
73
|
-
},
|
|
74
91
|
},
|
|
75
92
|
});
|
|
76
93
|
});
|
|
77
94
|
it("should call p0 request with reason arg", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
-
void (0,
|
|
95
|
+
void (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance --reason reason --provider aws`);
|
|
79
96
|
yield (0, util_1.sleep)(100);
|
|
80
97
|
const hiddenFilenameRequestArgs = (0, lodash_1.omit)(mockFetchCommand.mock.calls[0][1], "$0");
|
|
81
98
|
expect(hiddenFilenameRequestArgs).toMatchSnapshot("args");
|
|
82
99
|
}));
|
|
83
100
|
it("should wait for access grant", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
-
const promise = (0,
|
|
101
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
85
102
|
const wait = (0, util_1.sleep)(100);
|
|
86
103
|
yield Promise.race([wait, promise]);
|
|
87
104
|
yield expect(wait).resolves.toBeUndefined();
|
|
88
105
|
}));
|
|
89
106
|
it("should wait for provisioning", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
90
|
-
const promise = (0,
|
|
107
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
91
108
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
92
109
|
firestore_2.onSnapshot.trigger({
|
|
93
110
|
status: "APPROVED",
|
|
@@ -97,30 +114,26 @@ describe("ssh", () => {
|
|
|
97
114
|
yield expect(wait).resolves.toBeUndefined();
|
|
98
115
|
}));
|
|
99
116
|
it("should call sshOrScp with non-interactive command", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
|
-
const promise = (0,
|
|
117
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance do something`);
|
|
101
118
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
102
119
|
firestore_2.onSnapshot.trigger({
|
|
103
120
|
status: "APPROVED",
|
|
104
121
|
});
|
|
105
122
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
106
|
-
firestore_2.onSnapshot.trigger(
|
|
107
|
-
status: "DONE",
|
|
108
|
-
});
|
|
123
|
+
firestore_2.onSnapshot.trigger(MOCK_REQUEST);
|
|
109
124
|
yield expect(promise).resolves.toBeDefined();
|
|
110
125
|
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
|
|
111
126
|
expect(mockPrint1).not.toHaveBeenCalled();
|
|
112
127
|
expect(mockSshOrScp).toHaveBeenCalled();
|
|
113
128
|
}));
|
|
114
129
|
it("should call sshOrScp with interactive session", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
115
|
-
const promise = (0,
|
|
130
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
116
131
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
117
132
|
firestore_2.onSnapshot.trigger({
|
|
118
133
|
status: "APPROVED",
|
|
119
134
|
});
|
|
120
135
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
121
|
-
firestore_2.onSnapshot.trigger(
|
|
122
|
-
status: "DONE",
|
|
123
|
-
});
|
|
136
|
+
firestore_2.onSnapshot.trigger(MOCK_REQUEST);
|
|
124
137
|
yield expect(promise).resolves.toBeDefined();
|
|
125
138
|
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
|
|
126
139
|
expect(mockPrint1).not.toHaveBeenCalled();
|
package/dist/commands/scp.js
CHANGED
|
@@ -20,10 +20,9 @@ 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
|
-
const
|
|
25
|
+
const ssh_1 = require("../plugins/ssh");
|
|
27
26
|
const shared_1 = require("./shared");
|
|
28
27
|
const scpCommand = (yargs) => yargs.command("scp <source> <destination>",
|
|
29
28
|
// TODO (ENG-1930): support scp across multiple remote hosts.
|
|
@@ -50,6 +49,11 @@ const scpCommand = (yargs) => yargs.command("scp <source> <destination>",
|
|
|
50
49
|
.option("account", {
|
|
51
50
|
type: "string",
|
|
52
51
|
describe: "The account on which the instance is located",
|
|
52
|
+
})
|
|
53
|
+
.option("provider", {
|
|
54
|
+
type: "string",
|
|
55
|
+
describe: "The cloud provider where the instance is hosted",
|
|
56
|
+
choices: shared_1.SUPPORTED_PROVIDERS,
|
|
53
57
|
})
|
|
54
58
|
.option("sudo", {
|
|
55
59
|
type: "boolean",
|
|
@@ -70,19 +74,15 @@ const scpAction = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
70
74
|
if (!host) {
|
|
71
75
|
throw "Could not determine host identifier from source or destination";
|
|
72
76
|
}
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
77
|
+
const result = yield (0, shared_1.provisionRequest)(authn, args, host);
|
|
78
|
+
if (!result) {
|
|
75
79
|
throw "Server did not return a request id. Please contact support@p0.dev for assistance.";
|
|
76
80
|
}
|
|
77
|
-
const {
|
|
78
|
-
const
|
|
79
|
-
requestId,
|
|
80
|
-
destination: host,
|
|
81
|
-
publicKey,
|
|
82
|
-
});
|
|
81
|
+
const { request, privateKey } = result;
|
|
82
|
+
const data = (0, shared_1.requestToSsh)(request);
|
|
83
83
|
// replace the host with the linuxUserName@instanceId
|
|
84
|
-
const { source, destination } = replaceHostWithInstance(
|
|
85
|
-
yield (0,
|
|
84
|
+
const { source, destination } = replaceHostWithInstance(data, args);
|
|
85
|
+
yield (0, ssh_1.sshOrScp)(authn, data, Object.assign(Object.assign({}, args), { source,
|
|
86
86
|
destination }), privateKey);
|
|
87
87
|
});
|
|
88
88
|
/** If a path is not explicitly local, use this pattern to determine if it's remote */
|
|
@@ -106,10 +106,10 @@ const replaceHostWithInstance = (result, args) => {
|
|
|
106
106
|
let source = args.source;
|
|
107
107
|
let destination = args.destination;
|
|
108
108
|
if (isExplicitlyRemote(source)) {
|
|
109
|
-
source = `${result.linuxUserName}@${result.
|
|
109
|
+
source = `${result.linuxUserName}@${result.id}:${source.split(":")[1]}`;
|
|
110
110
|
}
|
|
111
111
|
if (isExplicitlyRemote(destination)) {
|
|
112
|
-
destination = `${result.linuxUserName}@${result.
|
|
112
|
+
destination = `${result.linuxUserName}@${result.id}:${destination.split(":")[1]}`;
|
|
113
113
|
}
|
|
114
114
|
return { source, destination };
|
|
115
115
|
};
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import { Authn } from "../types/identity";
|
|
2
|
+
import { CliRequest, Request } from "../types/request";
|
|
2
3
|
import yargs from "yargs";
|
|
3
|
-
export declare
|
|
4
|
-
|
|
4
|
+
export declare const SUPPORTED_PROVIDERS: string[];
|
|
5
|
+
export declare type SshRequest = AwsSshRequest | GcpSshRequest;
|
|
6
|
+
export declare type AwsSshRequest = {
|
|
5
7
|
linuxUserName: string;
|
|
6
|
-
ok: true;
|
|
7
8
|
role: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
accountId: string;
|
|
10
|
+
region: string;
|
|
11
|
+
id: string;
|
|
12
|
+
type: "aws";
|
|
13
|
+
};
|
|
14
|
+
export declare type GcpSshRequest = {
|
|
15
|
+
linuxUserName: string;
|
|
16
|
+
projectId: string;
|
|
17
|
+
zone: string;
|
|
18
|
+
id: string;
|
|
19
|
+
type: "gcloud";
|
|
15
20
|
};
|
|
16
21
|
export declare type BaseSshCommandArgs = {
|
|
17
22
|
sudo?: boolean;
|
|
18
23
|
reason?: string;
|
|
19
24
|
account?: string;
|
|
25
|
+
provider?: "aws" | "gcloud";
|
|
26
|
+
debug?: boolean;
|
|
20
27
|
};
|
|
21
28
|
export declare type ScpCommandArgs = BaseSshCommandArgs & {
|
|
22
29
|
source: string;
|
|
23
30
|
destination: string;
|
|
24
31
|
recursive?: boolean;
|
|
25
|
-
debug?: boolean;
|
|
26
32
|
};
|
|
27
33
|
export declare type SshCommandArgs = BaseSshCommandArgs & {
|
|
28
34
|
sudo?: boolean;
|
|
@@ -32,10 +38,10 @@ export declare type SshCommandArgs = BaseSshCommandArgs & {
|
|
|
32
38
|
A?: boolean;
|
|
33
39
|
arguments: string[];
|
|
34
40
|
command?: string;
|
|
35
|
-
debug?: boolean;
|
|
36
41
|
};
|
|
37
|
-
export declare const provisionRequest: (authn: Authn, args: yargs.ArgumentsCamelCase<BaseSshCommandArgs>, destination: string) => Promise<
|
|
38
|
-
|
|
42
|
+
export declare const provisionRequest: (authn: Authn, args: yargs.ArgumentsCamelCase<BaseSshCommandArgs>, destination: string) => Promise<{
|
|
43
|
+
request: Request<CliRequest>;
|
|
39
44
|
publicKey: string;
|
|
40
45
|
privateKey: string;
|
|
41
|
-
}
|
|
46
|
+
} | undefined>;
|
|
47
|
+
export declare const requestToSsh: (request: Request<CliRequest>) => SshRequest;
|
package/dist/commands/shared.js
CHANGED
|
@@ -8,11 +8,8 @@ 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
|
-
exports.
|
|
12
|
+
exports.requestToSsh = exports.provisionRequest = exports.SUPPORTED_PROVIDERS = void 0;
|
|
16
13
|
/** Copyright © 2024-present P0 Security
|
|
17
14
|
|
|
18
15
|
This file is part of @p0security/cli
|
|
@@ -23,22 +20,32 @@ 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
|
**/
|
|
23
|
+
const keys_1 = require("../common/keys");
|
|
26
24
|
const firestore_1 = require("../drivers/firestore");
|
|
27
25
|
const stdio_1 = require("../drivers/stdio");
|
|
26
|
+
const ssh_1 = require("../plugins/aws/ssh");
|
|
27
|
+
const ssh_2 = require("../plugins/google/ssh");
|
|
28
|
+
const ssh_key_1 = require("../plugins/google/ssh-key");
|
|
28
29
|
const request_1 = require("../types/request");
|
|
30
|
+
const util_1 = require("../util");
|
|
29
31
|
const request_2 = require("./request");
|
|
30
32
|
const firestore_2 = require("firebase/firestore");
|
|
31
33
|
const lodash_1 = require("lodash");
|
|
32
|
-
const node_forge_1 = __importDefault(require("node-forge"));
|
|
33
34
|
/** Maximum amount of time to wait after access is approved to wait for access
|
|
34
35
|
* to be configured
|
|
35
36
|
*/
|
|
36
37
|
const GRANT_TIMEOUT_MILLIS = 60e3;
|
|
37
|
-
|
|
38
|
+
// The prefix of installed SSH accounts in P0 is the provider name
|
|
39
|
+
exports.SUPPORTED_PROVIDERS = ["aws", "gcloud"];
|
|
40
|
+
const validateSshInstall = (authn, args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
41
|
var _a;
|
|
39
42
|
const configDoc = yield (0, firestore_2.getDoc)((0, firestore_1.doc)(`o/${authn.identity.org.tenantId}/integrations/ssh`));
|
|
40
43
|
const configItems = (_a = configDoc.data()) === null || _a === void 0 ? void 0 : _a["iam-write"];
|
|
41
|
-
const
|
|
44
|
+
const providersToCheck = args.provider
|
|
45
|
+
? [args.provider]
|
|
46
|
+
: exports.SUPPORTED_PROVIDERS;
|
|
47
|
+
const items = Object.entries(configItems !== null && configItems !== void 0 ? configItems : {}).filter(([key, value]) => value.state == "installed" &&
|
|
48
|
+
providersToCheck.some((prefix) => key.startsWith(prefix)));
|
|
42
49
|
if (items.length === 0) {
|
|
43
50
|
throw "This organization is not configured for SSH access via the P0 CLI";
|
|
44
51
|
}
|
|
@@ -80,15 +87,25 @@ const waitForProvisioning = (authn, requestId) => __awaiter(void 0, void 0, void
|
|
|
80
87
|
clearTimeout(cancel);
|
|
81
88
|
return result;
|
|
82
89
|
});
|
|
90
|
+
const pluginToCliRequest = (request, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
return request.permission.spec.type === "gcloud"
|
|
92
|
+
? Object.assign(Object.assign({}, request), { cliLocalData: {
|
|
93
|
+
linuxUserName: yield (0, ssh_key_1.importSshKey)(request.permission.spec.publicKey, options),
|
|
94
|
+
} })
|
|
95
|
+
: request.permission.spec.type === "aws"
|
|
96
|
+
? request
|
|
97
|
+
: (0, util_1.throwAssertNever)(request.permission.spec);
|
|
98
|
+
});
|
|
83
99
|
const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
-
yield validateSshInstall(authn);
|
|
100
|
+
yield validateSshInstall(authn, args);
|
|
101
|
+
const { publicKey, privateKey } = yield (0, keys_1.createKeyPair)();
|
|
85
102
|
const response = yield (0, request_2.request)(Object.assign(Object.assign({}, (0, lodash_1.pick)(args, "$0", "_")), { arguments: [
|
|
86
103
|
"ssh",
|
|
87
104
|
"session",
|
|
88
105
|
destination,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
106
|
+
"--public-key",
|
|
107
|
+
publicKey,
|
|
108
|
+
...(args.provider ? ["--provider", args.provider] : []),
|
|
92
109
|
...(args.sudo || args.command === "sudo" ? ["--sudo"] : []),
|
|
93
110
|
...(args.reason ? ["--reason", args.reason] : []),
|
|
94
111
|
...(args.account ? ["--account", args.account] : []),
|
|
@@ -100,14 +117,25 @@ const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0,
|
|
|
100
117
|
const { id, isPreexisting } = response;
|
|
101
118
|
if (!isPreexisting)
|
|
102
119
|
(0, stdio_1.print2)("Waiting for access to be provisioned");
|
|
103
|
-
yield waitForProvisioning(authn, id);
|
|
104
|
-
|
|
120
|
+
const provisionedRequest = yield waitForProvisioning(authn, id);
|
|
121
|
+
if (provisionedRequest.permission.spec.publicKey !== publicKey) {
|
|
122
|
+
throw "Public key mismatch. Please revoke the request and try again.";
|
|
123
|
+
}
|
|
124
|
+
const cliRequest = yield pluginToCliRequest(provisionedRequest, {
|
|
125
|
+
debug: args.debug,
|
|
126
|
+
});
|
|
127
|
+
return { request: cliRequest, publicKey, privateKey };
|
|
105
128
|
});
|
|
106
129
|
exports.provisionRequest = provisionRequest;
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
const requestToSsh = (request) => {
|
|
131
|
+
if (request.permission.spec.type === "aws") {
|
|
132
|
+
return (0, ssh_1.awsRequestToSsh)(request);
|
|
133
|
+
}
|
|
134
|
+
else if (request.permission.spec.type === "gcloud") {
|
|
135
|
+
return (0, ssh_2.gcpRequestToSsh)(request);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
throw (0, util_1.assertNever)(request.permission.spec);
|
|
139
|
+
}
|
|
112
140
|
};
|
|
113
|
-
exports.
|
|
141
|
+
exports.requestToSsh = requestToSsh;
|
package/dist/commands/ssh.js
CHANGED
|
@@ -20,10 +20,9 @@ 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
|
-
const
|
|
25
|
+
const ssh_1 = require("../plugins/ssh");
|
|
27
26
|
const shared_1 = require("./shared");
|
|
28
27
|
const sshCommand = (yargs) => yargs.command("ssh <destination> [command [arguments..]]", "SSH into a virtual machine", (yargs) => yargs
|
|
29
28
|
.positional("destination", {
|
|
@@ -65,6 +64,11 @@ const sshCommand = (yargs) => yargs.command("ssh <destination> [command [argumen
|
|
|
65
64
|
.option("account", {
|
|
66
65
|
type: "string",
|
|
67
66
|
describe: "The account on which the instance is located",
|
|
67
|
+
})
|
|
68
|
+
.option("provider", {
|
|
69
|
+
type: "string",
|
|
70
|
+
describe: "The cloud provider where the instance is hosted",
|
|
71
|
+
choices: ["aws", "gcloud"],
|
|
68
72
|
})
|
|
69
73
|
.option("debug", {
|
|
70
74
|
type: "boolean",
|
|
@@ -82,15 +86,10 @@ const sshAction = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
82
86
|
// Prefix is required because the backend uses it to determine that this is an AWS request
|
|
83
87
|
const authn = yield (0, auth_1.authenticate)();
|
|
84
88
|
const destination = args.destination;
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
89
|
+
const result = yield (0, shared_1.provisionRequest)(authn, args, destination);
|
|
90
|
+
if (!result) {
|
|
87
91
|
throw "Server did not return a request id. Please contact support@p0.dev for assistance.";
|
|
88
92
|
}
|
|
89
|
-
const {
|
|
90
|
-
|
|
91
|
-
requestId,
|
|
92
|
-
destination,
|
|
93
|
-
publicKey,
|
|
94
|
-
});
|
|
95
|
-
yield (0, ssm_1.sshOrScp)(authn, result, Object.assign(Object.assign({}, args), { destination }), privateKey);
|
|
93
|
+
const { request, privateKey } = result;
|
|
94
|
+
yield (0, ssh_1.sshOrScp)(authn, (0, shared_1.requestToSsh)(request), Object.assign(Object.assign({}, args), { destination }), privateKey);
|
|
96
95
|
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AgentArgs } from "../plugins/ssh-agent/types";
|
|
2
|
+
import { SpawnOptionsWithoutStdio } from "node:child_process";
|
|
3
|
+
/** Spawns a subprocess with given command, args, and options.
|
|
4
|
+
* May write content to its standard input.
|
|
5
|
+
* Stdout and stderr of the subprocess is printed to stderr in debug mode.
|
|
6
|
+
* The returned promise resolves with stdout or rejects with stderr of the subprocess.
|
|
7
|
+
*
|
|
8
|
+
* The captured output is expected to be relatively small.
|
|
9
|
+
* For larger outputs we should implement this with streams. */
|
|
10
|
+
export declare const asyncSpawn: ({ debug }: AgentArgs, command: string, args?: ReadonlyArray<string>, options?: SpawnOptionsWithoutStdio, writeStdin?: string) => Promise<string>;
|
|
@@ -0,0 +1,72 @@
|
|
|
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.asyncSpawn = 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 stdio_1 = require("../drivers/stdio");
|
|
24
|
+
const node_child_process_1 = require("node:child_process");
|
|
25
|
+
/** Spawns a subprocess with given command, args, and options.
|
|
26
|
+
* May write content to its standard input.
|
|
27
|
+
* Stdout and stderr of the subprocess is printed to stderr in debug mode.
|
|
28
|
+
* The returned promise resolves with stdout or rejects with stderr of the subprocess.
|
|
29
|
+
*
|
|
30
|
+
* The captured output is expected to be relatively small.
|
|
31
|
+
* For larger outputs we should implement this with streams. */
|
|
32
|
+
const asyncSpawn = ({ debug }, command, args, options, writeStdin) => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
var _a;
|
|
35
|
+
const child = (0, node_child_process_1.spawn)(command, args, options);
|
|
36
|
+
// Use streams for larger output
|
|
37
|
+
let stdout = "";
|
|
38
|
+
let stderr = "";
|
|
39
|
+
if (writeStdin) {
|
|
40
|
+
if (!child.stdin)
|
|
41
|
+
return reject("Child process has no stdin");
|
|
42
|
+
child.stdin.write(writeStdin);
|
|
43
|
+
}
|
|
44
|
+
child.stdout.on("data", (data) => {
|
|
45
|
+
const str = data.toString("utf-8");
|
|
46
|
+
stdout += str;
|
|
47
|
+
if (debug) {
|
|
48
|
+
(0, stdio_1.print2)(str);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
child.stderr.on("data", (data) => {
|
|
52
|
+
const str = data.toString("utf-8");
|
|
53
|
+
stderr += str;
|
|
54
|
+
if (debug) {
|
|
55
|
+
(0, stdio_1.print2)(data.toString("utf-8"));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
child.on("exit", (code) => {
|
|
59
|
+
if (debug) {
|
|
60
|
+
(0, stdio_1.print2)("Process exited with code " + code);
|
|
61
|
+
}
|
|
62
|
+
if (code !== 0) {
|
|
63
|
+
return reject(stderr);
|
|
64
|
+
}
|
|
65
|
+
resolve(stdout);
|
|
66
|
+
});
|
|
67
|
+
if (writeStdin) {
|
|
68
|
+
(_a = child.stdin) === null || _a === void 0 ? void 0 : _a.end();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
exports.asyncSpawn = asyncSpawn;
|
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>;
|