@composurecdk/iam 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/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @composurecdk/iam
2
+
3
+ IAM role, customer-managed policy, and policy-statement builders for [ComposureCDK](../../README.md).
4
+
5
+ This package provides fluent builders for the most commonly configured IAM resources and centralises least-privilege guardrails so that consuming packages (Lambda, Budgets, SNS topic policies, …) do not have to reinvent them.
6
+
7
+ ## Role Builder
8
+
9
+ ```ts
10
+ import { createRoleBuilder, createStatementBuilder } from "@composurecdk/iam";
11
+ import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
12
+
13
+ const role = createRoleBuilder()
14
+ .assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
15
+ .description("Execution role for the budget remediation Lambda")
16
+ .addInlinePolicyStatements("StopEC2", [
17
+ createStatementBuilder()
18
+ .allow()
19
+ .actions(["ec2:StopInstances", "ec2:DescribeInstances"])
20
+ .resources(["*"])
21
+ .allowWildcardResources(true),
22
+ ])
23
+ .build(stack, "StopEC2Role");
24
+ ```
25
+
26
+ Every [RoleProps](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam.RoleProps.html) property is available as a fluent setter. `permissionsBoundary` additionally accepts a `Resolvable<IManagedPolicy>` so a sibling component can supply a boundary policy via `ref(...)`.
27
+
28
+ ### Defaults
29
+
30
+ | Property | Default | Rationale |
31
+ | -------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
32
+ | `maxSessionDuration` | `Duration.hours(1)` | Short-lived credentials reduce the blast radius of leaked sessions. See AWS Well-Architected Security pillar. |
33
+
34
+ Exported as `ROLE_DEFAULTS`.
35
+
36
+ ### Result
37
+
38
+ ```ts
39
+ interface RoleBuilderResult {
40
+ role: Role;
41
+ inlinePolicies: Record<string, PolicyDocument>; // keyed by the name passed to addInlinePolicyStatements
42
+ }
43
+ ```
44
+
45
+ Inline policies are embedded in the underlying `AWS::IAM::Role` resource via its native `Policies` array — no separate `AWS::IAM::Policy` resources are created.
46
+
47
+ ## Managed Policy Builder
48
+
49
+ ```ts
50
+ import { createManagedPolicyBuilder } from "@composurecdk/iam";
51
+
52
+ const boundary = createManagedPolicyBuilder()
53
+ .managedPolicyName("ops-boundary")
54
+ .addStatements([
55
+ createStatementBuilder()
56
+ .allow()
57
+ .actions(["s3:GetObject"])
58
+ .resources(["arn:aws:s3:::my-bucket/*"]),
59
+ ])
60
+ .build(stack, "OpsBoundary");
61
+ ```
62
+
63
+ ## Statement Builder
64
+
65
+ `createStatementBuilder()` is a fluent wrapper around the CDK [PolicyStatement](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam.PolicyStatement.html). Unlike the other builders in this package it is **not** a `Lifecycle` — its `build()` method returns a `PolicyStatement` synchronously.
66
+
67
+ ### Wildcard guard
68
+
69
+ By default, `Allow` statements with `resources: ["*"]` fail with `WildcardResourceError`. Opt in explicitly with `.allowWildcardResources(true)` when an action genuinely requires unrestricted scope (such as `ec2:DescribeInstances`, which does not support resource-level permissions).
70
+
71
+ ```ts
72
+ createStatementBuilder()
73
+ .allow()
74
+ .actions(["ec2:DescribeInstances"])
75
+ .resources(["*"])
76
+ .allowWildcardResources(true);
77
+ ```
78
+
79
+ ## Service Role Helper
80
+
81
+ ```ts
82
+ import { createServiceRoleBuilder } from "@composurecdk/iam";
83
+
84
+ const lambdaRole = createServiceRoleBuilder("lambda.amazonaws.com")
85
+ .description("Execution role for StopEC2 Lambda")
86
+ .addInlinePolicyStatements("StopEC2", [
87
+ /* statements */
88
+ ]);
89
+ ```
90
+
91
+ Thin sugar over `createRoleBuilder().assumedBy(new ServicePrincipal(...))`.
@@ -0,0 +1,6 @@
1
+ export { createRoleBuilder, type IRoleBuilder, type RoleBuilderResult } from "./role-builder.js";
2
+ export { ROLE_DEFAULTS } from "./role-defaults.js";
3
+ export { createManagedPolicyBuilder, type IManagedPolicyBuilder, type ManagedPolicyBuilderResult, } from "./managed-policy-builder.js";
4
+ export { createServiceRoleBuilder } from "./service-role-builder.js";
5
+ export { createStatementBuilder, StatementBuilder, WildcardResourceError, } from "./statement-builder.js";
6
+ //# 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,iBAAiB,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,0BAA0B,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { createRoleBuilder } from "./role-builder.js";
2
+ export { ROLE_DEFAULTS } from "./role-defaults.js";
3
+ export { createManagedPolicyBuilder, } from "./managed-policy-builder.js";
4
+ export { createServiceRoleBuilder } from "./service-role-builder.js";
5
+ export { createStatementBuilder, StatementBuilder, WildcardResourceError, } from "./statement-builder.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA6C,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,0BAA0B,GAG3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { ManagedPolicy, type ManagedPolicyProps, PolicyStatement } from "aws-cdk-lib/aws-iam";
2
+ import type { IConstruct } from "constructs";
3
+ import { type IBuilder, type Lifecycle } from "@composurecdk/core";
4
+ import { StatementBuilder } from "./statement-builder.js";
5
+ /**
6
+ * Configuration properties for the customer-managed IAM policy builder.
7
+ *
8
+ * Extends the CDK {@link ManagedPolicyProps} unchanged — the builder adds
9
+ * an {@link IManagedPolicyBuilder.addStatements | addStatements} method that
10
+ * accepts either {@link PolicyStatement} or {@link StatementBuilder}.
11
+ */
12
+ export type ManagedPolicyBuilderProps = ManagedPolicyProps;
13
+ /**
14
+ * The build output of an {@link IManagedPolicyBuilder}.
15
+ */
16
+ export interface ManagedPolicyBuilderResult {
17
+ /** The customer-managed policy created by the builder. */
18
+ policy: ManagedPolicy;
19
+ }
20
+ /**
21
+ * A fluent builder for configuring and creating an AWS IAM
22
+ * customer-managed policy.
23
+ *
24
+ * @see https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam.ManagedPolicy.html
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const boundary = createManagedPolicyBuilder()
29
+ * .managedPolicyName("ops-boundary")
30
+ * .addStatements([
31
+ * createStatementBuilder()
32
+ * .allow()
33
+ * .actions(["s3:GetObject"])
34
+ * .resources(["arn:aws:s3:::my-bucket/*"]),
35
+ * ]);
36
+ * ```
37
+ */
38
+ export type IManagedPolicyBuilder = IBuilder<ManagedPolicyBuilderProps, ManagedPolicyBuilder>;
39
+ declare class ManagedPolicyBuilder implements Lifecycle<ManagedPolicyBuilderResult> {
40
+ props: Partial<ManagedPolicyBuilderProps>;
41
+ private readonly _extraStatements;
42
+ /**
43
+ * Append policy statements to the managed policy.
44
+ *
45
+ * Accepts either {@link PolicyStatement} or {@link StatementBuilder}.
46
+ * Statement builders are resolved during {@link build} so wildcard-resource
47
+ * validation runs at the composition boundary.
48
+ */
49
+ addStatements(statements: (PolicyStatement | StatementBuilder)[]): this;
50
+ build(scope: IConstruct, id: string): ManagedPolicyBuilderResult;
51
+ }
52
+ /**
53
+ * Creates a new {@link IManagedPolicyBuilder} for configuring an AWS IAM
54
+ * customer-managed policy.
55
+ *
56
+ * @returns A fluent builder for a customer-managed policy.
57
+ */
58
+ export declare function createManagedPolicyBuilder(): IManagedPolicyBuilder;
59
+ export {};
60
+ //# sourceMappingURL=managed-policy-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-policy-builder.d.ts","sourceRoot":"","sources":["../src/managed-policy-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAW,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,0DAA0D;IAC1D,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,yBAAyB,EAAE,oBAAoB,CAAC,CAAC;AAE9F,cAAM,oBAAqB,YAAW,SAAS,CAAC,0BAA0B,CAAC;IACzE,KAAK,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAM;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA8C;IAE/E;;;;;;OAMG;IACH,aAAa,CAAC,UAAU,EAAE,CAAC,eAAe,GAAG,gBAAgB,CAAC,EAAE,GAAG,IAAI;IAKvE,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,0BAA0B;CAajE;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,qBAAqB,CAElE"}
@@ -0,0 +1,37 @@
1
+ import { ManagedPolicy } from "aws-cdk-lib/aws-iam";
2
+ import { Builder } from "@composurecdk/core";
3
+ import { StatementBuilder } from "./statement-builder.js";
4
+ class ManagedPolicyBuilder {
5
+ props = {};
6
+ _extraStatements = [];
7
+ /**
8
+ * Append policy statements to the managed policy.
9
+ *
10
+ * Accepts either {@link PolicyStatement} or {@link StatementBuilder}.
11
+ * Statement builders are resolved during {@link build} so wildcard-resource
12
+ * validation runs at the composition boundary.
13
+ */
14
+ addStatements(statements) {
15
+ this._extraStatements.push(...statements);
16
+ return this;
17
+ }
18
+ build(scope, id) {
19
+ const resolvedExtras = this._extraStatements.map((s) => s instanceof StatementBuilder ? s.build() : s);
20
+ const mergedProps = {
21
+ ...this.props,
22
+ statements: [...(this.props.statements ?? []), ...resolvedExtras],
23
+ };
24
+ const policy = new ManagedPolicy(scope, id, mergedProps);
25
+ return { policy };
26
+ }
27
+ }
28
+ /**
29
+ * Creates a new {@link IManagedPolicyBuilder} for configuring an AWS IAM
30
+ * customer-managed policy.
31
+ *
32
+ * @returns A fluent builder for a customer-managed policy.
33
+ */
34
+ export function createManagedPolicyBuilder() {
35
+ return Builder(ManagedPolicyBuilder);
36
+ }
37
+ //# sourceMappingURL=managed-policy-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-policy-builder.js","sourceRoot":"","sources":["../src/managed-policy-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA4C,MAAM,qBAAqB,CAAC;AAE9F,OAAO,EAAE,OAAO,EAAiC,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAuC1D,MAAM,oBAAoB;IACxB,KAAK,GAAuC,EAAE,CAAC;IAC9B,gBAAgB,GAA2C,EAAE,CAAC;IAE/E;;;;;;OAMG;IACH,aAAa,CAAC,UAAkD;QAC9D,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,KAAiB,EAAE,EAAU;QACjC,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACrD,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAC9C,CAAC;QAEF,MAAM,WAAW,GAAuB;YACtC,GAAG,IAAI,CAAC,KAAK;YACb,UAAU,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,GAAG,cAAc,CAAC;SAClE,CAAC;QAEF,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,KAAK,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;QACzD,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B;IACxC,OAAO,OAAO,CAAkD,oBAAoB,CAAC,CAAC;AACxF,CAAC"}
@@ -0,0 +1,110 @@
1
+ import { type IManagedPolicy, PolicyDocument, PolicyStatement, Role, type RoleProps } from "aws-cdk-lib/aws-iam";
2
+ import type { IConstruct } from "constructs";
3
+ import { type IBuilder, type Lifecycle, type Resolvable } from "@composurecdk/core";
4
+ import { StatementBuilder } from "./statement-builder.js";
5
+ /**
6
+ * Configuration properties for the IAM role builder.
7
+ *
8
+ * Extends the CDK {@link RoleProps} with builder-specific options for
9
+ * cross-component wiring: `permissionsBoundary` accepts a {@link Resolvable}
10
+ * so boundary policies built by sibling components can be referenced at
11
+ * configuration time.
12
+ */
13
+ interface RoleBuilderProps extends Omit<RoleProps, "permissionsBoundary"> {
14
+ /**
15
+ * A permissions boundary that caps the maximum permissions this role
16
+ * can ever grant, regardless of inline or managed policies attached.
17
+ *
18
+ * Accepts a concrete {@link IManagedPolicy} or a {@link Resolvable} for
19
+ * cross-component wiring (e.g. `ref("boundary", r => r.policy)`).
20
+ *
21
+ * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html
22
+ */
23
+ permissionsBoundary?: Resolvable<IManagedPolicy>;
24
+ }
25
+ /**
26
+ * The build output of an {@link IRoleBuilder}.
27
+ *
28
+ * Exposes every CDK construct the builder creates so consumers can reference,
29
+ * extend, or attach additional policies to them.
30
+ */
31
+ export interface RoleBuilderResult {
32
+ /** The IAM role construct created by the builder. */
33
+ role: Role;
34
+ /**
35
+ * Inline {@link PolicyDocument}s created for each
36
+ * {@link IRoleBuilder.addInlinePolicyStatements} call, keyed by the
37
+ * policy name supplied to the call.
38
+ *
39
+ * The documents are embedded in the underlying `AWS::IAM::Role`
40
+ * resource via the native `Policies` array — no separate
41
+ * `AWS::IAM::Policy` resources are created.
42
+ *
43
+ * Inline policies supplied directly via the native `inlinePolicies`
44
+ * prop on {@link RoleProps} do not appear in this map.
45
+ */
46
+ inlinePolicies: Record<string, PolicyDocument>;
47
+ }
48
+ /**
49
+ * A fluent builder for configuring and creating an AWS IAM role.
50
+ *
51
+ * Each configuration property from the CDK {@link RoleProps} is exposed as
52
+ * an overloaded method: call with a value to set it, or with no arguments
53
+ * to read the current value.
54
+ *
55
+ * The builder implements {@link Lifecycle}, so it can be used directly as a
56
+ * component in a {@link compose | composed system}. When built it creates
57
+ * an IAM role with well-architected defaults ({@link ROLE_DEFAULTS}) and
58
+ * returns a {@link RoleBuilderResult}.
59
+ *
60
+ * @see https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam.Role.html
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const role = createRoleBuilder()
65
+ * .assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
66
+ * .description("Execution role for the budget remediation Lambda")
67
+ * .addInlinePolicyStatements("StopEC2", [
68
+ * createStatementBuilder()
69
+ * .allow()
70
+ * .actions(["ec2:StopInstances", "ec2:DescribeInstances"])
71
+ * .resources(["*"])
72
+ * .allowWildcardResources(true)
73
+ * .build(),
74
+ * ]);
75
+ * ```
76
+ */
77
+ export type IRoleBuilder = IBuilder<RoleBuilderProps, RoleBuilder>;
78
+ declare class RoleBuilder implements Lifecycle<RoleBuilderResult> {
79
+ props: Partial<RoleBuilderProps>;
80
+ private readonly _inlinePolicies;
81
+ /**
82
+ * Append an inline policy to the role, embedded in the underlying
83
+ * `AWS::IAM::Role` resource's `Policies` array. The policy name becomes
84
+ * the key under which the resulting {@link PolicyDocument} appears in
85
+ * {@link RoleBuilderResult.inlinePolicies}.
86
+ *
87
+ * Accepts either {@link PolicyStatement} instances or
88
+ * {@link StatementBuilder}s (which are built lazily during {@link build}
89
+ * so that wildcard-resource validation runs at the composition boundary
90
+ * rather than at configuration time).
91
+ */
92
+ addInlinePolicyStatements(name: string, statements: (PolicyStatement | StatementBuilder)[]): this;
93
+ build(scope: IConstruct, id: string, context?: Record<string, object>): RoleBuilderResult;
94
+ }
95
+ /**
96
+ * Creates a new {@link IRoleBuilder} for configuring an AWS IAM role.
97
+ *
98
+ * @returns A fluent builder for an AWS IAM role.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const role = createRoleBuilder()
103
+ * .assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
104
+ * .description("Lambda execution role")
105
+ * .build(stack, "LambdaRole");
106
+ * ```
107
+ */
108
+ export declare function createRoleBuilder(): IRoleBuilder;
109
+ export {};
110
+ //# sourceMappingURL=role-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-builder.d.ts","sourceRoot":"","sources":["../src/role-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,cAAc,EACd,eAAe,EACf,IAAI,EACJ,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EAEd,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;GAOG;AACH,UAAU,gBAAiB,SAAQ,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC;IACvE;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC;CAClD;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,EAAE,IAAI,CAAC;IAEX;;;;;;;;;;;OAWG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;AAOnE,cAAM,WAAY,YAAW,SAAS,CAAC,iBAAiB,CAAC;IACvD,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAM;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2B;IAE3D;;;;;;;;;;OAUG;IACH,yBAAyB,CACvB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,CAAC,eAAe,GAAG,gBAAgB,CAAC,EAAE,GACjD,IAAI;IAKP,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,iBAAiB;CA8C9F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,YAAY,CAEhD"}
@@ -0,0 +1,70 @@
1
+ import { PolicyDocument, Role, } from "aws-cdk-lib/aws-iam";
2
+ import { Builder, resolve, } from "@composurecdk/core";
3
+ import { ROLE_DEFAULTS } from "./role-defaults.js";
4
+ import { StatementBuilder } from "./statement-builder.js";
5
+ class RoleBuilder {
6
+ props = {};
7
+ _inlinePolicies = [];
8
+ /**
9
+ * Append an inline policy to the role, embedded in the underlying
10
+ * `AWS::IAM::Role` resource's `Policies` array. The policy name becomes
11
+ * the key under which the resulting {@link PolicyDocument} appears in
12
+ * {@link RoleBuilderResult.inlinePolicies}.
13
+ *
14
+ * Accepts either {@link PolicyStatement} instances or
15
+ * {@link StatementBuilder}s (which are built lazily during {@link build}
16
+ * so that wildcard-resource validation runs at the composition boundary
17
+ * rather than at configuration time).
18
+ */
19
+ addInlinePolicyStatements(name, statements) {
20
+ this._inlinePolicies.push({ name, statements });
21
+ return this;
22
+ }
23
+ build(scope, id, context = {}) {
24
+ const { permissionsBoundary, assumedBy, inlinePolicies: propsInlinePolicies, ...rest } = this.props;
25
+ if (!assumedBy) {
26
+ throw new Error(`RoleBuilder "${id}": assumedBy(...) must be called before build(). ` +
27
+ `An IAM role requires a trust policy principal.`);
28
+ }
29
+ const resolvedBoundary = permissionsBoundary
30
+ ? resolve(permissionsBoundary, context)
31
+ : undefined;
32
+ const addedInlinePolicies = {};
33
+ for (const entry of this._inlinePolicies) {
34
+ const resolvedStatements = entry.statements.map((s) => s instanceof StatementBuilder ? s.build() : s);
35
+ addedInlinePolicies[entry.name] = new PolicyDocument({ statements: resolvedStatements });
36
+ }
37
+ const mergedInlinePolicies = {
38
+ ...(propsInlinePolicies ?? {}),
39
+ ...addedInlinePolicies,
40
+ };
41
+ const mergedProps = {
42
+ ...ROLE_DEFAULTS,
43
+ ...rest,
44
+ assumedBy,
45
+ ...(Object.keys(mergedInlinePolicies).length > 0
46
+ ? { inlinePolicies: mergedInlinePolicies }
47
+ : {}),
48
+ ...(resolvedBoundary ? { permissionsBoundary: resolvedBoundary } : {}),
49
+ };
50
+ const role = new Role(scope, id, mergedProps);
51
+ return { role, inlinePolicies: addedInlinePolicies };
52
+ }
53
+ }
54
+ /**
55
+ * Creates a new {@link IRoleBuilder} for configuring an AWS IAM role.
56
+ *
57
+ * @returns A fluent builder for an AWS IAM role.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const role = createRoleBuilder()
62
+ * .assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
63
+ * .description("Lambda execution role")
64
+ * .build(stack, "LambdaRole");
65
+ * ```
66
+ */
67
+ export function createRoleBuilder() {
68
+ return Builder(RoleBuilder);
69
+ }
70
+ //# sourceMappingURL=role-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-builder.js","sourceRoot":"","sources":["../src/role-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EAEd,IAAI,GAEL,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,OAAO,EAGP,OAAO,GAER,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAoF1D,MAAM,WAAW;IACf,KAAK,GAA8B,EAAE,CAAC;IACrB,eAAe,GAAwB,EAAE,CAAC;IAE3D;;;;;;;;;;OAUG;IACH,yBAAyB,CACvB,IAAY,EACZ,UAAkD;QAElD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,KAAiB,EAAE,EAAU,EAAE,UAAkC,EAAE;QACvE,MAAM,EACJ,mBAAmB,EACnB,SAAS,EACT,cAAc,EAAE,mBAAmB,EACnC,GAAG,IAAI,EACR,GAAG,IAAI,CAAC,KAAK,CAAC;QAEf,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,gBAAgB,EAAE,mDAAmD;gBACnE,gDAAgD,CACnD,CAAC;QACJ,CAAC;QAED,MAAM,gBAAgB,GAAG,mBAAmB;YAC1C,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC;YACvC,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,mBAAmB,GAAmC,EAAE,CAAC;QAC/D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzC,MAAM,kBAAkB,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACpD,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAC9C,CAAC;YACF,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,cAAc,CAAC,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,MAAM,oBAAoB,GAAmC;YAC3D,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC;YAC9B,GAAG,mBAAmB;SACvB,CAAC;QAEF,MAAM,WAAW,GAAc;YAC7B,GAAG,aAAa;YAChB,GAAG,IAAI;YACP,SAAS;YACT,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,MAAM,GAAG,CAAC;gBAC9C,CAAC,CAAC,EAAE,cAAc,EAAE,oBAAoB,EAAE;gBAC1C,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;QAE9C,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACvD,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAgC,WAAW,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { RoleProps } from "aws-cdk-lib/aws-iam";
2
+ /**
3
+ * Secure, AWS-recommended defaults applied to every IAM role built with
4
+ * {@link createRoleBuilder}. Each property can be individually overridden
5
+ * via the builder's fluent API.
6
+ */
7
+ export declare const ROLE_DEFAULTS: Partial<RoleProps>;
8
+ //# sourceMappingURL=role-defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-defaults.d.ts","sourceRoot":"","sources":["../src/role-defaults.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,OAAO,CAAC,SAAS,CAa5C,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ /**
3
+ * Secure, AWS-recommended defaults applied to every IAM role built with
4
+ * {@link createRoleBuilder}. Each property can be individually overridden
5
+ * via the builder's fluent API.
6
+ */
7
+ export const ROLE_DEFAULTS = {
8
+ /**
9
+ * Cap the session duration to one hour by default.
10
+ *
11
+ * Short-lived credentials reduce the blast radius of leaked or misused
12
+ * role sessions. Callers that genuinely need longer sessions (for
13
+ * example, long-running batch jobs that assume the role once) should
14
+ * override via {@link IRoleBuilder.maxSessionDuration}.
15
+ *
16
+ * @see https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/sec_permissions_define_guardrails.html
17
+ * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html
18
+ */
19
+ maxSessionDuration: Duration.hours(1),
20
+ };
21
+ //# sourceMappingURL=role-defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-defaults.js","sourceRoot":"","sources":["../src/role-defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C;;;;;;;;;;OAUG;IACH,kBAAkB,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;CACtC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type IRoleBuilder } from "./role-builder.js";
2
+ /**
3
+ * Creates a pre-configured {@link IRoleBuilder} whose trust policy allows
4
+ * the given AWS service principal to assume the role.
5
+ *
6
+ * Thin sugar over {@link createRoleBuilder} for the most common role shape:
7
+ * a service-assumable role (Lambda, EC2, Budgets, etc.) with no extra
8
+ * trust-policy conditions. Any property set by the caller afterwards
9
+ * (including `assumedBy`) still wins, because the underlying builder
10
+ * simply records the last value written.
11
+ *
12
+ * @param servicePrincipal - The service identifier, e.g.
13
+ * `"lambda.amazonaws.com"` or `"budgets.amazonaws.com"`.
14
+ * @returns A role builder with `assumedBy` preset to the given service.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const role = createServiceRoleBuilder("lambda.amazonaws.com")
19
+ * .description("Execution role for StopEC2 Lambda")
20
+ * .addInlinePolicyStatements("StopEC2", [ ... ]);
21
+ * ```
22
+ */
23
+ export declare function createServiceRoleBuilder(servicePrincipal: string): IRoleBuilder;
24
+ //# sourceMappingURL=service-role-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-role-builder.d.ts","sourceRoot":"","sources":["../src/service-role-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,YAAY,CAE/E"}
@@ -0,0 +1,27 @@
1
+ import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
2
+ import { createRoleBuilder } from "./role-builder.js";
3
+ /**
4
+ * Creates a pre-configured {@link IRoleBuilder} whose trust policy allows
5
+ * the given AWS service principal to assume the role.
6
+ *
7
+ * Thin sugar over {@link createRoleBuilder} for the most common role shape:
8
+ * a service-assumable role (Lambda, EC2, Budgets, etc.) with no extra
9
+ * trust-policy conditions. Any property set by the caller afterwards
10
+ * (including `assumedBy`) still wins, because the underlying builder
11
+ * simply records the last value written.
12
+ *
13
+ * @param servicePrincipal - The service identifier, e.g.
14
+ * `"lambda.amazonaws.com"` or `"budgets.amazonaws.com"`.
15
+ * @returns A role builder with `assumedBy` preset to the given service.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const role = createServiceRoleBuilder("lambda.amazonaws.com")
20
+ * .description("Execution role for StopEC2 Lambda")
21
+ * .addInlinePolicyStatements("StopEC2", [ ... ]);
22
+ * ```
23
+ */
24
+ export function createServiceRoleBuilder(servicePrincipal) {
25
+ return createRoleBuilder().assumedBy(new ServicePrincipal(servicePrincipal));
26
+ }
27
+ //# sourceMappingURL=service-role-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-role-builder.js","sourceRoot":"","sources":["../src/service-role-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAqB,MAAM,mBAAmB,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,wBAAwB,CAAC,gBAAwB;IAC/D,OAAO,iBAAiB,EAAE,CAAC,SAAS,CAAC,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { Effect, type IPrincipal, PolicyStatement } from "aws-cdk-lib/aws-iam";
2
+ /**
3
+ * Thrown when a {@link StatementBuilder} is built with an `Allow` effect and
4
+ * an unrestricted resource (`"*"`) without the caller having explicitly
5
+ * opted in via {@link StatementBuilder.allowWildcardResources}.
6
+ *
7
+ * Wildcard-resource allow statements grant the widest possible permission
8
+ * surface and should be an intentional choice, not an accident.
9
+ *
10
+ * @see https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/permissions-management.html
11
+ */
12
+ export declare class WildcardResourceError extends Error {
13
+ constructor(sid?: string);
14
+ }
15
+ /**
16
+ * Fluent wrapper around the CDK {@link PolicyStatement}.
17
+ *
18
+ * Unlike other ComposureCDK builders this one is **not** a
19
+ * {@link Lifecycle} — a policy statement is inline data attached to a Role,
20
+ * ManagedPolicy, or resource policy rather than a standalone CDK construct,
21
+ * so there is nothing to attach to a scope.
22
+ *
23
+ * The builder exists to:
24
+ * - centralise least-privilege validation (wildcard-resource guard,
25
+ * {@link WildcardResourceError}),
26
+ * - give every consumer (Role, ManagedPolicy, SNS TopicPolicy, future
27
+ * SQS/S3 bucket policies) one fluent API,
28
+ * - remain interchangeable with raw {@link PolicyStatement} instances via
29
+ * {@link StatementBuilder.build}.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const stmt = createStatementBuilder()
34
+ * .sid("StopDevInstances")
35
+ * .allow()
36
+ * .actions(["ec2:StopInstances", "ec2:DescribeInstances"])
37
+ * .resources(["*"])
38
+ * .allowWildcardResources(true)
39
+ * .build();
40
+ * ```
41
+ */
42
+ export declare class StatementBuilder {
43
+ private _sid?;
44
+ private _effect;
45
+ private _actions;
46
+ private _notActions;
47
+ private _resources;
48
+ private _notResources;
49
+ private _principals;
50
+ private _notPrincipals;
51
+ private _conditions?;
52
+ private _allowWildcardResources;
53
+ sid(sid: string): this;
54
+ allow(): this;
55
+ deny(): this;
56
+ effect(effect: Effect): this;
57
+ actions(actions: string[]): this;
58
+ notActions(actions: string[]): this;
59
+ resources(resources: string[]): this;
60
+ notResources(resources: string[]): this;
61
+ principals(principals: IPrincipal[]): this;
62
+ notPrincipals(principals: IPrincipal[]): this;
63
+ conditions(conditions: Record<string, Record<string, unknown>>): this;
64
+ /**
65
+ * Opt in to Effect=Allow statements with wildcard resources (`"*"`).
66
+ *
67
+ * The builder rejects wildcard resources by default to surface
68
+ * least-privilege violations; call this to acknowledge that the
69
+ * statement genuinely needs unrestricted scope (for example actions
70
+ * such as `ec2:DescribeInstances` that do not support resource-level
71
+ * permissions).
72
+ *
73
+ * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html
74
+ */
75
+ allowWildcardResources(allow?: boolean): this;
76
+ /**
77
+ * Construct and return a {@link PolicyStatement} from the configured state.
78
+ *
79
+ * @throws {WildcardResourceError} when the statement is an Allow with a
80
+ * wildcard resource and wildcard resources have not been opted in to.
81
+ */
82
+ build(): PolicyStatement;
83
+ }
84
+ /**
85
+ * Creates a new {@link StatementBuilder} for configuring an IAM
86
+ * {@link PolicyStatement} with least-privilege guardrails.
87
+ *
88
+ * @returns A fluent builder that produces a {@link PolicyStatement} when
89
+ * {@link StatementBuilder.build} is called.
90
+ */
91
+ export declare function createStatementBuilder(): StatementBuilder;
92
+ //# sourceMappingURL=statement-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statement-builder.d.ts","sourceRoot":"","sources":["../src/statement-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,KAAK,UAAU,EACf,eAAe,EAEhB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,GAAG,CAAC,EAAE,MAAM;CAOzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAS;IACtB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,WAAW,CAAC,CAA0C;IAC9D,OAAO,CAAC,uBAAuB,CAAS;IAExC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKtB,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI;IAKZ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAKhC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAKnC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAKpC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAKvC,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI;IAK1C,aAAa,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI;IAK7C,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAKrE;;;;;;;;;;OAUG;IACH,sBAAsB,CAAC,KAAK,UAAO,GAAG,IAAI;IAK1C;;;;;OAKG;IACH,KAAK,IAAI,eAAe;CAuBzB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAEzD"}
@@ -0,0 +1,152 @@
1
+ import { Effect, PolicyStatement, } from "aws-cdk-lib/aws-iam";
2
+ /**
3
+ * Thrown when a {@link StatementBuilder} is built with an `Allow` effect and
4
+ * an unrestricted resource (`"*"`) without the caller having explicitly
5
+ * opted in via {@link StatementBuilder.allowWildcardResources}.
6
+ *
7
+ * Wildcard-resource allow statements grant the widest possible permission
8
+ * surface and should be an intentional choice, not an accident.
9
+ *
10
+ * @see https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/permissions-management.html
11
+ */
12
+ export class WildcardResourceError extends Error {
13
+ constructor(sid) {
14
+ super(`PolicyStatement${sid ? ` "${sid}"` : ""} has Effect=Allow with a wildcard resource ("*"). ` +
15
+ `Scope the resources or call allowWildcardResources(true) to opt in explicitly.`);
16
+ this.name = "WildcardResourceError";
17
+ }
18
+ }
19
+ /**
20
+ * Fluent wrapper around the CDK {@link PolicyStatement}.
21
+ *
22
+ * Unlike other ComposureCDK builders this one is **not** a
23
+ * {@link Lifecycle} — a policy statement is inline data attached to a Role,
24
+ * ManagedPolicy, or resource policy rather than a standalone CDK construct,
25
+ * so there is nothing to attach to a scope.
26
+ *
27
+ * The builder exists to:
28
+ * - centralise least-privilege validation (wildcard-resource guard,
29
+ * {@link WildcardResourceError}),
30
+ * - give every consumer (Role, ManagedPolicy, SNS TopicPolicy, future
31
+ * SQS/S3 bucket policies) one fluent API,
32
+ * - remain interchangeable with raw {@link PolicyStatement} instances via
33
+ * {@link StatementBuilder.build}.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const stmt = createStatementBuilder()
38
+ * .sid("StopDevInstances")
39
+ * .allow()
40
+ * .actions(["ec2:StopInstances", "ec2:DescribeInstances"])
41
+ * .resources(["*"])
42
+ * .allowWildcardResources(true)
43
+ * .build();
44
+ * ```
45
+ */
46
+ export class StatementBuilder {
47
+ _sid;
48
+ _effect = Effect.ALLOW;
49
+ _actions = [];
50
+ _notActions = [];
51
+ _resources = [];
52
+ _notResources = [];
53
+ _principals = [];
54
+ _notPrincipals = [];
55
+ _conditions;
56
+ _allowWildcardResources = false;
57
+ sid(sid) {
58
+ this._sid = sid;
59
+ return this;
60
+ }
61
+ allow() {
62
+ this._effect = Effect.ALLOW;
63
+ return this;
64
+ }
65
+ deny() {
66
+ this._effect = Effect.DENY;
67
+ return this;
68
+ }
69
+ effect(effect) {
70
+ this._effect = effect;
71
+ return this;
72
+ }
73
+ actions(actions) {
74
+ this._actions = [...actions];
75
+ return this;
76
+ }
77
+ notActions(actions) {
78
+ this._notActions = [...actions];
79
+ return this;
80
+ }
81
+ resources(resources) {
82
+ this._resources = [...resources];
83
+ return this;
84
+ }
85
+ notResources(resources) {
86
+ this._notResources = [...resources];
87
+ return this;
88
+ }
89
+ principals(principals) {
90
+ this._principals = [...principals];
91
+ return this;
92
+ }
93
+ notPrincipals(principals) {
94
+ this._notPrincipals = [...principals];
95
+ return this;
96
+ }
97
+ conditions(conditions) {
98
+ this._conditions = { ...conditions };
99
+ return this;
100
+ }
101
+ /**
102
+ * Opt in to Effect=Allow statements with wildcard resources (`"*"`).
103
+ *
104
+ * The builder rejects wildcard resources by default to surface
105
+ * least-privilege violations; call this to acknowledge that the
106
+ * statement genuinely needs unrestricted scope (for example actions
107
+ * such as `ec2:DescribeInstances` that do not support resource-level
108
+ * permissions).
109
+ *
110
+ * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html
111
+ */
112
+ allowWildcardResources(allow = true) {
113
+ this._allowWildcardResources = allow;
114
+ return this;
115
+ }
116
+ /**
117
+ * Construct and return a {@link PolicyStatement} from the configured state.
118
+ *
119
+ * @throws {WildcardResourceError} when the statement is an Allow with a
120
+ * wildcard resource and wildcard resources have not been opted in to.
121
+ */
122
+ build() {
123
+ if (this._effect === Effect.ALLOW &&
124
+ !this._allowWildcardResources &&
125
+ this._resources.some((r) => r === "*")) {
126
+ throw new WildcardResourceError(this._sid);
127
+ }
128
+ const props = {
129
+ sid: this._sid,
130
+ effect: this._effect,
131
+ actions: this._actions.length > 0 ? this._actions : undefined,
132
+ notActions: this._notActions.length > 0 ? this._notActions : undefined,
133
+ resources: this._resources.length > 0 ? this._resources : undefined,
134
+ notResources: this._notResources.length > 0 ? this._notResources : undefined,
135
+ principals: this._principals.length > 0 ? this._principals : undefined,
136
+ notPrincipals: this._notPrincipals.length > 0 ? this._notPrincipals : undefined,
137
+ conditions: this._conditions,
138
+ };
139
+ return new PolicyStatement(props);
140
+ }
141
+ }
142
+ /**
143
+ * Creates a new {@link StatementBuilder} for configuring an IAM
144
+ * {@link PolicyStatement} with least-privilege guardrails.
145
+ *
146
+ * @returns A fluent builder that produces a {@link PolicyStatement} when
147
+ * {@link StatementBuilder.build} is called.
148
+ */
149
+ export function createStatementBuilder() {
150
+ return new StatementBuilder();
151
+ }
152
+ //# sourceMappingURL=statement-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statement-builder.js","sourceRoot":"","sources":["../src/statement-builder.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@composurecdk/iam",
3
+ "version": "0.3.0",
4
+ "description": "Composable IAM role, policy, and statement builders with well-architected defaults",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/laazyj/composureCDK",
8
+ "directory": "packages/iam"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "clean": "rm -rf dist",
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "vitest run --passWithNoTests",
28
+ "test:watch": "vitest"
29
+ },
30
+ "keywords": [],
31
+ "author": "Jason Duffett (https://github.com/laazyj)",
32
+ "license": "MIT",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "type": "module",
37
+ "peerDependencies": {
38
+ "@composurecdk/core": "^0.3.0",
39
+ "aws-cdk-lib": "^2.0.0",
40
+ "constructs": "^10.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^25.6.0",
44
+ "aws-cdk-lib": "^2.250.0",
45
+ "constructs": "^10.6.0",
46
+ "typescript": "^6.0.3",
47
+ "vitest": "^4.1.4"
48
+ }
49
+ }