Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5326284
Added BackendDefaults to Virtual Node
sshver Oct 29, 2020
e476ae7
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Nov 3, 2020
dbc5870
Modeled TLSClientValidation to enforce trust in file or acm cetificate
sshver Nov 4, 2020
5b4c97b
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Nov 10, 2020
b4b7f95
Added check to have only one backendDefault per virtual node
sshver Nov 11, 2020
ce35692
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Nov 11, 2020
4aea038
adding backend validation context
sshver Nov 12, 2020
25208a4
created a seperate module validation-context
sshver Nov 12, 2020
5b260be
Created a seperate client-policy file as client policy wont be only s…
sshver Nov 18, 2020
a55a305
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Nov 18, 2020
7ca64da
Updated comments and removed newlines
sshver Nov 18, 2020
01be91b
1. Addressed comments
sshver Nov 24, 2020
7e2ddbc
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Nov 24, 2020
49824b1
Added skeleton L2 for ACMPCA and addressed comments
sshver Nov 30, 2020
0306a36
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Nov 30, 2020
045d2a7
Fixed minor comments
sshver Dec 2, 2020
2cad882
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Dec 2, 2020
20c0e96
Fixed comments
sshver Dec 3, 2020
d36fc42
Added validation check to ensure certificateAuthorityArns is not empty
sshver Dec 4, 2020
4e07587
Minor fix
sshver Dec 4, 2020
da25261
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Dec 4, 2020
41d2c12
Updated README and fixed build errors
sshver Dec 4, 2020
d18fdc4
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Dec 4, 2020
4ec4b21
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Dec 4, 2020
8b0b863
Minor changes
sshver Dec 7, 2020
bd2d85e
Updated README.md
sshver Dec 7, 2020
8b707ce
Merge remote-tracking branch 'upstream/master' into feature/backendDe…
sshver Dec 7, 2020
de939c3
Merge branch 'master' into feature/backendDefaults
mergify[bot] Dec 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appmesh/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './virtual-gateway';
export * from './virtual-gateway-listener';
export * from './gateway-route';
export * from './gateway-route-spec';
export * from './validation-context';
146 changes: 146 additions & 0 deletions packages/@aws-cdk/aws-appmesh/lib/validation-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import * as cdk from '@aws-cdk/core';
import { CfnVirtualNode } from './appmesh.generated';

/**
* Defines the TLS validation context trust.
*/
export abstract class TLSClientValidation {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend Client Policies won't be the only shapes that support validation contexts. Listeners could support validation contexts in the future (ex. mTLS).

Unless there is a limitation that forces us to have 2 different shapes for client validation and server validation, I'd recommend changing the name here to a more general TLSValidationContext. And creating a separate file for client policy shapes.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I have updated the PR.

/**
* TLS validation context trust for a local file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TLS validation provided from the local file system

This struct tells the envoy where to fetch the validation context from

same comment below

*/
public static fileTrust(props: FileTrustOptions): TLSClientValidation {
return new FileTrust(props);
}

/**
* TLS validation context trust for ACM Private Certificate Authority (CA).
*/
public static acmTrust(props: ACMTrustOptions): TLSClientValidation {
return new ACMTrust(props);
}

/**
* Returns Trust context based on trust type.
*/
public abstract bind(scope: cdk.Construct): TLSValidationConfig;
}
/**
* Represents a Transport Layer Security (TLS) validation context trust for a local file
*/
class FileTrust extends TLSClientValidation {
/**
* Path to the Certificate Chain file on the file system where the Envoy is deployed.
*/
readonly certificateChain: string;

constructor(props: FileTrustOptions) {
super();
this.certificateChain = props.certificateChain;
}

public bind(_scope: cdk.Construct): TLSValidationConfig {
return {
tlsValidation: {
trust: {
file: {
certificateChain: this.certificateChain,
},
},
},
};
}
}

/**
* Represents a TLS validation context trust for an AWS Certicate Manager (ACM) certificate.
*/
class ACMTrust extends TLSClientValidation {
/**
* Amazon Resource Name of the Certificates
*/
readonly certificateAuthorityArns: string[];

constructor(props: ACMTrustOptions) {
super();
this.certificateAuthorityArns = props.certificateAuthorityArns;
}

public bind(_scope: cdk.Construct): TLSValidationConfig {
return {
tlsValidation: {
trust: {
acm: {
certificateAuthorityArns: this.certificateAuthorityArns,
},
},
},
};
}
}

/**
* Properties of TLS validation context
*/
export interface TLSValidationConfig {
/**
* Represents single validation context property
*/
readonly tlsValidation: CfnVirtualNode.TlsValidationContextProperty;
}

