@p0security/cli 0.8.0 → 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 +11 -10
- package/dist/commands/scp.js +7 -2
- package/dist/commands/shared.d.ts +16 -7
- package/dist/commands/shared.js +38 -16
- package/dist/commands/ssh.js +8 -2
- package/dist/common/subprocess.d.ts +10 -0
- package/dist/common/subprocess.js +72 -0
- 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 -14
- 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 +2 -2
- package/dist/plugins/ssh/index.js +302 -0
- package/dist/plugins/ssh/types.d.ts +4 -0
- package/dist/plugins/ssh-agent/index.js +5 -39
- 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 -213
|
@@ -25,20 +25,20 @@ You should have received a copy of the GNU General Public License along with @p0
|
|
|
25
25
|
const keys_1 = require("../../common/__mocks__/keys");
|
|
26
26
|
const api_1 = require("../../drivers/api");
|
|
27
27
|
const stdio_1 = require("../../drivers/stdio");
|
|
28
|
-
const
|
|
28
|
+
const ssh_1 = require("../../plugins/ssh");
|
|
29
29
|
const firestore_1 = require("../../testing/firestore");
|
|
30
30
|
const util_1 = require("../../util");
|
|
31
|
-
const
|
|
31
|
+
const ssh_2 = require("../ssh");
|
|
32
32
|
const firestore_2 = require("firebase/firestore");
|
|
33
33
|
const lodash_1 = require("lodash");
|
|
34
34
|
const yargs_1 = __importDefault(require("yargs"));
|
|
35
35
|
jest.mock("../../drivers/api");
|
|
36
36
|
jest.mock("../../drivers/auth");
|
|
37
37
|
jest.mock("../../drivers/stdio");
|
|
38
|
-
jest.mock("../../plugins/
|
|
38
|
+
jest.mock("../../plugins/ssh");
|
|
39
39
|
jest.mock("../../common/keys");
|
|
40
40
|
const mockFetchCommand = api_1.fetchCommand;
|
|
41
|
-
const mockSshOrScp =
|
|
41
|
+
const mockSshOrScp = ssh_1.sshOrScp;
|
|
42
42
|
const mockPrint1 = stdio_1.print1;
|
|
43
43
|
const mockPrint2 = stdio_1.print2;
|
|
44
44
|
const MOCK_REQUEST = {
|
|
@@ -47,7 +47,6 @@ const MOCK_REQUEST = {
|
|
|
47
47
|
name: "name",
|
|
48
48
|
ssh: {
|
|
49
49
|
linuxUserName: "linuxUserName",
|
|
50
|
-
publicKey: keys_1.TEST_PUBLIC_KEY,
|
|
51
50
|
},
|
|
52
51
|
},
|
|
53
52
|
permission: {
|
|
@@ -55,6 +54,8 @@ const MOCK_REQUEST = {
|
|
|
55
54
|
instanceId: "instanceId",
|
|
56
55
|
accountId: "accountId",
|
|
57
56
|
region: "region",
|
|
57
|
+
publicKey: keys_1.TEST_PUBLIC_KEY,
|
|
58
|
+
type: "aws",
|
|
58
59
|
},
|
|
59
60
|
},
|
|
60
61
|
};
|
|
@@ -91,19 +92,19 @@ describe("ssh", () => {
|
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
it("should call p0 request with reason arg", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
94
|
-
void (0,
|
|
95
|
+
void (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance --reason reason --provider aws`);
|
|
95
96
|
yield (0, util_1.sleep)(100);
|
|
96
97
|
const hiddenFilenameRequestArgs = (0, lodash_1.omit)(mockFetchCommand.mock.calls[0][1], "$0");
|
|
97
98
|
expect(hiddenFilenameRequestArgs).toMatchSnapshot("args");
|
|
98
99
|
}));
|
|
99
100
|
it("should wait for access grant", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
|
-
const promise = (0,
|
|
101
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
101
102
|
const wait = (0, util_1.sleep)(100);
|
|
102
103
|
yield Promise.race([wait, promise]);
|
|
103
104
|
yield expect(wait).resolves.toBeUndefined();
|
|
104
105
|
}));
|
|
105
106
|
it("should wait for provisioning", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
106
|
-
const promise = (0,
|
|
107
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
107
108
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
108
109
|
firestore_2.onSnapshot.trigger({
|
|
109
110
|
status: "APPROVED",
|
|
@@ -113,7 +114,7 @@ describe("ssh", () => {
|
|
|
113
114
|
yield expect(wait).resolves.toBeUndefined();
|
|
114
115
|
}));
|
|
115
116
|
it("should call sshOrScp with non-interactive command", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
-
const promise = (0,
|
|
117
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance do something`);
|
|
117
118
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
118
119
|
firestore_2.onSnapshot.trigger({
|
|
119
120
|
status: "APPROVED",
|
|
@@ -126,7 +127,7 @@ describe("ssh", () => {
|
|
|
126
127
|
expect(mockSshOrScp).toHaveBeenCalled();
|
|
127
128
|
}));
|
|
128
129
|
it("should call sshOrScp with interactive session", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
129
|
-
const promise = (0,
|
|
130
|
+
const promise = (0, ssh_2.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
|
|
130
131
|
yield (0, util_1.sleep)(100); // Need to wait for listen before trigger in tests
|
|
131
132
|
firestore_2.onSnapshot.trigger({
|
|
132
133
|
status: "APPROVED",
|
package/dist/commands/scp.js
CHANGED
|
@@ -22,7 +22,7 @@ You should have received a copy of the GNU General Public License along with @p0
|
|
|
22
22
|
**/
|
|
23
23
|
const auth_1 = require("../drivers/auth");
|
|
24
24
|
const firestore_1 = require("../drivers/firestore");
|
|
25
|
-
const
|
|
25
|
+
const ssh_1 = require("../plugins/ssh");
|
|
26
26
|
const shared_1 = require("./shared");
|
|
27
27
|
const scpCommand = (yargs) => yargs.command("scp <source> <destination>",
|
|
28
28
|
// TODO (ENG-1930): support scp across multiple remote hosts.
|
|
@@ -49,6 +49,11 @@ const scpCommand = (yargs) => yargs.command("scp <source> <destination>",
|
|
|
49
49
|
.option("account", {
|
|
50
50
|
type: "string",
|
|
51
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,
|
|
52
57
|
})
|
|
53
58
|
.option("sudo", {
|
|
54
59
|
type: "boolean",
|
|
@@ -77,7 +82,7 @@ const scpAction = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
77
82
|
const data = (0, shared_1.requestToSsh)(request);
|
|
78
83
|
// replace the host with the linuxUserName@instanceId
|
|
79
84
|
const { source, destination } = replaceHostWithInstance(data, args);
|
|
80
|
-
yield (0,
|
|
85
|
+
yield (0, ssh_1.sshOrScp)(authn, data, Object.assign(Object.assign({}, args), { source,
|
|
81
86
|
destination }), privateKey);
|
|
82
87
|
});
|
|
83
88
|
/** If a path is not explicitly local, use this pattern to determine if it's remote */
|
|
@@ -1,24 +1,34 @@
|
|
|
1
|
-
import { AwsSsh } from "../plugins/aws/types";
|
|
2
1
|
import { Authn } from "../types/identity";
|
|
3
|
-
import { Request } from "../types/request";
|
|
2
|
+
import { CliRequest, Request } from "../types/request";
|
|
4
3
|
import yargs from "yargs";
|
|
5
|
-
export declare
|
|
4
|
+
export declare const SUPPORTED_PROVIDERS: string[];
|
|
5
|
+
export declare type SshRequest = AwsSshRequest | GcpSshRequest;
|
|
6
|
+
export declare type AwsSshRequest = {
|
|
6
7
|
linuxUserName: string;
|
|
7
8
|
role: string;
|
|
8
9
|
accountId: string;
|
|
9
10
|
region: string;
|
|
10
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";
|
|
11
20
|
};
|
|
12
21
|
export declare type BaseSshCommandArgs = {
|
|
13
22
|
sudo?: boolean;
|
|
14
23
|
reason?: string;
|
|
15
24
|
account?: string;
|
|
25
|
+
provider?: "aws" | "gcloud";
|
|
26
|
+
debug?: boolean;
|
|
16
27
|
};
|
|
17
28
|
export declare type ScpCommandArgs = BaseSshCommandArgs & {
|
|
18
29
|
source: string;
|
|
19
30
|
destination: string;
|
|
20
31
|
recursive?: boolean;
|
|
21
|
-
debug?: boolean;
|
|
22
32
|
};
|
|
23
33
|
export declare type SshCommandArgs = BaseSshCommandArgs & {
|
|
24
34
|
sudo?: boolean;
|
|
@@ -28,11 +38,10 @@ export declare type SshCommandArgs = BaseSshCommandArgs & {
|
|
|
28
38
|
A?: boolean;
|
|
29
39
|
arguments: string[];
|
|
30
40
|
command?: string;
|
|
31
|
-
debug?: boolean;
|
|
32
41
|
};
|
|
33
42
|
export declare const provisionRequest: (authn: Authn, args: yargs.ArgumentsCamelCase<BaseSshCommandArgs>, destination: string) => Promise<{
|
|
34
|
-
request: Request<
|
|
43
|
+
request: Request<CliRequest>;
|
|
35
44
|
publicKey: string;
|
|
36
45
|
privateKey: string;
|
|
37
46
|
} | undefined>;
|
|
38
|
-
export declare const requestToSsh: (request:
|
|
47
|
+
export declare const requestToSsh: (request: Request<CliRequest>) => 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.requestToSsh = exports.provisionRequest = void 0;
|
|
12
|
+
exports.requestToSsh = exports.provisionRequest = exports.SUPPORTED_PROVIDERS = void 0;
|
|
13
13
|
/** Copyright © 2024-present P0 Security
|
|
14
14
|
|
|
15
15
|
This file is part of @p0security/cli
|
|
@@ -23,7 +23,11 @@ You should have received a copy of the GNU General Public License along with @p0
|
|
|
23
23
|
const keys_1 = require("../common/keys");
|
|
24
24
|
const firestore_1 = require("../drivers/firestore");
|
|
25
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");
|
|
26
29
|
const request_1 = require("../types/request");
|
|
30
|
+
const util_1 = require("../util");
|
|
27
31
|
const request_2 = require("./request");
|
|
28
32
|
const firestore_2 = require("firebase/firestore");
|
|
29
33
|
const lodash_1 = require("lodash");
|
|
@@ -31,11 +35,17 @@ const lodash_1 = require("lodash");
|
|
|
31
35
|
* to be configured
|
|
32
36
|
*/
|
|
33
37
|
const GRANT_TIMEOUT_MILLIS = 60e3;
|
|
34
|
-
|
|
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* () {
|
|
35
41
|
var _a;
|
|
36
42
|
const configDoc = yield (0, firestore_2.getDoc)((0, firestore_1.doc)(`o/${authn.identity.org.tenantId}/integrations/ssh`));
|
|
37
43
|
const configItems = (_a = configDoc.data()) === null || _a === void 0 ? void 0 : _a["iam-write"];
|
|
38
|
-
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)));
|
|
39
49
|
if (items.length === 0) {
|
|
40
50
|
throw "This organization is not configured for SSH access via the P0 CLI";
|
|
41
51
|
}
|
|
@@ -77,8 +87,17 @@ const waitForProvisioning = (authn, requestId) => __awaiter(void 0, void 0, void
|
|
|
77
87
|
clearTimeout(cancel);
|
|
78
88
|
return result;
|
|
79
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
|
+
});
|
|
80
99
|
const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
|
-
yield validateSshInstall(authn);
|
|
100
|
+
yield validateSshInstall(authn, args);
|
|
82
101
|
const { publicKey, privateKey } = yield (0, keys_1.createKeyPair)();
|
|
83
102
|
const response = yield (0, request_2.request)(Object.assign(Object.assign({}, (0, lodash_1.pick)(args, "$0", "_")), { arguments: [
|
|
84
103
|
"ssh",
|
|
@@ -86,9 +105,7 @@ const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0,
|
|
|
86
105
|
destination,
|
|
87
106
|
"--public-key",
|
|
88
107
|
publicKey,
|
|
89
|
-
|
|
90
|
-
"--provider",
|
|
91
|
-
"aws",
|
|
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] : []),
|
|
@@ -101,19 +118,24 @@ const provisionRequest = (authn, args, destination) => __awaiter(void 0, void 0,
|
|
|
101
118
|
if (!isPreexisting)
|
|
102
119
|
(0, stdio_1.print2)("Waiting for access to be provisioned");
|
|
103
120
|
const provisionedRequest = yield waitForProvisioning(authn, id);
|
|
104
|
-
if (provisionedRequest.
|
|
121
|
+
if (provisionedRequest.permission.spec.publicKey !== publicKey) {
|
|
105
122
|
throw "Public key mismatch. Please revoke the request and try again.";
|
|
106
123
|
}
|
|
107
|
-
|
|
124
|
+
const cliRequest = yield pluginToCliRequest(provisionedRequest, {
|
|
125
|
+
debug: args.debug,
|
|
126
|
+
});
|
|
127
|
+
return { request: cliRequest, publicKey, privateKey };
|
|
108
128
|
});
|
|
109
129
|
exports.provisionRequest = provisionRequest;
|
|
110
130
|
const requestToSsh = (request) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
|
118
140
|
};
|
|
119
141
|
exports.requestToSsh = requestToSsh;
|
package/dist/commands/ssh.js
CHANGED
|
@@ -22,7 +22,7 @@ You should have received a copy of the GNU General Public License along with @p0
|
|
|
22
22
|
**/
|
|
23
23
|
const auth_1 = require("../drivers/auth");
|
|
24
24
|
const firestore_1 = require("../drivers/firestore");
|
|
25
|
-
const
|
|
25
|
+
const ssh_1 = require("../plugins/ssh");
|
|
26
26
|
const shared_1 = require("./shared");
|
|
27
27
|
const sshCommand = (yargs) => yargs.command("ssh <destination> [command [arguments..]]", "SSH into a virtual machine", (yargs) => yargs
|
|
28
28
|
.positional("destination", {
|
|
@@ -64,6 +64,11 @@ const sshCommand = (yargs) => yargs.command("ssh <destination> [command [argumen
|
|
|
64
64
|
.option("account", {
|
|
65
65
|
type: "string",
|
|
66
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"],
|
|
67
72
|
})
|
|
68
73
|
.option("debug", {
|
|
69
74
|
type: "boolean",
|
|
@@ -85,5 +90,6 @@ const sshAction = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
85
90
|
if (!result) {
|
|
86
91
|
throw "Server did not return a request id. Please contact support@p0.dev for assistance.";
|
|
87
92
|
}
|
|
88
|
-
|
|
93
|
+
const { request, privateKey } = result;
|
|
94
|
+
yield (0, ssh_1.sshOrScp)(authn, (0, shared_1.requestToSsh)(request), Object.assign(Object.assign({}, args), { destination }), privateKey);
|
|
89
95
|
});
|
|
@@ -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;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 { SshRequest } from "../../commands/shared";
|
|
12
|
+
import { AwsSsh } from "./types";
|
|
13
|
+
export declare const awsRequestToSsh: (request: AwsSsh) => SshRequest;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.awsRequestToSsh = void 0;
|
|
4
|
+
const awsRequestToSsh = (request) => {
|
|
5
|
+
return {
|
|
6
|
+
id: request.permission.spec.instanceId,
|
|
7
|
+
accountId: request.permission.spec.accountId,
|
|
8
|
+
region: request.permission.spec.region,
|
|
9
|
+
role: request.generated.name,
|
|
10
|
+
linuxUserName: request.generated.ssh.linuxUserName,
|
|
11
|
+
type: "aws",
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
exports.awsRequestToSsh = awsRequestToSsh;
|
|
@@ -8,6 +8,8 @@ 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 { CliPermissionSpec, PermissionSpec } from "../../types/request";
|
|
12
|
+
import { CommonSshPermissionSpec } from "../ssh/types";
|
|
11
13
|
export declare type AwsCredentials = {
|
|
12
14
|
AWS_ACCESS_KEY_ID: string;
|
|
13
15
|
AWS_SECRET_ACCESS_KEY: string;
|
|
@@ -50,21 +52,21 @@ export declare type AwsItem = {
|
|
|
50
52
|
export declare type AwsConfig = {
|
|
51
53
|
"iam-write": Record<string, AwsItemConfig>;
|
|
52
54
|
};
|
|
53
|
-
export declare type
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
type: "session";
|
|
55
|
+
export declare type AwsSshPermission = {
|
|
56
|
+
spec: CommonSshPermissionSpec & {
|
|
57
|
+
instanceId: string;
|
|
58
|
+
accountId: string;
|
|
59
|
+
region: string;
|
|
60
|
+
type: "aws";
|
|
61
61
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
type: "session";
|
|
63
|
+
};
|
|
64
|
+
export declare type AwsSshGenerated = {
|
|
65
|
+
name: string;
|
|
66
|
+
ssh: {
|
|
67
|
+
linuxUserName: string;
|
|
68
68
|
};
|
|
69
69
|
};
|
|
70
|
+
export declare type AwsPermissionSpec = PermissionSpec<"ssh", AwsSshPermission, AwsSshGenerated>;
|
|
71
|
+
export declare type AwsSsh = CliPermissionSpec<AwsPermissionSpec>;
|
|
70
72
|
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds an ssh public key to the user object's sshPublicKeys array in Google Workspace.
|
|
3
|
+
* GCP OS Login uses these public keys to authenticate the user.
|
|
4
|
+
* Importing the same public key multiple times is idempotent.
|
|
5
|
+
*
|
|
6
|
+
* The user account and the access token is retrieved from the gcloud CLI.
|
|
7
|
+
*
|
|
8
|
+
* Returns the posix account to use for SSH access.
|
|
9
|
+
*
|
|
10
|
+
* See https://cloud.google.com/compute/docs/oslogin/rest/v1/users/importSshPublicKey
|
|
11
|
+
*/
|
|
12
|
+
export declare const importSshKey: (publicKey: string, options?: {
|
|
13
|
+
debug?: boolean;
|
|
14
|
+
}) => Promise<string>;
|
|
@@ -0,0 +1,80 @@
|
|
|
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.importSshKey = 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 subprocess_1 = require("../../common/subprocess");
|
|
24
|
+
const stdio_1 = require("../../drivers/stdio");
|
|
25
|
+
/**
|
|
26
|
+
* Adds an ssh public key to the user object's sshPublicKeys array in Google Workspace.
|
|
27
|
+
* GCP OS Login uses these public keys to authenticate the user.
|
|
28
|
+
* Importing the same public key multiple times is idempotent.
|
|
29
|
+
*
|
|
30
|
+
* The user account and the access token is retrieved from the gcloud CLI.
|
|
31
|
+
*
|
|
32
|
+
* Returns the posix account to use for SSH access.
|
|
33
|
+
*
|
|
34
|
+
* See https://cloud.google.com/compute/docs/oslogin/rest/v1/users/importSshPublicKey
|
|
35
|
+
*/
|
|
36
|
+
const importSshKey = (publicKey, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
|
+
var _a;
|
|
38
|
+
const debug = (_a = options === null || options === void 0 ? void 0 : options.debug) !== null && _a !== void 0 ? _a : false;
|
|
39
|
+
// Force debug=false otherwise it prints the access token
|
|
40
|
+
const accessToken = yield (0, subprocess_1.asyncSpawn)({ debug: false }, "gcloud", [
|
|
41
|
+
"auth",
|
|
42
|
+
"print-access-token",
|
|
43
|
+
]);
|
|
44
|
+
const account = yield (0, subprocess_1.asyncSpawn)({ debug }, "gcloud", [
|
|
45
|
+
"config",
|
|
46
|
+
"get-value",
|
|
47
|
+
"account",
|
|
48
|
+
]);
|
|
49
|
+
if (debug) {
|
|
50
|
+
(0, stdio_1.print2)(`Retrieved access token ${accessToken.slice(0, 10)}... for account ${account}`);
|
|
51
|
+
}
|
|
52
|
+
const url = `https://oslogin.googleapis.com/v1/users/${account}:importSshPublicKey`;
|
|
53
|
+
const response = yield fetch(url, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
key: publicKey,
|
|
57
|
+
}),
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${accessToken}`,
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const data = yield response.json();
|
|
64
|
+
if (debug) {
|
|
65
|
+
(0, stdio_1.print2)(`Login profile for user after importing public key: ${JSON.stringify(data)}`);
|
|
66
|
+
}
|
|
67
|
+
const { loginProfile } = data;
|
|
68
|
+
// Find the primary POSIX account for the user, or the first in the array
|
|
69
|
+
const linuxAccounts = loginProfile.posixAccounts.filter((account) => account.operatingSystemType === "LINUX");
|
|
70
|
+
const posixAccount = linuxAccounts.find((account) => account.primary) ||
|
|
71
|
+
loginProfile.posixAccounts[0];
|
|
72
|
+
if (debug) {
|
|
73
|
+
(0, stdio_1.print2)(`Picked linux user name: ${posixAccount === null || posixAccount === void 0 ? void 0 : posixAccount.username}`);
|
|
74
|
+
}
|
|
75
|
+
if (!posixAccount) {
|
|
76
|
+
throw "No POSIX accounts configured for the user. Ask your Google Workspace administrator to configure the user's POSIX account.";
|
|
77
|
+
}
|
|
78
|
+
return posixAccount.username;
|
|
79
|
+
});
|
|
80
|
+
exports.importSshKey = importSshKey;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 { SshRequest } from "../../commands/shared";
|
|
12
|
+
import { GcpSsh } from "./types";
|
|
13
|
+
export declare const gcpRequestToSsh: (request: GcpSsh) => SshRequest;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gcpRequestToSsh = void 0;
|
|
4
|
+
const gcpRequestToSsh = (request) => {
|
|
5
|
+
return {
|
|
6
|
+
id: request.permission.spec.instanceName,
|
|
7
|
+
projectId: request.permission.spec.projectId,
|
|
8
|
+
zone: request.permission.spec.zone,
|
|
9
|
+
linuxUserName: request.cliLocalData.linuxUserName,
|
|
10
|
+
type: "gcloud",
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
exports.gcpRequestToSsh = gcpRequestToSsh;
|
|
@@ -0,0 +1,49 @@
|
|
|
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 { CliPermissionSpec, PermissionSpec } from "../../types/request";
|
|
12
|
+
import { CommonSshPermissionSpec } from "../ssh/types";
|
|
13
|
+
export declare type GcpSshPermission = {
|
|
14
|
+
spec: CommonSshPermissionSpec & {
|
|
15
|
+
instanceName: string;
|
|
16
|
+
projectId: string;
|
|
17
|
+
zone: string;
|
|
18
|
+
type: "gcloud";
|
|
19
|
+
};
|
|
20
|
+
type: "session";
|
|
21
|
+
};
|
|
22
|
+
export declare type GcpPermissionSpec = PermissionSpec<"ssh", GcpSshPermission>;
|
|
23
|
+
export declare type GcpSsh = CliPermissionSpec<GcpPermissionSpec, {
|
|
24
|
+
linuxUserName: string;
|
|
25
|
+
}>;
|
|
26
|
+
declare type PosixAccount = {
|
|
27
|
+
username: string;
|
|
28
|
+
uid: string;
|
|
29
|
+
gid: string;
|
|
30
|
+
operatingSystemType: "LINUX" | "OPERATING_SYSTEM_TYPE_UNSPECIFIED" | "WINDOWS";
|
|
31
|
+
homeDirectory?: string;
|
|
32
|
+
primary?: boolean;
|
|
33
|
+
};
|
|
34
|
+
declare type SshPublicKey = {
|
|
35
|
+
key: string;
|
|
36
|
+
fingerprint?: string;
|
|
37
|
+
expirationTimeUsec?: number;
|
|
38
|
+
};
|
|
39
|
+
declare type LoginProfile = {
|
|
40
|
+
name: string;
|
|
41
|
+
posixAccounts: PosixAccount[];
|
|
42
|
+
sshPublicKeys: {
|
|
43
|
+
[fingerprint: string]: SshPublicKey;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export declare type ImportSshPublicKeyResponse = {
|
|
47
|
+
loginProfile: LoginProfile;
|
|
48
|
+
};
|
|
49
|
+
export {};
|
|
@@ -8,6 +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 { ScpCommandArgs, SshCommandArgs, SshRequest } from "
|
|
12
|
-
import { Authn } from "
|
|
11
|
+
import { ScpCommandArgs, SshCommandArgs, SshRequest } from "../../commands/shared";
|
|
12
|
+
import { Authn } from "../../types/identity";
|
|
13
13
|
export declare const sshOrScp: (authn: Authn, data: SshRequest, cmdArgs: ScpCommandArgs | SshCommandArgs, privateKey: string) => Promise<number | null>;
|