@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.
@@ -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 ssm_1 = require("../../plugins/aws/ssm");
28
+ const ssh_1 = require("../../plugins/ssh");
29
29
  const firestore_1 = require("../../testing/firestore");
30
30
  const util_1 = require("../../util");
31
- const ssh_1 = require("../ssh");
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/aws/ssm");
38
+ jest.mock("../../plugins/ssh");
39
39
  jest.mock("../../common/keys");
40
40
  const mockFetchCommand = api_1.fetchCommand;
41
- const mockSshOrScp = ssm_1.sshOrScp;
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, 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`);
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, 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`);
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, 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`);
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, 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`);
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, 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`);
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",
@@ -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 ssm_1 = require("../plugins/aws/ssm");
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, ssm_1.sshOrScp)(authn, data, Object.assign(Object.assign({}, args), { source,
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 type SshRequest = {
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<AwsSsh>;
43
+ request: Request<CliRequest>;
35
44
  publicKey: string;
36
45
  privateKey: string;
37
46
  } | undefined>;
38
- export declare const requestToSsh: (request: AwsSsh) => SshRequest;
47
+ export declare const requestToSsh: (request: Request<CliRequest>) => SshRequest;
@@ -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
- 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* () {
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 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)));
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
- // Prefix is required because the backend uses it to determine that this is an AWS request
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.generated.ssh.publicKey !== publicKey) {
121
+ if (provisionedRequest.permission.spec.publicKey !== publicKey) {
105
122
  throw "Public key mismatch. Please revoke the request and try again.";
106
123
  }
107
- return { request: provisionedRequest, publicKey, privateKey };
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
- return {
112
- id: request.permission.spec.instanceId,
113
- accountId: request.permission.spec.accountId,
114
- region: request.permission.spec.region,
115
- role: request.generated.name,
116
- linuxUserName: request.generated.ssh.linuxUserName,
117
- };
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;
@@ -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 ssm_1 = require("../plugins/aws/ssm");
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
- yield (0, ssm_1.sshOrScp)(authn, (0, shared_1.requestToSsh)(result.request), Object.assign(Object.assign({}, args), { destination }), result.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);
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 AwsSsh = {
54
- permission: {
55
- spec: {
56
- instanceId: string;
57
- accountId: string;
58
- region: string;
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
- generated: {
63
- name: string;
64
- ssh: {
65
- linuxUserName: string;
66
- publicKey: string;
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 {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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 "../../../commands/shared";
12
- import { Authn } from "../../../types/identity";
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>;