/**
* Default configuration that is applied to all backends for the virtual node.
* Any configuration defined will be overwritten by configurations specified for a particular backend.
*/
export interface ClientPolicy {
/**
* Client policy for TLS
*/
readonly tlsClientPolicy: TLSClientPolicyOptions;
}

/**
* TLS Connections with downstream server will always be enforced if True
*/
export interface TLSClientPolicyOptions {
/**
* TLS enforced if True.
*
* @default - True
*/
readonly enforce?: boolean;

/**
* TLS enforced on these ports. If not specified it is enforced on all ports.
*
* @default - none
*/
readonly ports?: number[];

/**
* Policy used to determine if the TLS certificate the server presents is accepted
*
* @default - none
*/
readonly validation: TLSClientValidation;
}

/**
* ACM Trust Properties
*/
export interface ACMTrustOptions {
/**
* Amazon Resource Names (ARN) of trusted ACM Private Certificate Authorities
*/
readonly certificateAuthorityArns: string[];
}

/**
* File Trust Properties
*/
export interface FileTrustOptions {
/**
* Path to the Certificate Chain file on the file system where the Envoy is deployed.
*/
readonly certificateChain: string;
}
39 changes: 39 additions & 0 deletions packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Construct } from 'constructs';
import { CfnVirtualNode } from './appmesh.generated';
import { IMesh, Mesh } from './mesh';
import { AccessLog } from './shared-interfaces';
import { ClientPolicy } from './validation-context';
import { VirtualNodeListener, VirtualNodeListenerConfig } from './virtual-node-listener';
import { IVirtualService } from './virtual-service';

Expand Down Expand Up @@ -94,6 +95,13 @@ export interface VirtualNodeBaseProps {
* @default - No access logging
*/
readonly accessLog?: AccessLog;

/**
* Default Configuration Virtual Node uses to communicate with Vritual Service
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: Vritual => Virtual

*
* @default - No Config
*/
readonly backendDefaults?: ClientPolicy;
}

/**
Expand Down Expand Up @@ -175,6 +183,7 @@ export class VirtualNode extends VirtualNodeBase {
*/
public readonly mesh: IMesh;

private readonly backendDefaults = new Array<CfnVirtualNode.BackendDefaultsProperty>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use an Array here? This could just be CfnVirtualNode.BackendDefaultsProperty

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the backendDefaults is of type CfnVirtualNode.BackendDefaultsProperty and a private member of VirtualNode, I would have to initialize it say, with an empty object because it cannot be undefined. There are no options to omit the empty object and all the resources would have backendDefaults: {}, if not provided by the client. Whereas if we use Array we could set omitEmptyArray true.

Copy link
Owner Author

@sshver sshver Nov 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or something like this would work as well.

    if (props.backendDefaults) {
      this.backendDefaults = this.addBackendDefaults(props.backendDefaults);
    } else {
      this.backendDefaults = {};
    }

 const node = new CfnVirtualNode(this, 'Resource', {
     backendDefaults: Object.keys(this.backendDefaults).length != 0 ? this.backendDefaults : undefined,
. . .
 });

private readonly backends = new Array<CfnVirtualNode.BackendProperty>();
private readonly listeners = new Array<VirtualNodeListenerConfig>();

