@p0security/cli 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,9 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
11
  Object.defineProperty(exports, "__esModule", { value: true });
15
12
  exports.getSamlResponse = exports.oktaLogin = void 0;
16
13
  /** Copyright © 2024-present P0 Security
@@ -25,81 +22,13 @@ You should have received a copy of the GNU General Public License along with @p0
25
22
  **/
26
23
  const oidc_1 = require("../../common/auth/oidc");
27
24
  const fetch_1 = require("../../common/fetch");
28
- const stdio_1 = require("../../drivers/stdio");
29
- const util_1 = require("../../util");
25
+ const login_1 = require("../oidc/login");
30
26
  const jsdom_1 = require("jsdom");
31
27
  const lodash_1 = require("lodash");
32
- const open_1 = __importDefault(require("open"));
33
- const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
34
28
  const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
35
29
  const ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token";
36
30
  const TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
37
31
  const WEB_SSO_TOKEN_TYPE = "urn:okta:oauth:token-type:web_sso_token";
38
- const validateProviderDomain = (org) => {
39
- if (!org.providerDomain)
40
- throw "Okta login requires a configured provider domain.";
41
- };
42
- /** Executes the first step of Okta's device-authorization grant flow */
43
- // cf. https://developer.okta.com/docs/guides/device-authorization-grant/main/
44
- const authorize = (org) => __awaiter(void 0, void 0, void 0, function* () {
45
- const init = {
46
- method: "POST",
47
- headers: oidc_1.OIDC_HEADERS,
48
- body: (0, fetch_1.urlEncode)({
49
- client_id: org.clientId,
50
- scope: "openid email profile okta.apps.sso",
51
- }),
52
- };
53
- validateProviderDomain(org);
54
- // This is the "org" authorization server; the okta.apps.* scopes are not
55
- // available with custom authorization servers
56
- const response = yield fetch(`https:${org.providerDomain}/oauth2/v1/device/authorize`, init);
57
- yield (0, fetch_1.validateResponse)(response);
58
- return (yield response.json());
59
- });
60
- /** Attempts to fetch this device's OIDC token
61
- *
62
- * The authorization may or may not be granted at this stage. If it is not, the
63
- * authorization server will return "authorization_pending", in which case this
64
- * function will return undefined.
65
- */
66
- const fetchOidcToken = (org, authorize) => __awaiter(void 0, void 0, void 0, function* () {
67
- const init = {
68
- method: "POST",
69
- headers: oidc_1.OIDC_HEADERS,
70
- body: (0, fetch_1.urlEncode)({
71
- client_id: org.clientId,
72
- device_code: authorize.device_code,
73
- grant_type: DEVICE_GRANT_TYPE,
74
- }),
75
- };
76
- validateProviderDomain(org);
77
- const response = yield fetch(`https:${org.providerDomain}/oauth2/v1/token`, init);
78
- if (!response.ok) {
79
- if (response.status === 400) {
80
- const data = yield response.json();
81
- if (data.error === "authorization_pending")
82
- return undefined;
83
- }
84
- yield (0, fetch_1.validateResponse)(response);
85
- }
86
- return (yield response.json());
87
- });
88
- /** Waits until user device authorization is complete
89
- *
90
- * Returns the OIDC token after completion.
91
- */
92
- const waitForActivation = (org, authorize) => __awaiter(void 0, void 0, void 0, function* () {
93
- const start = Date.now();
94
- while (Date.now() - start <= authorize.expires_in * 1e3) {
95
- const response = yield fetchOidcToken(org, authorize);
96
- if (!response)
97
- yield (0, util_1.sleep)(authorize.interval * 1e3);
98
- else
99
- return response;
100
- }
101
- throw "Expired awaiting in-browser authorization.";
102
- });
103
32
  /** Exchanges an Okta OIDC SSO token for an Okta app SSO token */
