diff --git a/backend/package.json b/backend/package.json index 1f8272c0..60e6d484 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,8 +22,10 @@ "@aws-sdk/client-pricing": "^3.142.0", "aws-sdk": "^2.1189.0", "body-parser": "^1.20.0", + "connect-typeorm": "^2.0.0", "dotenv": "^16.0.1", "express": "^4.18.1", + "express-session": "^1.17.3", "js-yaml": "^4.1.0", "multer": "^1.4.5-lts.1", "node-schedule": "^2.1.0", @@ -38,6 +40,7 @@ }, "devDependencies": { "@types/express": "^4.17.13", + "@types/express-session": "^1.17.5", "@types/js-yaml": "^4.0.5", "@types/multer": "^1.4.7", "@types/node": "^18.6.1", diff --git a/backend/src/api/setup/index.ts b/backend/src/api/setup/index.ts new file mode 100644 index 00000000..6f748822 --- /dev/null +++ b/backend/src/api/setup/index.ts @@ -0,0 +1,87 @@ +import { Request, Response } from "express"; +import ApiResponseHandler from "api-response-handler"; +import { STEP_RESPONSE } from "@common/types"; +import { ConnectionType } from "@common/enums"; +import { setup } from "aws-services/setup-suricata"; +import "express-session"; +import { EC2_CONN } from "~/aws-services/create-ec2-instance"; +import { VirtualizationType } from "@aws-sdk/client-ec2"; + +declare module "express-session" { + interface SessionData { + connection_config: Record< + string, // id + { + step?: STEP_RESPONSE["step_number"]; + status?: STEP_RESPONSE["status"]; + id?: string; + type?: ConnectionType; + data?: STEP_RESPONSE["data"]; + } + >; + } +} + +export const setup_connection = async ( + req: Request, + res: Response +): Promise => { + const { step, id, status, type, params } = req.body; + if (!req.session.connection_config) { + req.session.connection_config = {}; + } + if (!req.session.connection_config[id]) { + req.session.connection_config[id] = { + status: "STARTED", + id, + type, + data: {}, + }; + } + + let combined_params = { + ...req.session.connection_config[id].data, + ...params, + }; + let resp = await setup(step, type, combined_params); + req.session.connection_config[id] = { + ...req.session.connection_config[id], + ...resp, + }; + + delete resp.data; + + await ApiResponseHandler.success(res, resp); +}; + +export const aws_os_choices = async ( + req: Request, + res: Response +): Promise => { + const { id } = req.body; + const { access_id, secret_access_key } = + req.session.connection_config[id].data; + let conn = new EC2_CONN(access_id, secret_access_key); + let choices = await conn.get_latest_image(); + await ApiResponseHandler.success(res, [ + [choices.Description, choices.ImageId], + ]); +}; + +export const aws_instance_choices = async ( + req: Request, + res: Response +): Promise => { + const { id, specs } = req.body; + const { access_id, secret_access_key, virtualization_type } = + req.session.connection_config[id].data; + let conn = new EC2_CONN(access_id, secret_access_key); + let choices = await conn.get_valid_types( + virtualization_type as VirtualizationType, + specs + ); + await ApiResponseHandler.success( + res, + choices.map((v) => v.InstanceType) + ); +}; diff --git a/backend/src/aws-services/create-ec2-instance.ts b/backend/src/aws-services/create-ec2-instance.ts index ffa5ac74..4a27aa6d 100644 --- a/backend/src/aws-services/create-ec2-instance.ts +++ b/backend/src/aws-services/create-ec2-instance.ts @@ -25,48 +25,15 @@ import { AuthorizeSecurityGroupIngressCommand, AuthorizeSecurityGroupIngressCommandInput, } from "@aws-sdk/client-ec2"; + +import { MachineSpecifications } from "@common/types"; // For pricing approximation // import { -// PricingClient, +// Pricing // PricingClientConfig, // GetProductsCommand, // } from "@aws-sdk/client-pricing"; -export interface MachineSpecifications { - minCpu: number; - maxCpu: number; - minMem: number; - maxMem?: number; -} - -export async function get_all_images( - img_names: Array, - client: EC2Client -): Promise> { - // Create anAmazon EC2 service client object. - const ec2Client = client; - const input: DescribeImagesCommandInput = { - Filters: [ - { Name: "architecture", Values: ["x86_64"] }, - { Name: "is-public", Values: ["true"] }, - { Name: "image-type", Values: ["machine"] }, - { Name: "state", Values: ["available"] }, - { - Name: "name", - Values: img_names, - }, - ], - Owners: ["099720109477"], - IncludeDeprecated: false, - }; - const command = new DescribeImagesCommand(input); - const response = await ec2Client.send(command); - return response.Images.sort( - (a, b) => - new Date(a.CreationDate).getTime() - new Date(b.CreationDate).getTime() - ); -} - const supported_instances = [ "a1", "c5", @@ -179,209 +146,275 @@ const supported_instances = [ "z1d.metal", ]; -export async function get_latest_image( - client: EC2Client, - img_names: Array = [ - "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-????????", - ] -) { - let resp = (await get_all_images(img_names, client)).pop(); - return resp; -} +export class EC2_CONN { + private access_id: string; + private secret_key: string; + private region?: string; + private conn: EC2Client; + constructor(access_id: string, secret_key: string, region?: string) { + this.access_id = access_id; + this.secret_key = secret_key; + this.region = region; + } -export async function get_valid_types( - client: EC2Client, - vtx_type: VirtualizationType, - specs: MachineSpecifications -): Promise> { - let command = new GetInstanceTypesFromInstanceRequirementsCommand({ - ArchitectureTypes: ["x86_64"], - VirtualizationTypes: [vtx_type], - InstanceRequirements: { - VCpuCount: { Min: specs.minCpu, Max: specs.maxCpu }, - MemoryMiB: { - Min: specs.minMem * 1024, - Max: specs.maxMem ? specs.maxMem * 1024 : null, + public get_conn() { + if (this.conn) { + return this.conn; + } + this.conn = new EC2Client({ + credentials: { + accessKeyId: this.access_id, + secretAccessKey: this.secret_key, }, - InstanceGenerations: ["current"], - BurstablePerformance: "included", - BareMetal: "included", - ExcludedInstanceTypes: [ - "t2.nano", - "t2.micro", - "t2.small", - "t2.medium", - "t2.large", - "t2.xlarge", - "t2.2xlarge", - "c3.large", - "c3.xlarge", - "c3.2xlarge", - "c3.4xlarge", - "c3.8xlarge", - "r3.large", - "r3.xlarge", - "r3.2xlarge", - "r3.4xlarge", - "r3.8xlarge", - "i3.xlarge", - "i3.2xlarge", - "i3.4xlarge", - "i3.8xlarge", + region: this.region, + }); + return this.conn; + } + + public disconnect() { + if (this.conn) this.conn.destroy(); + } + + public async get_all_images(img_names: Array): Promise> { + // Create an Amazon EC2 service client object. + const input: DescribeImagesCommandInput = { + Filters: [ + { Name: "architecture", Values: ["x86_64"] }, + { Name: "is-public", Values: ["true"] }, + { Name: "image-type", Values: ["machine"] }, + { Name: "state", Values: ["available"] }, + { + Name: "name", + Values: img_names, + }, ], - }, - } as GetInstanceTypesFromInstanceRequirementsCommandInput); - let resp = await client.send(command); - return resp.InstanceTypes.filter((x) => { - let a = supported_instances.filter((y) => x.InstanceType.includes(y)); - return a.length > 0; - }); -} + Owners: ["099720109477"], + IncludeDeprecated: false, + }; + const command = new DescribeImagesCommand(input); + const response = await this.get_conn().send(command); + return response.Images.sort( + (a, b) => + new Date(a.CreationDate).getTime() - new Date(b.CreationDate).getTime() + ); + } -export async function describe_type(client: EC2Client, Instance_type: string) { - let command = new DescribeInstanceTypesCommand({ - InstanceTypes: [Instance_type], - } as DescribeInstanceTypesCommandInput); - let resp = await client.send(command); - return resp.InstanceTypes[0]; -} + public async get_latest_image( + img_names: Array = [ + "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-????????", + ] + ) { + let resp = (await this.get_all_images(img_names)).pop(); + return resp; + } -// TODO : Pricing API gives somewhat random results. -// As in, systems with linux come out to $0.00 per hour -// async function get_pricing( -// config: PricingClientConfig, -// instance_type: string, -// location: string -// ) { -// let client = new PricingClient(config); -// let command = new GetProductsCommand({ -// Filters: [ -// { Field: "instanceType", Type: "TERM_MATCH", Value: instance_type }, -// { Field: "location", Type: "TERM_MATCH", Value: location }, -// ], -// FormatVersion: "aws_v1", -// ServiceCode: "AmazonEC2", -// }); -// let resp = await client.send(command); -// return resp; -// } + public async image_from_ami(ami: string) { + const input: DescribeImagesCommandInput = { + Filters: [ + { Name: "architecture", Values: ["x86_64"] }, + { Name: "is-public", Values: ["true"] }, + { Name: "image-type", Values: ["machine"] }, + { Name: "state", Values: ["available"] }, + ], + ImageIds: [ami], + IncludeDeprecated: false, + }; + const command = new DescribeImagesCommand(input); + const response = await this.get_conn().send(command); + return response.Images.sort( + (a, b) => + new Date(a.CreationDate).getTime() - new Date(b.CreationDate).getTime() + ); + } -export async function create_new_keypair(client: EC2Client, name: string) { - let command = new CreateKeyPairCommand({ - KeyName: name, - KeyType: KeyType.ed25519, - // TagSpecifications: [ - // { - // ResourceType: "instance", - // Tags: [{ Key: "Created By", Value: "Metlo" }], - // }, - // ], - } as CreateKeyPairCommandInput); - let resp = await client.send(command); - return resp; -} + public async get_valid_types( + vtx_type: VirtualizationType, + specs: MachineSpecifications + ): Promise> { + let command = new GetInstanceTypesFromInstanceRequirementsCommand({ + ArchitectureTypes: ["x86_64"], + VirtualizationTypes: [vtx_type], + InstanceRequirements: { + VCpuCount: { Min: specs.minCpu, Max: specs.maxCpu }, + MemoryMiB: { + Min: specs.minMem * 1024, + Max: specs.maxMem ? specs.maxMem * 1024 : null, + }, + InstanceGenerations: ["current"], + BurstablePerformance: "included", + BareMetal: "included", + ExcludedInstanceTypes: [ + "t2.nano", + "t2.micro", + "t2.small", + "t2.medium", + "t2.large", + "t2.xlarge", + "t2.2xlarge", + "c3.large", + "c3.xlarge", + "c3.2xlarge", + "c3.4xlarge", + "c3.8xlarge", + "r3.large", + "r3.xlarge", + "r3.2xlarge", + "r3.4xlarge", + "r3.8xlarge", + "i3.xlarge", + "i3.2xlarge", + "i3.4xlarge", + "i3.8xlarge", + ], + }, + } as GetInstanceTypesFromInstanceRequirementsCommandInput); + let conn = this.get_conn(); + let resp = await conn.send(command); + return resp.InstanceTypes.filter((x) => { + let a = supported_instances.filter((y) => x.InstanceType.includes(y)); + return a.length > 0; + }); + } -export async function list_keypairs(client: EC2Client) { - let command = new DescribeKeyPairsCommand({} as DescribeKeyPairsCommandInput); - let resp = await client.send(command); - return resp; -} + public async describe_type(Instance_type: string) { + let command = new DescribeInstanceTypesCommand({ + InstanceTypes: [Instance_type], + } as DescribeInstanceTypesCommandInput); + let resp = await this.get_conn().send(command); + return resp.InstanceTypes[0]; + } -export async function create_security_group(client: EC2Client, id: string) { - let command = new CreateSecurityGroupCommand({ - GroupName: id, - Description: "Default security group created by METLO for mirror instance", - } as CreateSecurityGroupCommandInput); - let resp = await client.send(command); - return resp; -} + // TODO : Pricing API gives somewhat random results. + // As in, systems with linux come out to $0.00 per hour + // async function get_pricing( + // config: PricingClientConfig, + // instance_type: string, + // location: string + // ) { + // let client = new PricingClient(config); + // let command = new GetProductsCommand({ + // Filters: [ + // { Field: "instanceType", Type: "TERM_MATCH", Value: instance_type }, + // { Field: "location", Type: "TERM_MATCH", Value: location }, + // ], + // FormatVersion: "aws_v1", + // ServiceCode: "AmazonEC2", + // }); + // let resp = await this.get_conn().send(command); + // return resp; + // } -export async function create_security_group_ingress( - client: EC2Client, - security_group_id: string, - protocol: string, - port: number -) { - let command = new AuthorizeSecurityGroupIngressCommand({ - GroupId: security_group_id, - IpProtocol: protocol, - FromPort: port, - ToPort: port, - CidrIp: "0.0.0.0/0", - TagSpecifications: [ - { - ResourceType: "security-group-rule", - Tags: [{ Key: "Created By", Value: "Created by METLO" }], - }, - ], - }); - let resp = await client.send(command); - return resp; -} + public async create_new_keypair(name: string) { + let command = new CreateKeyPairCommand({ + KeyName: name, + KeyType: KeyType.ed25519, + TagSpecifications: [ + { + ResourceType: "key-pair", + Tags: [{ Key: "Created By", Value: "Metlo" }], + }, + ], + } as CreateKeyPairCommandInput); + let resp = await this.get_conn().send(command); + return resp; + } -export async function create_new_instance( - client: EC2Client, - instance_ami: string, - instance_type: string, - id: string -): Promise<[RunInstancesCommandOutput, CreateKeyPairCommandOutput]> { - const key = await create_new_keypair(client, `METLO-Instance-${id}-Key`); - const security_group = await create_security_group( - client, - `METLO-SECURITY-GROUP-${id}` - ); - await create_security_group_ingress( - client, - security_group.GroupId, - "tcp", - 22 - ); - await create_security_group_ingress( - client, - security_group.GroupId, - "udp", - 4789 - ); - const command = new RunInstancesCommand({ - MaxCount: 1, - MinCount: 1, - ImageId: instance_ami, - InstanceType: instance_type, - KeyName: key.KeyName, - TagSpecifications: [ - { - ResourceType: "instance", - Tags: [ - { - Key: "Name", - Value: `METLO-Mirror-instance-${id}`, - }, - { Key: "Created By", Value: "Metlo" }, - ], - }, - { - ResourceType: "volume", - Tags: [ - { - Key: "Name", - Value: `METLO-Mirror-volume-${id}`, + public async list_keypairs() { + let command = new DescribeKeyPairsCommand( + {} as DescribeKeyPairsCommandInput + ); + let resp = await this.get_conn().send(command); + return resp; + } + + public async create_security_group(id: string) { + let command = new CreateSecurityGroupCommand({ + GroupName: id, + Description: + "Default security group created by METLO for mirror instance", + } as CreateSecurityGroupCommandInput); + let resp = await this.get_conn().send(command); + return resp; + } + + public async create_security_group_ingress( + security_group_id: string, + protocol: string, + port: number + ) { + let command = new AuthorizeSecurityGroupIngressCommand({ + GroupId: security_group_id, + IpProtocol: protocol, + FromPort: port, + ToPort: port, + CidrIp: "0.0.0.0/0", + TagSpecifications: [ + { + ResourceType: "security-group-rule", + Tags: [{ Key: "Created By", Value: "Created by METLO" }], + }, + ], + }); + let resp = await this.get_conn().send(command); + return resp; + } + + public async create_new_instance( + instance_ami: string, + instance_type: string, + id: string + ): Promise<[RunInstancesCommandOutput, CreateKeyPairCommandOutput]> { + const key = await this.create_new_keypair(`METLO-Instance-${id}-Key`); + const security_group = await this.create_security_group( + `METLO-SECURITY-GROUP-${id}` + ); + await this.create_security_group_ingress(security_group.GroupId, "tcp", 22); + await this.create_security_group_ingress( + security_group.GroupId, + "udp", + 4789 + ); + const command = new RunInstancesCommand({ + MaxCount: 1, + MinCount: 1, + ImageId: instance_ami, + InstanceType: instance_type, + KeyName: key.KeyName, + TagSpecifications: [ + { + ResourceType: "instance", + Tags: [ + { + Key: "Name", + Value: `METLO-Mirror-instance-${id}`, + }, + { Key: "Created By", Value: "Metlo" }, + ], + }, + { + ResourceType: "volume", + Tags: [ + { + Key: "Name", + Value: `METLO-Mirror-volume-${id}`, + }, + { Key: "Created By", Value: "Metlo" }, + ], + }, + ], + BlockDeviceMappings: [ + { + DeviceName: "/dev/sda1", + Ebs: { + DeleteOnTermination: true, + VolumeSize: 8, + VolumeType: "gp2", + Encrypted: true, }, - { Key: "Created By", Value: "Metlo" }, - ], - }, - ], - BlockDeviceMappings: [ - { - DeviceName: "/dev/sda1", - Ebs: { - DeleteOnTermination: true, - VolumeSize: 8, - VolumeType: "gp2", - Encrypted: true, }, - }, - ], - } as RunInstancesCommandInput); - const response = await client.send(command); - return [response, key]; + ], + } as RunInstancesCommandInput); + const response = await this.get_conn().send(command); + return [response, key]; + } } diff --git a/backend/src/aws-services/create-mirror.ts b/backend/src/aws-services/create-mirror.ts index 8055f712..51feea29 100644 --- a/backend/src/aws-services/create-mirror.ts +++ b/backend/src/aws-services/create-mirror.ts @@ -8,26 +8,11 @@ import { CreateTrafficMirrorSessionCommand, CreateTrafficMirrorSessionCommandInput, EC2Client, - TrafficMirrorFilterRule, - TagSpecification, DeleteTrafficMirrorFilterCommand, DeleteTrafficMirrorFilterCommandInput, } from "@aws-sdk/client-ec2"; -import { createHash, Hash, randomUUID } from "crypto"; - -export enum protocols { - TCP = 6, - UDP = 17, -} - -export interface TrafficFilterRuleSpecs { - destination_CIDR: string; - source_CIDR: string; - source_port?: string; - destination_port?: string; - protocol: protocols; - direction: "out" | "in"; -} +import { createHash, randomUUID } from "crypto"; +import { TrafficFilterRuleSpecs } from "@common/types"; export async function create_mirror_target( client: EC2Client, @@ -71,12 +56,13 @@ export async function create_mirror_filter_rules( ) { let command_resps = []; for (const v of filter_rules) { - let hash = createHash("sha256"); - hash.update(unique_id); - hash.update(JSON.stringify(filter_rules)); - let hash_str = hash.digest("base64").toString(); + // let hash = createHash("sha256"); + // hash.update(unique_id); + // hash.update(JSON.stringify(filter_rules)); + // let hash_str = hash.digest("base64").toString(); + // console.log(hash_str); let command = new CreateTrafficMirrorFilterRuleCommand({ - ClientToken: hash_str, + ClientToken: randomUUID(), TrafficDirection: v.direction === "out" ? "ingress" : "egress", RuleNumber: 100, SourceCidrBlock: v.source_CIDR, diff --git a/backend/src/aws-services/setup-suricata.ts b/backend/src/aws-services/setup-suricata.ts index 509b6267..8370c93d 100644 --- a/backend/src/aws-services/setup-suricata.ts +++ b/backend/src/aws-services/setup-suricata.ts @@ -5,107 +5,75 @@ import { EC2Client, } from "@aws-sdk/client-ec2"; import { randomUUID } from "crypto"; -import { - create_new_instance, - get_latest_image, - get_valid_types, - MachineSpecifications, -} from "./create-ec2-instance"; +import { EC2_CONN } from "./create-ec2-instance"; import { create_mirror_filter, create_mirror_filter_rules, create_mirror_session, create_mirror_target, delete_mirror_filter, - protocols, - TrafficFilterRuleSpecs, } from "./create-mirror"; +import { format, put_data_file, remove_file, SSH_CONN } from "./ssh-setup"; import { - create_ssh_connection, - format, - putfiles, - put_data_file, - remove_file, - run_command, - test_connection, -} from "./ssh-setup"; -import { get_network_id_for_instance, match_av_to_region } from "./utils"; + get_network_id_for_instance, + get_public_ip_for_network_interface, + list_all_instances, + match_av_to_region, +} from "./utils"; -export enum STEPS { - // SETUP MIRROR INSTANCE - AWS_KEY_SETUP = 1, - SOURCE_INSTANCE_ID = 2, - SELECT_OS = 3, - SELECT_INSTANCE_TYPE = 4, - CREATE_INSTANCE = 5, - CREATE_MIRROR_TARGET = 6, - CREATE_MIRROR_FILTER = 7, - CREATE_MIRROR_SESSION = 8, - TEST_SSH = 9, - PUSH_FILES = 10, - EXEC_COMMAND = 11, -} +import { STEP_RESPONSE } from "@common/types"; +import { ConnectionType } from "@common/enums"; -export interface STEP_RESPONSE { - success: "OK" | "FAIL"; - step_number: number; - last_completed: number; - error?: { - message: string; - err: any; - }; - keep: { - secret_access_key?: string; - access_id?: string; - source_instance_id?: string; - region?: string; - ami?: string; - os_types?: string[]; - instance_types?: string[]; - machine_specs?: MachineSpecifications; - selected_instance_type?: string; - mirror_instance_id?: string; - mirror_target_id?: string; - mirror_filter_id?: string; - mirror_rules?: Array; - keypair?: string; - destination_eni_id?: string; - virtualization_type?: string; - backend_url?: string; - remote_machine_url?: string; - }; -} - -async function setup( +export async function setup( step: number = 0, + type: ConnectionType, metadata_for_step: Object = {} ): Promise { - switch (step) { - case 1: - return await aws_key_setup(metadata_for_step as any); - case 2: - return await aws_source_identification(metadata_for_step as any); - case 3: - return await aws_os_selection(metadata_for_step as any); - case 4: - return await aws_instance_selection(metadata_for_step as any); - case 5: - return await aws_instance_creation(metadata_for_step as any); - case 6: - return await aws_mirror_target_creation(metadata_for_step as any); - case 7: - return await aws_mirror_filter_creation(metadata_for_step as any); - case 8: - return await aws_mirror_session_creation(metadata_for_step as any); - case 9: - return await test_ssh(metadata_for_step as any); - case 10: - return await push_files(metadata_for_step as any); - case 11: - return await execute_commands(metadata_for_step as any); - default: - throw Error(`Don't have step ${step} registered`); - break; + if (type == ConnectionType.AWS) { + switch (step) { + case 1: + return await aws_key_setup(metadata_for_step as any); + case 2: + return await aws_source_identification(metadata_for_step as any); + case 2: + return await aws_source_identification(metadata_for_step as any); + case 3: + return await aws_os_selection(metadata_for_step as any); + case 4: + return await aws_instance_selection(metadata_for_step as any); + case 5: + return await aws_instance_creation(metadata_for_step as any); + case 6: + return await get_public_ip(metadata_for_step as any); + case 7: + return await aws_mirror_target_creation(metadata_for_step as any); + case 8: + return await aws_mirror_filter_creation(metadata_for_step as any); + case 9: + return await aws_mirror_session_creation(metadata_for_step as any); + case 10: + return await test_ssh(metadata_for_step as any); + case 11: + return await push_files(metadata_for_step as any); + case 12: + return await execute_commands(metadata_for_step as any); + default: + throw Error(`Don't have step ${step} registered`); + break; + } + } else if (type == ConnectionType.GCP) { + return { + success: "FAIL", + status: "COMPLETE", + step_number: 1, + next_step: 2, + last_completed: 1, + message: "Not configured yet for GCP", + error: { + err: "Not configured yet for GCP", + }, + data: {}, + }; } } @@ -120,13 +88,17 @@ async function aws_key_setup({ accessKeyId: access_id, }, }); + let region = await list_all_instances(client); client.destroy(); return { success: "OK", + status: "IN-PROGRESS", step_number: 1, + next_step: 2, last_completed: 1, + message: "Verified AWS Credentials", error: null, - keep: { + data: { secret_access_key: secret_access_key, access_id: access_id, }, @@ -134,14 +106,16 @@ async function aws_key_setup({ } catch (err) { return { success: "FAIL", + status: "IN-PROGRESS", step_number: 1, + next_step: 2, last_completed: 0, + message: + "Couldn't verify AWS Credentials. Please verify that access id and secret access key are correct.", error: { - message: - "Couldn't verify AWS Credentials. Please verify that access id and secret access key are correct.", err: err, }, - keep: {}, + data: {}, }; } } @@ -169,10 +143,13 @@ async function aws_source_identification({ client.destroy(); return { success: "OK", + status: "IN-PROGRESS", step_number: 2, + next_step: 3, last_completed: 2, + message: "Verfied EC2 data mirror instance", error: null, - keep: { + data: { secret_access_key: secret_access_key, access_id: access_id, source_instance_id: source_instance_id, @@ -182,13 +159,15 @@ async function aws_source_identification({ } catch (err) { return { success: "FAIL", + status: "IN-PROGRESS", step_number: 2, + next_step: 3, last_completed: 1, + message: "Couldn't verify EC2 source instance for mirroring traffic", error: { - message: "Couldn't verify EC2 source instance for mirroring traffic", err: err, }, - keep: { + data: { secret_access_key: secret_access_key, access_id: access_id, }, @@ -199,40 +178,41 @@ async function aws_source_identification({ async function aws_os_selection({ access_id, secret_access_key, + ami, ...rest }): Promise { try { - let client = new EC2Client({ - credentials: { - secretAccessKey: secret_access_key, - accessKeyId: access_id, - }, - }); - let resp = await get_latest_image(client); - client.destroy(); + let conn = new EC2_CONN(access_id, secret_access_key); + let resp = await conn.image_from_ami(ami); + conn.disconnect(); return { success: "OK", + status: "IN-PROGRESS", step_number: 3, + next_step: 4, last_completed: 3, + message: "Compatible OS images found", error: null, - keep: { + data: { secret_access_key, access_id, + ami, + virtualization_type: resp[0].VirtualizationType, ...rest, - ami: resp.ImageId, - virtualization_type: resp.VirtualizationType, }, }; } catch (err) { return { success: "FAIL", + status: "IN-PROGRESS", step_number: 3, + next_step: 4, last_completed: 2, + message: "Couldn't obtain proper image for EC2 instance.", error: { - message: "Couldn't obtain proper image for EC2 instance.", err: err, }, - keep: { + data: { secret_access_key: secret_access_key, access_id: access_id, ...rest, @@ -247,46 +227,42 @@ async function aws_instance_selection({ region, virtualization_type, machine_specs, + selected_instance_type, + ami, ...rest }): Promise { try { - let client = new EC2Client({ - credentials: { - secretAccessKey: secret_access_key, - accessKeyId: access_id, - }, - }); - let resp = await get_valid_types( - client, - virtualization_type, - machine_specs - ); - client.destroy(); return { success: "OK", + status: "IN-PROGRESS", step_number: 4, + next_step: 5, last_completed: 4, + message: "Compatiable instances shown.", error: null, - keep: { + data: { secret_access_key, access_id, region, - instance_types: resp.map((v) => v.InstanceType), + selected_instance_type, virtualization_type, machine_specs, + ami, ...rest, }, }; } catch (err) { return { success: "FAIL", + status: "IN-PROGRESS", step_number: 4, + next_step: 5, last_completed: 3, + message: "Couldn't list valid instance type for EC2 instance.", error: { - message: "Couldn't list valid instance type for EC2 instance.", err: err, }, - keep: { + data: { secret_access_key, access_id, region, @@ -307,26 +283,22 @@ async function aws_instance_creation({ ...rest }): Promise { try { - let client = new EC2Client({ - credentials: { - secretAccessKey: secret_access_key, - accessKeyId: access_id, - }, - region: region, - }); - let resp = await create_new_instance( - client, + let conn = new EC2_CONN(access_id, secret_access_key, region); + let resp = await conn.create_new_instance( ami, selected_instance_type, randomUUID() ); - client.destroy(); + conn.disconnect(); return { success: "OK", + status: "IN-PROGRESS", error: null, step_number: 5, + next_step: 6, last_completed: 5, - keep: { + message: `AWS of type ${selected_instance_type} Instance created`, + data: { secret_access_key: secret_access_key, access_id: access_id, region, @@ -336,21 +308,21 @@ async function aws_instance_creation({ destination_eni_id: resp[0].Instances[0].NetworkInterfaces[0].NetworkInterfaceId, ami, - remote_machine_url: - resp[0].Instances[0].NetworkInterfaces[0].Association.PublicIp, ...rest, }, }; } catch (err) { return { success: "FAIL", + status: "IN-PROGRESS", step_number: 5, + next_step: 6, last_completed: 4, + message: `Couldn't create new instance of type ${selected_instance_type} with ami ${ami} on EC2.`, error: { - message: `Couldn't create new instance of type ${selected_instance_type} with ami ${ami} on EC2.`, err: err, }, - keep: { + data: { secret_access_key, access_id, region, @@ -362,6 +334,65 @@ async function aws_instance_creation({ } } +async function get_public_ip({ + access_id, + secret_access_key, + region, + destination_eni_id, + ...rest +}): Promise { + try { + let client = new EC2Client({ + credentials: { + secretAccessKey: secret_access_key, + accessKeyId: access_id, + }, + region: region, + }); + let resp = await get_public_ip_for_network_interface( + client, + destination_eni_id + ); + client.destroy(); + return { + success: "OK", + status: "IN-PROGRESS", + message: "Found IP for mirror target instance", + error: null, + step_number: 6, + next_step: 7, + last_completed: 5, + data: { + secret_access_key: secret_access_key, + access_id: access_id, + region, + remote_machine_url: resp, + destination_eni_id, + ...rest, + }, + }; + } catch (err) { + return { + success: "FAIL", + status: "IN-PROGRESS", + step_number: 6, + next_step: 7, + last_completed: 5, + message: `Couldn't get public ip for instance ${destination_eni_id} on EC2.`, + error: { + err: err, + }, + data: { + secret_access_key, + access_id, + region, + destination_eni_id, + ...rest, + }, + }; + } +} + async function aws_mirror_target_creation({ access_id, secret_access_key, @@ -385,10 +416,13 @@ async function aws_mirror_target_creation({ client.destroy(); return { success: "OK", - step_number: 6, - last_completed: 6, + status: "IN-PROGRESS", + message: "Configured mirror target on AWS", + step_number: 7, + next_step: 8, + last_completed: 7, error: null, - keep: { + data: { secret_access_key: secret_access_key, access_id: access_id, region, @@ -400,13 +434,15 @@ async function aws_mirror_target_creation({ } catch (err) { return { success: "FAIL", - step_number: 6, + status: "IN-PROGRESS", + step_number: 7, + next_step: 8, last_completed: 5, + message: `Couldn't create a mirror target out of ${source_instance_id}`, error: { - message: `Couldn't create a mirror target out of ${source_instance_id}`, err: err, }, - keep: { + data: { secret_access_key, access_id, region, @@ -437,13 +473,15 @@ async function aws_mirror_filter_creation({ } catch (err) { return { success: "FAIL", - step_number: 7, - last_completed: 6, + status: "IN-PROGRESS", + step_number: 8, + next_step: 9, + last_completed: 7, + message: `Couldn't create a mirror filter`, error: { - message: `Couldn't create a mirror filter`, err: err, }, - keep: { + data: { secret_access_key, access_id, region, @@ -462,10 +500,13 @@ async function aws_mirror_filter_creation({ client.destroy(); return { success: "OK", - step_number: 7, - last_completed: 7, + status: "IN-PROGRESS", + step_number: 8, + next_step: 9, + last_completed: 8, error: null, - keep: { + message: "Created provided Traffic Filter(s)", + data: { secret_access_key: secret_access_key, access_id: access_id, region, @@ -482,13 +523,15 @@ async function aws_mirror_filter_creation({ ); return { success: "FAIL", - step_number: 7, - last_completed: 6, + status: "IN-PROGRESS", + step_number: 8, + next_step: 9, + last_completed: 7, + message: `Couldn't create rules for filter id. Rolling back changes to filter and deleting it.`, error: { - message: `Couldn't create rules for filter id. Rolling back changes to filter and deleting it.`, err: err, }, - keep: { + data: { secret_access_key, access_id, region, @@ -526,10 +569,13 @@ async function aws_mirror_session_creation({ client.destroy(); return { success: "OK", - step_number: 8, - last_completed: 8, + status: "IN-PROGRESS", + step_number: 9, + next_step: 10, + last_completed: 9, error: null, - keep: { + message: "Configured mirror session succesfully", + data: { secret_access_key: secret_access_key, access_id: access_id, region, @@ -543,13 +589,15 @@ async function aws_mirror_session_creation({ } catch (err) { return { success: "FAIL", - step_number: 8, - last_completed: 7, + status: "IN-PROGRESS", + step_number: 9, + next_step: 10, + last_completed: 8, + message: `Couldn't create a mirror session targeting ${destination_eni_id}`, error: { - message: `Couldn't create a mirror session targeting ${destination_eni_id}`, err: err, }, - keep: { + data: { access_id, secret_access_key, region, @@ -567,35 +615,38 @@ async function test_ssh({ remote_machine_url, ...rest }): Promise { + var conn; try { - let conn = await create_ssh_connection( - keypair, - remote_machine_url, - "ubuntu" - ); - await test_connection(conn); - conn.dispose(); + conn = new SSH_CONN(keypair, remote_machine_url, "ubuntu"); + await conn.test_connection(); + conn.disconnect(); return { success: "OK", - step_number: 9, - last_completed: 9, + status: "IN-PROGRESS", + step_number: 10, + next_step: 11, + last_completed: 10, + message: "Testing SSH connection to remote machine.", error: null, - keep: { + data: { keypair, remote_machine_url, ...rest, }, }; } catch (err) { + if (conn && conn instanceof SSH_CONN) conn.disconnect(); return { success: "FAIL", - step_number: 9, - last_completed: 8, + status: "IN-PROGRESS", + step_number: 10, + next_step: 11, + last_completed: 9, + message: `Couldn't connect to ssh. Please check if key was constructed`, error: { - message: `Couldn't connect to ssh. Please check if key was constructed`, err: err, }, - keep: { + data: { keypair, remote_machine_url, ...rest, @@ -610,7 +661,7 @@ async function push_files({ remote_machine_url, ...rest }): Promise { - let conn = await create_ssh_connection(keypair, remote_machine_url, "ubuntu"); + let conn = new SSH_CONN(keypair, remote_machine_url, "ubuntu"); try { let filepath = `./src/aws-services/scripts/metlo-ingestor-${randomUUID()}.service`; await put_data_file( @@ -619,8 +670,7 @@ async function push_files({ ]), filepath ); - await putfiles( - conn, + await conn.putfiles( [ "./src/aws-services/scripts/install.sh", "./src/aws-services/scripts/install-nvm.sh", @@ -637,13 +687,16 @@ async function push_files({ ] ); remove_file(filepath); - conn.dispose(); + conn.disconnect(); return { success: "OK", - step_number: 10, - last_completed: 10, + status: "IN-PROGRESS", + step_number: 11, + next_step: 12, + last_completed: 11, + message: "Pushed configuration files to remote machine", error: null, - keep: { + data: { keypair, remote_machine_url, backend_url, @@ -651,16 +704,18 @@ async function push_files({ }, }; } catch (err) { - conn.dispose(); + conn.disconnect(); return { success: "FAIL", - step_number: 10, - last_completed: 9, + status: "IN-PROGRESS", + step_number: 11, + next_step: 12, + last_completed: 10, + message: `Couldn't push configuration files to remote machine`, error: { - message: `Couldn't push files to remote machine`, err: err, }, - keep: { + data: { keypair, backend_url, remote_machine_url, @@ -675,186 +730,47 @@ async function execute_commands({ remote_machine_url, ...rest }): Promise { - let conn = await create_ssh_connection(keypair, remote_machine_url, "ubuntu"); + let conn = new SSH_CONN(keypair, remote_machine_url, "ubuntu"); try { - await run_command( - conn, + await conn.run_command( "source $HOME/.nvm/nvm.sh && cd ~ && chmod +x install-nvm.sh && ./install-nvm.sh " ); - await run_command( - conn, + await conn.run_command( "source $HOME/.nvm/nvm.sh && cd ~ && chmod +x install.sh && ./install.sh " ); - conn.dispose(); + conn.disconnect(); return { success: "OK", - step_number: 11, - last_completed: 11, + status: "COMPLETE", + step_number: 12, + next_step: null, + last_completed: 12, + message: "Executed configuration files on remote machine succesfully", error: null, - keep: { + data: { keypair, remote_machine_url, ...rest, }, }; } catch (err) { - conn.dispose(); + conn.disconnect(); return { success: "FAIL", - step_number: 11, - last_completed: 10, + status: "IN-PROGRESS", + step_number: 12, + next_step: null, + last_completed: 11, + message: `Couldn't exec commands to install things`, error: { - message: `Couldn't exec commands to install things`, err: err, }, - keep: { + data: { keypair, remote_machine_url, ...rest, }, }; } -} - -async function main() { - var resp; - let info = { - // console.log(resp); - // if (resp.success != "OK") { - // return; - // } - // info = { ...resp.keep }; - // // STEP 2 - // info.source_instance_id = "i-0d2ca277bb3e4d0a7"; - // resp = await setup(2, info); - // console.log(resp); - // if (resp.success != "OK") { - // return; - // } - // info = { ...resp.keep }; - // // STEP 3 - // resp = await setup(3, info); - // console.log(resp); - // if (resp.success != "OK") { - // return; - // } - // info = { ...resp.keep }; - // // STEP 4 - // info.machine_specs = { - // minCpu: 1, - // maxCpu: 2, - // minMem: 2, - // maxMem: 8, - // } as MachineSpecifications; - // resp = await setup(4, info); - // console.log(resp); - // if (resp.success != "OK") { - // return; - // } - // info = { - // ...resp.keep, - // selected_instance_type: - // resp.keep.instance_types[ - // Math.floor(Math.random() * resp.keep.instance_types.length) - // ], - } as STEP_RESPONSE["keep"]; - resp = await get_valid_types( - new EC2Client({ - credentials: { - accessKeyId: info.access_id, - secretAccessKey: info.secret_access_key, - }, - }), - "hvm", - { minCpu: 0, maxCpu: 4, minMem: 2, maxMem: 8 } - ); - // STEP 1 - resp = await setup(1, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { ...resp.keep }; - // STEP 2 - info.source_instance_id = "i-0d2ca277bb3e4d0a7"; - resp = await setup(2, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { ...resp.keep }; - // STEP 3 - resp = await setup(3, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { ...resp.keep }; - // STEP 4 - info.machine_specs = { - minCpu: 1, - maxCpu: 2, - minMem: 2, - maxMem: 8, - } as MachineSpecifications; - resp = await setup(4, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { - ...resp.keep, - selected_instance_type: - resp.keep.instance_types[ - Math.floor(Math.random() * resp.keep.instance_types.length) - ], - }; - // STEP 5 - resp = await setup(5, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { ...resp.keep }; - // STEP 6 - resp = await setup(6, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { ...resp.keep }; - // STEP 7 - info = { ...resp.keep }; - resp = await setup(7, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - info = { ...resp.keep }; - // STEP 8 - resp = await setup(8, info); - console.log(resp); - if (resp.success != "OK") { - return; - } - // }; - // STEP 9 - resp = await setup(9, info); - console.log(resp); - if (resp.success != "OK") { - return; - } // STEP 10 - resp = await setup(10, info); - console.log(resp); - if (resp.success != "OK") { - return; - } // STEP 11 - resp = await setup(11, info); - console.log(resp); - if (resp.success != "OK") { - return; - } -} - -main(); +} \ No newline at end of file diff --git a/backend/src/aws-services/ssh-setup.ts b/backend/src/aws-services/ssh-setup.ts index eda4fc2d..7dfa9ea0 100644 --- a/backend/src/aws-services/ssh-setup.ts +++ b/backend/src/aws-services/ssh-setup.ts @@ -1,33 +1,78 @@ import { NodeSSH } from "node-ssh"; import { readFileSync, writeFileSync, openSync, unlinkSync } from "fs"; import { format as _format } from "util"; -import { randomUUID } from "crypto"; - -export async function create_ssh_connection(private_key, host, username) { - const ssh = new NodeSSH(); - let buff = Buffer.from(private_key, "utf-8"); - let conn = await ssh.connect({ - host: host, - username: username, - privateKey: private_key, - }); - return conn; -} -export async function test_connection( - client: NodeSSH -): Promise<[boolean, string]> { - let resp = await client.execCommand("lsb_release -i"); - if (resp.stdout !== "" && resp.stderr === "") { - return [true, ""]; - } else return [false, resp.stderr]; -} +export class SSH_CONN { + private key: string; + private host: string; + private user: string; + private conn: NodeSSH; + + constructor(private_key: string, host: string, user: string) { + this.key = private_key; + this.host = host; + this.user = user; + } + + public async get_conn(): Promise { + if (this.conn && this.conn.isConnected()) { + return this.conn; + } + const ssh = new NodeSSH(); + let conn = await ssh.connect({ + host: this.host, + username: this.user, + privateKey: this.key, + }); + this.conn = conn; + return this.conn; + } + + public async test_connection(): Promise<[boolean, string]> { + // Test if we can get release version of the OS. Should be compatible with all Linux based OS + var resp, error; + for (let i = 0; i < 5; i++) { + try { + resp = await (await this.get_conn()).execCommand("lsb_release -i"); + break; + } catch (err) { + error = err; + if (err instanceof Error) { + console.log(err); + } + } + } + return [resp.stdout !== "" && resp.stderr === "", resp.stderr]; + } -export async function run_command(client: NodeSSH, command: string) { - let resp = await client.execCommand(command); - console.log(resp.stdout); - console.log(resp.stderr); - return resp; + public async run_command(command: string) { + var resp, error; + for (let i = 0; i < 5; i++) { + try { + resp = await (await this.get_conn()).execCommand(command); + break; + } catch (err) { + error = err; + if (err instanceof Error) { + console.log(err); + } + } + } + return resp; + } + + public disconnect() { + if (this.conn) { + this.conn.dispose(); + } + } + + public async putfiles(files: string[], locations: string[]) { + let out_files = files.map((v, i) => { + return { local: v, remote: locations[i] }; + }); + await (await this.get_conn()).putFiles(out_files); + } } export async function put_data_file(data: string, location: string) { @@ -36,21 +81,14 @@ export async function put_data_file(data: string, location: string) { } export async function remove_file(location: string) { - unlinkSync(location); + try { + unlinkSync(location); + } catch (err) { + // Ignore error. Not a major issue if filled template isn't removed + } } export function format(filepath: string, attributes: string[]) { let str = readFileSync(filepath, "utf-8"); return _format(str, ...attributes); } - -export async function putfiles( - client: NodeSSH, - files: string[], - locations: string[] -) { - let out_files = files.map((v, i) => { - return { local: v, remote: locations[i] }; - }); - await client.putFiles(out_files); -} diff --git a/backend/src/aws-services/utils.ts b/backend/src/aws-services/utils.ts index 4c36637b..e4e37540 100644 --- a/backend/src/aws-services/utils.ts +++ b/backend/src/aws-services/utils.ts @@ -87,6 +87,16 @@ export async function get_region_for_network_interface( return (await client.send(command)).NetworkInterfaces[0].AvailabilityZone; } +export async function get_public_ip_for_network_interface( + client: EC2Client, + interface_id +) { + let command = new DescribeNetworkInterfacesCommand({ + NetworkInterfaceIds: [interface_id], + } as DescribeNetworkInterfacesCommandInput); + return (await client.send(command)).NetworkInterfaces[0].Association.PublicIp; +} + export async function describe_regions(client: EC2Client) { let command = new DescribeRegionsCommand({ AllRegions: true, @@ -101,3 +111,22 @@ export async function match_av_to_region( let regions = await describe_regions(client); return regions.Regions.find((v) => availability_zone.includes(v.RegionName)); } + +export function retry(fn: Function) { + return async (args) => { + var resp, error; + var idx = 0; + for (idx = 0; idx < 5; idx++) { + try { + resp = await fn(...args); + break; + } catch (err) { + error = err; + } + } + if (idx === 5 && error) { + throw error; + } + return resp; + }; +} diff --git a/backend/src/data-source.ts b/backend/src/data-source.ts index a105c4dc..45c7500c 100644 --- a/backend/src/data-source.ts +++ b/backend/src/data-source.ts @@ -7,12 +7,20 @@ import { OpenApiSpec, Alert, } from "models"; +import { Session } from "./models/sessions"; export const AppDataSource: DataSource = new DataSource({ type: "postgres", url: process.env.DB_URL, synchronize: true, - entities: [ApiEndpoint, MatchedDataClass, ApiTrace, OpenApiSpec, Alert], + entities: [ + ApiEndpoint, + MatchedDataClass, + ApiTrace, + OpenApiSpec, + Alert, + Session, + ], migrations: [], logging: false, }); diff --git a/backend/src/index.ts b/backend/src/index.ts index e6da20a8..7168752e 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,6 +1,9 @@ import express, { Express, Request, Response } from "express"; import dotenv from "dotenv"; import bodyParser from "body-parser"; +import { TypeormStore } from "connect-typeorm"; +import session from "express-session"; +import { Session as SessionModel } from "~/models/sessions"; import { logRequestBatchHandler, logRequestSingleHandler, @@ -27,6 +30,11 @@ import { updatePIIFieldHandler } from "api/data-class"; import { getSummaryHandler } from "api/summary"; import { AppDataSource } from "data-source"; import { MulterSource } from "multer-source"; +import { + aws_instance_choices, + aws_os_choices, + setup_connection, +} from "./api/setup"; dotenv.config(); @@ -35,6 +43,18 @@ const port = process.env.PORT || 8080; app.disable("x-powered-by"); app.use(bodyParser.json()); +app.use( + session({ + resave: false, + saveUninitialized: false, + store: new TypeormStore({ + cleanupLimit: 2, + limitSubquery: false, // If using MariaDB. + ttl: 86400, + }).connect(AppDataSource.getRepository(SessionModel)), + secret: "keyboard cat", + }) +); app.get("/api/v1", (req: Request, res: Response) => { res.send("OK"); @@ -65,6 +85,10 @@ app.get("/api/v1/alerts", getAlertsHandler); app.get("/api/v1/topAlerts", getTopAlertsHandler); app.put("/api/v1/alert/resolve/:alertId", resolveAlertHandler); +app.post("/api/v1/setup_connection", setup_connection); +app.post("/api/v1/setup_connection/aws/os", aws_os_choices); +app.post("/api/v1/setup_connection/aws/instances", aws_instance_choices); + const main = async () => { try { const datasource = await AppDataSource.initialize(); diff --git a/backend/src/models/sessions.ts b/backend/src/models/sessions.ts new file mode 100644 index 00000000..31c7bec0 --- /dev/null +++ b/backend/src/models/sessions.ts @@ -0,0 +1,24 @@ +import { ISession } from "connect-typeorm"; +import { + Column, + DeleteDateColumn, + Entity, + Index, + PrimaryColumn, +} from "typeorm"; + +@Entity() +export class Session implements ISession { + @Index() + @Column("bigint") + public expiredAt = Date.now(); + + @PrimaryColumn("varchar", { length: 255 }) + public id = ""; + + @Column("text") + public json = ""; + + @DeleteDateColumn() + public destroyedAt?: Date; +} diff --git a/backend/tsconfig.json b/backend/tsconfig.json index ca620f3e..58611d96 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -2,8 +2,15 @@ "compileOnSave": false, "compilerOptions": { "target": "es6", - "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], - "typeRoots": ["node_modules/@types"], + "lib": [ + "dom", + "es6", + "es2017", + "esnext.asynciterable" + ], + "typeRoots": [ + "node_modules/@types", + ], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, @@ -22,8 +29,12 @@ "importHelpers": true, "baseUrl": "./src", "paths": { - "@common/*": ["../../common/dist/*"], - "~/*": ["./*"], + "@common/*": [ + "../../common/dist/*" + ], + "~/*": [ + "./*" + ], } }, "include": [ @@ -33,6 +44,9 @@ "public/robots.txt", ".env", ], - "exclude": ["node_modules", "src/http", "src/tests"] -} - + "exclude": [ + "node_modules", + "src/http", + "src/tests" + ] +} \ No newline at end of file diff --git a/backend/yarn.lock b/backend/yarn.lock index cac1ecdf..d9234ecc 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -771,6 +771,11 @@ dependencies: "@types/node" "*" +"@types/debug@0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.31.tgz#bac8d8aab6a823e91deb7f79083b2a35fa638f33" + integrity sha512-LS1MCPaQKqspg7FvexuhmDbWUhE2yIJ+4AgVIyObfc06/UKZ8REgxGNjZc82wPLWmbeOm7S+gSsLgo75TanG4A== + "@types/express-serve-static-core@^4.17.18": version "4.17.30" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" @@ -780,6 +785,13 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-session@^1.15.5", "@types/express-session@^1.17.5": + version "1.17.5" + resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.17.5.tgz#13f48852b4aa60ff595835faeb4b4dda0ba0866e" + integrity sha512-l0DhkvNVfyUPEEis8fcwbd46VptfA/jmMwHfob2TfDMf3HyPLiB9mKD71LXhz5TMUobODXPD27zXSwtFQLHm+w== + dependencies: + "@types/express" "*" + "@types/express@*", "@types/express@^4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" @@ -1148,6 +1160,16 @@ concat-stream@^1.5.2: readable-stream "^2.2.2" typedarray "^0.0.6" +connect-typeorm@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-typeorm/-/connect-typeorm-2.0.0.tgz#892399038971434a6049184ddcdf426de3b269ad" + integrity sha512-0OcbHJkNMTJjSrbcKGljr4PKgRq13Dds7zQq3+8oaf4syQTgGvGv9OgnXo2qg+Bljkh4aJNzIvW74QOVLn8zrw== + dependencies: + "@types/debug" "0.0.31" + "@types/express-session" "^1.15.5" + debug "^4.1.1" + express-session "^1.15.6" + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -1165,6 +1187,11 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + cookie@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" @@ -1215,7 +1242,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.3.3: +debug@^4.1.1, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1230,7 +1257,7 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -depd@2.0.0: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -1328,6 +1355,20 @@ events@1.1.1: resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== +express-session@^1.15.6, express-session@^1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36" + integrity sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw== + dependencies: + cookie "0.4.2" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.1" + uid-safe "~2.1.5" + express@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" @@ -1965,6 +2006,11 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2164,6 +2210,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -2551,6 +2602,13 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" diff --git a/common/src/enums.ts b/common/src/enums.ts index 20fe9bdd..13973c46 100644 --- a/common/src/enums.ts +++ b/common/src/enums.ts @@ -46,3 +46,24 @@ export enum SpecExtension { JSON = "json", YAML = "yaml", } + +export enum STEPS { + // SETUP MIRROR INSTANCE + AWS_KEY_SETUP = 1, + SOURCE_INSTANCE_ID = 2, + SELECT_OS = 3, + SELECT_INSTANCE_TYPE = 4, + CREATE_INSTANCE = 5, + INSTANCE_IP = 6, + CREATE_MIRROR_TARGET = 7, + CREATE_MIRROR_FILTER = 8, + CREATE_MIRROR_SESSION = 9, + TEST_SSH = 10, + PUSH_FILES = 11, + EXEC_COMMAND = 12, +} + +export enum protocols { + TCP = 6, + UDP = 17, +} diff --git a/common/src/maps.ts b/common/src/maps.ts new file mode 100644 index 00000000..a62a208a --- /dev/null +++ b/common/src/maps.ts @@ -0,0 +1,31 @@ +import { STEPS } from "./enums"; + +export const NEXT_STEP: Record = { + [STEPS.AWS_KEY_SETUP]: STEPS.SOURCE_INSTANCE_ID, + [STEPS.SOURCE_INSTANCE_ID]: STEPS.SELECT_OS, + [STEPS.SELECT_OS]: STEPS.SELECT_INSTANCE_TYPE, + [STEPS.SELECT_INSTANCE_TYPE]: STEPS.CREATE_INSTANCE, + [STEPS.CREATE_INSTANCE]: STEPS.INSTANCE_IP, + [STEPS.INSTANCE_IP]: STEPS.CREATE_MIRROR_TARGET, + [STEPS.CREATE_MIRROR_TARGET]: STEPS.CREATE_MIRROR_FILTER, + [STEPS.CREATE_MIRROR_FILTER]: STEPS.CREATE_MIRROR_SESSION, + [STEPS.CREATE_MIRROR_SESSION]: STEPS.TEST_SSH, + [STEPS.TEST_SSH]: STEPS.PUSH_FILES, + [STEPS.PUSH_FILES]: STEPS.EXEC_COMMAND, + [STEPS.EXEC_COMMAND]: null, +}; + +export const STEP_TO_TITLE_MAP: Record = { + [STEPS.AWS_KEY_SETUP]: "AWS Credentials Setup", + [STEPS.SOURCE_INSTANCE_ID]: "EC2 Instance for mirroring source", + [STEPS.SELECT_OS]: "OS Selection", + [STEPS.SELECT_INSTANCE_TYPE]: "EC2 Instance type selection", + [STEPS.CREATE_INSTANCE]: "EC2 Instance Instantiation", + [STEPS.INSTANCE_IP]: "Source Mirror Instance IP", + [STEPS.CREATE_MIRROR_TARGET]: "Traffic Mirror Target Creation", + [STEPS.CREATE_MIRROR_FILTER]: "Traffic Mirror Filter Creation", + [STEPS.CREATE_MIRROR_SESSION]: "Traffic Mirror Session Creation", + [STEPS.TEST_SSH]: "SSH Connection Test", + [STEPS.PUSH_FILES]: "Push installation files to remote instance", + [STEPS.EXEC_COMMAND]: "Installing metlo", +}; diff --git a/common/src/types.ts b/common/src/types.ts index 8ff79298..163df022 100644 --- a/common/src/types.ts +++ b/common/src/types.ts @@ -1,9 +1,11 @@ import { AlertType, ConnectionType, + protocols, RestMethod, RiskScore, SpecExtension, + STEPS, } from "./enums"; export interface Meta { @@ -160,3 +162,55 @@ export interface Usage { date: Date; count: number; } + +export interface STEP_RESPONSE { + success: "OK" | "FAIL"; + status: "STARTED" | "COMPLETE" | "IN-PROGRESS"; + next_step: STEPS; + step_number: STEPS; + last_completed: STEPS; + message: string; + error?: { + err: string; + }; + data: { + secret_access_key?: string; + access_id?: string; + source_instance_id?: string; + region?: string; + ami?: string; + os_types?: [{ name: string; ami: string }]; + instance_types?: string[]; + machine_specs?: MachineSpecifications; + selected_instance_type?: string; + mirror_instance_id?: string; + mirror_target_id?: string; + mirror_filter_id?: string; + mirror_rules?: Array; + keypair?: string; + destination_eni_id?: string; + virtualization_type?: string; + backend_url?: string; + remote_machine_url?: string; + }; + returns?: { + os_types?: [{ name: string; ami: string }]; + instance_types?: string[]; + }; +} + +export interface MachineSpecifications { + minCpu: number; + maxCpu: number; + minMem: number; + maxMem?: number; +} + +export interface TrafficFilterRuleSpecs { + destination_CIDR: string; + source_CIDR: string; + source_port?: string; + destination_port?: string; + protocol: protocols; + direction: "out" | "in"; +} diff --git a/frontend/next.config.js b/frontend/next.config.js index 1862c361..5c3e823f 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -8,4 +8,13 @@ module.exports = { }, ]; }, + async redirects() { + return [ + { + source: '/connections/new', + destination: '/connections', + permanent: true, + }, + ] + } }; diff --git a/frontend/package.json b/frontend/package.json index 0666a0ef..b3295524 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,8 @@ "react-dom": "^18.2.0", "react-json-view": "^1.21.3", "styled-components": "^5.3.5", - "superjson": "^1.9.1" + "superjson": "^1.9.1", + "uuid": "^8.3.2" }, "devDependencies": { "@types/js-yaml": "^4.0.5", diff --git a/frontend/src/components/ConnectionConfiguration/AWS/configureAws.tsx b/frontend/src/components/ConnectionConfiguration/AWS/configureAws.tsx new file mode 100644 index 00000000..21e5e76c --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/configureAws.tsx @@ -0,0 +1,411 @@ +import { + Box, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + Text, + Spinner, + Button, + Flex, +} from "@chakra-ui/react"; +import { ConnectionType, STEPS } from "@common/enums"; +import { useState } from "react"; +import KeySetup from "./key_setup"; +import { v4 as uuidv4 } from "uuid"; +import SourceInstanceID from "./source_instance_id"; +import { STEP_RESPONSE } from "@common/types"; +import { STEP_TO_TITLE_MAP } from "@common/maps"; +import axios, { AxiosResponse, AxiosError } from "axios"; +import { id } from "date-fns/locale"; +import { getAPIURL } from "~/constants"; +import OsSelection from "./os_selection"; +import InstanceSelection from "./instance_selection"; +import GenericStepAWS from "./genericStepAws"; +import SetupRulesFilter from "./mirrorFilters"; +import { useToast } from "@chakra-ui/react"; +interface configureAWSParams { + selected: STEPS; + updateSelected: (x: STEPS) => void; +} + +const incrementStep = ( + id: string, + params: Record, + step: STEPS, + onStepSuccess: ( + data: AxiosResponse, any> + ) => void, + onStepError: (data: AxiosResponse, any>) => void, + onError: (data: AxiosError) => void, + setUpdating: (x: boolean) => void +) => { + axios + .post>(`${getAPIURL()}/setup_connection`, { + id: id, + params: params, + type: ConnectionType.AWS, + step: step, + }) + .then((value) => { + if (value.data.success === "OK") { + onStepSuccess(value); + } else if (value.data.success === "FAIL") { + onStepError(value); + } + }) + .catch((err: AxiosError) => { + onError(err); + }) + .finally(() => { + setUpdating(false); + }); + setUpdating(true); +}; + +const ConfigureAWS: React.FC = ({ + selected, + updateSelected, +}) => { + const toast = useToast(); + + const create_toast_with_message = (msg: string, step: STEPS) => { + toast({ + title: `Encountered an error on step ${STEPS[step]}`, + description: msg, + status: "error", + duration: 6000, + isClosable: true, + }); + }; + const [isUpdating, setUpdating] = useState(false); + const [id] = useState(uuidv4()); + let internals = (selectedIndex: STEPS): React.ReactElement => { + switch (selectedIndex) { + case STEPS.AWS_KEY_SETUP: + return ( + { + incrementStep( + id, + params, + STEPS.AWS_KEY_SETUP, + () => updateSelected(STEPS.AWS_KEY_SETUP + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + /> + ); + case STEPS.SOURCE_INSTANCE_ID: + return ( + { + incrementStep( + id, + params, + STEPS.SOURCE_INSTANCE_ID, + () => updateSelected(STEPS.SOURCE_INSTANCE_ID + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + /> + ); + case STEPS.SELECT_OS: + return ( + { + incrementStep( + id, + params, + STEPS.SELECT_OS, + () => updateSelected(STEPS.SELECT_OS + 1), + (err) => + create_toast_with_message(err.data.message, STEPS.SELECT_OS), + () => {}, + setUpdating + ); + }} + id={id} + /> + ); + case STEPS.SELECT_INSTANCE_TYPE: + return ( + { + incrementStep( + id, + params, + STEPS.SELECT_INSTANCE_TYPE, + () => updateSelected(STEPS.SELECT_INSTANCE_TYPE + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.CREATE_INSTANCE: + return ( + { + incrementStep( + id, + params, + STEPS.CREATE_INSTANCE, + () => updateSelected(STEPS.CREATE_INSTANCE + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.INSTANCE_IP: + return ( + { + incrementStep( + id, + params, + STEPS.INSTANCE_IP, + () => updateSelected(STEPS.INSTANCE_IP + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.CREATE_MIRROR_TARGET: + return ( + { + incrementStep( + id, + params, + STEPS.CREATE_MIRROR_TARGET, + () => updateSelected(STEPS.CREATE_MIRROR_TARGET + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.CREATE_MIRROR_FILTER: + return ( + { + incrementStep( + id, + params, + STEPS.CREATE_MIRROR_FILTER, + () => updateSelected(STEPS.CREATE_MIRROR_FILTER + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + /> + ); + case STEPS.CREATE_MIRROR_SESSION: + return ( + { + incrementStep( + id, + params, + STEPS.CREATE_MIRROR_SESSION, + () => updateSelected(STEPS.CREATE_MIRROR_SESSION + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.TEST_SSH: + return ( + { + incrementStep( + id, + params, + STEPS.TEST_SSH, + () => updateSelected(STEPS.TEST_SSH + 1), + (err) => + create_toast_with_message(err.data.message, STEPS.TEST_SSH), + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.PUSH_FILES: + return ( + { + incrementStep( + id, + params, + STEPS.PUSH_FILES, + () => updateSelected(STEPS.PUSH_FILES + 1), + (err) => + create_toast_with_message(err.data.message, STEPS.PUSH_FILES), + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + case STEPS.EXEC_COMMAND: + return ( + { + incrementStep( + id, + params, + STEPS.EXEC_COMMAND, + () => updateSelected(STEPS.EXEC_COMMAND + 1), + (err) => { + create_toast_with_message( + err.data.message, + STEPS.SOURCE_INSTANCE_ID + ); + console.log(err.data.error); + }, + () => {}, + setUpdating + ); + }} + isCurrent={selectedIndex == selected} + > + ); + + default: + return ( + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim + ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. + + ); + } + }; + return ( + <> + + {Array.from(Array(12)).map((_, i) => { + return ( + +

+ + + Step {i + 1}: {STEP_TO_TITLE_MAP[i + 1]} + + +

+ + {isUpdating && ( + + + + )} + + {internals(i + 1)} + + +
+ ); + })} +
+ + ); +}; + +export default ConfigureAWS; diff --git a/frontend/src/components/ConnectionConfiguration/AWS/genericStepAws.tsx b/frontend/src/components/ConnectionConfiguration/AWS/genericStepAws.tsx new file mode 100644 index 00000000..f87afd5c --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/genericStepAws.tsx @@ -0,0 +1,27 @@ +import { Flex, Spinner } from "@chakra-ui/react"; +import { useEffect } from "react"; + +interface GenericStepAWSInterface { + id: string; + complete: (params: Record) => void; + isCurrent: boolean; +} + +const GenericStepAWS: React.FC = ({ + id, + complete, + isCurrent, +}) => { + useEffect(() => { + if (isCurrent) { + complete({}); + } + }, [isCurrent]); + return ( + + + + ); +}; + +export default GenericStepAWS; diff --git a/frontend/src/components/ConnectionConfiguration/AWS/instance_selection.tsx b/frontend/src/components/ConnectionConfiguration/AWS/instance_selection.tsx new file mode 100644 index 00000000..51d07133 --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/instance_selection.tsx @@ -0,0 +1,194 @@ +import { + Box, + Button, + Flex, + Grid, + GridItem, + Input, + Select, + Spinner, + NumberInput, + NumberInputField, + NumberInputStepper, + NumberDecrementStepper, + NumberIncrementStepper, +} from "@chakra-ui/react"; +import { ConnectionType, STEPS } from "@common/enums"; +import { MachineSpecifications, STEP_RESPONSE } from "@common/types"; +import axios, { AxiosResponse, AxiosError } from "axios"; +import { useEffect, useState } from "react"; +import { getAPIURL } from "~/constants"; + +interface KeySetupInterface { + id: string; + complete: (params: Record) => void; + isCurrent: boolean; +} + +const InstanceSelection: React.FC = ({ + id, + complete, + isCurrent, +}) => { + const [instances, setInstances] = useState>(null); + const [selectedInstance, setSelectedInstance] = useState(null); + const [selectedInstanceSpecs, setSelectedInstanceSpecs] = + useState({ + maxCpu: 4, + minCpu: 1, + maxMem: 8, + minMem: 2, + }); + useEffect(() => { + if (isCurrent) { + axios + .post>(`${getAPIURL()}/setup_connection/aws/instances`, { + id: id, + specs: selectedInstanceSpecs, + }) + .then((res) => { + setInstances(res.data); + if (res.data.length > 0) { + setSelectedInstance(res.data[0]); + } + }) + .catch((err) => {}); + } + }, [isCurrent]); + if (instances != null) { + return ( + + + Minimum CPU Count + + + + setSelectedInstanceSpecs({ + ...selectedInstanceSpecs, + minCpu: parseInt(v), + }) + } + > + + + + + + + + + Maximum CPU Count + + + + setSelectedInstanceSpecs({ + ...selectedInstanceSpecs, + maxCpu: parseInt(v), + }) + } + > + + + + + + + + + Minimum Memory Count + + + + setSelectedInstanceSpecs({ + ...selectedInstanceSpecs, + minMem: parseInt(v), + }) + } + > + + + + + + + + + Maximum Memory Count + + + + setSelectedInstanceSpecs({ + ...selectedInstanceSpecs, + maxMem: parseInt(v), + }) + } + > + + + + + + + + + Compatible Instance Types + + + + + + + + + + + + + ); + } + return ( + + + + ); +}; +export default InstanceSelection; diff --git a/frontend/src/components/ConnectionConfiguration/AWS/key_setup.tsx b/frontend/src/components/ConnectionConfiguration/AWS/key_setup.tsx new file mode 100644 index 00000000..60526d8c --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/key_setup.tsx @@ -0,0 +1,64 @@ +import { Box, Button, Flex, Grid, GridItem, Input } from "@chakra-ui/react"; +import { ConnectionType, STEPS } from "@common/enums"; +import { STEP_RESPONSE } from "@common/types"; +import axios, { AxiosResponse, AxiosError } from "axios"; +import { useState } from "react"; +import { getAPIURL } from "~/constants"; + +interface KeySetupInterface { + complete: (params: Record) => void; +} + +const KeySetup: React.FC = ({ complete }) => { + const [accessId, setAccessId] = useState(""); + const [secretAccessKey, setSecretAccessKey] = useState(""); + + return ( + + + Access ID + + + + setAccessId(e.target.value)} + value={accessId} + /> + + + + Secret Access Key + + + + setSecretAccessKey(e.target.value)} + value={secretAccessKey} + /> + + + + + + + + + ); +}; +export default KeySetup; diff --git a/frontend/src/components/ConnectionConfiguration/AWS/mirrorFilters.tsx b/frontend/src/components/ConnectionConfiguration/AWS/mirrorFilters.tsx new file mode 100644 index 00000000..1b1b4cc5 --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/mirrorFilters.tsx @@ -0,0 +1,203 @@ +import { CloseIcon } from "@chakra-ui/icons"; +import { + Grid, + GridItem, + IconButton, + Input, + Select, + Spinner, + VStack, + Box, + Wrap, + Flex, + Button, + HStack, +} from "@chakra-ui/react"; +import { protocols } from "@common/enums"; +import { TrafficFilterRuleSpecs } from "@common/types"; +import { useEffect, useState } from "react"; + +interface GenericStepAWSInterface { + id: string; + complete: (params: Record) => void; + isCurrent: boolean; +} + +const update_attribute = ( + data: Array, + attribute_name, + change_at, + change_to +) => { + return data.map((v, idx) => { + if (idx === change_at) { + v[attribute_name] = change_to; + return v; + } + return v; + }); +}; + +const SetupRulesFilter: React.FC = ({ + id, + complete, + isCurrent, +}) => { + const [rules, updateRules] = useState>([ + { + destination_CIDR: "0.0.0.0/0", + source_CIDR: "0.0.0.0/0", + destination_port: "", + source_port: "", + direction: "in", + protocol: protocols.TCP, + }, + ]); + return ( + + Add Rules for mirroring data + + {rules.map((v, i) => ( + + + + + Destination CIDR + { + updateRules( + update_attribute( + rules, + "destination_CIDR", + i, + v.target.value + ) + ); + }} + /> + + + + + Source CIDR + { + updateRules( + update_attribute( + rules, + "source_CIDR", + i, + v.target.value + ) + ); + }} + /> + + + + + Destination Port + { + updateRules( + update_attribute( + rules, + "destination_port", + i, + v.target.value + ) + ); + }} + /> + + + + + Source Port + { + updateRules( + update_attribute( + rules, + "source_port", + i, + v.target.value + ) + ); + }} + /> + + + + + Traffic Direction + + + + + + } + aria-label={`remove id ${i}`} + onClick={() => { + // let new_rules = rules.splice(i, 1); + // console.log(new_rules); + // console.log(rules); + updateRules(rules.filter((_, idx) => idx != i)); + }} + /> + + + + + ))} + + + + + + + + + ); +}; +export default SetupRulesFilter; diff --git a/frontend/src/components/ConnectionConfiguration/AWS/os_selection.tsx b/frontend/src/components/ConnectionConfiguration/AWS/os_selection.tsx new file mode 100644 index 00000000..2f4d73cc --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/os_selection.tsx @@ -0,0 +1,93 @@ +import { + Box, + Button, + Flex, + Grid, + GridItem, + Input, + Select, + Spinner, +} from "@chakra-ui/react"; +import { ConnectionType, STEPS } from "@common/enums"; +import { STEP_RESPONSE } from "@common/types"; +import axios, { AxiosResponse, AxiosError } from "axios"; +import { useEffect, useState } from "react"; +import { getAPIURL } from "~/constants"; + +interface KeySetupInterface { + id: string; + complete: (params: Record) => void; + isCurrent: boolean; +} + +const OsSelection: React.FC = ({ + id, + complete, + isCurrent, +}) => { + const [os_choice, set_choice] = useState(""); + const [OSChoices, setOSChoices] = useState>(null); + useEffect(() => { + if (isCurrent) { + axios + .post>( + `${getAPIURL()}/setup_connection/aws/os`, + { + id: id, + } + ) + .then((res) => { + setOSChoices(res.data); + if (res.data.length > 0) { + set_choice(res.data[0][1]); + } + }) + .catch((err) => {}); + } + }, [isCurrent]); + if (OSChoices != null) { + return ( + + + Source EC2 OS Choices + + + + + + + + + + + + + ); + } + return ( + + + + ); +}; +export default OsSelection; diff --git a/frontend/src/components/ConnectionConfiguration/AWS/source_instance_id.tsx b/frontend/src/components/ConnectionConfiguration/AWS/source_instance_id.tsx new file mode 100644 index 00000000..1d1d0a77 --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/AWS/source_instance_id.tsx @@ -0,0 +1,47 @@ +import { Box, Button, Flex, Grid, GridItem, Input } from "@chakra-ui/react"; +import { ConnectionType, STEPS } from "@common/enums"; +import { STEP_RESPONSE } from "@common/types"; +import axios, { AxiosResponse, AxiosError } from "axios"; +import { useState } from "react"; +import { getAPIURL } from "~/constants"; + +interface KeySetupInterface { + complete: (params: Record) => void; +} + +const SourceInstanceID: React.FC = ({ complete }) => { + const [instanceID, setInstanceId] = useState(""); + + return ( + + + Source EC2 Instance ID + + + + setInstanceId(e.target.value)} + value={instanceID} + /> + + + + + + + + + ); +}; +export default SourceInstanceID; diff --git a/frontend/src/components/ConnectionConfiguration/output.tsx b/frontend/src/components/ConnectionConfiguration/output.tsx new file mode 100644 index 00000000..0a7eec47 --- /dev/null +++ b/frontend/src/components/ConnectionConfiguration/output.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Textarea, Text } from "@chakra-ui/react"; + +interface OutputLogInterface { + log: Array; + formatter: (prev: any, curr: any, idx: number) => string; +} +const OutputLog: React.FC = ({ log, formatter }) => { + return ( +