Skip to content

Commit

Permalink
Support for Transit Gateway Peers Imports and use in the Routes secti…
Browse files Browse the repository at this point in the history
…on. (#28)

* Feature - Support for existing Transit Gateway Peers to permit VPCs to route to them.

* - Prettier fix
  • Loading branch information
apmclean authored Mar 25, 2024
1 parent 263702f commit d99b9a7
Show file tree
Hide file tree
Showing 16 changed files with 3,403 additions and 2,690 deletions.
23 changes: 20 additions & 3 deletions config/config-walkthrough.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,23 @@ dxgws:
# REQUIRED: Existing Transit Gateway Route Table associated with the Direct Connect Gateway
existingDxGwTransitGatewayRouteTableId: tgw-rtb-12345

# OPTIONAL: These define EXISTING Transit Gateway Peers attached to an EXISTING Transit Gateway
# The Transit Gateway must be the same one you define under `transitGateways`.
# When creating the Transit Gateway using vpcBuilder:
# 1) Deploy the transit Gateway without modelling the `tgwPeers:`.
# 2) Attach / Accept the Transit Gateway Peer to the Transit Gateway deployed in 1)
# 3) Create a Transit Gateway Route Table and associate it with the Transit Gateway Peer
# 4) Model the `tgwPeers:` section below with the Transit Gateway ID, Route Table ID, and Attachment ID from above
# 5) Refer to the tgwPeers in `routesTo:` sections (static and default are supported) in your Transit Gateway Below
tgwPeers:
toUsEast2:
# REQUIRED: Existing Transit Gateway (see above if you're relying on vpcBuilder to create the TGW)
existingTgwId: tgw-12345
# REQUIRED: Existing Transit Gateway Attachment Identifier for the Direct Connect Gateway
existingTgwPeerTransitGatewayAttachId: tgw-attach-12345
# REQUIRED: Existing Transit Gateway Route Table associated with the Direct Connect Gateway
existingTgwPeerTransitGatewayRouteTableId: tgw-rtb-12345

# OPTIONAL|DEPENDANT. If any VPCs refer to, providers are defined, or VPNs are defined, this section is required.
# this describes the transit gateways to create (NOTE: at-present we only support one per config file).
transitGateways:
Expand All @@ -281,7 +298,7 @@ transitGateways:
defaultRoutes:
# REQUIRED: The name of the thing we're routing from. This can be a VPC, or a Provider name.
- vpcName: workload
# REQUIRED: The name of the thing we're routing toward. This can be a VPC, VPN, DxGw, or Provider name.
# REQUIRED: The name of the thing we're routing toward. This can be a VPC, VPN, DxGw, TgwPeer, or Provider name.
routesTo: egressViaNat
# OPTIONAL: If we're going to inspect the traffic. This must be the name of a firewall provider. Routes are adjusted automatically
# to assure it passes through the inspection VPC before arriving at its routesTo and vice versa.
Expand All @@ -294,7 +311,7 @@ transitGateways:
dynamicRoutes:
# REQUIRED: The name of the thing we're routing from. This can be a VPC, or a Provider name.
- vpcName: workload
# REQUIRED: The name of the thing we're routing toward. This can be a VPC, VPN, DxGw, or Provider name.
# REQUIRED: The name of the thing we're routing toward. This can be a VPC, VPN, DxGw, or Provider name (TgwPeers do not support propagation).
routesTo: workloadTwo
# OPTIONAL: The name of the firewall provider to inspect traffic. Routes adjust automatically.
# NOTE: inspectedBy is not available for VPN connections with a dynamic route. Use Static, or Default route instead.
Expand All @@ -305,7 +322,7 @@ transitGateways:
staticRoutes:
# REQUIRED: The name of the thing we're routing from. This can be a VPC, or a Provider name.
- vpcName: workload
# REQUIRED: The name of the thing we're routing toward. This can be a VPC, VPN, DxGw or Provider name.
# REQUIRED: The name of the thing we're routing toward. This can be a VPC, VPN, DxGw, TgwPeer or Provider name.
routesTo: toGroundDataCenterOne
# REQUIRED: the CIDR address for the static route entry
staticCidr: 192.168.168.0/24
Expand Down
73 changes: 73 additions & 0 deletions lib/abstract-buildertgwpeer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import {
IBuilderTgwStaticRoutes,
IBuilderTgwPeer,
IBuilderTgwPeerProps,
ITgwAttachType,
ITgwPropagateRouteAttachmentName,
ssmParameterImport,
ITgw,
ITgwRouteTable,
ITgwAttachment,
} from "./types";
import * as ssm from "aws-cdk-lib/aws-ssm";

export abstract class BuilderTgwPeer extends cdk.Stack implements IBuilderTgwPeer {
name: string;
globalPrefix: string;
// Always attached to a Transit Gateway
withTgw: true;
// Always false since this isn't VPC Based
tgwCreateTgwSubnets: false;
tgwAttachType: ITgwAttachType = "tgwPeer"
tgw: ITgw;
tgwRouteTable: ITgwRouteTable;
tgwRouteTableSsm: ssmParameterImport;
tgwAttachment: ITgwAttachment;
tgwAttachmentSsm: ssmParameterImport;
tgwPropagateRouteAttachmentNames: Array<ITgwPropagateRouteAttachmentName> =
[];
// Blackhole CIDRs not applicable for an imported peer connection
readonly tgwBlackHoleCidrs: [];
tgwStaticRoutes: Array<IBuilderTgwStaticRoutes> = [];
tgwDefaultRouteAttachmentName: ITgwPropagateRouteAttachmentName;
props: IBuilderTgwPeerProps;

protected constructor(scope: Construct, id: string, props: IBuilderTgwPeerProps) {
super(scope, id, props);
this.props = props;
this.globalPrefix = props.globalPrefix.toLowerCase();
}

// We only support imports, but this method is common to all stacks so needs to be present
saveTgwRouteInformation() {
}

async init() {}

createSsmParameters() {
const prefix =
`${this.props.ssmParameterPrefix}/networking/${this.globalPrefix}/tgwPeer/${this.name}`.toLowerCase();

this.tgwRouteTableSsm = {
name: `${prefix}/tgwRouteId`,
};
new ssm.StringParameter(this, `ssmTgwPeerTgwRouteTableSsm`, {
parameterName: `${prefix}/tgwRouteId`,
stringValue: this.tgwRouteTable.ref,
});

this.tgwAttachmentSsm = {
name: `${prefix}/tgwAttachId`,
};
new ssm.StringParameter(this, `ssmTgwPeerTgwAttachIdSsm`, {
parameterName: `${prefix}/tgwAttachId`,
stringValue: this.tgwAttachment.attrId,
});
}

// We only support imports, but this method is common to all stacks so needs to be present
attachToTGW() {
}
}
29 changes: 29 additions & 0 deletions lib/config/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,32 @@
],
"type": "object"
},
"IConfigTgwPeer": {
"additionalProperties": false,
"properties": {
"existingTgwId": {
"type": "string"
},
"existingTgwPeerTransitGatewayAttachId": {
"type": "string"
},
"existingTgwPeerTransitGatewayRouteTableId": {
"type": "string"
}
},
"required": [
"existingTgwId",
"existingTgwPeerTransitGatewayAttachId",
"existingTgwPeerTransitGatewayRouteTableId"
],
"type": "object"
},
"IConfigTgwPeers": {
"additionalProperties": {
"$ref": "#/definitions/IConfigTgwPeer"
},
"type": "object"
},
"IConfigTgwRoutes": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -611,6 +637,9 @@
"providers": {
"$ref": "#/definitions/IConfigProviders"
},
"tgwPeers": {
"$ref": "#/definitions/IConfigTgwPeers"
},
"transitGateways": {
"$ref": "#/definitions/IConfigTgws"
},
Expand Down
15 changes: 15 additions & 0 deletions lib/config/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ export interface IConfigDxGws {
[key: string]: IConfigDxGw;
}

