1 | # AWS Identity and Access Management Construct Library
|
2 |
|
3 |
|
4 | ---
|
5 |
|
6 | ![End-of-Support](https://img.shields.io/badge/End--of--Support-critical.svg?style=for-the-badge)
|
7 |
|
8 | > AWS CDK v1 has reached End-of-Support on 2023-06-01.
|
9 | > This package is no longer being updated, and users should migrate to AWS CDK v2.
|
10 | >
|
11 | > For more information on how to migrate, see the [_Migrating to AWS CDK v2_ guide][doc].
|
12 | >
|
13 | > [doc]: https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html
|
14 |
|
15 | ---
|
16 |
|
17 |
|
18 |
|
19 | Define a role and add permissions to it. This will automatically create and
|
20 | attach an IAM policy to the role:
|
21 |
|
22 | [attaching permissions to role](test/example.role.lit.ts)
|
23 |
|
24 | Define a policy and attach it to groups, users and roles. Note that it is possible to attach
|
25 | the policy either by calling `xxx.attachInlinePolicy(policy)` or `policy.attachToXxx(xxx)`.
|
26 |
|
27 | [attaching policies to user and group](test/example.attaching.lit.ts)
|
28 |
|
29 | Managed policies can be attached using `xxx.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(policyName))`:
|
30 |
|
31 | [attaching managed policies](test/example.managedpolicy.lit.ts)
|
32 |
|
33 | ## Granting permissions to resources
|
34 |
|
35 | Many of the AWS CDK resources have `grant*` methods that allow you to grant other resources access to that resource. As an example, the following code gives a Lambda function write permissions (Put, Update, Delete) to a DynamoDB table.
|
36 |
|
37 | ```ts
|
38 | declare const fn: lambda.Function;
|
39 | declare const table: dynamodb.Table;
|
40 |
|
41 | table.grantWriteData(fn);
|
42 | ```
|
43 |
|
44 | The more generic `grant` method allows you to give specific permissions to a resource:
|
45 |
|
46 | ```ts
|
47 | declare const fn: lambda.Function;
|
48 | declare const table: dynamodb.Table;
|
49 |
|
50 | table.grant(fn, 'dynamodb:PutItem');
|
51 | ```
|
52 |
|
53 | The `grant*` methods accept an `IGrantable` object. This interface is implemented by IAM principlal resources (groups, users and roles) and resources that assume a role such as a Lambda function, EC2 instance or a Codebuild project.
|
54 |
|
55 | You can find which `grant*` methods exist for a resource in the [AWS CDK API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html).
|
56 |
|
57 | ## Roles
|
58 |
|
59 | Many AWS resources require *Roles* to operate. These Roles define the AWS API
|
60 | calls an instance or other AWS service is allowed to make.
|
61 |
|
62 | Creating Roles and populating them with the right permissions *Statements* is
|
63 | a necessary but tedious part of setting up AWS infrastructure. In order to
|
64 | help you focus on your business logic, CDK will take care of creating
|
65 | roles and populating them with least-privilege permissions automatically.
|
66 |
|
67 | All constructs that require Roles will create one for you if don't specify
|
68 | one at construction time. Permissions will be added to that role
|
69 | automatically if you associate the construct with other constructs from the
|
70 | AWS Construct Library (for example, if you tell an *AWS CodePipeline* to trigger
|
71 | an *AWS Lambda Function*, the Pipeline's Role will automatically get
|
72 | `lambda:InvokeFunction` permissions on that particular Lambda Function),
|
73 | or if you explicitly grant permissions using `grant` functions (see the
|
74 | previous section).
|
75 |
|
76 | ### Opting out of automatic permissions management
|
77 |
|
78 | You may prefer to manage a Role's permissions yourself instead of having the
|
79 | CDK automatically manage them for you. This may happen in one of the
|
80 | following cases:
|
81 |
|
82 | * You don't like the permissions that CDK automatically generates and
|
83 | want to substitute your own set.
|
84 | * The least-permissions policy that the CDK generates is becoming too
|
85 | big for IAM to store, and you need to add some wildcards to keep the
|
86 | policy size down.
|
87 |
|
88 | To prevent constructs from updating your Role's policy, pass the object
|
89 | returned by `myRole.withoutPolicyUpdates()` instead of `myRole` itself.
|
90 |
|
91 | For example, to have an AWS CodePipeline *not* automatically add the required
|
92 | permissions to trigger the expected targets, do the following:
|
93 |
|
94 | ```ts
|
95 | const role = new iam.Role(this, 'Role', {
|
96 | assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
|
97 | // custom description if desired
|
98 | description: 'This is a custom role...',
|
99 | });
|
100 |
|
101 | new codepipeline.Pipeline(this, 'Pipeline', {
|
102 | // Give the Pipeline an immutable view of the Role
|
103 | role: role.withoutPolicyUpdates(),
|
104 | });
|
105 |
|
106 | // You now have to manage the Role policies yourself
|
107 | role.addToPolicy(new iam.PolicyStatement({
|
108 | actions: [/* whatever actions you want */],
|
109 | resources: [/* whatever resources you intend to touch */],
|
110 | }));
|
111 | ```
|
112 |
|
113 | ### Using existing roles
|
114 |
|
115 | If there are Roles in your account that have already been created which you
|
116 | would like to use in your CDK application, you can use `Role.fromRoleArn` to
|
117 | import them, as follows:
|
118 |
|
119 | ```ts
|
120 | const role = iam.Role.fromRoleArn(this, 'Role', 'arn:aws:iam::123456789012:role/MyExistingRole', {
|
121 | // Set 'mutable' to 'false' to use the role as-is and prevent adding new
|
122 | // policies to it. The default is 'true', which means the role may be
|
123 | // modified as part of the deployment.
|
124 | mutable: false,
|
125 | });
|
126 | ```
|
127 |
|
128 | ## Configuring an ExternalId
|
129 |
|
130 | If you need to create Roles that will be assumed by third parties, it is generally a good idea to [require an `ExternalId`
|
131 | to assume them](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). Configuring
|
132 | an `ExternalId` works like this:
|
133 |
|
134 | [supplying an external ID](test/example.external-id.lit.ts)
|
135 |
|
136 | ## Principals vs Identities
|
137 |
|
138 | When we say *Principal*, we mean an entity you grant permissions to. This
|
139 | entity can be an AWS Service, a Role, or something more abstract such as "all
|
140 | users in this account" or even "all users in this organization". An
|
141 | *Identity* is an IAM representing a single IAM entity that can have
|
142 | a policy attached, one of `Role`, `User`, or `Group`.
|
143 |
|
144 | ## IAM Principals
|
145 |
|
146 | When defining policy statements as part of an AssumeRole policy or as part of a
|
147 | resource policy, statements would usually refer to a specific IAM principal
|
148 | under `Principal`.
|
149 |
|
150 | IAM principals are modeled as classes that derive from the `iam.PolicyPrincipal`
|
151 | abstract class. Principal objects include principal type (string) and value
|
152 | (array of string), optional set of conditions and the action that this principal
|
153 | requires when it is used in an assume role policy document.
|
154 |
|
155 | To add a principal to a policy statement you can either use the abstract
|
156 | `statement.addPrincipal`, one of the concrete `addXxxPrincipal` methods:
|
157 |
|
158 | * `addAwsPrincipal`, `addArnPrincipal` or `new ArnPrincipal(arn)` for `{ "AWS": arn }`
|
159 | * `addAwsAccountPrincipal` or `new AccountPrincipal(accountId)` for `{ "AWS": account-arn }`
|
160 | * `addServicePrincipal` or `new ServicePrincipal(service)` for `{ "Service": service }`
|
161 | * `addAccountRootPrincipal` or `new AccountRootPrincipal()` for `{ "AWS": { "Ref: "AWS::AccountId" } }`
|
162 | * `addCanonicalUserPrincipal` or `new CanonicalUserPrincipal(id)` for `{ "CanonicalUser": id }`
|
163 | * `addFederatedPrincipal` or `new FederatedPrincipal(federated, conditions, assumeAction)` for
|
164 | `{ "Federated": arn }` and a set of optional conditions and the assume role action to use.
|
165 | * `addAnyPrincipal` or `new AnyPrincipal` for `{ "AWS": "*" }`
|
166 |
|
167 | If multiple principals are added to the policy statement, they will be merged together:
|
168 |
|
169 | ```ts
|
170 | const statement = new iam.PolicyStatement();
|
171 | statement.addServicePrincipal('cloudwatch.amazonaws.com');
|
172 | statement.addServicePrincipal('ec2.amazonaws.com');
|
173 | statement.addArnPrincipal('arn:aws:boom:boom');
|
174 | ```
|
175 |
|
176 | Will result in:
|
177 |
|
178 | ```json
|
179 | {
|
180 | "Principal": {
|
181 | "Service": [ "cloudwatch.amazonaws.com", "ec2.amazonaws.com" ],
|
182 | "AWS": "arn:aws:boom:boom"
|
183 | }
|
184 | }
|
185 | ```
|
186 |
|
187 | The `CompositePrincipal` class can also be used to define complex principals, for example:
|
188 |
|
189 | ```ts
|
190 | const role = new iam.Role(this, 'MyRole', {
|
191 | assumedBy: new iam.CompositePrincipal(
|
192 | new iam.ServicePrincipal('ec2.amazonaws.com'),
|
193 | new iam.AccountPrincipal('1818188181818187272')
|
194 | ),
|
195 | });
|
196 | ```
|
197 |
|
198 | The `PrincipalWithConditions` class can be used to add conditions to a
|
199 | principal, especially those that don't take a `conditions` parameter in their
|
200 | constructor. The `principal.withConditions()` method can be used to create a
|
201 | `PrincipalWithConditions` from an existing principal, for example:
|
202 |
|
203 | ```ts
|
204 | const principal = new iam.AccountPrincipal('123456789000')
|
205 | .withConditions({ StringEquals: { foo: "baz" } });
|
206 | ```
|
207 |
|
208 | > NOTE: If you need to define an IAM condition that uses a token (such as a
|
209 | > deploy-time attribute of another resource) in a JSON map key, use `CfnJson` to
|
210 | > render this condition. See [this test](./test/integ.condition-with-ref.ts) for
|
211 | > an example.
|
212 |
|
213 | The `WebIdentityPrincipal` class can be used as a principal for web identities like
|
214 | Cognito, Amazon, Google or Facebook, for example:
|
215 |
|
216 | ```ts
|
217 | const principal = new iam.WebIdentityPrincipal('cognito-identity.amazonaws.com', {
|
218 | 'StringEquals': { 'cognito-identity.amazonaws.com:aud': 'us-east-2:12345678-abcd-abcd-abcd-123456' },
|
219 | 'ForAnyValue:StringLike': {'cognito-identity.amazonaws.com:amr': 'unauthenticated' },
|
220 | });
|
221 | ```
|
222 |
|
223 | If your identity provider is configured to assume a Role with [session
|
224 | tags](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html), you
|
225 | need to call `.withSessionTags()` to add the required permissions to the Role's
|
226 | policy document:
|
227 |
|
228 | ```ts
|
229 | new iam.Role(this, 'Role', {
|
230 | assumedBy: new iam.WebIdentityPrincipal('cognito-identity.amazonaws.com', {
|
231 | 'StringEquals': {
|
232 | 'cognito-identity.amazonaws.com:aud': 'us-east-2:12345678-abcd-abcd-abcd-123456',
|
233 | },
|
234 | 'ForAnyValue:StringLike': {
|
235 | 'cognito-identity.amazonaws.com:amr': 'unauthenticated',
|
236 | },
|
237 | }).withSessionTags(),
|
238 | });
|
239 | ```
|
240 |
|
241 |
|
242 | ## Parsing JSON Policy Documents
|
243 |
|
244 | The `PolicyDocument.fromJson` and `PolicyStatement.fromJson` static methods can be used to parse JSON objects. For example:
|
245 |
|
246 | ```ts
|
247 | const policyDocument = {
|
248 | "Version": "2012-10-17",
|
249 | "Statement": [
|
250 | {
|
251 | "Sid": "FirstStatement",
|
252 | "Effect": "Allow",
|
253 | "Action": ["iam:ChangePassword"],
|
254 | "Resource": "*"
|
255 | },
|
256 | {
|
257 | "Sid": "SecondStatement",
|
258 | "Effect": "Allow",
|
259 | "Action": "s3:ListAllMyBuckets",
|
260 | "Resource": "*"
|
261 | },
|
262 | {
|
263 | "Sid": "ThirdStatement",
|
264 | "Effect": "Allow",
|
265 | "Action": [
|
266 | "s3:List*",
|
267 | "s3:Get*"
|
268 | ],
|
269 | "Resource": [
|
270 | "arn:aws:s3:::confidential-data",
|
271 | "arn:aws:s3:::confidential-data/*"
|
272 | ],
|
273 | "Condition": {"Bool": {"aws:MultiFactorAuthPresent": "true"}}
|
274 | }
|
275 | ]
|
276 | };
|
277 |
|
278 | const customPolicyDocument = iam.PolicyDocument.fromJson(policyDocument);
|
279 |
|
280 | // You can pass this document as an initial document to a ManagedPolicy
|
281 | // or inline Policy.
|
282 | const newManagedPolicy = new iam.ManagedPolicy(this, 'MyNewManagedPolicy', {
|
283 | document: customPolicyDocument,
|
284 | });
|
285 | const newPolicy = new iam.Policy(this, 'MyNewPolicy', {
|
286 | document: customPolicyDocument,
|
287 | });
|
288 | ```
|
289 |
|
290 | ## Permissions Boundaries
|
291 |
|
292 | [Permissions
|
293 | Boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html)
|
294 | can be used as a mechanism to prevent privilege esclation by creating new
|
295 | `Role`s. Permissions Boundaries are a Managed Policy, attached to Roles or
|
296 | Users, that represent the *maximum* set of permissions they can have. The
|
297 | effective set of permissions of a Role (or User) will be the intersection of
|
298 | the Identity Policy and the Permissions Boundary attached to the Role (or
|
299 | User). Permissions Boundaries are typically created by account
|
300 | Administrators, and their use on newly created `Role`s will be enforced by
|
301 | IAM policies.
|
302 |
|
303 | It is possible to attach Permissions Boundaries to all Roles created in a construct
|
304 | tree all at once:
|
305 |
|
306 | ```ts
|
307 | // This imports an existing policy.
|
308 | const boundary = iam.ManagedPolicy.fromManagedPolicyArn(this, 'Boundary', 'arn:aws:iam::123456789012:policy/boundary');
|
309 |
|
310 | // This creates a new boundary
|
311 | const boundary2 = new iam.ManagedPolicy(this, 'Boundary2', {
|
312 | statements: [
|
313 | new iam.PolicyStatement({
|
314 | effect: iam.Effect.DENY,
|
315 | actions: ['iam:*'],
|
316 | resources: ['*'],
|
317 | }),
|
318 | ],
|
319 | });
|
320 |
|
321 | // Directly apply the boundary to a Role you create
|
322 | declare const role: iam.Role;
|
323 | iam.PermissionsBoundary.of(role).apply(boundary);
|
324 |
|
325 | // Apply the boundary to an Role that was implicitly created for you
|
326 | declare const fn: lambda.Function;
|
327 | iam.PermissionsBoundary.of(fn).apply(boundary);
|
328 |
|
329 | // Apply the boundary to all Roles in a stack
|
330 | iam.PermissionsBoundary.of(this).apply(boundary);
|
331 |
|
332 | // Remove a Permissions Boundary that is inherited, for example from the Stack level
|
333 | declare const customResource: CustomResource;
|
334 | iam.PermissionsBoundary.of(customResource).clear();
|
335 | ```
|
336 |
|
337 | ## OpenID Connect Providers
|
338 |
|
339 | OIDC identity providers are entities in IAM that describe an external identity
|
340 | provider (IdP) service that supports the [OpenID Connect] (OIDC) standard, such
|
341 | as Google or Salesforce. You use an IAM OIDC identity provider when you want to
|
342 | establish trust between an OIDC-compatible IdP and your AWS account. This is
|
343 | useful when creating a mobile app or web application that requires access to AWS
|
344 | resources, but you don't want to create custom sign-in code or manage your own
|
345 | user identities. For more information about this scenario, see [About Web
|
346 | Identity Federation] and the relevant documentation in the [Amazon Cognito
|
347 | Identity Pools Developer Guide].
|
348 |
|
349 | [OpenID Connect]: http://openid.net/connect
|
350 | [About Web Identity Federation]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html
|
351 | [Amazon Cognito Identity Pools Developer Guide]: https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.html
|
352 |
|
353 | The following examples defines an OpenID Connect provider. Two client IDs
|
354 | (audiences) are will be able to send authentication requests to
|
355 | <https://openid/connect>.
|
356 |
|
357 | ```ts
|
358 | const provider = new iam.OpenIdConnectProvider(this, 'MyProvider', {
|
359 | url: 'https://openid/connect',
|
360 | clientIds: [ 'myclient1', 'myclient2' ],
|
361 | });
|
362 | ```
|
363 |
|
364 | You can specify an optional list of `thumbprints`. If not specified, the
|
365 | thumbprint of the root certificate authority (CA) will automatically be obtained
|
366 | from the host as described
|
367 | [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html).
|
368 |
|
369 | Once you define an OpenID connect provider, you can use it with AWS services
|
370 | that expect an IAM OIDC provider. For example, when you define an [Amazon
|
371 | Cognito identity
|
372 | pool](https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.html)
|
373 | you can reference the provider's ARN as follows:
|
374 |
|
375 | ```ts
|
376 | import * as cognito from '@aws-cdk/aws-cognito';
|
377 |
|
378 | declare const myProvider: iam.OpenIdConnectProvider;
|
379 | new cognito.CfnIdentityPool(this, 'IdentityPool', {
|
380 | openIdConnectProviderArns: [myProvider.openIdConnectProviderArn],
|
381 | // And the other properties for your identity pool
|
382 | allowUnauthenticatedIdentities: false,
|
383 | });
|
384 | ```
|
385 |
|
386 | The `OpenIdConnectPrincipal` class can be used as a principal used with a `OpenIdConnectProvider`, for example:
|
387 |
|
388 | ```ts
|
389 | const provider = new iam.OpenIdConnectProvider(this, 'MyProvider', {
|
390 | url: 'https://openid/connect',
|
391 | clientIds: [ 'myclient1', 'myclient2' ],
|
392 | });
|
393 | const principal = new iam.OpenIdConnectPrincipal(provider);
|
394 | ```
|
395 |
|
396 | ## SAML provider
|
397 |
|
398 | An IAM SAML 2.0 identity provider is an entity in IAM that describes an external
|
399 | identity provider (IdP) service that supports the SAML 2.0 (Security Assertion
|
400 | Markup Language 2.0) standard. You use an IAM identity provider when you want
|
401 | to establish trust between a SAML-compatible IdP such as Shibboleth or Active
|
402 | Directory Federation Services and AWS, so that users in your organization can
|
403 | access AWS resources. IAM SAML identity providers are used as principals in an
|
404 | IAM trust policy.
|
405 |
|
406 | ```ts
|
407 | new iam.SamlProvider(this, 'Provider', {
|
408 | metadataDocument: iam.SamlMetadataDocument.fromFile('/path/to/saml-metadata-document.xml'),
|
409 | });
|
410 | ```
|
411 |
|
412 | The `SamlPrincipal` class can be used as a principal with a `SamlProvider`:
|
413 |
|
414 | ```ts
|
415 | const provider = new iam.SamlProvider(this, 'Provider', {
|
416 | metadataDocument: iam.SamlMetadataDocument.fromFile('/path/to/saml-metadata-document.xml'),
|
417 | });
|
418 | const principal = new iam.SamlPrincipal(provider, {
|
419 | StringEquals: {
|
420 | 'SAML:iss': 'issuer',
|
421 | },
|
422 | });
|
423 | ```
|
424 |
|
425 | When creating a role for programmatic and AWS Management Console access, use the `SamlConsolePrincipal`
|
426 | class:
|
427 |
|
428 | ```ts
|
429 | const provider = new iam.SamlProvider(this, 'Provider', {
|
430 | metadataDocument: iam.SamlMetadataDocument.fromFile('/path/to/saml-metadata-document.xml'),
|
431 | });
|
432 | new iam.Role(this, 'Role', {
|
433 | assumedBy: new iam.SamlConsolePrincipal(provider),
|
434 | });
|
435 | ```
|
436 |
|
437 | ## Users
|
438 |
|
439 | IAM manages users for your AWS account. To create a new user:
|
440 |
|
441 | ```ts
|
442 | const user = new iam.User(this, 'MyUser');
|
443 | ```
|
444 |
|
445 | To import an existing user by name [with path](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-friendly-names):
|
446 |
|
447 | ```ts
|
448 | const user = iam.User.fromUserName(this, 'MyImportedUserByName', 'johnsmith');
|
449 | ```
|
450 |
|
451 | To import an existing user by ARN:
|
452 |
|
453 | ```ts
|
454 | const user = iam.User.fromUserArn(this, 'MyImportedUserByArn', 'arn:aws:iam::123456789012:user/johnsmith');
|
455 | ```
|
456 |
|
457 | To import an existing user by attributes:
|
458 |
|
459 | ```ts
|
460 | const user = iam.User.fromUserAttributes(this, 'MyImportedUserByAttributes', {
|
461 | userArn: 'arn:aws:iam::123456789012:user/johnsmith',
|
462 | });
|
463 | ```
|
464 |
|
465 | ### Access Keys
|
466 |
|
467 | The ability for a user to make API calls via the CLI or an SDK is enabled by the user having an
|
468 | access key pair. To create an access key:
|
469 |
|
470 | ```ts
|
471 | const user = new iam.User(this, 'MyUser');
|
472 | const accessKey = new iam.AccessKey(this, 'MyAccessKey', { user: user });
|
473 | ```
|
474 |
|
475 | You can force CloudFormation to rotate the access key by providing a monotonically increasing `serial`
|
476 | property. Simply provide a higher serial value than any number used previously:
|
477 |
|
478 | ```ts
|
479 | const user = new iam.User(this, 'MyUser');
|
480 | const accessKey = new iam.AccessKey(this, 'MyAccessKey', { user: user, serial: 1 });
|
481 | ```
|
482 |
|
483 | An access key may only be associated with a single user and cannot be "moved" between users. Changing
|
484 | the user associated with an access key replaces the access key (and its ID and secret value).
|
485 |
|
486 | ## Groups
|
487 |
|
488 | An IAM user group is a collection of IAM users. User groups let you specify permissions for multiple users.
|
489 |
|
490 | ```ts
|
491 | const group = new iam.Group(this, 'MyGroup');
|
492 | ```
|
493 |
|
494 | To import an existing group by ARN:
|
495 |
|
496 | ```ts
|
497 | const group = iam.Group.fromGroupArn(this, 'MyImportedGroupByArn', 'arn:aws:iam::account-id:group/group-name');
|
498 | ```
|
499 |
|
500 | To import an existing group by name [with path](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-friendly-names):
|
501 |
|
502 | ```ts
|
503 | const group = iam.Group.fromGroupName(this, 'MyImportedGroupByName', 'group-name');
|
504 | ```
|
505 |
|
506 | To add a user to a group (both for a new and imported user/group):
|
507 |
|
508 | ```ts
|
509 | const user = new iam.User(this, 'MyUser'); // or User.fromUserName(stack, 'User', 'johnsmith');
|
510 | const group = new iam.Group(this, 'MyGroup'); // or Group.fromGroupArn(stack, 'Group', 'arn:aws:iam::account-id:group/group-name');
|
511 |
|
512 | user.addToGroup(group);
|
513 | // or
|
514 | group.addUser(user);
|
515 | ```
|
516 |
|
517 | ## Features
|
518 |
|
519 | * Policy name uniqueness is enforced. If two policies by the same name are attached to the same
|
520 | principal, the attachment will fail.
|
521 | * Policy names are not required - the CDK logical ID will be used and ensured to be unique.
|
522 | * Policies are validated during synthesis to ensure that they have actions, and that policies
|
523 | attached to IAM principals specify relevant resources, while policies attached to resources
|
524 | specify which IAM principals they apply to.
|