104
33
  const fetchSsoWebToken = (appId, { org, credential }) => __awaiter(void 0, void 0, void 0, function* () {
105
34
  const init = {
@@ -116,7 +45,7 @@ const fetchSsoWebToken = (appId, { org, credential }) => __awaiter(void 0, void
116
45
  requested_token_type: WEB_SSO_TOKEN_TYPE,
117
46
  }),
118
47
  };
119
- validateProviderDomain(org);
48
+ (0, login_1.validateProviderDomain)(org);
120
49
  const response = yield fetch(`https:${org.providerDomain}/oauth2/v1/token`, init);
121
50
  yield (0, fetch_1.validateResponse)(response);
122
51
  return (yield response.json());
@@ -127,7 +56,7 @@ const fetchSamlResponse = (org, { access_token }) => __awaiter(void 0, void 0, v
127
56
  method: "GET",
128
57
  headers: (0, lodash_1.omit)(oidc_1.OIDC_HEADERS, "Content-Type"),
129
58
  };
130
- validateProviderDomain(org);
59
+ (0, login_1.validateProviderDomain)(org);
131
60
  const url = `https://${org.providerDomain}/login/token/sso?token=${encodeURIComponent(access_token)}`;
132
61
  const response = yield fetch(url, init);
133
62
  yield (0, fetch_1.validateResponse)(response);
@@ -137,20 +66,7 @@ const fetchSamlResponse = (org, { access_token }) => __awaiter(void 0, void 0, v
137
66
  return samlInput === null || samlInput === void 0 ? void 0 : samlInput.value;
138
67
  });
139
68
  /** Logs in to Okta via OIDC */
140
- const oktaLogin = (org) => __awaiter(void 0, void 0, void 0, function* () {
141
- const authorizeResponse = yield authorize(org);
142
- (0, stdio_1.print2)(`Please use the opened browser window to continue your P0 login.
143
-
144
- When prompted, confirm that Okta displays this code:
145
-
146
- ${authorizeResponse.user_code}
147
-
148
- Waiting for authorization...
149
- `);
150
- void (0, open_1.default)(authorizeResponse.verification_uri_complete);
151
- const oidcResponse = yield waitForActivation(org, authorizeResponse);
152
- return oidcResponse;
153
- });
69
+ const oktaLogin = (org) => __awaiter(void 0, void 0, void 0, function* () { return (0, login_1.oidcLogin)(org, "openid email profile okta.apps.sso"); });
154
70
  exports.oktaLogin = oktaLogin;
155
71
  /** Retrieves a SAML response for an okta app */
156
72
  // TODO: Inject Okta app
