Skip to content

Commit e4f2916

Browse files
authored
Merge branch 'main' into huijbers/sm-cfnapp-removal
2 parents 5f4e49f + 4e52091 commit e4f2916

15 files changed

Lines changed: 642 additions & 45 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
[![Go Reference](https://pkg.go.dev/badge/github.com/aws/aws-cdk-go/awscdk.svg)](https://pkg.go.dev/github.com/aws/aws-cdk-go/awscdk)
1010
[![Mergify](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/aws/aws-cdk&style=flat)](https://mergify.io)
1111

12+
[![View on Construct Hub](https://constructs.dev/badge?package=aws-cdk-lib)](https://constructs.dev/packages/aws-cdk-lib)
13+
1214
The **AWS Cloud Development Kit (AWS CDK)** is an open-source software development
1315
framework to define cloud infrastructure in code and provision it through AWS CloudFormation.
1416

packages/@aws-cdk/aws-cognito/lib/user-pool.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,12 @@ export interface IUserPool extends IResource {
703703
* Register an identity provider with this user pool.
704704
*/
705705
registerIdentityProvider(provider: IUserPoolIdentityProvider): void;
706+
707+
/**
708+
* Adds an IAM policy statement associated with this user pool to an
709+
* IAM principal's policy.
710+
*/
711+
grant(grantee: IGrantable, ...actions: string[]): Grant;
706712
}
707713

708714
abstract class UserPoolBase extends Resource implements IUserPool {
@@ -735,10 +741,6 @@ abstract class UserPoolBase extends Resource implements IUserPool {
735741
this.identityProviders.push(provider);
736742
}
737743

738-
/**
739-
* Adds an IAM policy statement associated with this user pool to an
740-
* IAM principal's policy.
741-
*/
742744
public grant(grantee: IGrantable, ...actions: string[]): Grant {
743745
return Grant.addToPrincipal({
744746
grantee,

packages/@aws-cdk/aws-docdb/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const cluster = new docdb.DatabaseCluster(this, 'Database', {
2525
excludeCharacters: '\"@/:', // optional, defaults to the set "\"@/" and is also used for eventually created rotations
2626
secretName: '/myapp/mydocdb/masteruser', // optional, if you prefer to specify the secret name
2727
},
28-
instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE),
28+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.MEMORY5, ec2.InstanceSize.LARGE),
2929
vpcSubnets: {
3030
subnetType: ec2.SubnetType.PUBLIC,
3131
},
@@ -78,7 +78,7 @@ const cluster = new docdb.DatabaseCluster(this, 'Database', {
7878
masterUser: {
7979
username: 'myuser',
8080
},
81-
instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE),
81+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.MEMORY5, ec2.InstanceSize.LARGE),
8282
vpcSubnets: {
8383
subnetType: ec2.SubnetType.PUBLIC,
8484
},
@@ -150,7 +150,7 @@ const cluster = new docdb.DatabaseCluster(this, 'Database', {
150150
masterUser: {
151151
username: 'myuser',
152152
},
153-
instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE),
153+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.MEMORY5, ec2.InstanceSize.LARGE),
154154
vpcSubnets: {
155155
subnetType: ec2.SubnetType.PUBLIC,
156156
},

packages/@aws-cdk/aws-lambda/README.md

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,13 @@ if (fn.timeout) {
155155

156156
AWS Lambda supports resource-based policies for controlling access to Lambda
157157
functions and layers on a per-resource basis. In particular, this allows you to
158-
give permission to AWS services and other AWS accounts to modify and invoke your
159-
functions. You can also restrict permissions given to AWS services by providing
160-
a source account or ARN (representing the account and identifier of the resource
161-
that accesses the function or layer).
158+
give permission to AWS services, AWS Organizations, or other AWS accounts to
159+
modify and invoke your functions.
160+
161+
### Grant function access to AWS services
162162

163163
```ts
164+
// Grant permissions to a service
164165
declare const fn: lambda.Function;
165166
const principal = new iam.ServicePrincipal('my-service');
166167

@@ -172,10 +173,58 @@ fn.addPermission('my-service Invocation', {
172173
});
173174
```
174175

175-
For more information, see [Resource-based
176-
policies](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html)
176+
You can also restrict permissions given to AWS services by providing
177+
a source account or ARN (representing the account and identifier of the resource
178+
that accesses the function or layer).
179+
180+
For more information, see
181+
[Granting function access to AWS services](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-serviceinvoke)
182+
in the AWS Lambda Developer Guide.
183+
184+
### Grant function access to an AWS Organization
185+
186+
```ts
187+
// Grant permissions to an entire AWS organization
188+
declare const fn: lambda.Function;
189+
const org = new iam.OrganizationPrincipal('o-xxxxxxxxxx');
190+
191+
fn.grantInvoke(org);
192+
```
193+
194+
In the above example, the `principal` will be `*` and all users in the
195+
organization `o-xxxxxxxxxx` will get function invocation permissions.
196+
197+
You can restrict permissions given to the organization by specifying an
198+
AWS account or role as the `principal`:
199+
200+
```ts
201+
// Grant permission to an account ONLY IF they are part of the organization
202+
declare const fn: lambda.Function;
203+
const account = new iam.AccountPrincipal('123456789012');
204+
205+
fn.grantInvoke(account.inOrganization('o-xxxxxxxxxx'));
206+
```
207+
208+
For more information, see
209+
[Granting function access to an organization](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-xorginvoke)
177210
in the AWS Lambda Developer Guide.
178211

212+
### Grant function access to other AWS accounts
213+
214+
```ts
215+
// Grant permission to other AWS account
216+
declare const fn: lambda.Function;
217+
const account = new iam.AccountPrincipal('123456789012');
218+
219+
fn.grantInvoke(account);
220+
```
221+
222+
For more information, see
223+
[Granting function access to other accounts](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-xaccountinvoke)
224+
in the AWS Lambda Developer Guide.
225+
226+
### Grant function access to unowned principals
227+
179228
Providing an unowned principal (such as account principals, generic ARN
180229
principals, service principals, and principals in other accounts) to a call to
181230
`fn.grantInvoke` will result in a resource-based policy being created. If the
@@ -198,13 +247,6 @@ const servicePrincipalWithConditions = servicePrincipal.withConditions({
198247
});
199248

200249
fn.grantInvoke(servicePrincipalWithConditions);
201-
202-
// Equivalent to:
203-
fn.addPermission('my-service Invocation', {
204-
principal: servicePrincipal,
205-
sourceArn: sourceArn,
206-
sourceAccount: sourceAccount,
207-
});
208250
```
209251

210252
## Versions

packages/@aws-cdk/aws-lambda/lib/function-base.ts

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,10 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
343343
return;
344344
}
345345

346-
const principal = this.parsePermissionPrincipal(permission.principal);
347-
const { sourceAccount, sourceArn } = this.parseConditions(permission.principal) ?? {};
346+
let principal = this.parsePermissionPrincipal(permission.principal);
347+
348+
let { sourceArn, sourceAccount, principalOrgID } = this.validateConditionCombinations(permission.principal) ?? {};
349+
348350
const action = permission.action ?? 'lambda:InvokeFunction';
349351
const scope = permission.scope ?? this;
350352

@@ -357,6 +359,7 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
357359
eventSourceToken: permission.eventSourceToken,
358360
sourceAccount: permission.sourceAccount ?? sourceAccount,
359361
sourceArn: permission.sourceArn ?? sourceArn,
362+
principalOrgId: permission.organizationId ?? principalOrgID,
360363
functionUrlAuthType: permission.functionUrlAuthType,
361364
});
362365
}
@@ -552,7 +555,6 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
552555
private parsePermissionPrincipal(principal: iam.IPrincipal) {
553556
// Try some specific common classes first.
554557
// use duck-typing, not instance of
555-
// @deprecated: after v2, we can change these to 'instanceof'
556558
if ('wrapped' in principal) {
557559
// eslint-disable-next-line dot-notation
558560
principal = principal['wrapped'];
@@ -570,6 +572,15 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
570572
return (principal as iam.ArnPrincipal).arn;
571573
}
572574

575+
const stringEquals = matchSingleKey('StringEquals', principal.policyFragment.conditions);
576+
if (stringEquals) {
577+
const orgId = matchSingleKey('aws:PrincipalOrgID', stringEquals);
578+
if (orgId) {
579+
// we will move the organization id to the `principalOrgId` property of `Permissions`.
580+
return '*';
581+
}
582+
}
583+
573584
// Try a best-effort approach to support simple principals that are not any of the predefined
574585
// classes, but are simple enough that they will fit into the Permission model. Main target
575586
// here: imported Roles, Users, Groups.
@@ -584,17 +595,67 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
584595
}
585596

586597
throw new Error(`Invalid principal type for Lambda permission statement: ${principal.constructor.name}. ` +
587-
'Supported: AccountPrincipal, ArnPrincipal, ServicePrincipal');
598+
'Supported: AccountPrincipal, ArnPrincipal, ServicePrincipal, OrganizationPrincipal');
599+
600+
/**
601+
* Returns the value at the key if the object contains the key and nothing else. Otherwise,
602+
* returns undefined.
603+
*/
604+
function matchSingleKey(key: string, obj: Record<string, any>): any | undefined {
605+
if (Object.keys(obj).length !== 1) { return undefined; }
606+
607+
return obj[key];
608+
}
609+
588610
}
589611

590-
private parseConditions(principal: iam.IPrincipal): { sourceAccount: string, sourceArn: string } | null {
612+
private validateConditionCombinations(principal: iam.IPrincipal): {
613+
sourceArn: string | undefined,
614+
sourceAccount: string | undefined,
615+
principalOrgID: string | undefined,
616+
} | undefined {
617+
const conditions = this.validateConditions(principal);
618+
619+
if (!conditions) { return undefined; }
620+
621+
const sourceArn = conditions.ArnLike ? conditions.ArnLike['aws:SourceArn'] : undefined;
622+
const sourceAccount = conditions.StringEquals ? conditions.StringEquals['aws:SourceAccount'] : undefined;
623+
const principalOrgID = conditions.StringEquals ? conditions.StringEquals['aws:PrincipalOrgID'] : undefined;
624+
625+
// PrincipalOrgID cannot be combined with any other conditions
626+
if (principalOrgID && (sourceArn || sourceAccount)) {
627+
throw new Error('PrincipalWithConditions had unsupported condition combinations for Lambda permission statement: principalOrgID cannot be set with other conditions.');
628+
}
629+
630+
return {
631+
sourceArn,
632+
sourceAccount,
633+
principalOrgID,
634+
};
635+
}
636+
637+
private validateConditions(principal: iam.IPrincipal): iam.Conditions | undefined {
591638
if (this.isPrincipalWithConditions(principal)) {
592639
const conditions: iam.Conditions = principal.policyFragment.conditions;
593640
const conditionPairs = flatMap(
594641
Object.entries(conditions),
595642
([operator, conditionObjs]) => Object.keys(conditionObjs as object).map(key => { return { operator, key }; }),
596643
);
597-
const supportedPrincipalConditions = [{ operator: 'ArnLike', key: 'aws:SourceArn' }, { operator: 'StringEquals', key: 'aws:SourceAccount' }];
644+
645+
// These are all the supported conditions. Some combinations are not supported,
646+
// like only 'aws:SourceArn' or 'aws:PrincipalOrgID' and 'aws:SourceAccount'.
647+
// These will be validated through `this.validateConditionCombinations`.
648+
const supportedPrincipalConditions = [{
649+
operator: 'ArnLike',
650+
key: 'aws:SourceArn',
651+
},
652+
{
653+
operator: 'StringEquals',
654+
key: 'aws:SourceAccount',
655+
}, {
656+
operator: 'StringEquals',
657+
key: 'aws:PrincipalOrgID',
658+
}];
598659

599660
const unsupportedConditions = conditionPairs.filter(
600661
(condition) => !supportedPrincipalConditions.some(
@@ -603,21 +664,18 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
603664
);
604665

605666
if (unsupportedConditions.length == 0) {
606-
return {
607-
sourceAccount: conditions.StringEquals['aws:SourceAccount'],
608-
sourceArn: conditions.ArnLike['aws:SourceArn'],
609-
};
667+
return conditions;
610668
} else {
611669
throw new Error(`PrincipalWithConditions had unsupported conditions for Lambda permission statement: ${JSON.stringify(unsupportedConditions)}. ` +
612670
`Supported operator/condition pairs: ${JSON.stringify(supportedPrincipalConditions)}`);
613671
}
614-
} else {
615-
return null;
616672
}
673+
674+
return undefined;
617675
}
618676

619-
private isPrincipalWithConditions(principal: iam.IPrincipal): principal is iam.PrincipalWithConditions {
620-
return 'conditions' in principal;
677+
private isPrincipalWithConditions(principal: iam.IPrincipal): boolean {
678+
return Object.keys(principal.policyFragment.conditions).length > 0;
621679
}
622680
}
623681

packages/@aws-cdk/aws-lambda/lib/permission.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,22 @@ export interface Permission {
2323
* A unique token that must be supplied by the principal invoking the
2424
* function.
2525
*
26-
* @default The caller would not need to present a token.
26+
* @default - The caller would not need to present a token.
2727
*/
2828
readonly eventSourceToken?: string;
2929

3030
/**
3131
* The entity for which you are granting permission to invoke the Lambda
32-
* function. This entity can be any valid AWS service principal, such as
33-
* s3.amazonaws.com or sns.amazonaws.com, or, if you are granting
34-
* cross-account permission, an AWS account ID. For example, you might want
35-
* to allow a custom application in another AWS account to push events to
36-
* Lambda by invoking your function.
32+
* function. This entity can be any of the following:
3733
*
38-
* The principal can be either an AccountPrincipal or a ServicePrincipal.
34+
* - a valid AWS service principal, such as `s3.amazonaws.com` or `sns.amazonaws.com`
35+
* - an AWS account ID for cross-account permissions. For example, you might want
36+
* to allow a custom application in another AWS account to push events to
37+
* Lambda by invoking your function.
38+
* - an AWS organization principal to grant permissions to an entire organization.
39+
*
40+
* The principal can be an AccountPrincipal, an ArnPrincipal, a ServicePrincipal,
41+
* or an OrganizationPrincipal.
3942
*/
4043
readonly principal: iam.IPrincipal;
4144

@@ -67,6 +70,19 @@ export interface Permission {
6770
*/
6871
readonly sourceArn?: string;
6972

73+
/**
74+
* The organization you want to grant permissions to. Use this ONLY if you
75+
* need to grant permissions to a subset of the organization. If you want to
76+
* grant permissions to the entire organization, sending the organization principal
77+
* through the `principal` property will suffice.
78+
*
79+
* You can use this property to ensure that all source principals are owned by
80+
* a specific organization.
81+
*
82+
* @default - No organizationId
83+
*/
84+
readonly organizationId?: string;
85+
7086
/**
7187
* The authType for the function URL that you are granting permissions for.
7288
*

0 commit comments

Comments
 (0)