diff --git a/.github/workflows/deploy-create-mirroring-sessions-lambda.yaml b/.github/workflows/deploy-create-mirroring-sessions-lambda.yaml new file mode 100644 index 0000000..6b11a63 --- /dev/null +++ b/.github/workflows/deploy-create-mirroring-sessions-lambda.yaml @@ -0,0 +1,92 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ feature/quick-setup ] + paths: ['lambdas/create-mirroring-sessions/**'] + pull_request: + branches: [ feature/quick-setup ] + paths: ['lambdas/create-mirroring-sessions/**'] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + inputs: + Environment: + required: true + type: choice + options: + - prod + - staging + default: staging + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + #Set working-directory for build job + defaults: + run: + working-directory: lambdas/create-mirroring-sessions + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + - uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '8' + architecture: x64 + + - uses: actions/setup-node@v2 + with: + node-version: '17' + - name: Install modules + run: npm install + - name: Configure AWS credentials + if: ${{ github.event.inputs.Environment == 'staging' || github.event.inputs.Environment == 'prod' }} + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + - name: Make zip file + env: + ZIP_NAME: ${{ github.event.inputs.Environment == 'staging' && '-staging' || '' }} + run: | + mkdir code + cp index.js ./code + cp package.json ./code + cd code + npm install + zip -r create-mirror-session${ZIP_NAME}.zip ./ + - name: Copy zip file to S3 + if: ${{ github.event.inputs.Environment == 'staging' || github.event.inputs.Environment == 'prod' }} + run: | + cd code + aws configure set default.s3.multipart_threshold 64MB + aws s3 cp *.zip s3://akto-setup-us-east-1/templates/ --acl bucket-owner-full-control --region us-east-1 + aws s3 cp *.zip s3://akto-setup-us-east-2/templates/ --acl bucket-owner-full-control --region us-east-2 + aws s3 cp *.zip s3://akto-setup-us-west-1/templates/ --acl bucket-owner-full-control --region us-west-1 + aws s3 cp *.zip s3://akto-setup-us-west-2/templates/ --acl bucket-owner-full-control --region us-west-2 + aws s3 cp *.zip s3://akto-setup-ap-east-1/templates/ --acl bucket-owner-full-control --region ap-east-1 + aws s3 cp *.zip s3://akto-setup-ap-south-1/templates/ --acl bucket-owner-full-control --region ap-south-1 + aws s3 cp *.zip s3://akto-setup-ap-northeast-2/templates/ --acl bucket-owner-full-control --region ap-northeast-2 + aws s3 cp *.zip s3://akto-setup-ap-southeast-1/templates/ --acl bucket-owner-full-control --region ap-southeast-1 + aws s3 cp *.zip s3://akto-setup-ap-southeast-2/templates/ --acl bucket-owner-full-control --region ap-southeast-2 + aws s3 cp *.zip s3://akto-setup-ap-northeast-1/templates/ --acl bucket-owner-full-control --region ap-northeast-1 + aws s3 cp *.zip s3://akto-setup-ca-central-1/templates/ --acl bucket-owner-full-control --region ca-central-1 + aws s3 cp *.zip s3://akto-setup-eu-central-1/templates/ --acl bucket-owner-full-control --region eu-central-1 + aws s3 cp *.zip s3://akto-setup-eu-west-1/templates/ --acl bucket-owner-full-control --region eu-west-1 + aws s3 cp *.zip s3://akto-setup-eu-west-2/templates/ --acl bucket-owner-full-control --region eu-west-2 + aws s3 cp *.zip s3://akto-setup-eu-west-3/templates/ --acl bucket-owner-full-control --region eu-west-3 + aws s3 cp *.zip s3://akto-setup-eu-north-1/templates/ --acl bucket-owner-full-control --region eu-north-1 + aws s3 cp *.zip s3://akto-setup-me-south-1/templates/ --acl bucket-owner-full-control --region me-south-1 + aws s3 cp *.zip s3://akto-setup-sa-east-1/templates/ --acl bucket-owner-full-control --region sa-east-1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 86103e0..185140d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,108 @@ env/ data/* -.idea/ \ No newline at end of file +.idea/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port \ No newline at end of file diff --git a/lambdas/create-mirroring-sessions/index.js b/lambdas/create-mirroring-sessions/index.js new file mode 100644 index 0000000..fc381e4 --- /dev/null +++ b/lambdas/create-mirroring-sessions/index.js @@ -0,0 +1,682 @@ +var aws = require('aws-sdk') +var response = require('cfn-response') +var saveCollectionNamesLambdaArn = process.env.SAVE_COLLECTION_NAMES_LAMBDA_ARN; var trafficMirrorTargetID = process.env.TRAFFIC_MIRROR_TARGET_ID; +var trafficMirrorFilterID = process.env.TRAFFIC_MIRROR_FILTER_ID; +var mirrorSessionNumber = "1"; +var tgNamesArray = process.env.ELB_NAMES.split(",").map(item => item.trim());; +var elbNamesArray = process.env.ELB_NAMES.split(",").map(item => item.trim());; +var sampleSize = 100; +var targetLB = process.env.TARGET_LB; +function filterDuplicates(value, index, self) { + return self.indexOf(value) === index; +} + +function getSlicedArr(arr, size) { + let sliced_arr = []; + for(let i=0; i= maxRetries) { + console.log('Max retries reached, throwing error') + throw error; + } + const delay = Math.pow(2, retryCount) * 1000; + console.log(`Throttling error, retrying in ${delay}ms`); + await new Promise(resolve => setTimeout(resolve, delay)); + retryCount++; + } + } +} + + +async function getEnisAndPortOfTargetGroup(params, allPorts, eniList) { + var isError = false; + var elbv2 = new aws.ELBv2(); + var ec2 = new aws.EC2(); + var i = 0, + j = 0, + k = 0; + var targetGroups = await retry(() => elbv2.describeTargetGroups(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + console.log("elbv2 describeTargetGroups ", JSON.stringify(targetGroups)); + if (!isError && targetGroups.TargetGroups != undefined) { + for (j = 0; j < targetGroups.TargetGroups.length; j++) { + var counter = sampleSize < 1 ? targetGroups.TargetGroups.length : sampleSize; + var targetGroupName = targetGroups.TargetGroups[j].TargetGroupName || ("TargetGroups-"+j); + isError = false; + params = { + TargetGroupArn: targetGroups.TargetGroups[j].TargetGroupArn + } + var backends = await retry(() => elbv2.describeTargetHealth(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + console.log("elbv2 describeTargetHealth ", JSON.stringify(backends)); + if (!isError && backends.TargetHealthDescriptions != undefined) { + if (targetGroups.TargetGroups[j].TargetType == "instance") { + var instanceIds = []; + for (k = 0; k < backends.TargetHealthDescriptions.length && counter > 0; k++) { + instanceIds.push(backends.TargetHealthDescriptions[k].Target.Id) + allPorts.push(backends.TargetHealthDescriptions[k].Target.Port) + counter -- + } + if (instanceIds.length > 0) { + var slicedInstanceIds = getSlicedArr(instanceIds, 30); + for(slicedInstanceIdList of slicedInstanceIds){ + params = { + Filters: [{ + Name: "attachment.instance-id", + Values: slicedInstanceIdList + }] + }; + var enis = await retry(() => ec2.describeNetworkInterfaces(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + console.log("elbv2 instance params ", JSON.stringify(params)); + console.log("elbv2 instance describeNetworkInterfaces ", JSON.stringify(enis)); + if (!isError && enis.NetworkInterfaces != undefined) { + for (var l = 0; l < enis.NetworkInterfaces.length; l++) { + // Use only primary interface + if (enis.NetworkInterfaces[l].Attachment != undefined && enis.NetworkInterfaces[l].Attachment.DeviceIndex == 0) { + eniList.push([targetGroupName, enis.NetworkInterfaces[l].NetworkInterfaceId]) + } + } + } + } + } + } + if (targetGroups.TargetGroups[j].TargetType == "ip") { + var ipList = []; + for (k = 0; k < backends.TargetHealthDescriptions.length && counter > 0; k++) { + ipList.push(backends.TargetHealthDescriptions[k].Target.Id) + allPorts.push(backends.TargetHealthDescriptions[k].Target.Port) + counter -- + } + if (ipList.length > 0) { + slicedIpList = getSlicedArr(ipList, 30) + for(slicedList of slicedIpList){ + params = { + Filters: [{ + Name: "addresses.private-ip-address", + Values: slicedList + }] + }; + var enis = await retry(() => ec2.describeNetworkInterfaces(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + console.log("elbv2 ip params ", JSON.stringify(params)); + console.log("elbv2 ip describeNetworkInterfaces ", JSON.stringify(enis)); + if (!isError && enis.NetworkInterfaces != undefined) { + for (var l = 0; l < enis.NetworkInterfaces.length; l++) { + eniList.push([targetGroupName, enis.NetworkInterfaces[l].NetworkInterfaceId]) + } + } + } + } + } + } + } + } + +} + +async function updateTrafficMirroringRule(allPorts) { + //console.log("eniList ", JSON.stringify(eniList)) + var params = {}; + console.log("params ", JSON.stringify(params)) + var isError = false; + var ec2 = new aws.EC2(); + var i = 0, + j = 0; + params = { + Filters: [{ + Name: "traffic-mirror-filter-id", + Values: [ + trafficMirrorFilterID + ] + }] + } + isError = false; + var filterList = await retry(() => ec2.describeTrafficMirrorFilters(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + var uniqueAllPorts = allPorts.filter(filterDuplicates); + uniqueAllPorts.sort(function(a, b) { + return a - b; + });; + var uniqueAllPortsRange = [] + for (i = 0; i < uniqueAllPorts.length; i++) { + uniqueAllPortsRange.push([uniqueAllPorts[i], uniqueAllPorts[i]]) + } + if (uniqueAllPorts.length > 10) { + // only 10 rules are allowed per filter + console.log("ports are more than 10, grouping them") + var diff = []; + for (i = 1; i < uniqueAllPorts.length; i++) { + diff.push(uniqueAllPorts[i] - uniqueAllPorts[1]); + } + console.log("1. uniqueAllPortsRange ", JSON.stringify(uniqueAllPortsRange)) + console.log("1. diff ", JSON.stringify(diff)) + for (i = (uniqueAllPortsRange.length - 10); i > 0; i--) { + var minDiff = 65535; // max port number + var minDiffIndex = 0; + for (j = 0; j < diff.length; j++) { + if (minDiff > diff[j]) { + minDiff = diff[j] + minDiffIndex = j; + } + } + + uniqueAllPortsRange[minDiffIndex][1] = uniqueAllPortsRange[minDiffIndex + 1][1] + uniqueAllPortsRange.splice(minDiffIndex + 1, 1) + diff.splice(minDiffIndex, 1) + console.log("2. uniqueAllPortsRange ", JSON.stringify(uniqueAllPortsRange)) + console.log("2. diff ", JSON.stringify(diff)) + } + } + console.log("describe mirror filters ", JSON.stringify(filterList)) + console.log("uniqueAllPortsRange ", JSON.stringify(uniqueAllPortsRange)) + var existingUnsedPorts = [] + var unusedFilterId = [] + var maxRuleNumber = 1 + if (!isError && filterList.TrafficMirrorFilters != undefined) { + for (i = 0; i < filterList.TrafficMirrorFilters.length; i++) { + + for (j = 0; j < filterList.TrafficMirrorFilters[i].IngressFilterRules.length; j++) { + if (maxRuleNumber < filterList.TrafficMirrorFilters[i].IngressFilterRules[j].RuleNumber) { + maxRuleNumber = filterList.TrafficMirrorFilters[i].IngressFilterRules[j].RuleNumber; + } + if (filterList.TrafficMirrorFilters[i].IngressFilterRules[j].DestinationPortRange != undefined) { + let currentFromPort = filterList.TrafficMirrorFilters[i].IngressFilterRules[j].DestinationPortRange.FromPort; + let currentToPort = filterList.TrafficMirrorFilters[i].IngressFilterRules[j].DestinationPortRange.ToPort; + if (existIn2dArray(uniqueAllPortsRange, [currentFromPort, currentToPort])) { + uniqueAllPortsRange = uniqueAllPortsRange.filter(item => (item[0] != currentFromPort && item[1] != currentToPort)) + } + else { + existingUnsedPorts.push([currentFromPort, currentToPort]) + unusedFilterId.push(filterList.TrafficMirrorFilters[i].IngressFilterRules[j].TrafficMirrorFilterRuleId) + } + } + + } + for (j = 0; j < filterList.TrafficMirrorFilters[i].EgressFilterRules.length; j++) { + if (maxRuleNumber < filterList.TrafficMirrorFilters[i].EgressFilterRules[j].RuleNumber) { + maxRuleNumber = filterList.TrafficMirrorFilters[i].EgressFilterRules[j].RuleNumber; + } + if (filterList.TrafficMirrorFilters[i].EgressFilterRules[j].SourcePortRange != undefined) { + let currentFromPort = filterList.TrafficMirrorFilters[i].EgressFilterRules[j].SourcePortRange.FromPort; + let currentToPort = filterList.TrafficMirrorFilters[i].EgressFilterRules[j].SourcePortRange.ToPort; + if (existIn2dArray(existingUnsedPorts, [currentFromPort, currentToPort])) { + unusedFilterId.push(filterList.TrafficMirrorFilters[i].EgressFilterRules[j].TrafficMirrorFilterRuleId) + } + } + + } + } + } + console.log("ports to remove from rule ", JSON.stringify(existingUnsedPorts)) + console.log("ports to add to rule ", JSON.stringify(uniqueAllPortsRange)) + unusedFilterId = [] + for (i = 0; i < unusedFilterId.length; i++) { + params = { + TrafficMirrorFilterRuleId: unusedFilterId[i] + } + await ec2.deleteTrafficMirrorFilterRule(params).promise().catch((err) => { + console.error("error deleting rule", err); + isError = true; + }); + + } + + + params = { + DestinationCidrBlock: '0.0.0.0/0', + RuleAction: 'accept', + RuleNumber: maxRuleNumber, + SourceCidrBlock: '0.0.0.0/0', + TrafficDirection: 'ingress', + TrafficMirrorFilterId: trafficMirrorFilterID, + Description: 'ingress rule for port ', + DestinationPortRange: { + FromPort: 0, + ToPort: 65535 + }, + DryRun: false, + Protocol: 6, + }; + await ec2.createTrafficMirrorFilterRule(params).promise().catch((err) => { + console.error("error creating rule", err); + isError = true; + }); + params = { + DestinationCidrBlock: '0.0.0.0/0', + RuleAction: 'accept', + RuleNumber: maxRuleNumber, + SourceCidrBlock: '0.0.0.0/0', + TrafficDirection: 'egress', + TrafficMirrorFilterId: trafficMirrorFilterID, + Description: 'egress rule for port ', + SourcePortRange: { + FromPort: 0, + ToPort: 65535 + }, + DryRun: false, + Protocol: 6, + }; + await ec2.createTrafficMirrorFilterRule(params).promise().catch((err) => { + console.error("error creating rule", err); + isError = true; + }); +} + +function checkEni(eniList, eni){ + return eniList.flat().includes(eni); +} + +async function updateMirroringSessions(eniList, failedEnis, + successEnis) { + //get Existing Mirroring session + var isError = false; + var ec2 = new aws.EC2(); + var i = 0; + var params = { + Filters: [ + { + Name: 'session-number', + Values: [ mirrorSessionNumber ] + } + ] + } + var mirrorSession = await retry(() => ec2.describeTrafficMirrorSessions(params).promise()).catch((err) => { + console.error("error describing traffic mirror sessions", err); + isError = true; + }); + var mirroringSessionToDelete = [] + if (!isError && mirrorSession.TrafficMirrorSessions != undefined) { + for (i = 0; i < mirrorSession.TrafficMirrorSessions.length; i++) { + if (checkEni(eniList,mirrorSession.TrafficMirrorSessions[i].NetworkInterfaceId)) { + // if mirroring session exist for eni but not with same target and filter + if ( mirrorSession.TrafficMirrorSessions[i].TrafficMirrorTargetId != trafficMirrorTargetID || + mirrorSession.TrafficMirrorSessions[i].TrafficMirrorFilterId != trafficMirrorFilterID ) { + mirroringSessionToDelete.push(mirrorSession.TrafficMirrorSessions[i].TrafficMirrorSessionId) + } else { + eniList = eniList.filter(item => item[1] != mirrorSession.TrafficMirrorSessions[i].NetworkInterfaceId) + } + } + else { + mirroringSessionToDelete.push(mirrorSession.TrafficMirrorSessions[i].TrafficMirrorSessionId) + } + } + } + if (mirroringSessionToDelete.length > 0) { + console.log("Deleting Mirroring sessions: ", JSON.stringify(mirroringSessionToDelete)) + slicedMirroringSessionsToDelete = getSlicedArr(mirroringSessionToDelete, 20) + for(slicedMirroringSessionsToDeleteList of slicedMirroringSessionsToDelete){ + params = { + DryRun: false, + Filters: [{ + Name: 'traffic-mirror-session-id', + Values: slicedMirroringSessionsToDeleteList + } + ] + } + await deleteTrafficMirrorSessionInternal(params) + } + } + + + console.log("New mirroring session to create: ", JSON.stringify(eniList)) + for (i = 0; i < eniList.length; i++) { + await createMirroringSession(eniList[i], mirrorSessionNumber, trafficMirrorFilterID, successEnis, failedEnis) + } +} + + +async function createTrafficMirrorSessionForTargetGroup(allPorts, eniList) { + if (tgNamesArray == undefined || tgNamesArray.length == 0) + return; + if (tgNamesArray[0].trim() == "") + return; + + + slicedTgNamesArr = getSlicedArr(tgNamesArray, 20); + for(slicedTgNamesList of slicedTgNamesArr){ + var params = { + Names: slicedTgNamesList + } + await getEnisAndPortOfTargetGroup(params, allPorts, eniList) + } +} + +async function createTrafficMirrorSessionForLBs(allPorts, eniList) { + if (elbNamesArray.length == 0) + return; + if (elbNamesArray[0].trim() == "") + return; + + //console.log("params ", JSON.stringify(params)) + var isError = false; + var elb = new aws.ELB(); + var elbv2 = new aws.ELBv2(); + var ec2 = new aws.EC2(); + var slicedElbNamesArray = getSlicedArr(elbNamesArray, 20) //not sure about max size, keeping it as 20 + for (let elbNames of slicedElbNamesArray){ + var params = { + LoadBalancerNames: elbNames + }; + console.log('elbv1 describeLoadBalancers request', params) + var data = await retry(() => elb.describeLoadBalancers(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + console.log("elbv1 describeLoadBalancers response", JSON.stringify(data)); + var i = 0, + j = 0, + k = 0; + if (!isError && data.LoadBalancerDescriptions != undefined) { + for (i = 0; i < data.LoadBalancerDescriptions.length; i++) { + var loadBalancerName = data.LoadBalancerDescriptions[i].LoadBalancerName; + if (!elbNamesArray.includes(data.LoadBalancerDescriptions[i].LoadBalancerName)) { + continue; + } + for (j = 0; j < data.LoadBalancerDescriptions[i].ListenerDescriptions.length; j++) { + if (data.LoadBalancerDescriptions[i].ListenerDescriptions[j].Listener.InstanceProtocol == "HTTP" || + data.LoadBalancerDescriptions[i].ListenerDescriptions[j].Listener.InstanceProtocol == "TCP") { + allPorts.push(data.LoadBalancerDescriptions[i].ListenerDescriptions[j].Listener.InstancePort); + } + } + + params = { + Filters: [{ + Name: "attachment.instance-id", + Values: [ + + ] + }] + }; + var counter = sampleSize < 1 ? targetGroups.TargetGroups.length : sampleSize + for (j = 0; j < data.LoadBalancerDescriptions[i].Instances.length && counter > 0; j++) { + params.Filters[0].Values[j] = data.LoadBalancerDescriptions[i].Instances[j].InstanceId + counter -- + } + console.log("elb eni params", JSON.stringify(params)); + if (params.Filters[0].Values.length > 0) { + var enis = await retry(() => ec2.describeNetworkInterfaces(params).promise()).catch((err) => { // to check + console.error(err); + isError = true; + }); + console.log("elb enis ", JSON.stringify(enis)); + if (!isError && enis.NetworkInterfaces != undefined) { + for (var k = 0; k < enis.NetworkInterfaces.length; k++) { + // Use only primary interface + if (enis.NetworkInterfaces[k].Attachment != undefined && enis.NetworkInterfaces[k].Attachment.DeviceIndex == 0) { + eniList.push([loadBalancerName, enis.NetworkInterfaces[k].NetworkInterfaceId]) + } + } + } + } + } + } + } + + + console.log("elb eniList after old ELB call ", JSON.stringify(eniList)); + + // V2 Load balancers + + for(let elbNames of slicedElbNamesArray){ + params = { + Names: elbNames + }; + console.log('elbv2 describeLoadBalancers request', params) + isError = false; + data = await retry(() => elbv2.describeLoadBalancers(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + console.log("elbv2 describeLoadBalancers response", JSON.stringify(data)); + if (!isError && data.LoadBalancers != undefined) { + for (i = 0; i < data.LoadBalancers.length; i++) { + if (!elbNamesArray.includes(data.LoadBalancers[i].LoadBalancerName)) { + continue; + } + isError = false; + params = { + LoadBalancerArn: data.LoadBalancers[i].LoadBalancerArn + } + await getEnisAndPortOfTargetGroup(params, allPorts, eniList) + } + } + } + + console.log("elb eniList after new ELB call ", JSON.stringify(eniList)); + +} +async function createMirroringSession(eni, sessionNumber, filterId, successEnis, failedEnis) { + var params = { + NetworkInterfaceId: eni[1], + SessionNumber: parseInt(sessionNumber), + /* required */ + TrafficMirrorFilterId: filterId, + TrafficMirrorTargetId: trafficMirrorTargetID, + VirtualNetworkId: calcVxLanId(eni[0]) + }; + console.log("params " + JSON.stringify(params)); + var ec2 = new aws.EC2(); + var promise = await ec2.createTrafficMirrorSession(params).promise().catch((err) => { + console.error(err); + failedEnis.push({ eni: eni, message: err.message }); + }); + if (promise != undefined) { + // get vpc cidr + var vpcPromise = await retry(() => ec2.describeVpcs().promise()).catch((err1) => { + console.error(err1) + }) + let cidrBlock = [] + if (vpcPromise !== undefined && vpcPromise["Vpcs"]) { + vpcPromise["Vpcs"].forEach((x) => { + cidrBlock.push(x["CidrBlock"]) + }) + console.log("cidrBlock: " + cidrBlock) + eni.push(cidrBlock) + } + successEnis.push(eni); + } + console.log(promise); + +} +async function deleteTrafficMirrorSessionInternal(params) { + var isError = false; + var ec2 = new aws.EC2(); + var data = await retry(() => ec2.describeTrafficMirrorSessions(params).promise()).catch((err) => { + console.error(err); + isError = true; + }); + if (!isError) { + console.log(data); // successful response + if (data.TrafficMirrorSessions != undefined) { + for (var i = 0; i < data.TrafficMirrorSessions.length; i++) { + params = { + TrafficMirrorSessionId: data.TrafficMirrorSessions[i].TrafficMirrorSessionId, + DryRun: false + }; + console.log(params); + var ec2Tmp = new aws.EC2(); + var promise = await ec2Tmp.deleteTrafficMirrorSession(params).promise().catch((err) => { console.error(err); }); // not required + console.log(promise); + } + } + } +} + +async function deleteTrafficMirrorSession() { + var params = { + DryRun: false, + Filters: [{ + Name: 'traffic-mirror-target-id', + Values: [ + trafficMirrorTargetID + ] + }] + }; + await deleteTrafficMirrorSessionInternal(params) + +} +async function initFromCF(event, context) { + console.log("event received:\n" + JSON.stringify(event)); + if (event.RequestType != undefined) { + // coming from cloudformation + if (event.RequestType == "Delete") { + await deleteTrafficMirrorSession(); + await response.send(event, context, "SUCCESS"); + return; + } + var successEnis = []; + var failedEnis = []; + await createTrafficMirrorSessionForLBsAndTargetGroup(successEnis, failedEnis) + + var responseData = {}; + + successEnis.forEach((x) => { + x[1] = "" + }); + let set = new Set(successEnis.map(JSON.stringify)); + successEnis = Array.from(set).map(JSON.parse); + + failedEnis.forEach((x) => { + x[1] = "" + }); + set = new Set(failedEnis.map(JSON.stringify)); + failedEnis = Array.from(set).map(JSON.parse); + + responseData['successEnis'] = JSON.stringify(successEnis); + responseData['failedEnis'] = JSON.stringify(failedEnis); + + console.log('Failed enis list', JSON.stringify(failedEnis)) + console.log('Success enis list', JSON.stringify(successEnis)) + console.log("starting to invoke lambda") + + var invokeSaveCollectionNamesLambdaParams = { + FunctionName: saveCollectionNamesLambdaArn, + InvocationType: 'RequestResponse', + LogType: 'Tail' , + Payload: JSON.stringify(responseData) + } + console.log("starting to invoke lambda with params", invokeSaveCollectionNamesLambdaParams) + var lambda = new aws.Lambda(); + + lambda.invoke(invokeSaveCollectionNamesLambdaParams, function(err, data) { + if (err) console.log("finished invoke lambda err", err, err.stack); + else console.log("finished invoke lambda data", data); + }); + + console.log("finished invoke lambda"); + console.log(await wait20()); + responseData = {} + + await response.send(event, context, "SUCCESS", responseData); + } + +} +function wait20(){ + return new Promise((resolve, reject) => { + setTimeout(() => resolve("hello"), 20000) + }); +} +async function handlePeriodicEvents(event) { + var successEnis = []; + var failedEnis = []; + await createTrafficMirrorSessionForLBsAndTargetGroup(successEnis, failedEnis) + console.log("Mirroring session created for ", JSON.stringify(successEnis)) + console.log("failed ENIs ", JSON.stringify(failedEnis)); + var responseData = {}; + responseData['successEnis'] = JSON.stringify(successEnis); + responseData['failedEnis'] = JSON.stringify(failedEnis); + + console.log("starting to invoke lambda") + + var invokeSaveCollectionNamesLambdaParams = { + FunctionName: saveCollectionNamesLambdaArn, + InvocationType: 'RequestResponse', + LogType: 'Tail' , + Payload: JSON.stringify(responseData) + } + console.log("starting to invoke lambda with params", invokeSaveCollectionNamesLambdaParams) + var lambda = new aws.Lambda(); + + lambda.invoke(invokeSaveCollectionNamesLambdaParams, function(err, data) { + if (err) console.log("finished invoke lambda err", err, err.stack); + else console.log("finished invoke lambda data", data); + }); + + console.log("finished invoke lambda"); + console.log(await wait20()); +} +async function createTrafficMirrorSessionForLBsAndTargetGroup(successEnis, failedEnis) { + var ports = []; + var enis = [] + await createTrafficMirrorSessionForLBs(ports, enis); + await createTrafficMirrorSessionForTargetGroup(ports, enis) + await updateTrafficMirroringRule(ports) + var uniqueEnis = enis.filter(filterDuplicates); + await updateMirroringSessions(uniqueEnis, failedEnis, successEnis) +} + +exports.handler = async function(event, context) { + if (event.RequestType != undefined) { + // coming from cf + await initFromCF(event, context) + } + else { + // coming from periodic rule + await handlePeriodicEvents(event) + } +}; + diff --git a/lambdas/create-mirroring-sessions/package-lock.json b/lambdas/create-mirroring-sessions/package-lock.json new file mode 100644 index 0000000..1b05874 --- /dev/null +++ b/lambdas/create-mirroring-sessions/package-lock.json @@ -0,0 +1,251 @@ +{ + "name": "createmirroringsession", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "createmirroringsession", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1066.0", + "cfn-response": "^1.0.1" + } + }, + "node_modules/aws-sdk": { + "version": "2.1066.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1066.0.tgz", + "integrity": "sha512-9BZPdJgIvau8Jf2l3PxInNqQd733uKLqGGDywMV71duxNTLgdBZe2zvCkbgl22+ldC8R2LVMdS64DzchfQIxHg==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/cfn-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cfn-response/-/cfn-response-1.0.1.tgz", + "integrity": "sha1-qOwDlQwGg8UUlejKaA2dwLiEsTc=" + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "engines": { + "node": ">=4.0" + } + } + }, + "dependencies": { + "aws-sdk": { + "version": "2.1066.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1066.0.tgz", + "integrity": "sha512-9BZPdJgIvau8Jf2l3PxInNqQd733uKLqGGDywMV71duxNTLgdBZe2zvCkbgl22+ldC8R2LVMdS64DzchfQIxHg==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "cfn-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cfn-response/-/cfn-response-1.0.1.tgz", + "integrity": "sha1-qOwDlQwGg8UUlejKaA2dwLiEsTc=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } +} diff --git a/lambdas/create-mirroring-sessions/package.json b/lambdas/create-mirroring-sessions/package.json new file mode 100644 index 0000000..50e2a59 --- /dev/null +++ b/lambdas/create-mirroring-sessions/package.json @@ -0,0 +1,15 @@ +{ + "name": "createmirroringsession", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1066.0", + "cfn-response": "^1.0.1" + } +}