@cloud-copilot/iam-utils 0.0.1 → 0.0.3

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/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # iam-utils
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/@cloud-copilot/iam-utils.svg?logo=nodedotjs)](https://www.npmjs.com/package/@cloud-copilot/iam-utils) [![License: AGPL v3](https://img.shields.io/github/license/cloud-copilot/iam-utils)](LICENSE.txt) [![GuardDog](https://github.com/cloud-copilot/iam-utils/actions/workflows/guarddog.yml/badge.svg)](https://github.com/cloud-copilot/iam-utils/actions/workflows/guarddog.yml) [![Known Vulnerabilities](https://snyk.io/test/github/cloud-copilot/iam-utils/badge.svg?targetFile=package.json&style=flat-square)](https://snyk.io/test/github/cloud-copilot/iam-utils?targetFile=package.json)
@@ -0,0 +1,26 @@
1
+ export interface ArnParts {
2
+ partition: string | undefined;
3
+ service: string | undefined;
4
+ region: string | undefined;
5
+ accountId: string | undefined;
6
+ resource: string | undefined;
7
+ resourceType: string | undefined;
8
+ resourcePath: string | undefined;
9
+ }
10
+ /**
11
+ * Split an ARN into its parts
12
+ *
13
+ * @param arn the arn to split
14
+ * @returns the parts of the ARN
15
+ */
16
+ export declare function splitArnParts(arn: string): ArnParts;
17
+ /**
18
+ * Get the product/id segments of the resource portion of an ARN.
19
+ * The first segment is the product segment and the second segment is the resource id segment.
20
+ * This could be split by a colon or a slash, so it checks for both. It also checks for S3 buckets/objects.
21
+ *
22
+ * @param resource The resource to get the resource segments. Must be an ARN resource.
23
+ * @returns a tuple with the first segment being the product segment (without the separator) and the second segment being the resource id.
24
+ */
25
+ export declare function getResourceSegments(service: string, accountId: string, region: string, resourceString: string): [string, string];
26
+ //# sourceMappingURL=arn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arn.d.ts","sourceRoot":"","sources":["../../src/arn.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAkBnD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,CAAC,MAAM,EAAE,MAAM,CAAC,CA+BlB"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitArnParts = splitArnParts;
4
+ exports.getResourceSegments = getResourceSegments;
5
+ /**
6
+ * Split an ARN into its parts
7
+ *
8
+ * @param arn the arn to split
9
+ * @returns the parts of the ARN
10
+ */
11
+ function splitArnParts(arn) {
12
+ const parts = arn.split(':');
13
+ const partition = parts.at(1);
14
+ const service = parts.at(2);
15
+ const region = parts.at(3);
16
+ const accountId = parts.at(4);
17
+ const resource = parts.slice(5).join(':');
18
+ const [resourceType, resourcePath] = getResourceSegments(service, accountId, region, resource);
19
+ return {
20
+ partition,
21
+ service,
22
+ region,
23
+ accountId,
24
+ resource,
25
+ resourceType,
26
+ resourcePath
27
+ };
28
+ }
29
+ /**
30
+ * Get the product/id segments of the resource portion of an ARN.
31
+ * The first segment is the product segment and the second segment is the resource id segment.
32
+ * This could be split by a colon or a slash, so it checks for both. It also checks for S3 buckets/objects.
33
+ *
34
+ * @param resource The resource to get the resource segments. Must be an ARN resource.
35
+ * @returns a tuple with the first segment being the product segment (without the separator) and the second segment being the resource id.
36
+ */
37
+ function getResourceSegments(service, accountId, region, resourceString) {
38
+ // This is terrible, and I hate it
39
+ if ((service === 's3' && accountId === '' && region === '') ||
40
+ service === 'sns' ||
41
+ service === 'sqs') {
42
+ return ['', resourceString];
43
+ }
44
+ if (resourceString.startsWith('/')) {
45
+ resourceString = resourceString.slice(1);
46
+ }
47
+ const slashIndex = resourceString.indexOf('/');
48
+ const colonIndex = resourceString.indexOf(':');
49
+ let splitIndex = slashIndex;
50
+ if (slashIndex != -1 && colonIndex != -1) {
51
+ splitIndex = Math.min(slashIndex, colonIndex) + 1;
52
+ }
53
+ else if (slashIndex == -1 && colonIndex == -1) {
54
+ splitIndex = resourceString.length + 1;
55
+ }
56
+ else if (colonIndex == -1) {
57
+ splitIndex = slashIndex + 1;
58
+ }
59
+ else if (slashIndex == -1) {
60
+ splitIndex = colonIndex + 1;
61
+ }
62
+ else {
63
+ throw new Error(`Unable to split resource ${resourceString}`);
64
+ }
65
+ return [resourceString.slice(0, splitIndex - 1), resourceString.slice(splitIndex)];
66
+ }
67
+ //# sourceMappingURL=arn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arn.js","sourceRoot":"","sources":["../../src/arn.ts"],"names":[],"mappings":";;AAgBA,sCAkBC;AAUD,kDAoCC;AAtED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,GAAW;IACvC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAE,CAAA;IAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;IAE9F,OAAO;QACL,SAAS;QACT,OAAO;QACP,MAAM;QACN,SAAS;QACT,QAAQ;QACR,YAAY;QACZ,YAAY;KACb,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CACjC,OAAe,EACf,SAAiB,EACjB,MAAc,EACd,cAAsB;IAEtB,kCAAkC;IAClC,IACE,CAAC,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,EAAE,IAAI,MAAM,KAAK,EAAE,CAAC;QACvD,OAAO,KAAK,KAAK;QACjB,OAAO,KAAK,KAAK,EACjB,CAAC;QACD,OAAO,CAAC,EAAE,EAAE,cAAc,CAAC,CAAA;IAC7B,CAAC;IAED,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,cAAc,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9C,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAE9C,IAAI,UAAU,GAAG,UAAU,CAAA;IAC3B,IAAI,UAAU,IAAI,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QACzC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAA;IACnD,CAAC;SAAM,IAAI,UAAU,IAAI,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAChD,UAAU,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA;IACxC,CAAC;SAAM,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5B,UAAU,GAAG,UAAU,GAAG,CAAC,CAAA;IAC7B,CAAC;SAAM,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5B,UAAU,GAAG,UAAU,GAAG,CAAC,CAAA;IAC7B,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,4BAA4B,cAAc,EAAE,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAA;AACpF,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { getResourceSegments, splitArnParts, type ArnParts } from './arn';
2
+ export { convertAssumedRoleArnToRoleArn, convertRoleArnToAssumedRoleArn, isAssumedRoleArn, isIamUserArn } from './principals';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzE,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,gBAAgB,EAChB,YAAY,EACb,MAAM,cAAc,CAAA"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isIamUserArn = exports.isAssumedRoleArn = exports.convertRoleArnToAssumedRoleArn = exports.convertAssumedRoleArnToRoleArn = exports.splitArnParts = exports.getResourceSegments = void 0;
4
+ var arn_1 = require("./arn");
5
+ Object.defineProperty(exports, "getResourceSegments", { enumerable: true, get: function () { return arn_1.getResourceSegments; } });
6
+ Object.defineProperty(exports, "splitArnParts", { enumerable: true, get: function () { return arn_1.splitArnParts; } });
7
+ var principals_1 = require("./principals");
8
+ Object.defineProperty(exports, "convertAssumedRoleArnToRoleArn", { enumerable: true, get: function () { return principals_1.convertAssumedRoleArnToRoleArn; } });
9
+ Object.defineProperty(exports, "convertRoleArnToAssumedRoleArn", { enumerable: true, get: function () { return principals_1.convertRoleArnToAssumedRoleArn; } });
10
+ Object.defineProperty(exports, "isAssumedRoleArn", { enumerable: true, get: function () { return principals_1.isAssumedRoleArn; } });
11
+ Object.defineProperty(exports, "isIamUserArn", { enumerable: true, get: function () { return principals_1.isIamUserArn; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,6BAAyE;AAAhE,0GAAA,mBAAmB,OAAA;AAAE,oGAAA,aAAa,OAAA;AAC3C,2CAKqB;AAJnB,4HAAA,8BAA8B,OAAA;AAC9B,4HAAA,8BAA8B,OAAA;AAC9B,8GAAA,gBAAgB,OAAA;AAChB,0GAAA,YAAY,OAAA"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Transform an assumed role session ARN into a role ARN
3
+ *
4
+ * @param assumedRoleArn the assumed role session ARN
5
+ * @returns the role ARN for the assumed role session
6
+ */
7
+ export declare function convertAssumedRoleArnToRoleArn(assumedRoleArn: string): string;
8
+ /**
9
+ * Create an assumed role ARN from a role ARN and a session name
10
+ *
11
+ * @param roleArn the role ARN to create an assumed role ARN from
12
+ * @param sessionName the session name to use
13
+ * @returns the assumed role ARN
14
+ */
15
+ export declare function convertRoleArnToAssumedRoleArn(roleArn: string, sessionName: string): string;
16
+ /**
17
+ * Tests if a principal string is an assumed role ARN
18
+ *
19
+ * @param principal the principal string to test
20
+ * @returns true if the principal is an assumed role ARN, false otherwise
21
+ */
22
+ export declare function isAssumedRoleArn(principal: string): boolean;
23
+ /**
24
+ * Test if a principal string is an IAM user ARN
25
+ *
26
+ * @param principal the principal string to test
27
+ * @returns true if the principal is an IAM user ARN, false otherwise
28
+ */
29
+ export declare function isIamUserArn(principal: string): boolean;
30
+ /**
31
+ * Test if a principal string is a federated user ARN
32
+ *
33
+ * @param principal the principal string to test
34
+ * @returns true if the principal is a federated user ARN, false otherwise
35
+ */
36
+ export declare function isFederatedUserArn(principal: string): boolean;
37
+ //# sourceMappingURL=principals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principals.d.ts","sourceRoot":"","sources":["../../src/principals.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAI7E;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAI3F;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D;AAID;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvD;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7D"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertAssumedRoleArnToRoleArn = convertAssumedRoleArnToRoleArn;
4
+ exports.convertRoleArnToAssumedRoleArn = convertRoleArnToAssumedRoleArn;
5
+ exports.isAssumedRoleArn = isAssumedRoleArn;
6
+ exports.isIamUserArn = isIamUserArn;
7
+ exports.isFederatedUserArn = isFederatedUserArn;
8
+ const arn_js_1 = require("./arn.js");
9
+ /**
10
+ * Transform an assumed role session ARN into a role ARN
11
+ *
12
+ * @param assumedRoleArn the assumed role session ARN
13
+ * @returns the role ARN for the assumed role session
14
+ */
15
+ function convertAssumedRoleArnToRoleArn(assumedRoleArn) {
16
+ const arnParts = (0, arn_js_1.splitArnParts)(assumedRoleArn);
17
+ const rolePathAndName = arnParts.resourcePath?.split('/').slice(0, -1).join('/');
18
+ return `arn:${arnParts.partition}:iam::${arnParts.accountId}:role/${rolePathAndName}`;
19
+ }
20
+ /**
21
+ * Create an assumed role ARN from a role ARN and a session name
22
+ *
23
+ * @param roleArn the role ARN to create an assumed role ARN from
24
+ * @param sessionName the session name to use
25
+ * @returns the assumed role ARN
26
+ */
27
+ function convertRoleArnToAssumedRoleArn(roleArn, sessionName) {
28
+ const arnParts = (0, arn_js_1.splitArnParts)(roleArn);
29
+ const rolePathAndName = arnParts.resourcePath;
30
+ return `arn:${arnParts.partition}:sts::${arnParts.accountId}:assumed-role/${rolePathAndName}/${sessionName}`;
31
+ }
32
+ const assumedRoleArnRegex = /^arn:[a-zA-Z\-]+:sts::\d{12}:assumed-role\/.*$/;
33
+ /**
34
+ * Tests if a principal string is an assumed role ARN
35
+ *
36
+ * @param principal the principal string to test
37
+ * @returns true if the principal is an assumed role ARN, false otherwise
38
+ */
39
+ function isAssumedRoleArn(principal) {
40
+ return assumedRoleArnRegex.test(principal);
41
+ }
42
+ const userArnRegex = /^arn:[a-zA-Z\-]+:iam::\d{12}:user\/.*$/;
43
+ /**
44
+ * Test if a principal string is an IAM user ARN
45
+ *
46
+ * @param principal the principal string to test
47
+ * @returns true if the principal is an IAM user ARN, false otherwise
48
+ */
49
+ function isIamUserArn(principal) {
50
+ return userArnRegex.test(principal);
51
+ }
52
+ const federatedUserArnRegex = /^arn:[a-zA-Z\-]+:sts::\d{12}:federated-user\/.*$/;
53
+ /**
54
+ * Test if a principal string is a federated user ARN
55
+ *
56
+ * @param principal the principal string to test
57
+ * @returns true if the principal is a federated user ARN, false otherwise
58
+ */
59
+ function isFederatedUserArn(principal) {
60
+ return federatedUserArnRegex.test(principal);
61
+ }
62
+ //# sourceMappingURL=principals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principals.js","sourceRoot":"","sources":["../../src/principals.ts"],"names":[],"mappings":";;AAQA,wEAIC;AASD,wEAIC;AAUD,4CAEC;AAUD,oCAEC;AAUD,gDAEC;AA7DD,qCAAwC;AAExC;;;;;GAKG;AACH,SAAgB,8BAA8B,CAAC,cAAsB;IACnE,MAAM,QAAQ,GAAG,IAAA,sBAAa,EAAC,cAAc,CAAC,CAAA;IAC9C,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChF,OAAO,OAAO,QAAQ,CAAC,SAAS,SAAS,QAAQ,CAAC,SAAS,SAAS,eAAe,EAAE,CAAA;AACvF,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,8BAA8B,CAAC,OAAe,EAAE,WAAmB;IACjF,MAAM,QAAQ,GAAG,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAA;IACvC,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,CAAA;IAC7C,OAAO,OAAO,QAAQ,CAAC,SAAS,SAAS,QAAQ,CAAC,SAAS,iBAAiB,eAAe,IAAI,WAAW,EAAE,CAAA;AAC9G,CAAC;AAED,MAAM,mBAAmB,GAAG,gDAAgD,CAAA;AAE5E;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,SAAiB;IAChD,OAAO,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,YAAY,GAAG,wCAAwC,CAAA;AAE7D;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,SAAiB;IAC5C,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,qBAAqB,GAAG,kDAAkD,CAAA;AAEhF;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,SAAiB;IAClD,OAAO,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AAC9C,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface ArnParts {
2
+ partition: string | undefined;
3
+ service: string | undefined;
4
+ region: string | undefined;
5
+ accountId: string | undefined;
6
+ resource: string | undefined;
7
+ resourceType: string | undefined;
8
+ resourcePath: string | undefined;
9
+ }
10
+ /**
11
+ * Split an ARN into its parts
12
+ *
13
+ * @param arn the arn to split
14
+ * @returns the parts of the ARN
15
+ */
16
+ export declare function splitArnParts(arn: string): ArnParts;
17
+ /**
18
+ * Get the product/id segments of the resource portion of an ARN.
19
+ * The first segment is the product segment and the second segment is the resource id segment.
20
+ * This could be split by a colon or a slash, so it checks for both. It also checks for S3 buckets/objects.
21
+ *
22
+ * @param resource The resource to get the resource segments. Must be an ARN resource.
23
+ * @returns a tuple with the first segment being the product segment (without the separator) and the second segment being the resource id.
24
+ */
25
+ export declare function getResourceSegments(service: string, accountId: string, region: string, resourceString: string): [string, string];
26
+ //# sourceMappingURL=arn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arn.d.ts","sourceRoot":"","sources":["../../src/arn.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAkBnD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,CAAC,MAAM,EAAE,MAAM,CAAC,CA+BlB"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Split an ARN into its parts
3
+ *
4
+ * @param arn the arn to split
5
+ * @returns the parts of the ARN
6
+ */
7
+ export function splitArnParts(arn) {
8
+ const parts = arn.split(':');
9
+ const partition = parts.at(1);
10
+ const service = parts.at(2);
11
+ const region = parts.at(3);
12
+ const accountId = parts.at(4);
13
+ const resource = parts.slice(5).join(':');
14
+ const [resourceType, resourcePath] = getResourceSegments(service, accountId, region, resource);
15
+ return {
16
+ partition,
17
+ service,
18
+ region,
19
+ accountId,
20
+ resource,
21
+ resourceType,
22
+ resourcePath
23
+ };
24
+ }
25
+ /**
26
+ * Get the product/id segments of the resource portion of an ARN.
27
+ * The first segment is the product segment and the second segment is the resource id segment.
28
+ * This could be split by a colon or a slash, so it checks for both. It also checks for S3 buckets/objects.
29
+ *
30
+ * @param resource The resource to get the resource segments. Must be an ARN resource.
31
+ * @returns a tuple with the first segment being the product segment (without the separator) and the second segment being the resource id.
32
+ */
33
+ export function getResourceSegments(service, accountId, region, resourceString) {
34
+ // This is terrible, and I hate it
35
+ if ((service === 's3' && accountId === '' && region === '') ||
36
+ service === 'sns' ||
37
+ service === 'sqs') {
38
+ return ['', resourceString];
39
+ }
40
+ if (resourceString.startsWith('/')) {
41
+ resourceString = resourceString.slice(1);
42
+ }
43
+ const slashIndex = resourceString.indexOf('/');
44
+ const colonIndex = resourceString.indexOf(':');
45
+ let splitIndex = slashIndex;
46
+ if (slashIndex != -1 && colonIndex != -1) {
47
+ splitIndex = Math.min(slashIndex, colonIndex) + 1;
48
+ }
49
+ else if (slashIndex == -1 && colonIndex == -1) {
50
+ splitIndex = resourceString.length + 1;
51
+ }
52
+ else if (colonIndex == -1) {
53
+ splitIndex = slashIndex + 1;
54
+ }
55
+ else if (slashIndex == -1) {
56
+ splitIndex = colonIndex + 1;
57
+ }
58
+ else {
59
+ throw new Error(`Unable to split resource ${resourceString}`);
60
+ }
61
+ return [resourceString.slice(0, splitIndex - 1), resourceString.slice(splitIndex)];
62
+ }
63
+ //# sourceMappingURL=arn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arn.js","sourceRoot":"","sources":["../../src/arn.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAE,CAAA;IAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;IAE9F,OAAO;QACL,SAAS;QACT,OAAO;QACP,MAAM;QACN,SAAS;QACT,QAAQ;QACR,YAAY;QACZ,YAAY;KACb,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,SAAiB,EACjB,MAAc,EACd,cAAsB;IAEtB,kCAAkC;IAClC,IACE,CAAC,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,EAAE,IAAI,MAAM,KAAK,EAAE,CAAC;QACvD,OAAO,KAAK,KAAK;QACjB,OAAO,KAAK,KAAK,EACjB,CAAC;QACD,OAAO,CAAC,EAAE,EAAE,cAAc,CAAC,CAAA;IAC7B,CAAC;IAED,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,cAAc,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9C,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAE9C,IAAI,UAAU,GAAG,UAAU,CAAA;IAC3B,IAAI,UAAU,IAAI,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QACzC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAA;IACnD,CAAC;SAAM,IAAI,UAAU,IAAI,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAChD,UAAU,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAA;IACxC,CAAC;SAAM,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5B,UAAU,GAAG,UAAU,GAAG,CAAC,CAAA;IAC7B,CAAC;SAAM,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5B,UAAU,GAAG,UAAU,GAAG,CAAC,CAAA;IAC7B,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,4BAA4B,cAAc,EAAE,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAA;AACpF,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { getResourceSegments, splitArnParts, type ArnParts } from './arn';
2
+ export { convertAssumedRoleArnToRoleArn, convertRoleArnToAssumedRoleArn, isAssumedRoleArn, isIamUserArn } from './principals';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzE,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,gBAAgB,EAChB,YAAY,EACb,MAAM,cAAc,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { getResourceSegments, splitArnParts } from './arn';
2
+ export { convertAssumedRoleArnToRoleArn, convertRoleArnToAssumedRoleArn, isAssumedRoleArn, isIamUserArn } from './principals';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAiB,MAAM,OAAO,CAAA;AACzE,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,gBAAgB,EAChB,YAAY,EACb,MAAM,cAAc,CAAA"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Transform an assumed role session ARN into a role ARN
3
+ *
4
+ * @param assumedRoleArn the assumed role session ARN
5
+ * @returns the role ARN for the assumed role session
6
+ */
7
+ export declare function convertAssumedRoleArnToRoleArn(assumedRoleArn: string): string;
8
+ /**
9
+ * Create an assumed role ARN from a role ARN and a session name
10
+ *
11
+ * @param roleArn the role ARN to create an assumed role ARN from
12
+ * @param sessionName the session name to use
13
+ * @returns the assumed role ARN
14
+ */
15
+ export declare function convertRoleArnToAssumedRoleArn(roleArn: string, sessionName: string): string;
16
+ /**
17
+ * Tests if a principal string is an assumed role ARN
18
+ *
19
+ * @param principal the principal string to test
20
+ * @returns true if the principal is an assumed role ARN, false otherwise
21
+ */
22
+ export declare function isAssumedRoleArn(principal: string): boolean;
23
+ /**
24
+ * Test if a principal string is an IAM user ARN
25
+ *
26
+ * @param principal the principal string to test
27
+ * @returns true if the principal is an IAM user ARN, false otherwise
28
+ */
29
+ export declare function isIamUserArn(principal: string): boolean;
30
+ /**
31
+ * Test if a principal string is a federated user ARN
32
+ *
33
+ * @param principal the principal string to test
34
+ * @returns true if the principal is a federated user ARN, false otherwise
35
+ */
36
+ export declare function isFederatedUserArn(principal: string): boolean;
37
+ //# sourceMappingURL=principals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principals.d.ts","sourceRoot":"","sources":["../../src/principals.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAI7E;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAI3F;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D;AAID;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvD;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7D"}
@@ -1,17 +1,15 @@
1
- import { splitArnParts } from './arn.js'
2
-
1
+ import { splitArnParts } from './arn.js';
3
2
  /**
4
3
  * Transform an assumed role session ARN into a role ARN
5
4
  *
6
5
  * @param assumedRoleArn the assumed role session ARN
7
6
  * @returns the role ARN for the assumed role session
8
7
  */
9
- export function convertAssumedRoleArnToRoleArn(assumedRoleArn: string): string {
10
- const arnParts = splitArnParts(assumedRoleArn)
11
- const rolePathAndName = arnParts.resourcePath?.split('/').slice(0, -1).join('/')
12
- return `arn:${arnParts.partition}:iam::${arnParts.accountId}:role/${rolePathAndName}`
8
+ export function convertAssumedRoleArnToRoleArn(assumedRoleArn) {
9
+ const arnParts = splitArnParts(assumedRoleArn);
10
+ const rolePathAndName = arnParts.resourcePath?.split('/').slice(0, -1).join('/');
11
+ return `arn:${arnParts.partition}:iam::${arnParts.accountId}:role/${rolePathAndName}`;
13
12
  }
14
-
15
13
  /**
16
14
  * Create an assumed role ARN from a role ARN and a session name
17
15
  *
@@ -19,44 +17,39 @@ export function convertAssumedRoleArnToRoleArn(assumedRoleArn: string): string {
19
17
  * @param sessionName the session name to use
20
18
  * @returns the assumed role ARN
21
19
  */
22
- export function convertRoleArnToAssumedRoleArn(roleArn: string, sessionName: string): string {
23
- const arnParts = splitArnParts(roleArn)
24
- const rolePathAndName = arnParts.resourcePath
25
- return `arn:${arnParts.partition}:sts::${arnParts.accountId}:assumed-role/${rolePathAndName}/${sessionName}`
20
+ export function convertRoleArnToAssumedRoleArn(roleArn, sessionName) {
21
+ const arnParts = splitArnParts(roleArn);
22
+ const rolePathAndName = arnParts.resourcePath;
23
+ return `arn:${arnParts.partition}:sts::${arnParts.accountId}:assumed-role/${rolePathAndName}/${sessionName}`;
26
24
  }
27
-
28
- const assumedRoleArnRegex = /^arn:[a-zA-Z\-]+:sts::\d{12}:assumed-role\/.*$/
29
-
25
+ const assumedRoleArnRegex = /^arn:[a-zA-Z\-]+:sts::\d{12}:assumed-role\/.*$/;
30
26
  /**
31
27
  * Tests if a principal string is an assumed role ARN
32
28
  *
33
29
  * @param principal the principal string to test
34
30
  * @returns true if the principal is an assumed role ARN, false otherwise
35
31
  */
36
- export function isAssumedRoleArn(principal: string): boolean {
37
- return assumedRoleArnRegex.test(principal)
32
+ export function isAssumedRoleArn(principal) {
33
+ return assumedRoleArnRegex.test(principal);
38
34
  }
39
-
40
- const userArnRegex = /^arn:[a-zA-Z\-]+:iam::\d{12}:user\/.*$/
41
-
35
+ const userArnRegex = /^arn:[a-zA-Z\-]+:iam::\d{12}:user\/.*$/;
42
36
  /**
43
37
  * Test if a principal string is an IAM user ARN
44
38
  *
45
39
  * @param principal the principal string to test
46
40
  * @returns true if the principal is an IAM user ARN, false otherwise
47
41
  */
48
- export function isIamUserArn(principal: string): boolean {
49
- return userArnRegex.test(principal)
42
+ export function isIamUserArn(principal) {
43
+ return userArnRegex.test(principal);
50
44
  }
51
-
52
- const federatedUserArnRegex = /^arn:[a-zA-Z\-]+:sts::\d{12}:federated-user\/.*$/
53
-
45
+ const federatedUserArnRegex = /^arn:[a-zA-Z\-]+:sts::\d{12}:federated-user\/.*$/;
54
46
  /**
55
47
  * Test if a principal string is a federated user ARN
56
48
  *
57
49
  * @param principal the principal string to test
58
50
  * @returns true if the principal is a federated user ARN, false otherwise
59
51
  */
60
- export function isFederatedUserArn(principal: string): boolean {
61
- return federatedUserArnRegex.test(principal)
52
+ export function isFederatedUserArn(principal) {
53
+ return federatedUserArnRegex.test(principal);
62
54
  }
55
+ //# sourceMappingURL=principals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principals.js","sourceRoot":"","sources":["../../src/principals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAAC,cAAsB;IACnE,MAAM,QAAQ,GAAG,aAAa,CAAC,cAAc,CAAC,CAAA;IAC9C,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChF,OAAO,OAAO,QAAQ,CAAC,SAAS,SAAS,QAAQ,CAAC,SAAS,SAAS,eAAe,EAAE,CAAA;AACvF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,OAAe,EAAE,WAAmB;IACjF,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,CAAA;IAC7C,OAAO,OAAO,QAAQ,CAAC,SAAS,SAAS,QAAQ,CAAC,SAAS,iBAAiB,eAAe,IAAI,WAAW,EAAE,CAAA;AAC9G,CAAC;AAED,MAAM,mBAAmB,GAAG,gDAAgD,CAAA;AAE5E;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,YAAY,GAAG,wCAAwC,CAAA;AAE7D;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,qBAAqB,GAAG,kDAAkD,CAAA;AAEhF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AAC9C,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "@cloud-copilot/iam-utils",
3
- "version": "0.0.1",
4
- "description": "",
3
+ "version": "0.0.3",
4
+ "description": "Various utilities for working with AWS IAM information",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/esm/index.js",
8
+ "require": "./dist/cjs/index.js"
9
+ }
10
+ },
11
+ "files": [
12
+ "dist/**/*"
13
+ ],
14
+ "types": "dist/cjs/index.d.ts",
5
15
  "keywords": [
6
16
  "aws",
7
17
  "iam",
@@ -1,31 +0,0 @@
1
- name: GuardDog
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- workflow_dispatch:
8
-
9
- permissions:
10
- contents: read
11
-
12
- jobs:
13
- guarddog:
14
- permissions:
15
- contents: read
16
- name: Scan Dependencies and Source Code
17
- runs-on: ubuntu-latest
18
-
19
- steps:
20
- - uses: actions/checkout@v4
21
-
22
- - name: Set up Python
23
- uses: actions/setup-python@v5
24
- with:
25
- python-version: '3.10'
26
-
27
- - name: Install GuardDog
28
- run: pip install guarddog
29
-
30
- - run: guarddog npm scan src/ --exit-non-zero-on-finding
31
- - run: guarddog npm verify package.json --exclude-rules empty_information --exit-non-zero-on-finding
@@ -1,86 +0,0 @@
1
- name: 'Lint PR'
2
-
3
- on:
4
- pull_request_target:
5
- types:
6
- - opened
7
- - edited
8
- - synchronize
9
- - reopened
10
-
11
- permissions:
12
- contents: read
13
-
14
- jobs:
15
- main:
16
- name: Validate PR title
17
- runs-on: ubuntu-latest
18
- steps:
19
- - uses: amannn/action-semantic-pull-request@v5
20
- env:
21
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22
-
23
- lint:
24
- name: Code Formatting Check
25
- runs-on: ubuntu-latest
26
- steps:
27
- - name: Check out the repository
28
- uses: actions/checkout@v4
29
- with:
30
- ref: ${{ github.event.pull_request.head.sha }}
31
-
32
- - name: Set up Node
33
- uses: actions/setup-node@v4
34
- with:
35
- node-version: '22'
36
-
37
- - name: Install dependencies
38
- run: npm ci
39
-
40
- - name: Check Code Formatting
41
- run: npm run format-check
42
-
43
- test:
44
- name: Build and Test
45
- runs-on: ubuntu-latest
46
- steps:
47
- - name: Check out the repository
48
- uses: actions/checkout@v4
49
- with:
50
- ref: ${{ github.event.pull_request.head.sha }}
51
-
52
- - name: Set up Node
53
- uses: actions/setup-node@v4
54
- with:
55
- node-version: '22'
56
-
57
- - name: Install dependencies
58
- run: npm ci
59
-
60
- - name: Build
61
- run: npm run build
62
-
63
- - name: Check Tests
64
- run: npm test
65
-
66
- guarddog:
67
- permissions:
68
- contents: read
69
- name: GuardDog Check
70
- runs-on: ubuntu-latest
71
-
72
- steps:
73
- - name: Check out the repository
74
- uses: actions/checkout@v4
75
- with:
76
- ref: ${{ github.event.pull_request.head.sha }}
77
-
78
- - name: Set up Python
79
- uses: actions/setup-python@v5
80
- with:
81
- python-version: '3.10'
82
-
83
- - name: Install GuardDog
84
- run: pip install guarddog
85
-
86
- - run: guarddog npm scan src/ --exit-non-zero-on-finding
@@ -1,33 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- workflow_dispatch:
8
-
9
- jobs:
10
- release:
11
- runs-on: ubuntu-latest
12
- permissions:
13
- contents: write
14
- issues: write
15
- steps:
16
- - name: Check out
17
- uses: actions/checkout@v4
18
-
19
- - name: Set up Node
20
- uses: actions/setup-node@v4
21
- with:
22
- node-version: '22'
23
-
24
- - name: Run semantic-release
25
- env:
26
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
28
- run: |
29
- npm ci
30
- npm run format-check
31
- npm run build
32
- npm run test
33
- npx semantic-release
@@ -1,16 +0,0 @@
1
- name: Update Dependencies
2
-
3
- on:
4
- schedule:
5
- - cron: '0 12 * * 6' # Every Saturday at 12:00 PM UTC
6
- workflow_dispatch:
7
-
8
- jobs:
9
- update-dependencies:
10
- runs-on: ubuntu-latest
11
- permissions:
12
- contents: write # Push branches
13
- pull-requests: write # Create PRs
14
- steps:
15
- - name: Run dependency update
16
- uses: cloud-copilot/update-dependencies@main
package/postbuild.sh DELETED
@@ -1,11 +0,0 @@
1
- cat >dist/cjs/package.json <<!EOF
2
- {
3
- "type": "commonjs"
4
- }
5
- !EOF
6
-
7
- cat >dist/esm/package.json <<!EOF
8
- {
9
- "type": "module"
10
- }
11
- !EOF
package/src/arn.test.ts DELETED
@@ -1,126 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { ArnParts, splitArnParts } from './arn.js'
3
-
4
- const splitArnPartsTests: {
5
- name: string
6
- arn: string
7
- expected: ArnParts
8
- only?: boolean
9
- }[] = [
10
- {
11
- name: 'should split a full ARN',
12
- arn: 'arn:aws:iam::123456789012:user/Development/user1',
13
- expected: {
14
- partition: 'aws',
15
- service: 'iam',
16
- region: '',
17
- accountId: '123456789012',
18
- resource: 'user/Development/user1',
19
- resourceType: 'user',
20
- resourcePath: 'Development/user1'
21
- }
22
- },
23
- {
24
- name: 'should split an S3 bucket ARN',
25
- arn: 'arn:aws:s3:::my_corporate_bucket',
26
- expected: {
27
- partition: 'aws',
28
- service: 's3',
29
- region: '',
30
- accountId: '',
31
- resource: 'my_corporate_bucket',
32
- resourceType: '',
33
- resourcePath: 'my_corporate_bucket'
34
- }
35
- },
36
- {
37
- name: 'should split an S3 object ARN',
38
- arn: 'arn:aws:s3:::my_corporate_bucket/my_corporate_object.txt',
39
- expected: {
40
- partition: 'aws',
41
- service: 's3',
42
- region: '',
43
- accountId: '',
44
- resource: 'my_corporate_bucket/my_corporate_object.txt',
45
- resourceType: '',
46
- resourcePath: 'my_corporate_bucket/my_corporate_object.txt'
47
- }
48
- },
49
- {
50
- name: 'should split an SNS topic ARN',
51
- arn: 'arn:aws:sns:us-east-1:123456789012:MyTopic',
52
- expected: {
53
- partition: 'aws',
54
- service: 'sns',
55
- region: 'us-east-1',
56
- accountId: '123456789012',
57
- resource: 'MyTopic',
58
- resourceType: '',
59
- resourcePath: 'MyTopic'
60
- }
61
- },
62
- {
63
- name: 'should split an SQS queue ARN',
64
- arn: 'arn:aws:sqs:us-east-1:123456789012:MyQueue',
65
- expected: {
66
- partition: 'aws',
67
- service: 'sqs',
68
- region: 'us-east-1',
69
- accountId: '123456789012',
70
- resource: 'MyQueue',
71
- resourceType: '',
72
- resourcePath: 'MyQueue'
73
- }
74
- },
75
- {
76
- name: 'should split a Lambda function ARN',
77
- arn: 'arn:aws:lambda:us-west-2:123456789012:function:my-function',
78
- expected: {
79
- partition: 'aws',
80
- service: 'lambda',
81
- region: 'us-west-2',
82
- accountId: '123456789012',
83
- resource: 'function:my-function',
84
- resourceType: 'function',
85
- resourcePath: 'my-function'
86
- }
87
- },
88
- {
89
- name: 'rest api ARN',
90
- arn: 'arn:aws:apigateway:us-east-1::/restapis/1234567890',
91
- expected: {
92
- partition: 'aws',
93
- service: 'apigateway',
94
- region: 'us-east-1',
95
- accountId: '',
96
- resource: '/restapis/1234567890',
97
- resourceType: 'restapis',
98
- resourcePath: '1234567890'
99
- }
100
- },
101
- {
102
- name: 'should split a glue root catalog ARN',
103
- arn: 'arn:aws:glue:us-east-1:111111111111:catalog',
104
- expected: {
105
- partition: 'aws',
106
- service: 'glue',
107
- region: 'us-east-1',
108
- accountId: '111111111111',
109
- resource: 'catalog',
110
- resourceType: 'catalog',
111
- resourcePath: ''
112
- }
113
- }
114
- ]
115
-
116
- describe('splitArnParts', () => {
117
- for (const test of splitArnPartsTests) {
118
- const { name, arn, expected, only } = test
119
- const testFn = only ? it.only : it
120
-
121
- testFn(name, () => {
122
- const result = splitArnParts(arn)
123
- expect(result).toEqual(expected)
124
- })
125
- }
126
- })
package/src/arn.ts DELETED
@@ -1,81 +0,0 @@
1
- export interface ArnParts {
2
- partition: string | undefined
3
- service: string | undefined
4
- region: string | undefined
5
- accountId: string | undefined
6
- resource: string | undefined
7
- resourceType: string | undefined
8
- resourcePath: string | undefined
9
- }
10
-
11
- /**
12
- * Split an ARN into its parts
13
- *
14
- * @param arn the arn to split
15
- * @returns the parts of the ARN
16
- */
17
- export function splitArnParts(arn: string): ArnParts {
18
- const parts = arn.split(':')
19
- const partition = parts.at(1)
20
- const service = parts.at(2)!
21
- const region = parts.at(3)!
22
- const accountId = parts.at(4)!
23
- const resource = parts.slice(5).join(':')
24
- const [resourceType, resourcePath] = getResourceSegments(service, accountId, region, resource)
25
-
26
- return {
27
- partition,
28
- service,
29
- region,
30
- accountId,
31
- resource,
32
- resourceType,
33
- resourcePath
34
- }
35
- }
36
-
37
- /**
38
- * Get the product/id segments of the resource portion of an ARN.
39
- * The first segment is the product segment and the second segment is the resource id segment.
40
- * This could be split by a colon or a slash, so it checks for both. It also checks for S3 buckets/objects.
41
- *
42
- * @param resource The resource to get the resource segments. Must be an ARN resource.
43
- * @returns a tuple with the first segment being the product segment (without the separator) and the second segment being the resource id.
44
- */
45
- export function getResourceSegments(
46
- service: string,
47
- accountId: string,
48
- region: string,
49
- resourceString: string
50
- ): [string, string] {
51
- // This is terrible, and I hate it
52
- if (
53
- (service === 's3' && accountId === '' && region === '') ||
54
- service === 'sns' ||
55
- service === 'sqs'
56
- ) {
57
- return ['', resourceString]
58
- }
59
-
60
- if (resourceString.startsWith('/')) {
61
- resourceString = resourceString.slice(1)
62
- }
63
-
64
- const slashIndex = resourceString.indexOf('/')
65
- const colonIndex = resourceString.indexOf(':')
66
-
67
- let splitIndex = slashIndex
68
- if (slashIndex != -1 && colonIndex != -1) {
69
- splitIndex = Math.min(slashIndex, colonIndex) + 1
70
- } else if (slashIndex == -1 && colonIndex == -1) {
71
- splitIndex = resourceString.length + 1
72
- } else if (colonIndex == -1) {
73
- splitIndex = slashIndex + 1
74
- } else if (slashIndex == -1) {
75
- splitIndex = colonIndex + 1
76
- } else {
77
- throw new Error(`Unable to split resource ${resourceString}`)
78
- }
79
-
80
- return [resourceString.slice(0, splitIndex - 1), resourceString.slice(splitIndex)]
81
- }
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- export { getResourceSegments, splitArnParts, type ArnParts } from './arn'
2
- export {
3
- convertAssumedRoleArnToRoleArn,
4
- convertRoleArnToAssumedRoleArn,
5
- isAssumedRoleArn,
6
- isIamUserArn
7
- } from './principals'
@@ -1,183 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import {
3
- convertAssumedRoleArnToRoleArn,
4
- convertRoleArnToAssumedRoleArn,
5
- isAssumedRoleArn,
6
- isFederatedUserArn,
7
- isIamUserArn
8
- } from './principals.js'
9
-
10
- describe('convertAssumedRoleArnToRoleArn', () => {
11
- it('should return the role ARN from an assumed role ARN', () => {
12
- //Given an assumed role ARN
13
- const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/role-name/session-name'
14
-
15
- //When we get the role ARN from the assumed role ARN
16
- const result = convertAssumedRoleArnToRoleArn(assumedRoleArn)
17
-
18
- //Then it should return the role ARN
19
- expect(result).toBe('arn:aws:iam::123456789012:role/role-name')
20
- })
21
-
22
- it('should return the role ARN from an assumed role ARN with a path', () => {
23
- //Given an assumed role ARN
24
- const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/admin/global-admin/session-name'
25
-
26
- //When we get the role ARN from the assumed role ARN
27
- const result = convertAssumedRoleArnToRoleArn(assumedRoleArn)
28
-
29
- //Then it should return the role ARN
30
- expect(result).toBe('arn:aws:iam::123456789012:role/admin/global-admin')
31
- })
32
-
33
- it('should work in a different partition', () => {
34
- //Given an assumed role ARN with a different partition
35
- const assumedRoleArn = 'arn:aws-cn:sts::123456789012:assumed-role/role-name/session-name'
36
-
37
- //When we get the role ARN from the assumed role ARN
38
- const result = convertAssumedRoleArnToRoleArn(assumedRoleArn)
39
-
40
- //Then it should return the role ARN
41
- expect(result).toBe('arn:aws-cn:iam::123456789012:role/role-name')
42
- })
43
- })
44
-
45
- describe('convertRoleArnToAssumedRoleArn', () => {
46
- it('should return the assumed role ARN from a role ARN', () => {
47
- //Given a role ARN
48
- const roleArn = 'arn:aws:iam::123456789012:role/role-name'
49
-
50
- //When we get the assumed role ARN from the role ARN
51
- const result = convertRoleArnToAssumedRoleArn(roleArn, 'session-name')
52
-
53
- //Then it should return the assumed role ARN
54
- expect(result).toBe('arn:aws:sts::123456789012:assumed-role/role-name/session-name')
55
- })
56
-
57
- it('should return the assumed role ARN from a role ARN with a path', () => {
58
- //Given a role ARN
59
- const roleArn = 'arn:aws:iam::123456789012:role/admin/global-admin'
60
-
61
- //When we get the assumed role ARN from the role ARN
62
- const result = convertRoleArnToAssumedRoleArn(roleArn, 'session-name')
63
-
64
- //Then it should return the assumed role ARN
65
- expect(result).toBe('arn:aws:sts::123456789012:assumed-role/admin/global-admin/session-name')
66
- })
67
-
68
- it('should work with a different partition', () => {
69
- //Given a role ARN with a different partition
70
- const roleArn = 'arn:aws-cn:iam::123456789012:role/role-name'
71
-
72
- //When we get the assumed role ARN from the role ARN
73
- const result = convertRoleArnToAssumedRoleArn(roleArn, 'session-name')
74
-
75
- //Then it should return the assumed role ARN
76
- expect(result).toBe('arn:aws-cn:sts::123456789012:assumed-role/role-name/session-name')
77
- })
78
- })
79
-
80
- describe('isAssumedRoleArn', () => {
81
- it('should return true for assumed role ARN', () => {
82
- //Given an assumed role ARN
83
- const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/role-name/session-name'
84
-
85
- //When we check if it is an assumed role ARN
86
- const result = isAssumedRoleArn(assumedRoleArn)
87
-
88
- //Then it should return true
89
- expect(result).toBe(true)
90
- })
91
-
92
- it('should return false for non-assumed role ARN', () => {
93
- //Given a non-assumed role ARN
94
- const userArn = 'arn:aws:iam::123456789012:user/user-name'
95
-
96
- //When we check if it is an assumed role ARN
97
- const result = isAssumedRoleArn(userArn)
98
-
99
- //Then it should return false
100
- expect(result).toBe(false)
101
- })
102
-
103
- it('should work for a different partition', () => {
104
- //Given an assumed role ARN with a different partition
105
- const assumedRoleArn = 'arn:aws-cn:sts::123456789012:assumed-role/role-name/session-name'
106
-
107
- //When we check if it is an assumed role ARN
108
- const result = isAssumedRoleArn(assumedRoleArn)
109
-
110
- //Then it should return true
111
- expect(result).toBe(true)
112
- })
113
- })
114
-
115
- describe('isIamUserArn', () => {
116
- it('should return true for IAM user ARN', () => {
117
- //Given an IAM user ARN
118
- const userArn = 'arn:aws:iam::123456789012:user/user-name'
119
-
120
- //When we check if it is an IAM user ARN
121
- const result = isIamUserArn(userArn)
122
-
123
- //Then it should return true
124
- expect(result).toBe(true)
125
- })
126
-
127
- it('should return false for non-IAM user ARN', () => {
128
- //Given a non-IAM user ARN
129
- const roleArn = 'arn:aws:sts::123456789012:assumed-role/role-name/session-name'
130
-
131
- //When we check if it is an IAM user ARN
132
- const result = isIamUserArn(roleArn)
133
-
134
- //Then it should return false
135
- expect(result).toBe(false)
136
- })
137
-
138
- it('should work for a different partition', () => {
139
- //Given an IAM user ARN with a different partition
140
- const userArn = 'arn:aws-cn:iam::123456789012:user/user-name'
141
-
142
- //When we check if it is an IAM user ARN
143
- const result = isIamUserArn(userArn)
144
-
145
- //Then it should return true
146
- expect(result).toBe(true)
147
- })
148
- })
149
-
150
- describe('isFederatedUserArn', () => {
151
- it('should return true for federated user ARN', () => {
152
- //Given a federated user ARN
153
- const federatedUserArn = 'arn:aws:sts::123456789012:federated-user/user-name'
154
-
155
- //When we check if it is a federated user ARN
156
- const result = isFederatedUserArn(federatedUserArn)
157
-
158
- //Then it should return true
159
- expect(result).toBe(true)
160
- })
161
-
162
- it('should return false for non-federated user ARN', () => {
163
- //Given a non-federated user ARN
164
- const roleArn = 'arn:aws:sts::123456789012:assumed-role/role-name/session-name'
165
-
166
- //When we check if it is a federated user ARN
167
- const result = isFederatedUserArn(roleArn)
168
-
169
- //Then it should return false
170
- expect(result).toBe(false)
171
- })
172
-
173
- it('should work for a different partition', () => {
174
- //Given a federated user ARN with a different partition
175
- const federatedUserArn = 'arn:aws-cn:sts::123456789012:federated-user/user-name'
176
-
177
- //When we check if it is a federated user ARN
178
- const result = isFederatedUserArn(federatedUserArn)
179
-
180
- //Then it should return true
181
- expect(result).toBe(true)
182
- })
183
- })
package/tsconfig.cjs.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
-
4
- "include": ["src/**/*"],
5
- "exclude": ["**/*.test.ts"],
6
-
7
- "compilerOptions": {
8
- "rootDir": "src",
9
- "outDir": "dist/cjs",
10
- }
11
- }
package/tsconfig.esm.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
-
4
- "include": ["src/**/*"],
5
- "exclude": ["**/*.test.ts"],
6
-
7
- "compilerOptions": {
8
- "target": "ES2020",
9
- "module": "ES2020",
10
- "moduleResolution": "node",
11
- "rootDir": "src",
12
- "outDir": "dist/esm"
13
- }
14
- }
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "commonjs",
4
- "target": "es2022",
5
- "outDir": "dist",
6
- "rootDir": "src",
7
- "sourceMap": true,
8
- "strict": true,
9
- "declaration": true,
10
- "declarationMap": true,
11
- "lib": ["es2023", "DOM"],
12
- "noUnusedLocals": false,
13
- "noUnusedParameters": false,
14
- "noImplicitReturns": true,
15
- "noFallthroughCasesInSwitch": false,
16
- "experimentalDecorators": true,
17
- "emitDecoratorMetadata": true,
18
- "esModuleInterop": false,
19
- "forceConsistentCasingInFileNames": true,
20
- "paths": {
21
- // workaround for: https://github.com/vitest-dev/vitest/issues/4567
22
- "rollup/parseAst": ["./node_modules/rollup/dist/parseAst"]
23
- }
24
- },
25
- "exclude": ["tests", "test", "dist", "bin", "**/bin", "**/dist", "node_modules", "cdk.out"],
26
- }