diff --git a/backend/src/controllers/terraform.ts b/backend/src/controllers/terraform.ts index ea9924d..86ec820 100644 --- a/backend/src/controllers/terraform.ts +++ b/backend/src/controllers/terraform.ts @@ -23,6 +23,7 @@ import {rootBlockSplitBackend} from "../terraform/terraform"; import {internalErrorHandler} from "../types/errorHandler"; import {TerraformResource} from "../types/terraform"; import {jsonToHcl} from "../util"; +import {AwsLoadBalancer} from "../terraform/awsLoadBalancer"; export const createTerraformSettings = (req: Request, res: Response): void => { const provider = req.body.settings?.provider as "aws" | "google" | "azure"; @@ -31,6 +32,7 @@ export const createTerraformSettings = (req: Request, res: Response): void => { const allowSsh = req.body.settings.allowSsh ?? false; const allowEgressWeb = req.body.settings.allowEgressWeb ?? false; const allowIngressWeb = req.body.settings.allowIngressWeb ?? false; + const autoLoadBalance = req.body.settings.autoLoadBalance ?? false; //Only needed for google const project = @@ -88,7 +90,30 @@ export const createTerraformSettings = (req: Request, res: Response): void => { return; } - const [gce, lambda, networkedResources] = splitForPrefab(resources); + /* eslint-disable prefer-const */ + let [gce, lambda, networkedResources] = splitForPrefab(resources); + /* eslint-enable prefer-const */ + + if (autoLoadBalance) { + networkedResources = [ + ...networkedResources, + new AwsLoadBalancer( + `http_load_balancer`, + "TBD", + "application", + true, + "TBD", + "TBD", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + "http-load-balancer" + ) + ]; + } const network = networkedResources.length > 0 && provider === "aws" diff --git a/backend/src/index.ts b/backend/src/index.ts index eb03af9..52f4b5e 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -15,46 +15,43 @@ server.serve("/about"); server.serve("/contact"); server.route("/", mainRouter); -// import {testToFileAws} from "./util"; -// import {Ec2} from "./terraform/ec2"; -// import {prefabNetwork} from "./terraform/prefab"; -// import {S3} from "./terraform/s3"; -// import {GlacierVault} from "./terraform/glacierVault"; -// import {DynamoDb} from "./terraform/DynamoDb"; +import {testToFileAws} from "./util"; +import {Ec2} from "./terraform/ec2"; +import {prefabNetwork} from "./terraform/prefab"; +import {AwsLoadBalancer} from "./terraform/awsLoadBalancer"; -// testToFileAws( -// "/home/brennan/aws_test/devxp.tf", -// prefabNetwork( -// { -// ec2: [new Ec2("AUTO_UBUNTU", "t2.micro", "instance_a", true)], -// s3: [ -// new S3( -// "devxp_test_bucket_a", -// false, -// false, -// "devxp-test-bucket-a" -// ) -// ], -// glacier: new GlacierVault( -// "devxp_test_vault", -// false, -// "devxp-test-vault" -// ), -// dynamo: new DynamoDb("devxp_test_dynamo_db", [ -// { -// name: "field1", -// type: "S", -// isHash: true -// } -// ]) -// }, -// { -// ssh: true, -// webEgress: true, -// webIngress: true -// } -// ) -// ); +testToFileAws( + "/home/brennan/aws_test/devxp.tf", + prefabNetwork( + { + ec2: [ + new Ec2("AUTO_UBUNTU", "t2.micro", "instance_a", true), + new Ec2("AUTO_UBUNTU", "t2.micro", "instance_b", true), + new Ec2("AUTO_UBUNTU", "t2.micro", "instance_c", true) + ], + load_balancer: new AwsLoadBalancer( + `http_load_balancer`, + "TBD", + "application", + true, + "TBD", + "TBD", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + "http-load-balancer" + ) + }, + { + ssh: true, + webEgress: true, + webIngress: true + } + ) +); mongoose.connection.on( "error", diff --git a/backend/src/terraform/awsLoadBalancer.ts b/backend/src/terraform/awsLoadBalancer.ts new file mode 100644 index 0000000..60909f2 --- /dev/null +++ b/backend/src/terraform/awsLoadBalancer.ts @@ -0,0 +1,97 @@ +import {jsonRoot} from "./util"; +import {Resource} from "./resource"; +import {load_balancer_type} from "../types/terraform"; +import {arr} from "../util"; +import {Ec2} from "./ec2"; + +export interface AwsLoadBalancer { + vpc: string; + internal: boolean; + type: load_balancer_type; + securityGroups: string[]; + subnet: string[]; + protocol: string; + port: number; + instances: Ec2[]; + enable_http2: boolean; + enable_deletion_protection: boolean; +} +export class AwsLoadBalancer + extends Resource + implements AwsLoadBalancer +{ + constructor( + id: string, + + vpc: string, + type: load_balancer_type, + internal: boolean, + securityGroups: string[] | string, + subnet: string | string[], + protocol = "HTTP", + port = 80, + + instances: Ec2[] = [], + + enable_http2 = false, + enable_deletion_protection = true, + + autoIam?: boolean, + name?: string + ) { + super(id, "AwsLoadBalancer", autoIam, name); + + this.vpc = vpc; + this.type = type; + this.internal = internal; + this.securityGroups = arr(securityGroups); + this.subnet = arr(subnet); + this.protocol = protocol; + this.port = port; + this.instances = instances; + this.enable_http2 = enable_http2; + this.enable_deletion_protection = enable_deletion_protection; + } + + //Returns a resource block + toJSON() { + return [ + jsonRoot("aws_lb", this.id, { + name: this.name, + internal: this.internal, + security_groups: this.securityGroups.map( + g => `\${aws_security_group.${g}.id}` + ), + subnets: this.subnet.map(s => `\${aws_subnet.${s}.id}`), + enable_http2: this.enable_http2, + enable_deletion_protection: this.enable_deletion_protection + }), + jsonRoot("aws_lb_target_group", `${this.id}_target`, { + name: `${this.name}-target`, + port: this.port, + protocol: this.protocol, + vpc_id: `\${aws_vpc.${this.vpc}.id}` + }), + jsonRoot("aws_lb_listener", `${this.id}_listener`, { + port: this.port, + protocol: this.protocol, + load_balancer_arn: `\${aws_lb.${this.id}.id}`, + default_action: { + type: "forward", + target_group_arn: `\${aws_lb_target_group.${this.id}_target.arn}` + } + }), + ...this.instances.map(ec2 => + jsonRoot( + "aws_lb_target_group_attachment", + `${this.id}_${ec2.id}_attachment`, + { + target_group_arn: `\${aws_lb_target_group.${this.id}_target.arn}`, + target_id: `\${aws_instance.${ec2.id}.id}`, + port: this.port + } + ) + ) + ]; + } +} diff --git a/backend/src/terraform/awsVpc.ts b/backend/src/terraform/awsVpc.ts index d383413..560e169 100644 --- a/backend/src/terraform/awsVpc.ts +++ b/backend/src/terraform/awsVpc.ts @@ -4,20 +4,23 @@ import {AwsSubnet} from "./awsSubnet"; import {AwsInternetGateway} from "./AwsInternetGateway"; import {AwsRouteTable} from "./AwsRouteTable"; import {AwsRoute} from "./AwsRoute"; +import {awsRegion} from "../types/terraform"; export interface AwsVpc { cidr_block: string; private_cidr: string; - public_cidr: string; + public_cidr: string[]; privateDns: boolean; + zones: awsRegion[]; } export class AwsVpc extends Resource implements AwsVpc { constructor( cidr_block: string, private_cidr: string, - public_cidr: string, + public_cidr: string[], id: string, + zones: awsRegion[] = ["us-west-2a"], autoIam?: boolean, name?: string, privateDns = false @@ -27,19 +30,20 @@ export class AwsVpc extends Resource implements AwsVpc { this.private_cidr = private_cidr; this.public_cidr = public_cidr; this.privateDns = privateDns; + this.zones = zones; } //Returns an array of resource blocks toJSON() { const gatewayId = `${this.id}_internetgateway`; const publicRouteTableId = `${this.id}_routetable_pub`; - const privateRouteTableId = `${this.id}_routetable_priv`; + //const privateRouteTableId = `${this.id}_routetable_priv`; const publicSubetId = `${this.id}_subnet_public`; - const privateSubnetId = `${this.id}_subnet_private`; + //const privateSubnetId = `${this.id}_subnet_private`; return [ //PRIVATE - new AwsSubnet( + /*new AwsSubnet( this.id, this.private_cidr, false, @@ -53,17 +57,20 @@ export class AwsVpc extends Resource implements AwsVpc { subnet_id: `\${aws_subnet.${privateSubnetId}.id}`, route_table_id: `\${aws_route_table.${privateRouteTableId}.id}` } - ), + ),*/ //----------------------------------------------------------------// //PUBLIC - new AwsSubnet( - this.id, - this.public_cidr, - true, - publicSubetId - ).toJSON(), + ...this.zones.map((zone, i) => + new AwsSubnet( + this.id, + this.public_cidr[i] ?? this.public_cidr[0], + true, + `${publicSubetId}${i}`, + zone + ).toJSON() + ), new AwsInternetGateway(gatewayId, this.id).toJSON(), new AwsRouteTable( publicRouteTableId, @@ -86,7 +93,7 @@ export class AwsVpc extends Resource implements AwsVpc { "aws_route_table_association", `${this.id}_subnet_public_assoc`, { - subnet_id: `\${aws_subnet.${publicSubetId}.id}`, + subnet_id: `\${aws_subnet.${publicSubetId}0.id}`, route_table_id: `\${aws_route_table.${publicRouteTableId}.id}` } ), diff --git a/backend/src/terraform/prefab.ts b/backend/src/terraform/prefab.ts index 693fac9..8820502 100644 --- a/backend/src/terraform/prefab.ts +++ b/backend/src/terraform/prefab.ts @@ -2,6 +2,7 @@ import {Firewall, TerraformResource} from "../types/terraform"; import {arr} from "../util"; import {AwsIamInstanceProfile} from "./AwsIamInstanceProfile"; import {AwsIamRolePolicyAttachment} from "./awsIamRolePolicyAttachment"; +import {AwsLoadBalancer} from "./awsLoadBalancer"; import {AwsSecurityGroup} from "./AwsSecurityGroup"; import {AwsVpc} from "./awsVpc"; //import {AwsVpcEndpoint} from "./AwsVpcEndpoint"; @@ -13,7 +14,12 @@ import {IamRole} from "./iamRole"; import {lambdaFunction} from "./lambdaFunction"; import {S3} from "./s3"; -export type PrefabSupports = Ec2 | S3 | GlacierVault | DynamoDb; +export type PrefabSupports = + | Ec2 + | S3 + | GlacierVault + | DynamoDb + | AwsLoadBalancer; export const splitForPrefab = ( resources: TerraformResource[] @@ -47,8 +53,9 @@ export const prefabNetworkFromArr = ( webCidr?: string[]; }, vpc_cidr = "10.0.0.0/16", - public_cidr = "10.0.0.0/24", - private_cidr = "10.0.128.0/24", + public_cidr = "10.0.0.0/25", + public_cidr_2 = "10.0.128.0/25", + private_cidr = "10.0.128.0/25", vpc = "devxp_vpc", securityGroup = "devxp_security_group" ) => @@ -61,11 +68,15 @@ export const prefabNetworkFromArr = ( ) as DynamoDb[], glacier: resources.filter( r => r.type.toLowerCase() === "glaciervault" - ) as GlacierVault[] + ) as GlacierVault[], + load_balancer: resources.filter( + r => r.type.toLowerCase() === "awsloadbalancer" + ) as AwsLoadBalancer[] }, rules, vpc_cidr, public_cidr, + public_cidr_2, private_cidr, vpc, securityGroup @@ -77,6 +88,7 @@ export const prefabNetwork = ( s3?: S3[] | S3; glacier?: GlacierVault[] | GlacierVault; dynamo?: DynamoDb[] | DynamoDb; + load_balancer?: AwsLoadBalancer[] | AwsLoadBalancer; }, rules: { ssh?: boolean; @@ -88,8 +100,9 @@ export const prefabNetwork = ( webCidr?: string[]; }, vpc_cidr = "10.0.0.0/16", - public_cidr = "10.0.0.0/24", - private_cidr = "10.0.128.0/24", + public_cidr = "10.0.0.0/25", + public_cidr_2 = "10.0.128.0/25", + private_cidr = "10.0.128.0/25", vpc = "devxp_vpc", securityGroup = "devxp_security_group" ): TerraformResource[] => { @@ -103,7 +116,7 @@ export const prefabNetwork = ( 2, //TODO: Find a way to put this in the private subnet - `${vpc}_subnet_public`, + `${vpc}_subnet_public0`, //`${vpc}_subnet_private`, securityGroup ) @@ -117,6 +130,24 @@ export const prefabNetwork = ( const dbs = arr(resources.dynamo ?? []).map( (db: DynamoDb) => new DynamoDb(db.id, db.attributes, true, db.name) ); + const lbs = arr(resources.load_balancer ?? []).map( + (lb: AwsLoadBalancer) => + new AwsLoadBalancer( + lb.id, + vpc, + lb.type, + true, + [securityGroup], + [`${vpc}_subnet_public0`, `${vpc}_subnet_public1`], + lb.protocol, + lb.port, + instances, + lb.enable_http2, + lb.enable_deletion_protection, + false, + lb.name + ) + ); const policies = [...buckets, ...vaults, ...dbs].map( bucket => `${bucket.id}_iam_policy0` @@ -237,12 +268,14 @@ export const prefabNetwork = ( new AwsVpc( vpc_cidr, private_cidr, - public_cidr, + [public_cidr, public_cidr_2], vpc, + ["us-west-2a", "us-west-2b"], false, undefined, true ), - new AwsSecurityGroup(securityGroup, vpc, firewalls) + new AwsSecurityGroup(securityGroup, vpc, firewalls), + ...lbs ]; }; diff --git a/backend/src/types/terraform.ts b/backend/src/types/terraform.ts index 6112a52..6e1966f 100644 --- a/backend/src/types/terraform.ts +++ b/backend/src/types/terraform.ts @@ -24,6 +24,7 @@ import {IamRole} from "../terraform/iamRole"; import {AwsRoute as AwsRouteResource} from "../terraform/AwsRoute"; import {AwsVpcEndpoint} from "../terraform/AwsVpcEndpoint"; import {DynamoDb} from "../terraform/DynamoDb"; +import {AwsLoadBalancer} from "../terraform/awsLoadBalancer"; // ---------------------------------Variable---------------------------------- // export type VariableType = @@ -268,7 +269,8 @@ export type TerraformResource = | IamRole | AwsVpcEndpoint | AwsRouteResource - | DynamoDb; + | DynamoDb + | AwsLoadBalancer; export interface PolicyStatement { actions: string[]; @@ -326,8 +328,11 @@ export type char = export type digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; export type countryCode = "us" | "af" | "ap" | "ca" | "eu" | "me" | "sa"; export type awsZone = `${countryCode}-${string}-${digit}`; +export type awsRegion = `${awsZone}${char}`; export type gcpRegion = `${string}-${string}${digit}`; -export type gcpZone = `${string}-${string}${digit}-${char}`; +export type gcpZone = `${gcpRegion}-${char}`; + +export type load_balancer_type = "application" | "gateway" | "network"; // ----------------------------Terraform Root-------------------------------- // diff --git a/backend/src/util.ts b/backend/src/util.ts index ec2e995..75ddb2a 100644 --- a/backend/src/util.ts +++ b/backend/src/util.ts @@ -25,11 +25,6 @@ export const testToFile = ( resources ); - /* - fs.writeFileSync(`${filename}.json`, JSON.stringify(root, null, 2), { - flag: "w" - }); - */ fs.writeFileSync(filename, jsonToHcl(root), { flag: "w" }); @@ -82,7 +77,7 @@ export const jsonToHcl = (json: string | Record) => { //Remove incorrect block as attribute styles hcl = hcl.replace( - /(lifecycle|ingress|egress|statement|filter|route|notification|ttl|attribute) = {/g, + /(lifecycle|ingress|egress|statement|filter|route|notification|ttl|attribute|default_action) = {/g, (_match, $1) => `${$1} {` ); diff --git a/backend/src/validators/terraformValidator.ts b/backend/src/validators/terraformValidator.ts index 4c05cb5..38330e9 100644 --- a/backend/src/validators/terraformValidator.ts +++ b/backend/src/validators/terraformValidator.ts @@ -55,6 +55,12 @@ export const settingsValidator = [ .isBoolean() .default(false) .withMessage("allowEgressWeb flag must be boolean"), + body("settings.autoLoadBalance") + .if(body("tool").equals("terraform")) + .optional() + .isBoolean() + .default(false) + .withMessage("autoLoadBalance flag must be boolean"), body("settings.project") .if(body("tool").equals("terraform")) .if(body("settings.provider").equals("google"))