@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.
@@ -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 ssm_1 = require("../../plugins/aws/ssm");
28
+ const ssh_1 = require("../../plugins/ssh");
28
29
  const firestore_1 = require("../../testing/firestore");
29
30
  const util_1 = require("../../util");
30
- const ssh_1 = require("../ssh");
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/aws/ssm");
38
+ jest.mock("../../plugins/ssh");
39
+ jest.mock("../../common/keys");
38
40
  const mockFetchCommand = api_1.fetchCommand;
39
- const mockSshOrScp = ssm_1.sshOrScp;
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, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance --reason reason`);
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, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
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, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
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, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance do something`);
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, ssh_1.sshCommand)((0, yargs_1.default)()).parse(`ssh some-instance`);
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();
@@ -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 ssm_1 = require("../plugins/aws/ssm");
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 requestId = yield (0, shared_1.provisionRequest)(authn, args, host);
74
- if (!requestId) {
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 { publicKey, privateKey } = (0, shared_1.createKeyPair)();
78
- const result = yield (0, api_1.fetchExerciseGrant)(authn, {
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(result, args);
85
- yield (0, ssm_1.sshOrScp)(authn, result, Object.assign(Object.assign({}, args), { source,
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.instance.id}:${source.split(":")[1]}`;
109
+ source = `${result.linuxUserName}@${result.id}:${source.split(":")[1]}`;
110
110
  }
111
111
  if (isExplicitlyRemote(destination)) {
112
- destination = `${result.linuxUserName}@${result.instance.id}:${destination.split(":")[1]}`;
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 type ExerciseGrantResponse = {
4
- documentName: string;
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
- instance: {
9
- arn: string;
10
- accountId: string;
11
- region: string;
12
- id: string;
13
- name?: string;
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<string | undefined>;
38
- export declare const createKeyPair: () => {
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;
@@ -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.createKeyPair = exports.provisionRequest = void 0;
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
- const validateSshInstall = (authn) => __awaiter(void 0, void 0, void 0, function* () {
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 items = Object.entries(configItems !== null && configItems !== void 0 ? configItems : {}).filter(([key, value]) => value.state == "installed" && key.startsWith("aws"));
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
- // Prefix is required because the backend uses it to determine that this is an AWS request
90
- "--provider",
91
- "aws",
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
- return id;
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 createKeyPair = () => {
108
- const rsaKeyPair = node_forge_1.default.pki.rsa.generateKeyPair({ bits: 2048 });
109
- const privateKey = node_forge_1.default.pki.privateKeyToPem(rsaKeyPair.privateKey);
110
- const publicKey = node_forge_1.default.ssh.publicKeyToOpenSSH(rsaKeyPair.publicKey);
111
- return { publicKey, privateKey };
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.createKeyPair = createKeyPair;
141
+ exports.requestToSsh = requestToSsh;
@@ -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 ssm_1 = require("../plugins/aws/ssm");
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 requestId = yield (0, shared_1.provisionRequest)(authn, args, destination);
86
- if (!requestId) {
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 { publicKey, privateKey } = (0, shared_1.createKeyPair)();
90
- const result = yield (0, api_1.fetchExerciseGrant)(authn, {
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;
@@ -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>;