/*
****** Transit Gateway Peers
*/

export interface IConfigTgwPeer {
existingTgwId: string;
existingTgwPeerTransitGatewayAttachId: string;
existingTgwPeerTransitGatewayRouteTableId: string;
}

export interface IConfigTgwPeers {
[key: string]: IConfigTgwPeer;
}

/*
****** dns:
*/
Expand Down Expand Up @@ -230,6 +244,7 @@ export interface IConfig {
vpcs: IConfigVpcs;
vpns?: IConfigVpns;
dxgws?: IConfigDxGws;
tgwPeers?: IConfigTgwPeers;
dns?: IConfigDns;
transitGateways?: IConfigTgws;
}
75 changes: 71 additions & 4 deletions lib/config/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export class ConfigParser {
this.dxGwAssureRequiredArguments();
}

//** TgwPeers
if (configRaw.hasOwnProperty("tgwPeers")) {
this.tgwPeerAssureRequiredArguments();
}

// ** Transits
if (configRaw.hasOwnProperty("transitGateways")) {
this.verifyOnlyOneTransitGateway();
Expand Down Expand Up @@ -515,6 +520,7 @@ export class ConfigParser {
...this.allVpnNames(),
...this.allProviderNames(),
...this.allDxGwNames(),
...this.allTgwPeerNames(),
];
}

