@p0security/cli 0.4.1 → 0.5.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/dist/commands/__tests__/ssh.test.js +14 -1
- package/dist/commands/aws/__tests__/role.test.js +12 -8
- package/dist/commands/aws/role.d.ts +5 -3
- package/dist/commands/aws/role.js +10 -9
- package/dist/commands/request.d.ts +2 -2
- package/dist/commands/ssh.js +14 -2
- package/dist/plugins/aws/config.d.ts +6 -1
- package/dist/plugins/aws/config.js +10 -4
- package/dist/plugins/aws/ssm/index.js +1 -1
- package/dist/plugins/aws/types.d.ts +34 -21
- package/dist/plugins/okta/aws.js +1 -1
- package/dist/plugins/okta/login.d.ts +2 -2
- package/dist/plugins/okta/login.js +1 -1
- package/dist/types/request.d.ts +2 -1
- package/package.json +1 -1
|
@@ -43,9 +43,9 @@ const mockPrint2 = stdio_1.print2;
|
|
|
43
43
|
workflows: {
|
|
44
44
|
items: [
|
|
45
45
|
{
|
|
46
|
-
identifier: "test-account",
|
|
47
46
|
state: "installed",
|
|
48
47
|
type: "aws",
|
|
48
|
+
identifier: "test-account",
|
|
49
49
|
},
|
|
50
50
|
],
|
|
51
51
|
},
|
|
@@ -64,6 +64,19 @@ describe("ssh", () => {
|
|
|
64
64
|
id: "abcefg",
|
|
65
65
|
isPreexisting: false,
|
|
66
66
|
isPersistent,
|
|
67
|
+
event: {
|
|
68
|
+
permission: {
|
|
69
|
+
type: "session",
|
|
70
|
+
spec: {
|
|
71
|
+
resource: {
|
|
72
|
+
arn: "arn:aws:ec2:us-west-2:391052057035:instance/i-0b1b7b7b7b7b7b7b7",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
generated: {
|
|
77
|
+
documentName: "documentName",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
67
80
|
});
|
|
68
81
|
});
|
|
69
82
|
it("should call p0 request with reason arg", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -53,29 +53,33 @@ beforeEach(() => {
|
|
|
53
53
|
describe("aws role", () => {
|
|
54
54
|
describe("a single installed account", () => {
|
|
55
55
|
const item = {
|
|
56
|
-
|
|
57
|
-
id: "1",
|
|
58
|
-
description: "1 (test)",
|
|
59
|
-
},
|
|
56
|
+
label: "test",
|
|
60
57
|
state: "installed",
|
|
61
58
|
};
|
|
62
59
|
describe("without Okta SAML", () => {
|
|
63
|
-
(0, firestore_1.mockGetDoc)({
|
|
60
|
+
(0, firestore_1.mockGetDoc)({ "iam-write": { "1": item } });
|
|
64
61
|
describe.each([
|
|
65
62
|
["ls", "aws role ls"],
|
|
66
63
|
["assume", "aws role assume Role1"],
|
|
67
64
|
])("%s", (_, command) => {
|
|
68
65
|
it("should print a friendly error message", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
66
|
const error = yield (0, yargs_1.failure)((0, __1.awsCommand)((0, yargs_2.default)()), command);
|
|
70
|
-
expect(error).toMatchInlineSnapshot(`"Account
|
|
67
|
+
expect(error).toMatchInlineSnapshot(`"Account test is not configured for Okta SAML login."`);
|
|
71
68
|
}));
|
|
72
69
|
});
|
|
73
70
|
});
|
|
74
71
|
describe("with Okta SAML", () => {
|
|
75
72
|
beforeEach(() => {
|
|
76
73
|
(0, firestore_1.mockGetDoc)({
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
"iam-write": {
|
|
75
|
+
"1": Object.assign(Object.assign({}, item), { login: {
|
|
76
|
+
type: "federated",
|
|
77
|
+
provider: {
|
|
78
|
+
type: "okta",
|
|
79
|
+
appId: "0oabcdefgh",
|
|
80
|
+
identityProvider: "okta",
|
|
81
|
+
},
|
|
82
|
+
} }),
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
85
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AwsFederatedLogin } from "../../plugins/aws/types";
|
|
2
2
|
import { Authn } from "../../types/identity";
|
|
3
3
|
import yargs from "yargs";
|
|
4
4
|
export declare const role: (yargs: yargs.Argv<{
|
|
@@ -15,8 +15,10 @@ export declare const role: (yargs: yargs.Argv<{
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const initOktaSaml: (authn: Authn, account: string | undefined) => Promise<{
|
|
17
17
|
samlResponse: string;
|
|
18
|
-
config:
|
|
19
|
-
|
|
18
|
+
config: {
|
|
19
|
+
id: string;
|
|
20
|
+
} & import("../../plugins/aws/types").AwsItemConfig & {
|
|
21
|
+
login: AwsFederatedLogin;
|
|
20
22
|
};
|
|
21
23
|
account: string;
|
|
22
24
|
}>;
|
|
@@ -42,21 +42,22 @@ const role = (yargs) => yargs.command("role", "Interact with AWS roles", (yargs)
|
|
|
42
42
|
(0, firestore_1.guard)(oktaAwsAssumeRole))
|
|
43
43
|
.demandCommand(1));
|
|
44
44
|
exports.role = role;
|
|
45
|
-
const
|
|
45
|
+
const isFederatedLogin = (config) => { var _a; return ((_a = config.login) === null || _a === void 0 ? void 0 : _a.type) === "federated"; };
|
|
46
46
|
/** Retrieves the configured Okta SAML response for the specified account
|
|
47
47
|
*
|
|
48
48
|
* If no account is passed, and the organization only has one account configured,
|
|
49
49
|
* assumes that account.
|
|
50
50
|
*/
|
|
51
51
|
const initOktaSaml = (authn, account) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
var _a;
|
|
52
53
|
const { identity, config } = yield (0, config_1.getAwsConfig)(authn, account);
|
|
53
|
-
if (!
|
|
54
|
-
throw `Account ${config.
|
|
55
|
-
const samlResponse = yield (0, login_1.getSamlResponse)(identity, config.
|
|
54
|
+
if (!isFederatedLogin(config))
|
|
55
|
+
throw `Account ${(_a = config.label) !== null && _a !== void 0 ? _a : config.id} is not configured for Okta SAML login.`;
|
|
56
|
+
const samlResponse = yield (0, login_1.getSamlResponse)(identity, config.login);
|
|
56
57
|
return {
|
|
57
58
|
samlResponse,
|
|
58
59
|
config,
|
|
59
|
-
account: config.
|
|
60
|
+
account: config.id,
|
|
60
61
|
};
|
|
61
62
|
});
|
|
62
63
|
exports.initOktaSaml = initOktaSaml;
|
|
@@ -88,10 +89,10 @@ exports.rolesFromSaml = rolesFromSaml;
|
|
|
88
89
|
* - The requested role is assigned to the user in Okta
|
|
89
90
|
*/
|
|
90
91
|
const oktaAwsAssumeRole = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
-
var
|
|
92
|
+
var _b;
|
|
92
93
|
const authn = yield (0, auth_1.authenticate)();
|
|
93
94
|
const awsCredential = yield (0, aws_1.assumeRoleWithOktaSaml)(authn, args);
|
|
94
|
-
const isTty = (
|
|
95
|
+
const isTty = (_b = typescript_1.sys.writeOutputIsTTY) === null || _b === void 0 ? void 0 : _b.call(typescript_1.sys);
|
|
95
96
|
if (isTty)
|
|
96
97
|
(0, stdio_1.print2)("Execute the following commands:\n");
|
|
97
98
|
const indent = isTty ? " " : "";
|
|
@@ -107,11 +108,11 @@ Or, populate these environment variables using BASH command substitution:
|
|
|
107
108
|
});
|
|
108
109
|
/** Lists assigned AWS roles for this user on this account */
|
|
109
110
|
const oktaAwsListRoles = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
-
var
|
|
111
|
+
var _c;
|
|
111
112
|
const authn = yield (0, auth_1.authenticate)();
|
|
112
113
|
const { account, samlResponse } = yield (0, exports.initOktaSaml)(authn, args.account);
|
|
113
114
|
const { arns, roles } = (0, exports.rolesFromSaml)(account, samlResponse);
|
|
114
|
-
const isTty = (
|
|
115
|
+
const isTty = (_c = typescript_1.sys.writeOutputIsTTY) === null || _c === void 0 ? void 0 : _c.call(typescript_1.sys);
|
|
115
116
|
if (isTty)
|
|
116
117
|
(0, stdio_1.print2)(`Your available roles for account ${account}:`);
|
|
117
118
|
if (!(roles === null || roles === void 0 ? void 0 : roles.length)) {
|
|
@@ -4,9 +4,9 @@ import yargs from "yargs";
|
|
|
4
4
|
export declare const requestCommand: (yargs: yargs.Argv<{}>) => yargs.Argv<{
|
|
5
5
|
arguments: string[];
|
|
6
6
|
}>;
|
|
7
|
-
export declare const request: (args: yargs.ArgumentsCamelCase<{
|
|
7
|
+
export declare const request: <T>(args: yargs.ArgumentsCamelCase<{
|
|
8
8
|
arguments: string[];
|
|
9
9
|
wait?: boolean;
|
|
10
10
|
}>, authn?: Authn, options?: {
|
|
11
11
|
message?: "all" | "approval-required" | "none";
|
|
12
|
-
}) => Promise<RequestResponse | undefined>;
|
|
12
|
+
}) => Promise<RequestResponse<T> | undefined>;
|
package/dist/commands/ssh.js
CHANGED
|
@@ -146,10 +146,22 @@ const ssh = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
146
146
|
(0, stdio_1.print2)("Did not receive access ID from server");
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
|
-
const { id, isPreexisting } = response;
|
|
149
|
+
const { id, isPreexisting, event } = response;
|
|
150
150
|
if (!isPreexisting)
|
|
151
151
|
(0, stdio_1.print2)("Waiting for access to be provisioned");
|
|
152
|
+
/**
|
|
153
|
+
* TODO TECH-DEBT ENG-1813:
|
|
154
|
+
* We use the id and waitForProvisioning to find the permission request document which has
|
|
155
|
+
* critical data, such as the document name and generated role, that we need to build up a
|
|
156
|
+
* viable SSM request.
|
|
157
|
+
*
|
|
158
|
+
* Replacing the permission with event.permission is necessary when trying to connect to an
|
|
159
|
+
* instance which has been granted approval through it's group. The event.permission object
|
|
160
|
+
* will contain details about the specific instance we are trying to connect to such as the
|
|
161
|
+
* instance id. Without an instance id, which an SSH group permission request document does
|
|
162
|
+
* not contain we cannot construct a valid SSM command.
|
|
163
|
+
*/
|
|
152
164
|
const requestData = yield waitForProvisioning(authn, id);
|
|
153
|
-
const requestWithId = Object.assign(Object.assign({}, requestData), { id });
|
|
165
|
+
const requestWithId = Object.assign(Object.assign({}, requestData), { id, permission: event.permission });
|
|
154
166
|
yield (0, ssm_1.ssm)(authn, requestWithId, args);
|
|
155
167
|
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Authn } from "../../types/identity";
|
|
2
2
|
export declare const getAwsConfig: (authn: Authn, account: string | undefined) => Promise<{
|
|
3
3
|
identity: import("../../types/identity").Identity;
|
|
4
|
-
config:
|
|
4
|
+
config: {
|
|
5
|
+
label?: string | undefined;
|
|
6
|
+
state: string;
|
|
7
|
+
login?: (import("./types").AwsIamLogin | import("./types").AwsIdcLogin | import("./types").AwsFederatedLogin) | undefined;
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
5
10
|
}>;
|
|
@@ -22,17 +22,23 @@ You should have received a copy of the GNU General Public License along with @p0
|
|
|
22
22
|
**/
|
|
23
23
|
const firestore_1 = require("../../drivers/firestore");
|
|
24
24
|
const firestore_2 = require("firebase/firestore");
|
|
25
|
+
const lodash_1 = require("lodash");
|
|
25
26
|
const getAwsConfig = (authn, account) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
-
var _a
|
|
27
|
+
var _a;
|
|
27
28
|
const { identity } = authn;
|
|
28
29
|
const snapshot = yield (0, firestore_2.getDoc)((0, firestore_1.doc)(`o/${identity.org.tenantId}/integrations/aws`));
|
|
29
30
|
const config = snapshot.data();
|
|
30
31
|
// TODO: Support alias lookup
|
|
32
|
+
const allItems = (0, lodash_1.sortBy)(Object.entries((_a = config === null || config === void 0 ? void 0 : config["iam-write"]) !== null && _a !== void 0 ? _a : {}).filter(([, { state }]) => state === "installed"), ([id]) => id);
|
|
31
33
|
const item = account
|
|
32
|
-
? (
|
|
33
|
-
:
|
|
34
|
+
? allItems.find(([id, { label }]) => id === account || label === account)
|
|
35
|
+
: allItems.length !== 1
|
|
36
|
+
? (() => {
|
|
37
|
+
throw `Please select a unique AWS account with --account; valid accounts are:\n${allItems.map(([id, { label }]) => label !== null && label !== void 0 ? label : id).join("\n")}`;
|
|
38
|
+
})()
|
|
39
|
+
: allItems[0];
|
|
34
40
|
if (!item)
|
|
35
41
|
throw `P0 is not installed on AWS account ${account}`;
|
|
36
|
-
return { identity, config: item };
|
|
42
|
+
return { identity, config: Object.assign({ id: item[0] }, item[1]) };
|
|
37
43
|
});
|
|
38
44
|
exports.getAwsConfig = getAwsConfig;
|
|
@@ -236,7 +236,7 @@ const ssm = (authn, request, args) => __awaiter(void 0, void 0, void 0, function
|
|
|
236
236
|
const isInstalled = yield (0, install_1.ensureSsmInstall)();
|
|
237
237
|
if (!isInstalled)
|
|
238
238
|
throw "Please try again after installing the required AWS utilities";
|
|
239
|
-
const match = request.permission.spec.arn.match(INSTANCE_ARN_PATTERN);
|
|
239
|
+
const match = request.permission.spec.awsResourcePermission.resource.arn.match(INSTANCE_ARN_PATTERN);
|
|
240
240
|
if (!match)
|
|
241
241
|
throw "Did not receive a properly formatted instance identifier";
|
|
242
242
|
const [, region, account, instance] = match;
|
|
@@ -13,37 +13,50 @@ export declare type AwsCredentials = {
|
|
|
13
13
|
AWS_SECRET_ACCESS_KEY: string;
|
|
14
14
|
AWS_SESSION_TOKEN: string;
|
|
15
15
|
};
|
|
16
|
-
export declare type
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
export declare type AwsIamLogin = {
|
|
17
|
+
type: "iam";
|
|
18
|
+
identity: {
|
|
19
|
+
type: "email";
|
|
20
|
+
} | {
|
|
21
|
+
type: "tag";
|
|
22
|
+
tagName: string;
|
|
23
|
+
};
|
|
20
24
|
};
|
|
21
|
-
declare type
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} | {
|
|
25
|
-
id: "user_tag";
|
|
26
|
-
tagName: string;
|
|
27
|
-
} | {
|
|
28
|
-
id: "username";
|
|
25
|
+
export declare type AwsIdcLogin = {
|
|
26
|
+
type: "idc";
|
|
27
|
+
parent: string;
|
|
29
28
|
};
|
|
30
|
-
export declare type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
export declare type AwsFederatedLogin = {
|
|
30
|
+
type: "federated";
|
|
31
|
+
provider: {
|
|
32
|
+
type: "okta";
|
|
33
|
+
appId: string;
|
|
34
|
+
identityProvider: string;
|
|
35
|
+
method: {
|
|
36
|
+
type: "saml";
|
|
37
|
+
};
|
|
34
38
|
};
|
|
39
|
+
};
|
|
40
|
+
declare type AwsLogin = AwsFederatedLogin | AwsIamLogin | AwsIdcLogin;
|
|
41
|
+
export declare type AwsItemConfig = {
|
|
42
|
+
label?: string;
|
|
35
43
|
state: string;
|
|
36
|
-
|
|
44
|
+
login?: AwsLogin;
|
|
37
45
|
};
|
|
46
|
+
export declare type AwsItem = {
|
|
47
|
+
id: string;
|
|
48
|
+
} & AwsItemConfig;
|
|
38
49
|
export declare type AwsConfig = {
|
|
39
|
-
|
|
40
|
-
items: AwsItemConfig[];
|
|
41
|
-
};
|
|
50
|
+
"iam-write": Record<string, AwsItemConfig>;
|
|
42
51
|
};
|
|
43
52
|
export declare type AwsSsh = {
|
|
44
53
|
permission: {
|
|
45
54
|
spec: {
|
|
46
|
-
|
|
55
|
+
awsResourcePermission: {
|
|
56
|
+
resource: {
|
|
57
|
+
arn: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
47
60
|
};
|
|
48
61
|
type: "session";
|
|
49
62
|
};
|
package/dist/plugins/okta/aws.js
CHANGED
|
@@ -33,7 +33,7 @@ const assumeRoleWithOktaSaml = (authn, args) => __awaiter(void 0, void 0, void 0
|
|
|
33
33
|
account,
|
|
34
34
|
role: args.role,
|
|
35
35
|
saml: {
|
|
36
|
-
providerName: config.
|
|
36
|
+
providerName: config.login.provider.identityProvider,
|
|
37
37
|
response: samlResponse,
|
|
38
38
|
},
|
|
39
39
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Identity } from "../../types/identity";
|
|
2
2
|
import { TokenResponse } from "../../types/oidc";
|
|
3
3
|
import { OrgData } from "../../types/org";
|
|
4
|
-
import {
|
|
4
|
+
import { AwsFederatedLogin } from "../aws/types";
|
|
5
5
|
/** Logs in to Okta via OIDC */
|
|
6
6
|
export declare const oktaLogin: (org: OrgData) => Promise<TokenResponse>;
|
|
7
7
|
/** Retrieves a SAML response for an okta app */
|
|
8
|
-
export declare const getSamlResponse: (identity: Identity, config:
|
|
8
|
+
export declare const getSamlResponse: (identity: Identity, config: AwsFederatedLogin) => Promise<string>;
|
|
@@ -155,7 +155,7 @@ exports.oktaLogin = oktaLogin;
|
|
|
155
155
|
/** Retrieves a SAML response for an okta app */
|
|
156
156
|
// TODO: Inject Okta app
|
|
157
157
|
const getSamlResponse = (identity, config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
158
|
-
const webTokenResponse = yield fetchSsoWebToken(config.appId, identity);
|
|
158
|
+
const webTokenResponse = yield fetchSsoWebToken(config.provider.appId, identity);
|
|
159
159
|
const samlResponse = yield fetchSamlResponse(identity.org, webTokenResponse);
|
|
160
160
|
if (!samlResponse) {
|
|
161
161
|
throw "No SAML assertion obtained from Okta.";
|
package/dist/types/request.d.ts
CHANGED
|
@@ -26,10 +26,11 @@ export declare type Request<P extends PluginRequest = {
|
|
|
26
26
|
permission: P["permission"];
|
|
27
27
|
principal: string;
|
|
28
28
|
};
|
|
29
|
-
export declare type RequestResponse = {
|
|
29
|
+
export declare type RequestResponse<T> = {
|
|
30
30
|
ok: true;
|
|
31
31
|
message: string;
|
|
32
32
|
id: string;
|
|
33
|
+
event: T;
|
|
33
34
|
isPreexisting: boolean;
|
|
34
35
|
isPersistent: boolean;
|
|
35
36
|
};
|