Expand All @@ -187,6 +196,9 @@ export class VirtualNode extends VirtualNodeBase {

props.backends?.forEach(backend => this.addBackend(backend));
props.listeners?.forEach(listener => this.addListener(listener));
if (props.backendDefaults) {
this.addBackendDefaults(props.backendDefaults);
}
const accessLogging = props.accessLog?.bind(this);

const node = new CfnVirtualNode(this, 'Resource', {
Expand All @@ -195,6 +207,7 @@ export class VirtualNode extends VirtualNodeBase {
spec: {
backends: cdk.Lazy.anyValue({ produce: () => this.backends }, { omitEmptyArray: true }),
listeners: cdk.Lazy.anyValue({ produce: () => this.listeners.map(listener => listener.listener) }, { omitEmptyArray: true }),
backendDefaults: cdk.Lazy.anyValue({ produce: () => this.backendDefaults[0] }, { omitEmptyArray: true }),
serviceDiscovery: {
dns: props.dnsHostName !== undefined ? { hostname: props.dnsHostName } : undefined,
awsCloudMap: props.cloudMapService !== undefined ? {
Expand All @@ -216,6 +229,24 @@ export class VirtualNode extends VirtualNodeBase {
resourceName: this.physicalName,
});
}
/**
* Adds Default Backend Configuration for virtual node to communicate with Virtual Services.
*/
public addBackendDefaults(clientPolicy: ClientPolicy) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we need this method?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that the backendDefault can be added to virtual node after it has been created. Hence a public method.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, I have made this method private.

if (this.backendDefaults.length === 0) {
this.backendDefaults.push({
clientPolicy: {
tls: {
enforce: clientPolicy.tlsClientPolicy.enforce ?? true,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically we could just do clientPolicy.tlsClientPolicy.enforce and accept the undefined since the API defaults to true. Not sure what the right thing to do here for CDK is

validation: clientPolicy.tlsClientPolicy.validation.bind(this).tlsValidation,
ports: clientPolicy.tlsClientPolicy.ports,
},
},
});
} else {
throw new Error('Virtual Node can have only one backend default');
}
}

/**
* Utility method to add an inbound listener for this VirtualNode
Expand All @@ -231,11 +262,19 @@ export class VirtualNode extends VirtualNodeBase {
this.backends.push({
virtualService: {
virtualServiceName: virtualService.virtualServiceName,
clientPolicy: virtualService.clientPolicy ? {
tls: {
enforce: virtualService.clientPolicy?.tlsClientPolicy.enforce ?? true,
validation: virtualService.clientPolicy?.tlsClientPolicy.validation.bind(this).tlsValidation,
ports: virtualService.clientPolicy?.tlsClientPolicy.ports,
},
} : undefined,
},
});
}
}


function renderAttributes(attrs?: {[key: string]: string}) {
if (attrs === undefined) { return undefined; }
return Object.entries(attrs).map(([key, value]) => ({ key, value }));
Expand Down
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnVirtualService } from './appmesh.generated';
import { IMesh, Mesh } from './mesh';
import { ClientPolicy } from './validation-context';
import { IVirtualNode } from './virtual-node';
import { IVirtualRouter } from './virtual-router';

Expand All @@ -27,6 +28,11 @@ export interface IVirtualService extends cdk.IResource {
* The Mesh which the VirtualService belongs to
*/
readonly mesh: IMesh;

/**
* Client policy for Virtual Service
*/
readonly clientPolicy?: ClientPolicy;
}

/**
Expand Down Expand Up @@ -57,6 +63,13 @@ export interface VirtualServiceBaseProps {
* @default - At most one of virtualRouter and virtualNode is allowed.
*/
readonly virtualNode?: IVirtualNode;

/**
* Client policy for Virtual Service
*
* @default - as specified in backend defaults
*/
readonly clientPolicy?: ClientPolicy;
}

/**
Expand Down Expand Up @@ -119,6 +132,11 @@ export class VirtualService extends cdk.Resource implements IVirtualService {
*/
public readonly mesh: IMesh;

/**
* Client policy for Virtual Service
*/
public readonly clientPolicy?: ClientPolicy;

private readonly virtualServiceProvider?: CfnVirtualService.VirtualServiceProviderProperty;

constructor(scope: Construct, id: string, props: VirtualServiceProps) {
Expand All @@ -131,6 +149,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService {
}

this.mesh = props.mesh;
this.clientPolicy = props.clientPolicy;

// Check which provider to use node or router (or neither)
if (props.virtualRouter) {
Expand Down
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,20 @@
]
},
"Spec": {
"BackendDefaults": {
"ClientPolicy": {
"TLS": {
"Enforce": true,
"Validation": {
"Trust": {
"File": {
"CertificateChain": "path-to-file"
}
}
}
}
}
},
"Backends": [
{
"VirtualService": {
Expand Down Expand Up @@ -739,6 +753,20 @@
]
},
"Spec": {
"BackendDefaults": {
"ClientPolicy": {
"TLS": {
"Enforce": true,
"Validation": {
"Trust": {
"File": {
"CertificateChain": "path-to-file"
}
}
}
}
}
},
"Listeners": [
{
"HealthCheck": {
Expand Down
15 changes: 15 additions & 0 deletions packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ const node2 = mesh.addVirtualNode('node2', {
unhealthyThreshold: 2,
},
})],
backendDefaults: {
tlsClientPolicy: {
validation: appmesh.TLSClientValidation.fileTrust({
certificateChain: 'path-to-file',
}),
},
},
backends: [
new appmesh.VirtualService(stack, 'service-3', {
virtualServiceName: 'service3.domain.local',
Expand All @@ -95,6 +102,14 @@ const node3 = mesh.addVirtualNode('node3', {
accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'),
});

node3.addBackendDefaults({
tlsClientPolicy: {
validation: appmesh.TLSClientValidation.fileTrust({
certificateChain: 'path-to-file',
}),
},
});

router.addRoute('route-2', {
routeTargets: [
{
Expand Down
Loading