@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 +91 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/managed-policy-builder.d.ts +60 -0
- package/dist/managed-policy-builder.d.ts.map +1 -0
- package/dist/managed-policy-builder.js +37 -0
- package/dist/managed-policy-builder.js.map +1 -0
- package/dist/role-builder.d.ts +110 -0
- package/dist/role-builder.d.ts.map +1 -0
- package/dist/role-builder.js +70 -0
- package/dist/role-builder.js.map +1 -0
- package/dist/role-defaults.d.ts +8 -0
- package/dist/role-defaults.d.ts.map +1 -0
- package/dist/role-defaults.js +21 -0
- package/dist/role-defaults.js.map +1 -0
- package/dist/service-role-builder.d.ts +24 -0
- package/dist/service-role-builder.d.ts.map +1 -0
- package/dist/service-role-builder.js +27 -0
- package/dist/service-role-builder.js.map +1 -0
- package/dist/statement-builder.d.ts +92 -0
- package/dist/statement-builder.d.ts.map +1 -0
- package/dist/statement-builder.js +152 -0
- package/dist/statement-builder.js.map +1 -0
- package/package.json +49 -0
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(...))`.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|