@@ -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 { OrgData } from "../../types/org";
12
+ /** Logs in to PingOne via OIDC */
13
+ export declare const pingLogin: (org: OrgData) => Promise<import("../../types/oidc").TokenResponse>;
@@ -0,0 +1,16 @@
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.pingLogin = void 0;
13
+ const login_1 = require("../oidc/login");
14
+ /** Logs in to PingOne via OIDC */
15
+ const pingLogin = (org) => __awaiter(void 0, void 0, void 0, function* () { return (0, login_1.oidcLogin)(org, "openid email profile"); });
16
+ exports.pingLogin = pingLogin;
@@ -0,0 +1,4 @@
1
+ import { AgentArgs } from "./types";
2
+ export declare const privateKeyExists: (args: AgentArgs) => Promise<boolean>;
3
+ export declare const addPrivateKey: (args: AgentArgs) => Promise<boolean>;
4
+ export declare const withSshAgent: <T>(args: AgentArgs, fn: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,136 @@
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.withSshAgent = exports.addPrivateKey = exports.privateKeyExists = 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 keys_1 = require("../../common/keys");
24
+ const stdio_1 = require("../../drivers/stdio");
25
+ const node_child_process_1 = require("node:child_process");
26
+ /** Spawns a subprocess with given command, args, and options.
27
+ * May write content to its standard input.
28
+ * Stdout and stderr of the subprocess is printed to stderr in debug mode.
29
+ * The returned promise resolves or rejects with the exit code. */
30
+ const asyncSpawn = ({ debug }, command, args, options, writeStdin) => __awaiter(void 0, void 0, void 0, function* () {
31
+ return new Promise((resolve, reject) => {
32
+ var _a;
33
+ const child = (0, node_child_process_1.spawn)(command, args, options);
34
+ if (writeStdin) {
35
+ if (!child.stdin)
36
+ return reject("Child process has no stdin");
37
+ child.stdin.write(writeStdin);
38
+ }
39
+ child.stdout.on("data", (data) => {
40
+ if (debug) {
41
+ (0, stdio_1.print2)(data.toString("utf-8"));
42
+ }
43
+ });
44
+ child.stderr.on("data", (data) => {
45
+ if (debug) {
46
+ (0, stdio_1.print2)(data.toString("utf-8"));
47
+ }
48
+ });
49
+ child.on("exit", (code) => {
50
+ if (code !== 0) {
51
+ return reject(code);
52
+ }
53
+ resolve(code);
54
+ });
55
+ if (writeStdin) {
56
+ (_a = child.stdin) === null || _a === void 0 ? void 0 : _a.end();
57
+ }
58
+ });
59
+ });
60
+ const isSshAgentRunning = (args) => __awaiter(void 0, void 0, void 0, function* () {
61
+ try {
62
+ if (args.debug)
63
+ (0, stdio_1.print2)("Searching for active ssh-agents");
64
+ // TODO: There's a possible edge-case but unlikely that ssh-agent has an invalid process or PID.
65
+ // We can check to see if the active PID matches the current socket to mitigate this.
66
+ yield asyncSpawn(args, `pgrep`, ["-x", "ssh-agent"]);
67
+ if (args.debug)
68
+ (0, stdio_1.print2)("At least one SSH agent is running");
69
+ return true;
70
+ }
71
+ catch (_a) {
72
+ if (args.debug)
73
+ (0, stdio_1.print2)("No SSH agent is running!");
74
+ return false;
75
+ }
76
+ });
77
+ const isSshAgentAuthSocketSet = (args) => __awaiter(void 0, void 0, void 0, function* () {
78
+ try {
79
+ yield asyncSpawn(args, `sh`, ["-c", '[ -n "$SSH_AUTH_SOCK" ]']);
80
+ if (args.debug)
81
+ (0, stdio_1.print2)(`SSH_AUTH_SOCK=${process.env.SSH_AUTH_SOCK}`);
82
+ return true;
83
+ }
84
+ catch (_b) {
85
+ if (args.debug)
86
+ (0, stdio_1.print2)("SSH_AUTH_SOCK is not set!");
87
+ return false;
88
+ }
89
+ });
90
+ const privateKeyExists = (args) => __awaiter(void 0, void 0, void 0, function* () {
91
+ try {
92
+ yield asyncSpawn(args, `sh`, [
93
+ "-c",
94
+ `KEY_PATH="${keys_1.PRIVATE_KEY_PATH}" && KEY_FINGERPRINT=$(ssh-keygen -lf "$KEY_PATH" | awk '{print $2}') && ssh-add -l | grep -q "$KEY_FINGERPRINT" && exit 0 || exit 1`,
95
+ ]);
96
+ if (args.debug)
97
+ (0, stdio_1.print2)("Private key exists in ssh agent");
98
+ return true;
99
+ }
100
+ catch (_c) {
101
+ if (args.debug)
102
+ (0, stdio_1.print2)("Private key does not exist in ssh agent");
103
+ return false;
104
+ }
105
+ });
106
+ exports.privateKeyExists = privateKeyExists;
107
+ const addPrivateKey = (args) => __awaiter(void 0, void 0, void 0, function* () {
108
+ try {
109
+ yield asyncSpawn(args, `ssh-add`, [
110
+ keys_1.PRIVATE_KEY_PATH,
111
+ ...(args.debug ? ["-v", "-v", "-v"] : ["-q"]),
112
+ ]);
113
+ if (args.debug)
114
+ (0, stdio_1.print2)("Private key added to ssh agent");
115
+ return true;
116
+ }
117
+ catch (_d) {
118
+ if (args.debug)
119
+ (0, stdio_1.print2)("Failed to add private key to ssh agent");
120
+ return false;
121
+ }
122
+ });
123
+ exports.addPrivateKey = addPrivateKey;
124
+ const withSshAgent = (args, fn) => __awaiter(void 0, void 0, void 0, function* () {
125
+ const isRunning = yield isSshAgentRunning(args);
126
+ const hasSocket = yield isSshAgentAuthSocketSet(args);
127
+ if (!isRunning || !hasSocket) {
128
+ throw "SSH agent is not running. Please start it by running: eval $(ssh-agent)";
129
+ }
130
+ const hasKey = yield (0, exports.privateKeyExists)(args);
131
+ if (!hasKey) {
132
+ yield (0, exports.addPrivateKey)(args);
133
+ }
134
+ return yield fn();
135
+ });
136
+ exports.withSshAgent = withSshAgent;
@@ -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
+ export declare type AgentArgs = {
12
+ debug?: boolean;
13
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,14 @@
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
+ export declare type AllowResponse = {
12
+ ok: true;
13
+ message: string;
14
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -8,13 +8,21 @@ 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
- /** Publicly readable organization data */
12
- export declare type OrgData = {
11
+ declare type BaseOrgData = {
13
12
  clientId: string;
14
13
  providerId: string;
15
14
  providerDomain?: string;
16
- providerType?: "okta";
17
15
  ssoProvider: "azure-oidc" | "google-oidc" | "google" | "microsoft" | "oidc-pkce" | "okta";
18
- slug: string;
19
16
  tenantId: string;
20
17
  };
18
+ /** Publicly readable organization data */
19
+ export declare type RawOrgData = BaseOrgData & ({
20
+ providerType?: "okta";
21
+ } | {
22
+ providerType?: "ping";
23
+ environmentId: string;
24
+ });
25
+ export declare type OrgData = RawOrgData & {
26
+ slug: string;
27
+ };
28
+ export {};
package/dist/types/org.js CHANGED
@@ -1,2 +1,12 @@
1
1
  "use strict";
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
+ **/
2
12
  Object.defineProperty(exports, "__esModule", { value: true });
package/dist/util.d.ts CHANGED
@@ -40,3 +40,6 @@ export declare const exec: (command: string, args: string[], options?: child_pro
40
40
  stdout: string;
41
41
  stderr: string;
42
42
  }>;
43
+ export declare const throwAssertNever: (value: never) => never;
44
+ export declare const assertNever: (value: never) => Error;
45
+ export declare const unexpectedValueError: (value: any) => Error;
package/dist/util.js CHANGED
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.exec = exports.timeout = exports.sleep = exports.P0_PATH = void 0;
15
+ exports.unexpectedValueError = exports.assertNever = exports.throwAssertNever = exports.exec = exports.timeout = exports.sleep = exports.P0_PATH = void 0;
16
16
  /** Copyright © 2024-present P0 Security
17
17
 
18
18
  This file is part of @p0security/cli
@@ -85,3 +85,13 @@ const exec = (command, args, options) => __awaiter(void 0, void 0, void 0, funct
85
85
  });
86
86
  });
87
87
  exports.exec = exec;
88
+ const throwAssertNever = (value) => {
89
+ throw (0, exports.assertNever)(value);
90
+ };
91
+ exports.throwAssertNever = throwAssertNever;
92
+ const assertNever = (value) => {
93
+ return (0, exports.unexpectedValueError)(value);
94
+ };
95
+ exports.assertNever = assertNever;
96
+ const unexpectedValueError = (value) => new Error(`Unexpected code state: value ${value} had unexpected type`);
97
+ exports.unexpectedValueError = unexpectedValueError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p0security/cli",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Execute infra CLI commands with P0 grants",
5
5
  "main": "index.ts",
6
6
  "repository": {
@@ -31,7 +31,6 @@
31
31
  "open": "^8.4.0",
32
32
  "pkce-challenge": "^4.1.0",
33
33
  "pluralize": "^8.0.0",
34
- "ps-tree": "^1.2.0",
35
34
  "semver": "^7.6.0",
36
35
  "typescript": "^4.8.4",
37
36
  "which": "^4.0.0",
@@ -47,7 +46,6 @@
47
46
  "@types/node": "^18.11.7",
48
47
  "@types/node-forge": "^1.3.11",
49
48
  "@types/pluralize": "^0.0.33",
50
- "@types/ps-tree": "^1.1.6",
51
49
  "@types/which": "^3.0.3",
52
50
  "@types/yargs": "^17.0.13",
53
51
  "@typescript-eslint/eslint-plugin": "^6.4.0",