Expand Down Expand Up @@ -568,6 +574,27 @@ export class ConfigParser {
return dxGwNames;
}

tgwPeerNameExists(checkTgwPeerName: string) {
if (this.configRaw.tgwPeers) {
for (const tgwPeerName of Object.keys(this.configRaw.tgwPeers)) {
if (tgwPeerName == checkTgwPeerName) {
return true;
}
}
}
return false;
}

allTgwPeerNames(): Array<string> {
const tgwPeerNames: Array<string> = [];
if (this.configRaw.tgwPeers) {
for (const tgwPeerName of Object.keys(this.configRaw.tgwPeers)) {
tgwPeerNames.push(tgwPeerName);
}
}
return tgwPeerNames;
}

verifyResourceNamesUnique() {
// Find all our names in our config file.
const allNames = this.allResourceNames();
Expand All @@ -578,7 +605,7 @@ export class ConfigParser {
uniqueList.forEach((uniqueName) => {
if (countOccurrences(allNames, uniqueName) > 1) {
throw new Error(
`Providers, VPNs, VPCs, and DxGws must be named uniquely within the config file. Duplicate name ${uniqueName} was found`,
`Providers, VPNs, VPCs, TGW Peers, and DxGws must be named uniquely within the config file. Duplicate name ${uniqueName} was found`,
);
}
});
Expand Down Expand Up @@ -699,6 +726,36 @@ export class ConfigParser {
}
}

// Transit Gateway peers are currently always imported. Assure expected format exists for our values.
tgwPeerAssureRequiredArguments() {
for (const tgwPeerName of Object.keys(this.configRaw.tgwPeers)) {
const configStanza = this.configRaw.tgwPeers[tgwPeerName];
if (!configStanza.existingTgwId.startsWith("tgw-")) {
throw new Error(
`TgwPeer: ${tgwPeerName}: Existing Transit Gateway 'existingTgwId' must begin with tgw-`,
);
}
if (
!configStanza.existingTgwPeerTransitGatewayAttachId.startsWith(
"tgw-attach-",
)
) {
throw new Error(
`TgwPeer: ${tgwPeerName}: Transit Gateway Attachment Value 'existingTgwPeerTransitGatewayAttachId' must begin with tgw-attach-`,
);
}
if (
!configStanza.existingTgwPeerTransitGatewayRouteTableId.startsWith(
"tgw-rtb-",
)
) {
throw new Error(
`TgwPeer: ${tgwPeerName}: Transit Gateway Route Table Value 'existingTgwPeerTransitGatewayRouteTableId' must begin with tgw-rtb-`,
);
}
}
}

dnsVerifyRequiredArguments() {
for (const dnsConfigName of Object.keys(this.configRaw.dns)) {
const configStanza = this.configRaw.dns[dnsConfigName];
Expand Down Expand Up @@ -798,8 +855,10 @@ export class ConfigParser {
}

// Where present, inspectedBy routes a valid
// Dynamic route inspectedBy where routesTo is a VPN is not supported
twgRouteInspectedByValid(configStanza: any) {
// 1) Dynamic route with inspectedBy where routesTo is a VPN is not supported
// 2) Dynamic routes where routeTo is a Transit Gateway Peer are not supported:
// https://aws.amazon.com/transit-gateway/faqs/
twgRouteConfigValid(configStanza: any) {
const routeTypes = ["staticRoutes", "dynamicRoutes", "defaultRoutes"];
const routeHuman: Record<string, string> = {
staticRoutes: "static route",
Expand All @@ -810,6 +869,14 @@ export class ConfigParser {
routeTypes.forEach((routeType) => {
if (configStanza[routeType]) {
for (const route of configStanza[routeType]) {
if (routeType == "dynamicRoutes") {
if (this.tgwPeerNameExists(route.routesTo)) {
throw new Error(
`A Transit Gateway Peer as the 'routesTo' using Dynamic Routing is not supported. Implement via Static or Default Route instead.`,
);
}
}
// Where present, inspectedBy routes a valid
if (route.inspectedBy) {
if (!this.providerNameExists(route.inspectedBy, true)) {
throw new Error(
Expand Down Expand Up @@ -839,7 +906,7 @@ export class ConfigParser {
// vpcName is a VPC. routesTo is valid. For all Route Types
this.twgRouteNamesValid(configStanza);
// InspectedBy - if configured for static, dynamic, default points to a valid firewall
this.twgRouteInspectedByValid(configStanza);
this.twgRouteConfigValid(configStanza);
}
}
}
Expand Down
Loading

0 comments on commit d99b9a7

Please sign in to comment.