Skip to content

Commit

Permalink
feat(eks): endpoint access customization (#9095)
Browse files Browse the repository at this point in the history
Add an option to configure endpoint access to the cluster control plane.

Resolves #5220

In addition, there is now a way to pass environment variables into the kubectl handler. This is necessary for allowing private VPCs (with no internet access) to use an organizational proxy when installing Helm chart and in general needing to access the internet. See #9095 (comment).

BREAKING CHANGE: endpoint access is configured to private and public by default instead of just public

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
iliapolo committed Aug 5, 2020
1 parent 53cb749 commit 692864c
Show file tree
Hide file tree
Showing 14 changed files with 2,312 additions and 115 deletions.
18 changes: 14 additions & 4 deletions packages/@aws-cdk/aws-ec2/lib/vpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,16 @@ export class Vpc extends VpcBase {

public readonly internetConnectivityEstablished: IDependable;

/**
* Indicates if instances launched in this VPC will have public DNS hostnames.
*/
public readonly dnsHostnamesEnabled: boolean;

/**
* Indicates if DNS support is enabled for this VPC.
*/
public readonly dnsSupportEnabled: boolean;

/**
* The VPC resource
*/
Expand Down Expand Up @@ -1147,16 +1157,16 @@ export class Vpc extends VpcBase {

this.networkBuilder = new NetworkBuilder(cidrBlock);

const enableDnsHostnames = props.enableDnsHostnames == null ? true : props.enableDnsHostnames;
const enableDnsSupport = props.enableDnsSupport == null ? true : props.enableDnsSupport;
this.dnsHostnamesEnabled = props.enableDnsHostnames == null ? true : props.enableDnsHostnames;
this.dnsSupportEnabled = props.enableDnsSupport == null ? true : props.enableDnsSupport;
const instanceTenancy = props.defaultInstanceTenancy || 'default';
this.internetConnectivityEstablished = this._internetConnectivityEstablished;

// Define a VPC using the provided CIDR range
this.resource = new CfnVPC(this, 'Resource', {
cidrBlock,
enableDnsHostnames,
enableDnsSupport,
enableDnsHostnames: this.dnsHostnamesEnabled,
enableDnsSupport: this.dnsSupportEnabled,
instanceTenancy,
});

Expand Down
40 changes: 40 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/vpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,46 @@ nodeunitShim({
test.done();
},

'dns getters correspond to CFN properties': (() => {

const tests: any = { };

const inputs = [
{dnsSupport: false, dnsHostnames: false},
// {dnsSupport: false, dnsHostnames: true} - this configuration is illegal so its not part of the permutations.
{dnsSupport: true, dnsHostnames: false},
{dnsSupport: true, dnsHostnames: true},
];

for (const input of inputs) {

tests[`[dnsSupport=${input.dnsSupport},dnsHostnames=${input.dnsHostnames}]`] = (test: Test) => {

const stack = getTestStack();
const vpc = new Vpc(stack, 'TheVPC', {
cidr: '192.168.0.0/16',
enableDnsHostnames: input.dnsHostnames,
enableDnsSupport: input.dnsSupport,
defaultInstanceTenancy: DefaultInstanceTenancy.DEDICATED,
});

expect(stack).to(haveResource('AWS::EC2::VPC', {
CidrBlock: '192.168.0.0/16',
EnableDnsHostnames: input.dnsHostnames,
EnableDnsSupport: input.dnsSupport,
InstanceTenancy: DefaultInstanceTenancy.DEDICATED,
}));

test.equal(input.dnsSupport, vpc.dnsSupportEnabled);
test.equal(input.dnsHostnames, vpc.dnsHostnamesEnabled);
test.done();

};
}

return tests;
})(),

'contains the correct number of subnets'(test: Test) {
const stack = getTestStack();
const vpc = new Vpc(stack, 'TheVPC');
Expand Down
36 changes: 32 additions & 4 deletions packages/@aws-cdk/aws-eks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ cluster.addResource('mypod', {
});
```

### Endpoint Access

You can configure the [cluster endpoint access](https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html) by using the `endpointAccess` property:

```typescript
const cluster = new eks.Cluster(this, 'hello-eks', {
version: eks.KubernetesVersion.V1_16,
endpointAccess: eks.EndpointAccess.PRIVATE // No access outside of your VPC.
});
```

The default value is `eks.EndpointAccess.PUBLIC_AND_PRIVATE`. Which means the cluster endpoint is accessible from outside of your VPC, and worker node traffic to the endpoint will stay within your VPC.


### Capacity

By default, `eks.Cluster` is created with a managed nodegroup with x2 `m5.large` instances. You must specify the kubernetes version for the cluster with the `version` property.
Expand Down Expand Up @@ -78,7 +92,7 @@ new eks.Cluster(this, 'cluster', {
To disable the default capacity, simply set `defaultCapacity` to `0`:

```ts
new eks.Cluster(this, 'cluster-with-no-capacity', {
new eks.Cluster(this, 'cluster-with-no-capacity', {
defaultCapacity: 0,
version: eks.KubernetesVersion.V1_16,
});
Expand All @@ -105,8 +119,8 @@ cluster.addCapacity('frontend-nodes', {

### Managed Node Groups

Amazon EKS managed node groups automate the provisioning and lifecycle management of nodes (Amazon EC2 instances)
for Amazon EKS Kubernetes clusters. By default, `eks.Nodegroup` create a nodegroup with x2 `t3.medium` instances.
Amazon EKS managed node groups automate the provisioning and lifecycle management of nodes (Amazon EC2 instances)
for Amazon EKS Kubernetes clusters. By default, `eks.Nodegroup` create a nodegroup with x2 `t3.medium` instances.

```ts
new eks.Nodegroup(stack, 'nodegroup', { cluster });
Expand All @@ -128,7 +142,7 @@ AWS Fargate is a technology that provides on-demand, right-sized compute
capacity for containers. With AWS Fargate, you no longer have to provision,
configure, or scale groups of virtual machines to run containers. This removes
the need to choose server types, decide when to scale your node groups, or
optimize cluster packing.
optimize cluster packing.

You can control which pods start on Fargate and how they run with Fargate
Profiles, which are defined as part of your Amazon EKS cluster.
Expand Down Expand Up @@ -348,6 +362,20 @@ new KubernetesResource(this, 'hello-kub', {
cluster.addResource('hello-kub', service, deployment);
```

##### Kubectl Environment

The resources are created in the cluster by running `kubectl apply` from a python lambda function. You can configure the environment of this function by specifying it at cluster instantiation. For example, this can useful in order to configure an http proxy:

```typescript
const cluster = new eks.Cluster(this, 'hello-eks', {
version: eks.KubernetesVersion.V1_16,
kubectlEnvironment: {
'http_proxy': 'http://proxy.myproxy.com'
}
});

```

#### Adding resources from a URL

The following example will deploy the resource manifest hosting on remote server:
Expand Down
27 changes: 25 additions & 2 deletions packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,22 @@ export class ClusterResourceHandler extends ResourceHandler {
}

function parseProps(props: any): aws.EKS.CreateClusterRequest {
return props?.Config ?? { };

const parsed = props?.Config ?? { };

// this is weird but these boolean properties are passed by CFN as a string, and we need them to be booleanic for the SDK.
// Otherwise it fails with 'Unexpected Parameter: params.resourcesVpcConfig.endpointPrivateAccess is expected to be a boolean'

if (typeof(parsed.resourcesVpcConfig?.endpointPrivateAccess) === 'string') {
parsed.resourcesVpcConfig.endpointPrivateAccess = parsed.resourcesVpcConfig.endpointPrivateAccess === 'true';
}

if (typeof(parsed.resourcesVpcConfig?.endpointPublicAccess) === 'string') {
parsed.resourcesVpcConfig.endpointPublicAccess = parsed.resourcesVpcConfig.endpointPublicAccess === 'true';
}

return parsed;

}

interface UpdateMap {
Expand All @@ -280,16 +295,24 @@ function analyzeUpdate(oldProps: Partial<aws.EKS.CreateClusterRequest>, newProps
const newVpcProps = newProps.resourcesVpcConfig || { };
const oldVpcProps = oldProps.resourcesVpcConfig || { };

const oldPublicAccessCidrs = new Set(oldVpcProps.publicAccessCidrs ?? []);
const newPublicAccessCidrs = new Set(newVpcProps.publicAccessCidrs ?? []);

return {
replaceName: newProps.name !== oldProps.name,
replaceVpc:
JSON.stringify(newVpcProps.subnetIds) !== JSON.stringify(oldVpcProps.subnetIds) ||
JSON.stringify(newVpcProps.securityGroupIds) !== JSON.stringify(oldVpcProps.securityGroupIds),
updateAccess:
newVpcProps.endpointPrivateAccess !== oldVpcProps.endpointPrivateAccess ||
newVpcProps.endpointPublicAccess !== oldVpcProps.endpointPublicAccess,
newVpcProps.endpointPublicAccess !== oldVpcProps.endpointPublicAccess ||
!setsEqual(newPublicAccessCidrs, oldPublicAccessCidrs),
replaceRole: newProps.roleArn !== oldProps.roleArn,
updateVersion: newProps.version !== oldProps.version,
updateLogging: JSON.stringify(newProps.logging) !== JSON.stringify(oldProps.logging),
};
}

function setsEqual(first: Set<string>, second: Set<string>) {
return first.size === second.size || [...first].every((e: string) => second.has(e));
}
39 changes: 36 additions & 3 deletions packages/@aws-cdk/aws-eks/lib/cluster-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,26 @@ import * as iam from '@aws-cdk/aws-iam';
import { ArnComponents, Construct, CustomResource, Lazy, Stack, Token } from '@aws-cdk/core';
import { CLUSTER_RESOURCE_TYPE } from './cluster-resource-handler/consts';
import { ClusterResourceProvider } from './cluster-resource-provider';
import { CfnClusterProps } from './eks.generated';
import { CfnClusterProps, CfnCluster } from './eks.generated';

export interface ClusterResourceProps extends CfnClusterProps {

/**
* Enable private endpoint access to the cluster.
*/
readonly endpointPrivateAccess: boolean;

/**
* Enable public endpoint access to the cluster.
*/
readonly endpointPublicAccess: boolean;

/**
* Limit public address with CIDR blocks.
*/
readonly publicAccessCidrs?: string[];

}

/**
* A low-level CFN resource Amazon EKS cluster implemented through a custom
Expand Down Expand Up @@ -32,7 +51,7 @@ export class ClusterResource extends Construct {

private readonly trustedPrincipals: string[] = [];

constructor(scope: Construct, id: string, props: CfnClusterProps) {
constructor(scope: Construct, id: string, props: ClusterResourceProps) {
super(scope, id);

const stack = Stack.of(this);
Expand Down Expand Up @@ -117,7 +136,21 @@ export class ClusterResource extends Construct {
resourceType: CLUSTER_RESOURCE_TYPE,
serviceToken: provider.serviceToken,
properties: {
Config: props,
// the structure of config needs to be that of 'aws.EKS.CreateClusterRequest' since its passed as is
// to the eks.createCluster sdk invocation.
Config: {
name: props.name,
version: props.version,
roleArn: props.roleArn,
encryptionConfig: props.encryptionConfig,
resourcesVpcConfig: {
subnetIds: (props.resourcesVpcConfig as CfnCluster.ResourcesVpcConfigProperty).subnetIds,
securityGroupIds: (props.resourcesVpcConfig as CfnCluster.ResourcesVpcConfigProperty).securityGroupIds,
endpointPublicAccess: props.endpointPublicAccess,
endpointPrivateAccess: props.endpointPrivateAccess,
publicAccessCidrs: props.publicAccessCidrs,
},
},
AssumeRoleArn: this.creationRole.roleArn,

// IMPORTANT: increment this number when you add new attributes to the
Expand Down

0 comments on commit 692864c

Please sign in to comment.