From ab4867ca6114dc451fa9f84056bb613f86a4839d Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Thu, 27 Jul 2023 09:10:14 +0200 Subject: [PATCH 1/3] fix(lambda): validate securityGroups in VPC configuration --- .../aws-cdk-lib/aws-lambda/lib/function.ts | 21 ++- .../aws-lambda/test/function.test.ts | 127 ++++++++++++++++++ 2 files changed, 142 insertions(+), 6 deletions(-) diff --git a/packages/aws-cdk-lib/aws-lambda/lib/function.ts b/packages/aws-cdk-lib/aws-lambda/lib/function.ts index daef328fc8f50..2f53cda366ff8 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/function.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/function.ts @@ -1222,11 +1222,20 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett * Lambda creation properties. */ private configureVpc(props: FunctionProps): CfnFunction.VpcConfigProperty | undefined { - if ((props.securityGroup || props.allowAllOutbound !== undefined) && !props.vpc) { - throw new Error('Cannot configure \'securityGroup\' or \'allowAllOutbound\' without configuring a VPC'); + if (props.securityGroup && props.securityGroups) { + throw new Error('Only one of the function props, securityGroup or securityGroups, is allowed'); } if (!props.vpc) { + if (props.allowAllOutbound !== undefined) { + throw new Error('Cannot configure \'allowAllOutbound\' without configuring a VPC'); + } + if (props.securityGroup) { + throw new Error('Cannot configure \'securityGroup\' without configuring a VPC'); + } + if (props.securityGroups) { + throw new Error('Cannot configure \'securityGroups\' without configuring a VPC'); + } if (props.vpcSubnets) { throw new Error('Cannot configure \'vpcSubnets\' without configuring a VPC'); } @@ -1237,12 +1246,12 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett throw new Error('Configure \'allowAllOutbound\' directly on the supplied SecurityGroup.'); } - let securityGroups: ec2.ISecurityGroup[]; - - if (props.securityGroup && props.securityGroups) { - throw new Error('Only one of the function props, securityGroup or securityGroups, is allowed'); + if (props.securityGroups && props.allowAllOutbound !== undefined) { + throw new Error('Configure \'allowAllOutbound\' directly on the supplied SecurityGroups.'); } + let securityGroups: ec2.ISecurityGroup[]; + if (props.securityGroups) { securityGroups = props.securityGroups; } else { diff --git a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts index 83d25a6c13155..66e98e83abd66 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts @@ -3246,6 +3246,133 @@ test('set SnapStart to desired value', () => { }); }); +describe('VPC configuration', () => { + test('with both securityGroup and securityGroups', () => { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + securityGroup, + securityGroups: [securityGroup], + })).toThrow(/Only one of the function props, securityGroup or securityGroups, is allowed/); + }); + + test('with allowAllOutbound and no VPC', () => { + const stack = new cdk.Stack(); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + allowAllOutbound: true, + })).toThrow(/Cannot configure 'allowAllOutbound' without configuring a VPC/); + }); + + test('with allowAllOutbound and no VPC', () => { + const stack = new cdk.Stack(); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + allowAllOutbound: true, + })).toThrow(/Cannot configure 'allowAllOutbound' without configuring a VPC/); + }); + + test('with securityGroup and no VPC', () => { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + securityGroup, + })).toThrow(/Cannot configure 'securityGroup' without configuring a VPC/); + }); + + test('with securityGroups and no VPC', () => { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + securityGroups: [securityGroup], + })).toThrow(/Cannot configure 'securityGroups' without configuring a VPC/); + }); + + test('with vpcSubnets and no VPC', () => { + const stack = new cdk.Stack(); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, + })).toThrow(/Cannot configure 'vpcSubnets' without configuring a VPC/); + }); + + test('with securityGroup and allowAllOutbound', () => { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + expect(() => new lambda.Function(stack, 'MyLambda', { + vpc, + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + securityGroup, + allowAllOutbound: false, + })).toThrow(/Configure 'allowAllOutbound' directly on the supplied SecurityGroup./); + }); + + test('with securityGroups and allowAllOutbound', () => { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + const securityGroup = new ec2.SecurityGroup(stack, 'LambdaSG', { + vpc, + allowAllOutbound: false, + }); + expect(() => new lambda.Function(stack, 'MyLambda', { + vpc, + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_9, + securityGroups: [securityGroup], + allowAllOutbound: false, + })).toThrow(/Configure 'allowAllOutbound' directly on the supplied SecurityGroups./); + }); +}); + function newTestLambda(scope: constructs.Construct) { return new lambda.Function(scope, 'MyLambda', { code: new lambda.InlineCode('foo'), From cf4fcc9a00d65588382e240491833139f50ad31e Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Thu, 27 Jul 2023 11:56:36 +0200 Subject: [PATCH 2/3] updated provider condition for security groups --- packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts b/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts index 45792866a64d8..293de7ad4e669 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts @@ -145,8 +145,10 @@ export class KubectlProvider extends NestedStack implements IKubectlProvider { // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, - securityGroups: cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined, - vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined, + securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? + [cluster.kubectlSecurityGroup] : undefined, + vpcSubnets: cluster.kubectlPrivateSubnets && cluster.kubectlPrivateSubnets ? + { subnets: cluster.kubectlPrivateSubnets } : undefined, }); // allow user to customize the layers with the tools we need @@ -194,7 +196,7 @@ export class KubectlProvider extends NestedStack implements IKubectlProvider { onEventHandler: handler, vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined, - securityGroups: cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined, + securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined, }); this.serviceToken = provider.serviceToken; From a0868a93701015d784ae07cd308688fca8830b09 Mon Sep 17 00:00:00 2001 From: Luca Pizzini Date: Fri, 18 Aug 2023 17:32:09 +0200 Subject: [PATCH 3/3] fix: failing test --- packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts b/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts index 293de7ad4e669..a891e4d022aa5 100644 --- a/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts +++ b/packages/aws-cdk-lib/aws-eks/lib/kubectl-provider.ts @@ -145,10 +145,8 @@ export class KubectlProvider extends NestedStack implements IKubectlProvider { // defined only when using private access vpc: cluster.kubectlPrivateSubnets ? cluster.vpc : undefined, - securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? - [cluster.kubectlSecurityGroup] : undefined, - vpcSubnets: cluster.kubectlPrivateSubnets && cluster.kubectlPrivateSubnets ? - { subnets: cluster.kubectlPrivateSubnets } : undefined, + securityGroups: cluster.kubectlPrivateSubnets && cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined, + vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined, }); // allow user to customize the layers with the tools we need