@p0security/cli 0.3.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.
- package/CONTRIBUTING.md +23 -0
- package/LICENSE.md +675 -0
- package/README.md +201 -0
- package/dist/commands/__tests__/login.test.d.ts +1 -0
- package/dist/commands/__tests__/login.test.js +75 -0
- package/dist/commands/__tests__/ls.test.d.ts +1 -0
- package/dist/commands/__tests__/ls.test.js +84 -0
- package/dist/commands/__tests__/request.test.d.ts +1 -0
- package/dist/commands/__tests__/request.test.js +94 -0
- package/dist/commands/__tests__/ssh.test.d.ts +1 -0
- package/dist/commands/__tests__/ssh.test.js +107 -0
- package/dist/commands/aws/__tests__/__input__/saml-response.d.ts +11 -0
- package/dist/commands/aws/__tests__/__input__/saml-response.js +18 -0
- package/dist/commands/aws/__tests__/__input__/sts-response.d.ts +11 -0
- package/dist/commands/aws/__tests__/__input__/sts-response.js +37 -0
- package/dist/commands/aws/__tests__/role.test.d.ts +1 -0
- package/dist/commands/aws/__tests__/role.test.js +98 -0
- package/dist/commands/aws/index.d.ts +4 -0
- package/dist/commands/aws/index.js +26 -0
- package/dist/commands/aws/role.d.ts +27 -0
- package/dist/commands/aws/role.js +123 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +49 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.js +93 -0
- package/dist/commands/ls.d.ts +4 -0
- package/dist/commands/ls.js +78 -0
- package/dist/commands/request.d.ts +12 -0
- package/dist/commands/request.js +116 -0
- package/dist/commands/ssh.d.ts +11 -0
- package/dist/commands/ssh.js +154 -0
- package/dist/common/auth/oidc.d.ts +4 -0
- package/dist/common/auth/oidc.js +18 -0
- package/dist/common/auth/server.d.ts +15 -0
- package/dist/common/auth/server.js +73 -0
- package/dist/common/fetch.d.ts +16 -0
- package/dist/common/fetch.js +39 -0
- package/dist/common/mime.d.ts +14 -0
- package/dist/common/mime.js +17 -0
- package/dist/common/xml.d.ts +21 -0
- package/dist/common/xml.js +52 -0
- package/dist/drivers/__mocks__/auth.d.ts +30 -0
- package/dist/drivers/__mocks__/auth.js +46 -0
- package/dist/drivers/api.d.ts +3 -0
- package/dist/drivers/api.js +69 -0
- package/dist/drivers/auth.d.ts +11 -0
- package/dist/drivers/auth.js +122 -0
- package/dist/drivers/env.d.ts +15 -0
- package/dist/drivers/env.js +38 -0
- package/dist/drivers/firestore.d.ts +10 -0
- package/dist/drivers/firestore.js +53 -0
- package/dist/drivers/stdio.d.ts +25 -0
- package/dist/drivers/stdio.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +23 -0
- package/dist/middlewares/version.d.ts +8 -0
- package/dist/middlewares/version.js +77 -0
- package/dist/plugins/__mocks__/login.d.ts +14 -0
- package/dist/plugins/__mocks__/login.js +24 -0
- package/dist/plugins/aws/__mocks__/assumeRole.d.ts +12 -0
- package/dist/plugins/aws/__mocks__/assumeRole.js +20 -0
- package/dist/plugins/aws/api.d.ts +12 -0
- package/dist/plugins/aws/api.js +16 -0
- package/dist/plugins/aws/assumeRole.d.ts +14 -0
- package/dist/plugins/aws/assumeRole.js +55 -0
- package/dist/plugins/aws/config.d.ts +5 -0
- package/dist/plugins/aws/config.js +38 -0
- package/dist/plugins/aws/ssm/index.d.ts +18 -0
- package/dist/plugins/aws/ssm/index.js +274 -0
- package/dist/plugins/aws/ssm/install.d.ts +7 -0
- package/dist/plugins/aws/ssm/install.js +133 -0
- package/dist/plugins/aws/types.d.ts +54 -0
- package/dist/plugins/aws/types.js +2 -0
- package/dist/plugins/google/login.d.ts +2 -0
- package/dist/plugins/google/login.js +76 -0
- package/dist/plugins/login.d.ts +13 -0
- package/dist/plugins/login.js +19 -0
- package/dist/plugins/okta/aws.d.ts +5 -0
- package/dist/plugins/okta/aws.js +42 -0
- package/dist/plugins/okta/login.d.ts +8 -0
- package/dist/plugins/okta/login.js +165 -0
- package/dist/plugins/ssh/types.d.ts +22 -0
- package/dist/plugins/ssh/types.js +2 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/redirect-landing.html +40 -0
- package/dist/testing/firestore.d.ts +2 -0
- package/dist/testing/firestore.js +16 -0
- package/dist/testing/yargs.d.ts +12 -0
- package/dist/testing/yargs.js +23 -0
- package/dist/types/identity.d.ts +23 -0
- package/dist/types/identity.js +2 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.js +15 -0
- package/dist/types/oidc.d.ts +41 -0
- package/dist/types/oidc.js +2 -0
- package/dist/types/org.d.ts +20 -0
- package/dist/types/org.js +2 -0
- package/dist/types/request.d.ts +35 -0
- package/dist/types/request.js +20 -0
- package/dist/util.d.ts +42 -0
- package/dist/util.js +87 -0
- package/p0 +16 -0
- package/package.json +70 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stsResponse = 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.stsResponse = `<AssumeRoleWithSAMLResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
|
15
|
+
<AssumeRoleWithSAMLResult>
|
|
16
|
+
<Audience>https://signin.aws.amazon.com/saml</Audience>
|
|
17
|
+
<AssumedRoleUser>
|
|
18
|
+
<AssumedRoleId>ABCDEFGHIJLMNOPQRST:test-user@test.com</AssumedRoleId>
|
|
19
|
+
<Arn>arn:aws:sts::1:assumed-role/Role1/test-user@test.com</Arn>
|
|
20
|
+
</AssumedRoleUser>
|
|
21
|
+
<Credentials>
|
|
22
|
+
<AccessKeyId>test-access-key</AccessKeyId>
|
|
23
|
+
<SecretAccessKey>secret-access-key</SecretAccessKey>
|
|
24
|
+
<SessionToken>session-token</SessionToken>
|
|
25
|
+
<Expiration>2024-02-22T00:18:21Z</Expiration>
|
|
26
|
+
</Credentials>
|
|
27
|
+
<Subject>test-user@test.com</Subject>
|
|
28
|
+
<NameQualifier>abcdefghijklmnop</NameQualifier>
|
|
29
|
+
<SourceIdentity>test-user@test.com</SourceIdentity>
|
|
30
|
+
<PackedPolicySize>2</PackedPolicySize>
|
|
31
|
+
<SubjectType>unspecified</SubjectType>
|
|
32
|
+
<Issuer>http://www.okta.com/abc</Issuer>
|
|
33
|
+
</AssumeRoleWithSAMLResult>
|
|
34
|
+
<ResponseMetadata>
|
|
35
|
+
<RequestId>f5b94ad4-f322-4d7b-b568-84f2ec184cd7</RequestId>
|
|
36
|
+
</ResponseMetadata>
|
|
37
|
+
</AssumeRoleWithSAMLResponse>`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
/** Copyright © 2024-present P0 Security
|
|
16
|
+
|
|
17
|
+
This file is part of @p0security/cli
|
|
18
|
+
|
|
19
|
+
@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.
|
|
20
|
+
|
|
21
|
+
@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.
|
|
22
|
+
|
|
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
|
+
**/
|
|
25
|
+
const __1 = require("..");
|
|
26
|
+
const stdio_1 = require("../../../drivers/stdio");
|
|
27
|
+
const firestore_1 = require("../../../testing/firestore");
|
|
28
|
+
const yargs_1 = require("../../../testing/yargs");
|
|
29
|
+
const saml_response_1 = require("./__input__/saml-response");
|
|
30
|
+
const sts_response_1 = require("./__input__/sts-response");
|
|
31
|
+
const yargs_2 = __importDefault(require("yargs"));
|
|
32
|
+
jest.mock("fs/promises");
|
|
33
|
+
jest.mock("../../../drivers/auth");
|
|
34
|
+
jest.mock("../../../drivers/stdio");
|
|
35
|
+
jest.mock("typescript", () => (Object.assign(Object.assign({}, jest.requireActual("typescript")), { sys: {
|
|
36
|
+
writeOutputIsTTY: () => true,
|
|
37
|
+
} })));
|
|
38
|
+
const mockFetch = jest.spyOn(global, "fetch");
|
|
39
|
+
const mockPrint1 = stdio_1.print1;
|
|
40
|
+
const mockPrint2 = stdio_1.print2;
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
mockFetch.mockImplementation((url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
+
return ({
|
|
45
|
+
ok: true,
|
|
46
|
+
// This is the token response from fetchSsoWebToken
|
|
47
|
+
json: () => __awaiter(void 0, void 0, void 0, function* () { return ({}); }),
|
|
48
|
+
// This is the XML response from fetchSamlResponse or stsAssumeRole
|
|
49
|
+
text: () => __awaiter(void 0, void 0, void 0, function* () { return url.match(/okta.com/) ? saml_response_1.samlResponse : sts_response_1.stsResponse; }),
|
|
50
|
+
});
|
|
51
|
+
}));
|
|
52
|
+
});
|
|
53
|
+
describe("aws role", () => {
|
|
54
|
+
describe("a single installed account", () => {
|
|
55
|
+
const item = {
|
|
56
|
+
account: {
|
|
57
|
+
id: "1",
|
|
58
|
+
description: "1 (test)",
|
|
59
|
+
},
|
|
60
|
+
state: "installed",
|
|
61
|
+
};
|
|
62
|
+
describe("without Okta SAML", () => {
|
|
63
|
+
(0, firestore_1.mockGetDoc)({ workflows: { items: [item] } });
|
|
64
|
+
describe.each([
|
|
65
|
+
["ls", "aws role ls"],
|
|
66
|
+
["assume", "aws role assume Role1"],
|
|
67
|
+
])("%s", (_, command) => {
|
|
68
|
+
it("should print a friendly error message", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
|
+
const error = yield (0, yargs_1.failure)((0, __1.awsCommand)((0, yargs_2.default)()), command);
|
|
70
|
+
expect(error).toMatchInlineSnapshot(`"Account 1 (test) is not configured for Okta SAML login."`);
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("with Okta SAML", () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
(0, firestore_1.mockGetDoc)({
|
|
77
|
+
workflows: {
|
|
78
|
+
items: [Object.assign(Object.assign({}, item), { uidLocation: { id: "okta_saml_sso" } })],
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("assume", () => {
|
|
83
|
+
it("should assume a role", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
yield (0, __1.awsCommand)((0, yargs_2.default)()).parse("aws role assume Role1");
|
|
85
|
+
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
|
|
86
|
+
expect(mockPrint1.mock.calls).toMatchSnapshot("stdout");
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
describe("ls", () => {
|
|
90
|
+
it("lists roles", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
yield (0, __1.awsCommand)((0, yargs_2.default)()).parse("aws role ls");
|
|
92
|
+
expect(mockPrint2.mock.calls).toMatchSnapshot("stderr");
|
|
93
|
+
expect(mockPrint1.mock.calls).toMatchSnapshot("stdout");
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.awsCommand = 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
|
+
const role_1 = require("./role");
|
|
15
|
+
const awsCommands = [role_1.role];
|
|
16
|
+
const awsArgs = (yargs) => {
|
|
17
|
+
const base = yargs
|
|
18
|
+
.option("account", {
|
|
19
|
+
type: "string",
|
|
20
|
+
describe: "AWS account ID or alias (or set P0_AWS_ACCOUNT)",
|
|
21
|
+
})
|
|
22
|
+
.env("P0_AWS");
|
|
23
|
+
return awsCommands.reduce((m, c) => c(m), base).demandCommand(1);
|
|
24
|
+
};
|
|
25
|
+
const awsCommand = (yargs) => yargs.command("aws", "Execute AWS commands", awsArgs);
|
|
26
|
+
exports.awsCommand = awsCommand;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AwsItemConfig, AwsOktaSamlUidLocation } from "../../plugins/aws/types";
|
|
2
|
+
import { Authn } from "../../types/identity";
|
|
3
|
+
import yargs from "yargs";
|
|
4
|
+
export declare const role: (yargs: yargs.Argv<{
|
|
5
|
+
account: string | undefined;
|
|
6
|
+
}>) => yargs.Argv<{
|
|
7
|
+
account: string | undefined;
|
|
8
|
+
} & {
|
|
9
|
+
role: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** Retrieves the configured Okta SAML response for the specified account
|
|
12
|
+
*
|
|
13
|
+
* If no account is passed, and the organization only has one account configured,
|
|
14
|
+
* assumes that account.
|
|
15
|
+
*/
|
|
16
|
+
export declare const initOktaSaml: (authn: Authn, account: string | undefined) => Promise<{
|
|
17
|
+
samlResponse: string;
|
|
18
|
+
config: AwsItemConfig & {
|
|
19
|
+
uidLocation: AwsOktaSamlUidLocation;
|
|
20
|
+
};
|
|
21
|
+
account: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** Extracts all roles from a SAML assertion */
|
|
24
|
+
export declare const rolesFromSaml: (account: string, saml: string) => {
|
|
25
|
+
arns: string[];
|
|
26
|
+
roles: string[];
|
|
27
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
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.rolesFromSaml = exports.initOktaSaml = exports.role = 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 xml_1 = require("../../common/xml");
|
|
24
|
+
const auth_1 = require("../../drivers/auth");
|
|
25
|
+
const firestore_1 = require("../../drivers/firestore");
|
|
26
|
+
const stdio_1 = require("../../drivers/stdio");
|
|
27
|
+
const config_1 = require("../../plugins/aws/config");
|
|
28
|
+
const aws_1 = require("../../plugins/okta/aws");
|
|
29
|
+
const login_1 = require("../../plugins/okta/login");
|
|
30
|
+
const lodash_1 = require("lodash");
|
|
31
|
+
const typescript_1 = require("typescript");
|
|
32
|
+
const role = (yargs) => yargs.command("role", "Interact with AWS roles", (yargs) => yargs
|
|
33
|
+
.command("ls", "List available AWS roles", lodash_1.identity,
|
|
34
|
+
// TODO: select based on uidLocation
|
|
35
|
+
(0, firestore_1.guard)(oktaAwsListRoles))
|
|
36
|
+
.command("assume <role>", "Assume an AWS role", (y) => y.positional("role", {
|
|
37
|
+
type: "string",
|
|
38
|
+
demandOption: true,
|
|
39
|
+
describe: "An AWS role name",
|
|
40
|
+
}),
|
|
41
|
+
// TODO: select based on uidLocation
|
|
42
|
+
(0, firestore_1.guard)(oktaAwsAssumeRole))
|
|
43
|
+
.demandCommand(1));
|
|
44
|
+
exports.role = role;
|
|
45
|
+
const isOktaSamlConfig = (config) => { var _a; return ((_a = config.uidLocation) === null || _a === void 0 ? void 0 : _a.id) === "okta_saml_sso"; };
|
|
46
|
+
/** Retrieves the configured Okta SAML response for the specified account
|
|
47
|
+
*
|
|
48
|
+
* If no account is passed, and the organization only has one account configured,
|
|
49
|
+
* assumes that account.
|
|
50
|
+
*/
|
|
51
|
+
const initOktaSaml = (authn, account) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
const { identity, config } = yield (0, config_1.getAwsConfig)(authn, account);
|
|
53
|
+
if (!isOktaSamlConfig(config))
|
|
54
|
+
throw `Account ${config.account.description} is not configured for Okta SAML login.`;
|
|
55
|
+
const samlResponse = yield (0, login_1.getSamlResponse)(identity, config.uidLocation);
|
|
56
|
+
return {
|
|
57
|
+
samlResponse,
|
|
58
|
+
config,
|
|
59
|
+
account: config.account.id,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
exports.initOktaSaml = initOktaSaml;
|
|
63
|
+
/** Extracts all roles from a SAML assertion */
|
|
64
|
+
const rolesFromSaml = (account, saml) => {
|
|
65
|
+
var _a;
|
|
66
|
+
const samlText = Buffer.from(saml, "base64").toString("ascii");
|
|
67
|
+
const samlObject = (0, xml_1.parseXml)(samlText);
|
|
68
|
+
const samlAttributes = samlObject["saml2p:Response"]["saml2:Assertion"]["saml2:AttributeStatement"]["saml2:Attribute"];
|
|
69
|
+
const roleAttribute = samlAttributes.find((a) => a._attributes.Name === "https://aws.amazon.com/SAML/Attributes/Role");
|
|
70
|
+
// Format:
|
|
71
|
+
// 'arn:aws:iam::391052057035:saml-provider/p0dev-ext_okta_sso,arn:aws:iam::391052057035:role/path/to/role/SSOAmazonS3FullAccess'
|
|
72
|
+
const arns = (_a = (0, lodash_1.flatten)([roleAttribute === null || roleAttribute === void 0 ? void 0 : roleAttribute["saml2:AttributeValue"]])) === null || _a === void 0 ? void 0 : _a.map((r) => r.split(",")[1]);
|
|
73
|
+
const roles = arns
|
|
74
|
+
.filter((r) => r.startsWith(`arn:aws:iam::${account}:role/`))
|
|
75
|
+
.map((r) => r.split("/").slice(1).join("/"));
|
|
76
|
+
return { arns, roles };
|
|
77
|
+
};
|
|
78
|
+
exports.rolesFromSaml = rolesFromSaml;
|
|
79
|
+
/** Assumes a role in AWS via Okta SAML federation.
|
|
80
|
+
*
|
|
81
|
+
* Prerequisites:
|
|
82
|
+
* - AWS is configured with a SAML identity provider
|
|
83
|
+
* - This identity provider is integrated with a
|
|
84
|
+
* "AWS SAML Account Federation" app in Okta
|
|
85
|
+
* - The AWS SAML identity provider name, Okta domain,
|
|
86
|
+
* and Okta SAML app identifier are all contained in
|
|
87
|
+
* the user's identity blob
|
|
88
|
+
* - The requested role is assigned to the user in Okta
|
|
89
|
+
*/
|
|
90
|
+
const oktaAwsAssumeRole = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
var _a;
|
|
92
|
+
const authn = yield (0, auth_1.authenticate)();
|
|
93
|
+
const awsCredential = yield (0, aws_1.assumeRoleWithOktaSaml)(authn, args);
|
|
94
|
+
const isTty = (_a = typescript_1.sys.writeOutputIsTTY) === null || _a === void 0 ? void 0 : _a.call(typescript_1.sys);
|
|
95
|
+
if (isTty)
|
|
96
|
+
(0, stdio_1.print2)("Execute the following commands:\n");
|
|
97
|
+
const indent = isTty ? " " : "";
|
|
98
|
+
(0, stdio_1.print1)(Object.entries(awsCredential)
|
|
99
|
+
.map(([key, value]) => `${indent}export ${key}=${value}`)
|
|
100
|
+
.join("\n"));
|
|
101
|
+
if (isTty)
|
|
102
|
+
(0, stdio_1.print2)(`
|
|
103
|
+
Or, populate these environment variables using BASH command substitution:
|
|
104
|
+
|
|
105
|
+
$(p0 aws${args.account ? ` --account ${args.account}` : ""} role assume ${args.role})
|
|
106
|
+
`);
|
|
107
|
+
});
|
|
108
|
+
/** Lists assigned AWS roles for this user on this account */
|
|
109
|
+
const oktaAwsListRoles = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
+
var _b;
|
|
111
|
+
const authn = yield (0, auth_1.authenticate)();
|
|
112
|
+
const { account, samlResponse } = yield (0, exports.initOktaSaml)(authn, args.account);
|
|
113
|
+
const { arns, roles } = (0, exports.rolesFromSaml)(account, samlResponse);
|
|
114
|
+
const isTty = (_b = typescript_1.sys.writeOutputIsTTY) === null || _b === void 0 ? void 0 : _b.call(typescript_1.sys);
|
|
115
|
+
if (isTty)
|
|
116
|
+
(0, stdio_1.print2)(`Your available roles for account ${account}:`);
|
|
117
|
+
if (!(roles === null || roles === void 0 ? void 0 : roles.length)) {
|
|
118
|
+
const accounts = (0, lodash_1.uniq)(arns.map((a) => a.split(":")[4])).sort();
|
|
119
|
+
throw `No roles found. You have roles on these accounts:\n${accounts.join("\n")}`;
|
|
120
|
+
}
|
|
121
|
+
const indent = isTty ? " " : "";
|
|
122
|
+
(0, stdio_1.print1)(roles.map((r) => `${indent}${r}`).join("\n"));
|
|
123
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cli = void 0;
|
|
7
|
+
/** Copyright © 2024-present P0 Security
|
|
8
|
+
|
|
9
|
+
This file is part of @p0security/cli
|
|
10
|
+
|
|
11
|
+
@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.
|
|
12
|
+
|
|
13
|
+
@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.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
**/
|
|
17
|
+
const stdio_1 = require("../drivers/stdio");
|
|
18
|
+
const version_1 = require("../middlewares/version");
|
|
19
|
+
const aws_1 = require("./aws");
|
|
20
|
+
const login_1 = require("./login");
|
|
21
|
+
const ls_1 = require("./ls");
|
|
22
|
+
const request_1 = require("./request");
|
|
23
|
+
const ssh_1 = require("./ssh");
|
|
24
|
+
const lodash_1 = require("lodash");
|
|
25
|
+
const typescript_1 = require("typescript");
|
|
26
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
27
|
+
const helpers_1 = require("yargs/helpers");
|
|
28
|
+
const commands = [
|
|
29
|
+
aws_1.awsCommand,
|
|
30
|
+
login_1.loginCommand,
|
|
31
|
+
ls_1.lsCommand,
|
|
32
|
+
request_1.requestCommand,
|
|
33
|
+
ssh_1.sshCommand,
|
|
34
|
+
];
|
|
35
|
+
exports.cli = commands
|
|
36
|
+
.reduce((m, c) => c(m), (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)))
|
|
37
|
+
.middleware(version_1.checkVersion)
|
|
38
|
+
.strict()
|
|
39
|
+
.version(lodash_1.VERSION)
|
|
40
|
+
.demandCommand(1)
|
|
41
|
+
.fail((message, error, yargs) => {
|
|
42
|
+
if (error)
|
|
43
|
+
(0, stdio_1.print2)(error);
|
|
44
|
+
else {
|
|
45
|
+
(0, stdio_1.print2)(yargs.help());
|
|
46
|
+
(0, stdio_1.print2)(`\n${message}`);
|
|
47
|
+
}
|
|
48
|
+
typescript_1.sys.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import yargs from "yargs";
|
|
2
|
+
/** Logs in the user
|
|
3
|
+
*
|
|
4
|
+
* Currently only supports login to a single organization. Login credentials, together
|
|
5
|
+
* with organization details, are saved to {@link IDENTITY_FILE_PATH}.
|
|
6
|
+
*/
|
|
7
|
+
export declare const login: (args: {
|
|
8
|
+
org: string;
|
|
9
|
+
}, options?: {
|
|
10
|
+
skipAuthenticate?: boolean;
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
export declare const loginCommand: (yargs: yargs.Argv<{}>) => yargs.Argv<{
|
|
13
|
+
org: string;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.loginCommand = exports.login = void 0;
|
|
36
|
+
/** Copyright © 2024-present P0 Security
|
|
37
|
+
|
|
38
|
+
This file is part of @p0security/cli
|
|
39
|
+
|
|
40
|
+
@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.
|
|
41
|
+
|
|
42
|
+
@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.
|
|
43
|
+
|
|
44
|
+
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
|
|
45
|
+
**/
|
|
46
|
+
const auth_1 = require("../drivers/auth");
|
|
47
|
+
const firestore_1 = require("../drivers/firestore");
|
|
48
|
+
const stdio_1 = require("../drivers/stdio");
|
|
49
|
+
const login_1 = require("../plugins/login");
|
|
50
|
+
const firestore_2 = require("firebase/firestore");
|
|
51
|
+
const fs = __importStar(require("fs/promises"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
/** Logs in the user
|
|
54
|
+
*
|
|
55
|
+
* Currently only supports login to a single organization. Login credentials, together
|
|
56
|
+
* with organization details, are saved to {@link IDENTITY_FILE_PATH}.
|
|
57
|
+
*/
|
|
58
|
+
const login = (args, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
const orgDoc = yield (0, firestore_2.getDoc)((0, firestore_1.doc)(`orgs/${args.org}`));
|
|
60
|
+
const orgData = orgDoc.data();
|
|
61
|
+
if (!orgData)
|
|
62
|
+
throw "Could not find organization";
|
|
63
|
+
const orgWithSlug = Object.assign(Object.assign({}, orgData), { slug: args.org });
|
|
64
|
+
const plugin = orgWithSlug === null || orgWithSlug === void 0 ? void 0 : orgWithSlug.ssoProvider;
|
|
65
|
+
const loginFn = login_1.pluginLoginMap[plugin];
|
|
66
|
+
if (!loginFn)
|
|
67
|
+
throw "Unsupported login for your organization";
|
|
68
|
+
const tokenResponse = yield loginFn(orgWithSlug);
|
|
69
|
+
yield writeIdentity(orgWithSlug, tokenResponse);
|
|
70
|
+
// validate auth
|
|
71
|
+
if (!(options === null || options === void 0 ? void 0 : options.skipAuthenticate))
|
|
72
|
+
yield (0, auth_1.authenticate)({ noRefresh: true });
|
|
73
|
+
(0, stdio_1.print2)(`You are now logged in, and can use the p0 CLI.`);
|
|
74
|
+
});
|
|
75
|
+
exports.login = login;
|
|
76
|
+
const writeIdentity = (org, credential) => __awaiter(void 0, void 0, void 0, function* () {
|
|
77
|
+
const expires_at = Date.now() * 1e-3 + credential.expires_in - 1; // Add 1 second safety margin
|
|
78
|
+
(0, stdio_1.print2)(`Saving authorization to ${auth_1.IDENTITY_FILE_PATH}.`);
|
|
79
|
+
const dir = path.dirname(auth_1.IDENTITY_FILE_PATH);
|
|
80
|
+
yield fs.mkdir(dir, { recursive: true });
|
|
81
|
+
yield fs.writeFile(auth_1.IDENTITY_FILE_PATH, JSON.stringify({
|
|
82
|
+
credential: Object.assign(Object.assign({}, credential), { expires_at }),
|
|
83
|
+
org,
|
|
84
|
+
}, null, 2), {
|
|
85
|
+
mode: "600",
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
const loginCommand = (yargs) => yargs.command("login <org>", "Log in to p0 using a web browser", (yargs) => yargs.positional("org", {
|
|
89
|
+
demandOption: true,
|
|
90
|
+
type: "string",
|
|
91
|
+
describe: "Your P0 organization ID",
|
|
92
|
+
}), (0, firestore_1.guard)(exports.login));
|
|
93
|
+
exports.loginCommand = loginCommand;
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.lsCommand = void 0;
|
|
16
|
+
/** Copyright © 2024-present P0 Security
|
|
17
|
+
|
|
18
|
+
This file is part of @p0security/cli
|
|
19
|
+
|
|
20
|
+
@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.
|
|
21
|
+
|
|
22
|
+
@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.
|
|
23
|
+
|
|
24
|
+
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
|
+
**/
|
|
26
|
+
const api_1 = require("../drivers/api");
|
|
27
|
+
const auth_1 = require("../drivers/auth");
|
|
28
|
+
const firestore_1 = require("../drivers/firestore");
|
|
29
|
+
const stdio_1 = require("../drivers/stdio");
|
|
30
|
+
const lodash_1 = require("lodash");
|
|
31
|
+
const pluralize_1 = __importDefault(require("pluralize"));
|
|
32
|
+
const lsArgs = (yargs) => yargs
|
|
33
|
+
.parserConfiguration({ "unknown-options-as-args": true })
|
|
34
|
+
.option("arguments", {
|
|
35
|
+
array: true,
|
|
36
|
+
string: true,
|
|
37
|
+
default: [],
|
|
38
|
+
});
|
|
39
|
+
const lsCommand = (yargs) => yargs.command("ls [arguments..]", "List request-command arguments", lsArgs, (0, firestore_1.guard)(ls));
|
|
40
|
+
exports.lsCommand = lsCommand;
|
|
41
|
+
const ls = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
const authn = yield (0, auth_1.authenticate)();
|
|
43
|
+
const data = yield (0, api_1.fetchCommand)(authn, args, [
|
|
44
|
+
"ls",
|
|
45
|
+
...args.arguments,
|
|
46
|
+
]);
|
|
47
|
+
const allArguments = [...args._, ...args.arguments];
|
|
48
|
+
if (data && "ok" in data && data.ok) {
|
|
49
|
+
const label = (0, pluralize_1.default)(data.arg);
|
|
50
|
+
if (data.items.length === 0) {
|
|
51
|
+
(0, stdio_1.print2)(`No ${label}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const truncationPart = data.isTruncated
|
|
55
|
+
? ` the first ${data.items.length}`
|
|
56
|
+
: "";
|
|
57
|
+
const postfixPart = data.term
|
|
58
|
+
? ` matching '${data.term}'`
|
|
59
|
+
: data.isTruncated
|
|
60
|
+
? ` (use \`p0
|
|
61
|
+
${allArguments.join(" ")} <like>\` to narrow results)`
|
|
62
|
+
: "";
|
|
63
|
+
(0, stdio_1.print2)(`Showing${truncationPart} ${label}${postfixPart}:`);
|
|
64
|
+
const isSameValue = data.items.every((i) => !i.group && i.key === i.value);
|
|
65
|
+
const maxLength = (0, lodash_1.max)(data.items.map((i) => i.key.length)) || 0;
|
|
66
|
+
for (const item of data.items) {
|
|
67
|
+
const tagPart = `${item.group ? `${item.group} / ` : ""}${item.value}`;
|
|
68
|
+
(0, stdio_1.print1)(isSameValue
|
|
69
|
+
? item.key
|
|
70
|
+
: maxLength > 30
|
|
71
|
+
? `${item.key}\n ${stdio_1.Ansi.Dim}${tagPart}${stdio_1.Ansi.Reset}`
|
|
72
|
+
: `${item.key.padEnd(maxLength)}${stdio_1.Ansi.Dim} - ${tagPart}${stdio_1.Ansi.Reset}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw data;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Authn } from "../types/identity";
|
|
2
|
+
import { RequestResponse } from "../types/request";
|
|
3
|
+
import yargs from "yargs";
|
|
4
|
+
export declare const requestCommand: (yargs: yargs.Argv<{}>) => yargs.Argv<{
|
|
5
|
+
arguments: string[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare const request: (args: yargs.ArgumentsCamelCase<{
|
|
8
|
+
arguments: string[];
|
|
9
|
+
wait?: boolean;
|
|
10
|
+
}>, authn?: Authn, options?: {
|
|
11
|
+
message?: "all" | "approval-required" | "none";
|
|
12
|
+
}) => Promise<RequestResponse | undefined>;
|