diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index f572bc6e35c98..dbc34c53353af 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1018,13 +1018,20 @@ export class Cluster extends Resource implements ICluster { }; if (!this.endpointAccess._config.publicAccess) { + + const privateSubents = this.selectPrivateSubnets().slice(0, 16); + + if (privateSubents.length === 0) { + throw new Error('Vpc must contain private subnets to configure private endpoint access'); + } + // endpoint access is private only, we need to attach the // provider to the VPC so that it can access the cluster. providerProps = { ...providerProps, vpc: this.vpc, // lambda can only be accociated with max 16 subnets and they all need to be private. - vpcSubnets: {subnets: this.selectPrivateSubnets().slice(0, 16)}, + vpcSubnets: {subnets: privateSubents}, securityGroups: [this.kubctlProviderSecurityGroup], }; } @@ -1049,7 +1056,26 @@ export class Cluster extends Resource implements ICluster { const privateSubnets: ec2.ISubnet[] = []; for (const placement of this.vpcSubnets) { - privateSubnets.push(...this.vpc.selectSubnets(placement).subnets.filter(s => s instanceof ec2.PrivateSubnet)); + + for (const subnet of this.vpc.selectSubnets(placement).subnets) { + + if (this.vpc.privateSubnets.includes(subnet)) { + // definitely private, take it. + privateSubnets.push(subnet); + continue; + } + + if (this.vpc.publicSubnets.includes(subnet)) { + // definitely public, skip it. + continue; + } + + // neither public and nor private - what is it then? this means its a subnet instance that was explicitly passed + // in the subnet selection. since ISubnet doesn't contain information on type, we have to assume its private and let it + // fail at deploy time :\ (its better than filtering it out and preventing a possibly successful deployment) + privateSubnets.push(subnet); + } + } return privateSubnets; diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index aaf1d0a2de124..576e4081e83c0 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -1400,6 +1400,105 @@ export = { 'endpoint access': { + 'private endpoint access fails if selected subnets are empty'(test: Test) { + + const { stack } = testFixture(); + + test.throws(() => { + new eks.Cluster(stack, 'Cluster', { + vpc: new ec2.Vpc(stack, 'Vpc'), + version: CLUSTER_VERSION, + endpointAccess: eks.EndpointAccess.PRIVATE, + vpcSubnets: [{ subnetType: ec2.SubnetType.PUBLIC }], + }); + }, /Vpc must contain private subnets to configure private endpoint access/); + + test.done(); + }, + + 'private endpoint access selects only private subnets from looked up vpc'(test: Test) { + + const vpcId = 'vpc-12345'; + // can't use the regular fixture because it also adds a VPC to the stack, which prevents + // us from setting context. + const stack = new cdk.Stack(new cdk.App(), 'Stack', { + env: { + account: '11112222', + region: 'us-east-1', + }, + }); + stack.node.setContext(`vpc-provider:account=${stack.account}:filter.vpc-id=${vpcId}:region=${stack.region}:returnAsymmetricSubnets=true`, { + vpcId: vpcId, + vpcCidrBlock: '10.0.0.0/16', + subnetGroups: [ + { + name: 'Private', + type: 'Private', + subnets: [ + { + subnetId: 'subnet-private-in-us-east-1a', + cidr: '10.0.1.0/24', + availabilityZone: 'us-east-1a', + routeTableId: 'rtb-06068e4c4049921ef', + }, + ], + }, + { + name: 'Public', + type: 'Public', + subnets: [ + { + subnetId: 'subnet-public-in-us-east-1c', + cidr: '10.0.0.0/24', + availabilityZone: 'us-east-1c', + routeTableId: 'rtb-0ff08e62195198dbb', + }, + ], + }, + ], + }); + const vpc = ec2.Vpc.fromLookup(stack, 'Vpc', { + vpcId: vpcId, + }); + + new eks.Cluster(stack, 'Cluster', { + vpc, + version: CLUSTER_VERSION, + endpointAccess: eks.EndpointAccess.PRIVATE, + }); + + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const template = expect(nested).value; + + test.deepEqual(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds, [ + 'subnet-private-in-us-east-1a', + ]); + + test.done(); + }, + + 'private endpoint access considers specific subnet selection'(test: Test) { + const { stack } = testFixture(); + new eks.Cluster(stack, 'Cluster', { + version: CLUSTER_VERSION, endpointAccess: + eks.EndpointAccess.PRIVATE, + vpcSubnets: [{subnets: [ec2.PrivateSubnet.fromSubnetAttributes(stack, 'Private1', { + subnetId: 'subnet1', + availabilityZone: 'us-east-1a', + })]}], + }); + + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const template = expect(nested).value; + + test.deepEqual(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds, [ + 'subnet1', + ]); + + test.done(); + + }, + 'can configure private endpoint access'(test: Test) { // GIVEN const { stack } = testFixture(); @@ -1669,4 +1768,4 @@ export = { }, }, -}; +}; \ No newline at end of file