@p0security/cli 0.4.2 → 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.
@@ -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
  },
@@ -53,29 +53,33 @@ beforeEach(() => {
53
53
  describe("aws role", () => {
54
54
  describe("a single installed account", () => {
55
55
  const item = {
56
- account: {
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)({ workflows: { items: [item] } });
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 1 (test) is not configured for Okta SAML login."`);
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
- workflows: {
78
- items: [Object.assign(Object.assign({}, item), { uidLocation: { id: "okta_saml_sso" } })],
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 { AwsItemConfig, AwsOktaSamlUidLocation } from "../../plugins/aws/types";
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: AwsItemConfig & {
19
- uidLocation: AwsOktaSamlUidLocation;
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 isOktaSamlConfig = (config) => { var _a; return ((_a = config.uidLocation) === null || _a === void 0 ? void 0 : _a.id) === "okta_saml_sso"; };
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 (!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);
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.account.id,
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 _a;
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 = (_a = typescript_1.sys.writeOutputIsTTY) === null || _a === void 0 ? void 0 : _a.call(typescript_1.sys);
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 _b;
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 = (_b = typescript_1.sys.writeOutputIsTTY) === null || _b === void 0 ? void 0 : _b.call(typescript_1.sys);
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)) {
@@ -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: import("./types").AwsItemConfig;
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, _b;
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
- ? (_a = config === null || config === void 0 ? void 0 : config.workflows) === null || _a === void 0 ? void 0 : _a.items.find((i) => i.state === "installed" && i.account.id === account)
33
- : (_b = config === null || config === void 0 ? void 0 : config.workflows) === null || _b === void 0 ? void 0 : _b.items[0];
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;
@@ -13,32 +13,41 @@ export declare type AwsCredentials = {
13
13
  AWS_SECRET_ACCESS_KEY: string;
14
14
  AWS_SESSION_TOKEN: string;
15
15
  };
16
- export declare type AwsOktaSamlUidLocation = {
17
- id: "okta_saml_sso";
18
- samlProviderName: string;
19
- appId: string;
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 AwsUidLocation = AwsOktaSamlUidLocation | {
22
- id: "idc";
23
- parentId: string;
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 AwsItemConfig = {
31
- account: {
32
- id: string;
33
- description?: string;
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
- uidLocation?: AwsUidLocation;
44
+ login?: AwsLogin;
37
45
  };
46
+ export declare type AwsItem = {
47
+ id: string;
48
+ } & AwsItemConfig;
38
49
  export declare type AwsConfig = {
39
- workflows?: {
40
- items: AwsItemConfig[];
41
- };
50
+ "iam-write": Record<string, AwsItemConfig>;
42
51
  };
43
52
  export declare type AwsSsh = {
44
53
  permission: {
@@ -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.uidLocation.samlProviderName,
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 { AwsOktaSamlUidLocation } from "../aws/types";
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: AwsOktaSamlUidLocation) => Promise<string>;
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p0security/cli",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Execute infra CLI commands with P0 grants",
5
5
  "main": "index.ts",
6
6
  "repository": {