From 4e1f4dcfe0b85988d1ab1e47fe3b4a7b90d64979 Mon Sep 17 00:00:00 2001 From: Ryan Jaeger Date: Mon, 3 Jun 2024 17:43:16 +0000 Subject: [PATCH] v1.7.0-a release --- CHANGELOG.md | 13 + .../images/alb-forwarding-architecture.png | Bin 0 -> 91713 bytes architecture-doc/readme.md | 2 +- assets/certs/To_Create_Self_Signed-Cert.txt | 6 + .../attach-ec2-instance-profile.zip | Bin 987 -> 1006 bytes .../ec2-instance-profile-permissions.zip | Bin 1280 -> 1357 bytes config/customizations-config.yaml | 17 + .../AlbIpForwardingStack.template.json | 610 ++++++++++++++++++ config/global-config.yaml | 189 +++++- config/iam-config.yaml | 36 +- config/network-config.yaml | 232 +++++-- config/organization-config.yaml | 8 +- config/replacements-config.yaml | 42 +- config/security-config.yaml | 410 +++++++----- config/ssm-documents/s3-enforce-https.yaml | 2 +- documentation/FAQ.md | 57 +- install-controltower.md | 95 +-- install-organizations.md | 61 +- install.md | 6 - post-deployment.md | 83 ++- readme.md | 24 + .../setup-prerequisites.yaml | 10 +- source/alb-forwarder/alb-ip-monitor.ts | 211 ++++++ .../alb-target-record-monitor.ts | 396 ++++++++++++ source/alb-forwarder/index.ts | 15 + .../attach-ec2-instance-profile/index.js | 14 +- .../ec2-instance-profile-permissions/index.js | 44 +- update-instructions.md | 13 +- 28 files changed, 2227 insertions(+), 369 deletions(-) create mode 100644 architecture-doc/images/alb-forwarding-architecture.png create mode 100644 assets/certs/To_Create_Self_Signed-Cert.txt create mode 100644 config/customizations-config.yaml create mode 100644 config/customizations/AlbIpForwardingStack.template.json create mode 100644 readme.md create mode 100644 source/alb-forwarder/alb-ip-monitor.ts create mode 100644 source/alb-forwarder/alb-target-record-monitor.ts create mode 100644 source/alb-forwarder/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a8ea1..7b94046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.7.0-a] - 2024-06-03 +### Added +- feat(network-config): Add deployments of Application Load Balancers in perimeter VPC. Added sample to deploy ALB in workload accounts and ALB Forwarding feature. +- feat(replacements): Added use of replacements-config.yaml file to centralize deployment variables. + +### Changed +- fix(custom-config): Updated nodejs and AWS SDK version +- fix(network-config): Removed App2 subnets from the central network. These were used originally used for AWS Managed Active Directory; however, since MAD now supports running in a delegated account with IAM Identity Center, these are no longer needed. Customers should check there are no other resources deployed in these subnets prior to making change +- feat(global-config): Enabled additional regions by default with CMK region excludes for cost optimization +- fix(global-config): Add CWL subscription filter exclusion for organization CloudTrail logs +- fix(docs): Updated broken link to install instructions +- fix(docs): Updated documentation for Control Tower deployments with LZA v1.7.0 + ## [1.6.1-a] - 2024-03-04 ### Added - feat(replacements): Added use of replacements-config.yaml file to centralize global variables. diff --git a/architecture-doc/images/alb-forwarding-architecture.png b/architecture-doc/images/alb-forwarding-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..11eae30e39276a5449e79e31e14af69bc305bd06 GIT binary patch literal 91713 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 literal 0 HcmV?d00001 diff --git a/architecture-doc/readme.md b/architecture-doc/readme.md index 09a5fec..b93f74e 100644 --- a/architecture-doc/readme.md +++ b/architecture-doc/readme.md @@ -915,4 +915,4 @@ These services must still be appropriately configured. This includes ensuring bo --- -**Continue to [LZA configuration files and installation instructions](../config/readme.md)** +**Continue to [LZA installation instructions](../install.md)** diff --git a/assets/certs/To_Create_Self_Signed-Cert.txt b/assets/certs/To_Create_Self_Signed-Cert.txt new file mode 100644 index 0000000..107ea2d --- /dev/null +++ b/assets/certs/To_Create_Self_Signed-Cert.txt @@ -0,0 +1,6 @@ +Run the following: + +Example1: +openssl req -newkey rsa:2048 -nodes -keyout example1-cert.key -out example1-cert.csr -subj "/C=CA/ST=Ontario/L=Ottawa/O=AnyCompany/CN=*.example.ca" +openssl x509 -signkey example1-cert.key -in example1-cert.csr -req -days 1095 -out example1-cert.crt + diff --git a/config/custom-config-rules/attach-ec2-instance-profile.zip b/config/custom-config-rules/attach-ec2-instance-profile.zip index 1de00bf06d02573ed8890d48da07d3c50ed785dc..677e2a91b7a1b5986286d0ff8d86c117f28d33cc 100644 GIT binary patch literal 1006 zcmWIWW@Zs#U|`^2XbtL*IOgB8-h-Kep^XzHBEyiGmy%kcmsK1Z!pXpVGEp`yB3U-A zw1S&~k>w{N0|Qv+*08|*TLuDk&%^I5TglkDEJ%K)R_{_9gWLeoWws%quPxalO5dC* zdl7K7{=TJJ%ak>%m-VV|c)xr1`+d8OW3*pC;1pUbmSpL?SxiP)GhnA(jA-7Lv`qlOUgS{1_qXidGW>IY6&&+EqpAOQ&aDS8a^KgsPZvC7swir=jrH}U zr{_v4O21al;jyoiyBph|c6t5vlNoLYW@r4!HIwL>^!Z?dPnl0)PabEheR1xb-tZ}N zT%4Dk$rEXJUAV)4PxtFdyp`oYE`NQnlKEfzRiWgS)9?OXvcWICAl9R$)$Bm{3Hg>N zCe~&xv7Sh_a5sIHNW1Jb<9GUR!!K%|J>H!^oF%S z*4o+?7pi1ciV)#b@Bnc3LQzW z7T0AK8>lhw(CklO|2}_~wB_a2hZcV9$65B?znlA?b z$&1!siwTgnoc6T2C`aFO>AS)kizl}{y<-02;@(>?12o@y&35CEmy`s~MqB8lLXF z({MSUb;|y;f{U6c2q{+bJZw?4tb=>B?Qb z4;1z|^q+B(I@I`HZ5{VariTx#nL71?o*7#%xqj30Cwoyr(IumWy^|w0tf`7Adw(ih zQ~k33k#dWf|L5hZ3UKT`zWc@cmQOMF{@5E$e);5`Xv?2yomJZ_Tn}mTK7J@}pTO?_ z$#^T%ae-fNc%EH8z4H9=4%59~+V0g%<9^_C%IlWfruAN5zfWCW&*c4Izsc=3=hkd% zZ47j6wZm?vc@u=lg5@XAw7F6KdfA(jD$8nqfZR#bH?dQic>VN-T~u0D(F zpX%e;(^4A+LblF1V4R}~FRwR-!${OMK~i1d4x^c&R)1FH?mAy5u>2*L=IrOQWFLCFF_mpk zTpd;MxlyY1^FEb*3(hJmpQ(Fzmang~No3CPSE8xhdmMJzWIeO9ULj`l%zZ=Vir-Ug zG-sSLIUaY#O6B#VyftTqBh;U2ZrLn3%Yq{>7<#t{oL2L{{QrZl|Oi`P~s=;Bwwr0?Z0pQ5l}FFIxo7y-e_C) ztOIv8-sta67UzE}-@lo|_istR^wqgHU!DDKCHIB>@pGfVfPggXy)%2BbKHt`o9Lv> z%&p`5STusw)r&cc>)P6n)4yMCU8`<))G9vtz>S1+-*^PwLjuzNxCWlNt$QrPkn5k~ z-2)fbs<1y~i85lI=$gq(BwSQb`SYegdz0$Xv-@{` zIluId1^@*MvS$DQ diff --git a/config/custom-config-rules/ec2-instance-profile-permissions.zip b/config/custom-config-rules/ec2-instance-profile-permissions.zip index 49dad43991180a612caa238a358e20ac8bdf52f6..a5fec2aa0a9d04cdcdfb1754533134bcd4e897a5 100644 GIT binary patch delta 1344 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 diff --git a/config/customizations-config.yaml b/config/customizations-config.yaml new file mode 100644 index 0000000..863893a --- /dev/null +++ b/config/customizations-config.yaml @@ -0,0 +1,17 @@ +customizations: + cloudFormationStacks: + - name: AWSAccelerator-AlbIPForwardingStack + description: ALB Lambda Forwarder + runOrder: 10 + template: customizations/AlbIpForwardingStack.template.json + terminationProtection: true + parameters: + - name: acceleratorPrefix + value: AWSAccelerator + - name: vpcName + value: Perimeter + deploymentTargets: + accounts: + - Perimeter + regions: + - ca-central-1 \ No newline at end of file diff --git a/config/customizations/AlbIpForwardingStack.template.json b/config/customizations/AlbIpForwardingStack.template.json new file mode 100644 index 0000000..f952f4c --- /dev/null +++ b/config/customizations/AlbIpForwardingStack.template.json @@ -0,0 +1,610 @@ +{ + "Parameters": { + "vpcName": { + "Type": "String", + "Description": "The VPC Name for target ALBs" + }, + "acceleratorPrefix": { + "Type": "String", + "Default": "AWSAccelerator", + "Description": "The prefix used for the Landing Zone Accelerator with no dash. ex: AWSAccelerator" + } + }, + "Resources": { + "AlbIpForwardingalb2albKeyF92E9EA0": { + "Type": "AWS::KMS::Key", + "Properties": { + "EnableKeyRotation": true, + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete", + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/alb2albKey/Resource" + } + }, + "AlbIpForwardingddbDNSFirewallTableDE7BAC6C": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "PointInTimeRecoverySpecification": { + "PointInTimeRecoveryEnabled": true + }, + "SSESpecification": { + "KMSMasterKeyId": { + "Fn::GetAtt": [ + "AlbIpForwardingalb2albKeyF92E9EA0", + "Arn" + ] + }, + "SSEEnabled": true, + "SSEType": "KMS" + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "TableName": { + "Fn::Join": [ + "", + [ + { + "Ref": "acceleratorPrefix" + }, + "-Alb-Ip-Forwarding-", + { + "Ref": "vpcName" + } + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete", + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/ddbDNSFirewallTable/Resource", + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "Names must be set explicitly to be protected by accelerator SCPs" + } + ] + } + } + }, + "AlbIpForwardingdnsFWLambdaServiceRoleE2550228": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "RoleName": { + "Fn::Join": [ + "", + [ + { + "Ref": "acceleratorPrefix" + }, + "-dnsFWLambdaRole-", + { + "Ref": "vpcName" + } + ] + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "Names must be set explicitly to be protected by accelerator SCPs`" + } + ] + } + } + }, + "AlbIpForwardingdnsFWLambdaServiceRoleDefaultPolicyF5FC440E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AlbIpForwardingalb2albKeyF92E9EA0", + "Arn" + ] + } + }, + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:ConditionCheckItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:PutItem", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:UpdateItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "AlbIpForwardingddbDNSFirewallTableDE7BAC6C", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AlbIpForwardingdnsFWLambdaServiceRoleDefaultPolicyF5FC440E", + "Roles": [ + { + "Ref": "AlbIpForwardingdnsFWLambdaServiceRoleE2550228" + } + ] + }, + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/dnsFWLambda/ServiceRole/DefaultPolicy/Resource" + } + }, + "AlbIpForwardingdnsFWLambdaCDFE4DA7": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\"use strict\";var Td=Object.create;var rr=Object.defineProperty;var Ld=Object.getOwnPropertyDescriptor;var Cd=Object.getOwnPropertyNames;var Rd=Object.getPrototypeOf,Ed=Object.prototype.hasOwnProperty;var Sd=(u,v)=>()=>(v||u((v={exports:{}}).exports,v),v.exports),Od=(u,v)=>{for(var x in v)rr(u,x,{get:v[x],enumerable:!0})},co=(u,v,x,m)=>{if(v&&typeof v==\"object\"||typeof v==\"function\")for(let I of Cd(v))!Ed.call(u,I)&&I!==x&&rr(u,I,{get:()=>v[I],enumerable:!(m=Ld(v,I))||m.enumerable});return u};var ho=(u,v,x)=>(x=u!=null?Td(Rd(u)):{},co(v||!u||!u.__esModule?rr(x,\"default\",{value:u,enumerable:!0}):x,u)),Pd=u=>co(rr({},\"__esModule\",{value:!0}),u);var xo=Sd((Ft,oe)=>{(function(){var u,v=\"4.17.21\",x=200,m=\"Unsupported core-js use. Try https://npms.io/search?q=ponyfill.\",I=\"Expected a function\",j=\"Invalid `variable` option passed into `_.template`\",ut=\"__lodash_hash_undefined__\",Eo=500,se=\"__lodash_placeholder__\",Jn=1,Oi=2,mt=4,wt=1,ae=2,pn=1,ft=2,Pi=4,Sn=8,At=16,On=32,xt=64,Bn=128,Mt=256,ur=512,So=30,Oo=\"...\",Po=800,bo=16,bi=1,Do=2,Wo=3,ot=1/0,Yn=9007199254740991,Bo=17976931348623157e292,le=0/0,Pn=4294967295,Go=Pn-1,Fo=Pn>>>1,Mo=[[\"ary\",Bn],[\"bind\",pn],[\"bindKey\",ft],[\"curry\",Sn],[\"curryRight\",At],[\"flip\",ur],[\"partial\",On],[\"partialRight\",xt],[\"rearg\",Mt]],yt=\"[object Arguments]\",ce=\"[object Array]\",No=\"[object AsyncFunction]\",Nt=\"[object Boolean]\",Ut=\"[object Date]\",Uo=\"[object DOMException]\",he=\"[object Error]\",ge=\"[object Function]\",Di=\"[object GeneratorFunction]\",In=\"[object Map]\",$t=\"[object Number]\",$o=\"[object Null]\",Gn=\"[object Object]\",Wi=\"[object Promise]\",Ho=\"[object Proxy]\",Ht=\"[object RegExp]\",Tn=\"[object Set]\",Kt=\"[object String]\",pe=\"[object Symbol]\",Ko=\"[object Undefined]\",qt=\"[object WeakMap]\",qo=\"[object WeakSet]\",zt=\"[object ArrayBuffer]\",It=\"[object DataView]\",fr=\"[object Float32Array]\",or=\"[object Float64Array]\",sr=\"[object Int8Array]\",ar=\"[object Int16Array]\",lr=\"[object Int32Array]\",cr=\"[object Uint8Array]\",hr=\"[object Uint8ClampedArray]\",gr=\"[object Uint16Array]\",pr=\"[object Uint32Array]\",zo=/\\b__p \\+= '';/g,Zo=/\\b(__p \\+=) '' \\+/g,Jo=/(__e\\(.*?\\)|\\b__t\\)) \\+\\n'';/g,Bi=/&(?:amp|lt|gt|quot|#39);/g,Gi=/[&<>\"']/g,Yo=RegExp(Bi.source),Xo=RegExp(Gi.source),Qo=/<%-([\\s\\S]+?)%>/g,Vo=/<%([\\s\\S]+?)%>/g,Fi=/<%=([\\s\\S]+?)%>/g,ko=/\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,jo=/^\\w*$/,ns=/[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,dr=/[\\\\^$.*+?()[\\]{}|]/g,ts=RegExp(dr.source),_r=/^\\s+/,es=/\\s/,rs=/\\{(?:\\n\\/\\* \\[wrapped with .+\\] \\*\\/)?\\n?/,is=/\\{\\n\\/\\* \\[wrapped with (.+)\\] \\*/,us=/,? & /,fs=/[^\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]+/g,os=/[()=,{}\\[\\]\\/\\s]/,ss=/\\\\(\\\\)?/g,as=/\\$\\{([^\\\\}]*(?:\\\\.[^\\\\}]*)*)\\}/g,Mi=/\\w*$/,ls=/^[-+]0x[0-9a-f]+$/i,cs=/^0b[01]+$/i,hs=/^\\[object .+?Constructor\\]$/,gs=/^0o[0-7]+$/i,ps=/^(?:0|[1-9]\\d*)$/,ds=/[\\xc0-\\xd6\\xd8-\\xf6\\xf8-\\xff\\u0100-\\u017f]/g,de=/($^)/,_s=/['\\n\\r\\u2028\\u2029\\\\]/g,_e=\"\\\\ud800-\\\\udfff\",vs=\"\\\\u0300-\\\\u036f\",ms=\"\\\\ufe20-\\\\ufe2f\",ws=\"\\\\u20d0-\\\\u20ff\",Ni=vs+ms+ws,Ui=\"\\\\u2700-\\\\u27bf\",$i=\"a-z\\\\xdf-\\\\xf6\\\\xf8-\\\\xff\",As=\"\\\\xac\\\\xb1\\\\xd7\\\\xf7\",xs=\"\\\\x00-\\\\x2f\\\\x3a-\\\\x40\\\\x5b-\\\\x60\\\\x7b-\\\\xbf\",ys=\"\\\\u2000-\\\\u206f\",Is=\" \\\\t\\\\x0b\\\\f\\\\xa0\\\\ufeff\\\\n\\\\r\\\\u2028\\\\u2029\\\\u1680\\\\u180e\\\\u2000\\\\u2001\\\\u2002\\\\u2003\\\\u2004\\\\u2005\\\\u2006\\\\u2007\\\\u2008\\\\u2009\\\\u200a\\\\u202f\\\\u205f\\\\u3000\",Hi=\"A-Z\\\\xc0-\\\\xd6\\\\xd8-\\\\xde\",Ki=\"\\\\ufe0e\\\\ufe0f\",qi=As+xs+ys+Is,vr=\"['\\u2019]\",Ts=\"[\"+_e+\"]\",zi=\"[\"+qi+\"]\",ve=\"[\"+Ni+\"]\",Zi=\"\\\\d+\",Ls=\"[\"+Ui+\"]\",Ji=\"[\"+$i+\"]\",Yi=\"[^\"+_e+qi+Zi+Ui+$i+Hi+\"]\",mr=\"\\\\ud83c[\\\\udffb-\\\\udfff]\",Cs=\"(?:\"+ve+\"|\"+mr+\")\",Xi=\"[^\"+_e+\"]\",wr=\"(?:\\\\ud83c[\\\\udde6-\\\\uddff]){2}\",Ar=\"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\",Tt=\"[\"+Hi+\"]\",Qi=\"\\\\u200d\",Vi=\"(?:\"+Ji+\"|\"+Yi+\")\",Rs=\"(?:\"+Tt+\"|\"+Yi+\")\",ki=\"(?:\"+vr+\"(?:d|ll|m|re|s|t|ve))?\",ji=\"(?:\"+vr+\"(?:D|LL|M|RE|S|T|VE))?\",nu=Cs+\"?\",tu=\"[\"+Ki+\"]?\",Es=\"(?:\"+Qi+\"(?:\"+[Xi,wr,Ar].join(\"|\")+\")\"+tu+nu+\")*\",Ss=\"\\\\d*(?:1st|2nd|3rd|(?![123])\\\\dth)(?=\\\\b|[A-Z_])\",Os=\"\\\\d*(?:1ST|2ND|3RD|(?![123])\\\\dTH)(?=\\\\b|[a-z_])\",eu=tu+nu+Es,Ps=\"(?:\"+[Ls,wr,Ar].join(\"|\")+\")\"+eu,bs=\"(?:\"+[Xi+ve+\"?\",ve,wr,Ar,Ts].join(\"|\")+\")\",Ds=RegExp(vr,\"g\"),Ws=RegExp(ve,\"g\"),xr=RegExp(mr+\"(?=\"+mr+\")|\"+bs+eu,\"g\"),Bs=RegExp([Tt+\"?\"+Ji+\"+\"+ki+\"(?=\"+[zi,Tt,\"$\"].join(\"|\")+\")\",Rs+\"+\"+ji+\"(?=\"+[zi,Tt+Vi,\"$\"].join(\"|\")+\")\",Tt+\"?\"+Vi+\"+\"+ki,Tt+\"+\"+ji,Os,Ss,Zi,Ps].join(\"|\"),\"g\"),Gs=RegExp(\"[\"+Qi+_e+Ni+Ki+\"]\"),Fs=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Ms=[\"Array\",\"Buffer\",\"DataView\",\"Date\",\"Error\",\"Float32Array\",\"Float64Array\",\"Function\",\"Int8Array\",\"Int16Array\",\"Int32Array\",\"Map\",\"Math\",\"Object\",\"Promise\",\"RegExp\",\"Set\",\"String\",\"Symbol\",\"TypeError\",\"Uint8Array\",\"Uint8ClampedArray\",\"Uint16Array\",\"Uint32Array\",\"WeakMap\",\"_\",\"clearTimeout\",\"isFinite\",\"parseInt\",\"setTimeout\"],Ns=-1,U={};U[fr]=U[or]=U[sr]=U[ar]=U[lr]=U[cr]=U[hr]=U[gr]=U[pr]=!0,U[yt]=U[ce]=U[zt]=U[Nt]=U[It]=U[Ut]=U[he]=U[ge]=U[In]=U[$t]=U[Gn]=U[Ht]=U[Tn]=U[Kt]=U[qt]=!1;var N={};N[yt]=N[ce]=N[zt]=N[It]=N[Nt]=N[Ut]=N[fr]=N[or]=N[sr]=N[ar]=N[lr]=N[In]=N[$t]=N[Gn]=N[Ht]=N[Tn]=N[Kt]=N[pe]=N[cr]=N[hr]=N[gr]=N[pr]=!0,N[he]=N[ge]=N[qt]=!1;var Us={\\u00C0:\"A\",\\u00C1:\"A\",\\u00C2:\"A\",\\u00C3:\"A\",\\u00C4:\"A\",\\u00C5:\"A\",\\u00E0:\"a\",\\u00E1:\"a\",\\u00E2:\"a\",\\u00E3:\"a\",\\u00E4:\"a\",\\u00E5:\"a\",\\u00C7:\"C\",\\u00E7:\"c\",\\u00D0:\"D\",\\u00F0:\"d\",\\u00C8:\"E\",\\u00C9:\"E\",\\u00CA:\"E\",\\u00CB:\"E\",\\u00E8:\"e\",\\u00E9:\"e\",\\u00EA:\"e\",\\u00EB:\"e\",\\u00CC:\"I\",\\u00CD:\"I\",\\u00CE:\"I\",\\u00CF:\"I\",\\u00EC:\"i\",\\u00ED:\"i\",\\u00EE:\"i\",\\u00EF:\"i\",\\u00D1:\"N\",\\u00F1:\"n\",\\u00D2:\"O\",\\u00D3:\"O\",\\u00D4:\"O\",\\u00D5:\"O\",\\u00D6:\"O\",\\u00D8:\"O\",\\u00F2:\"o\",\\u00F3:\"o\",\\u00F4:\"o\",\\u00F5:\"o\",\\u00F6:\"o\",\\u00F8:\"o\",\\u00D9:\"U\",\\u00DA:\"U\",\\u00DB:\"U\",\\u00DC:\"U\",\\u00F9:\"u\",\\u00FA:\"u\",\\u00FB:\"u\",\\u00FC:\"u\",\\u00DD:\"Y\",\\u00FD:\"y\",\\u00FF:\"y\",\\u00C6:\"Ae\",\\u00E6:\"ae\",\\u00DE:\"Th\",\\u00FE:\"th\",\\u00DF:\"ss\",\\u0100:\"A\",\\u0102:\"A\",\\u0104:\"A\",\\u0101:\"a\",\\u0103:\"a\",\\u0105:\"a\",\\u0106:\"C\",\\u0108:\"C\",\\u010A:\"C\",\\u010C:\"C\",\\u0107:\"c\",\\u0109:\"c\",\\u010B:\"c\",\\u010D:\"c\",\\u010E:\"D\",\\u0110:\"D\",\\u010F:\"d\",\\u0111:\"d\",\\u0112:\"E\",\\u0114:\"E\",\\u0116:\"E\",\\u0118:\"E\",\\u011A:\"E\",\\u0113:\"e\",\\u0115:\"e\",\\u0117:\"e\",\\u0119:\"e\",\\u011B:\"e\",\\u011C:\"G\",\\u011E:\"G\",\\u0120:\"G\",\\u0122:\"G\",\\u011D:\"g\",\\u011F:\"g\",\\u0121:\"g\",\\u0123:\"g\",\\u0124:\"H\",\\u0126:\"H\",\\u0125:\"h\",\\u0127:\"h\",\\u0128:\"I\",\\u012A:\"I\",\\u012C:\"I\",\\u012E:\"I\",\\u0130:\"I\",\\u0129:\"i\",\\u012B:\"i\",\\u012D:\"i\",\\u012F:\"i\",\\u0131:\"i\",\\u0134:\"J\",\\u0135:\"j\",\\u0136:\"K\",\\u0137:\"k\",\\u0138:\"k\",\\u0139:\"L\",\\u013B:\"L\",\\u013D:\"L\",\\u013F:\"L\",\\u0141:\"L\",\\u013A:\"l\",\\u013C:\"l\",\\u013E:\"l\",\\u0140:\"l\",\\u0142:\"l\",\\u0143:\"N\",\\u0145:\"N\",\\u0147:\"N\",\\u014A:\"N\",\\u0144:\"n\",\\u0146:\"n\",\\u0148:\"n\",\\u014B:\"n\",\\u014C:\"O\",\\u014E:\"O\",\\u0150:\"O\",\\u014D:\"o\",\\u014F:\"o\",\\u0151:\"o\",\\u0154:\"R\",\\u0156:\"R\",\\u0158:\"R\",\\u0155:\"r\",\\u0157:\"r\",\\u0159:\"r\",\\u015A:\"S\",\\u015C:\"S\",\\u015E:\"S\",\\u0160:\"S\",\\u015B:\"s\",\\u015D:\"s\",\\u015F:\"s\",\\u0161:\"s\",\\u0162:\"T\",\\u0164:\"T\",\\u0166:\"T\",\\u0163:\"t\",\\u0165:\"t\",\\u0167:\"t\",\\u0168:\"U\",\\u016A:\"U\",\\u016C:\"U\",\\u016E:\"U\",\\u0170:\"U\",\\u0172:\"U\",\\u0169:\"u\",\\u016B:\"u\",\\u016D:\"u\",\\u016F:\"u\",\\u0171:\"u\",\\u0173:\"u\",\\u0174:\"W\",\\u0175:\"w\",\\u0176:\"Y\",\\u0177:\"y\",\\u0178:\"Y\",\\u0179:\"Z\",\\u017B:\"Z\",\\u017D:\"Z\",\\u017A:\"z\",\\u017C:\"z\",\\u017E:\"z\",\\u0132:\"IJ\",\\u0133:\"ij\",\\u0152:\"Oe\",\\u0153:\"oe\",\\u0149:\"'n\",\\u017F:\"s\"},$s={\"&\":\"&\",\"<\":\"<\",\">\":\">\",'\"':\""\",\"'\":\"'\"},Hs={\"&\":\"&\",\"<\":\"<\",\">\":\">\",\""\":'\"',\"'\":\"'\"},Ks={\"\\\\\":\"\\\\\",\"'\":\"'\",\"\\n\":\"n\",\"\\r\":\"r\",\"\\u2028\":\"u2028\",\"\\u2029\":\"u2029\"},qs=parseFloat,zs=parseInt,ru=typeof global==\"object\"&&global&&global.Object===Object&&global,Zs=typeof self==\"object\"&&self&&self.Object===Object&&self,Y=ru||Zs||Function(\"return this\")(),yr=typeof Ft==\"object\"&&Ft&&!Ft.nodeType&&Ft,st=yr&&typeof oe==\"object\"&&oe&&!oe.nodeType&&oe,iu=st&&st.exports===yr,Ir=iu&&ru.process,dn=function(){try{var l=st&&st.require&&st.require(\"util\").types;return l||Ir&&Ir.binding&&Ir.binding(\"util\")}catch{}}(),uu=dn&&dn.isArrayBuffer,fu=dn&&dn.isDate,ou=dn&&dn.isMap,su=dn&&dn.isRegExp,au=dn&&dn.isSet,lu=dn&&dn.isTypedArray;function sn(l,g,h){switch(h.length){case 0:return l.call(g);case 1:return l.call(g,h[0]);case 2:return l.call(g,h[0],h[1]);case 3:return l.call(g,h[0],h[1],h[2])}return l.apply(g,h)}function Js(l,g,h,A){for(var R=-1,B=l==null?0:l.length;++R-1}function Tr(l,g,h){for(var A=-1,R=l==null?0:l.length;++A-1;);return h}function mu(l,g){for(var h=l.length;h--&&Lt(g,l[h],0)>-1;);return h}function ea(l,g){for(var h=l.length,A=0;h--;)l[h]===g&&++A;return A}var ra=Er(Us),ia=Er($s);function ua(l){return\"\\\\\"+Ks[l]}function fa(l,g){return l==null?u:l[g]}function Ct(l){return Gs.test(l)}function oa(l){return Fs.test(l)}function sa(l){for(var g,h=[];!(g=l.next()).done;)h.push(g.value);return h}function br(l){var g=-1,h=Array(l.size);return l.forEach(function(A,R){h[++g]=[R,A]}),h}function wu(l,g){return function(h){return l(g(h))}}function Vn(l,g){for(var h=-1,A=l.length,R=0,B=[];++h-1}function Xa(n,t){var e=this.__data__,r=Be(e,n);return r<0?(++this.size,e.push([n,t])):e[r][1]=t,this}Fn.prototype.clear=za,Fn.prototype.delete=Za,Fn.prototype.get=Ja,Fn.prototype.has=Ya,Fn.prototype.set=Xa;function Mn(n){var t=-1,e=n==null?0:n.length;for(this.clear();++t=t?n:t)),n}function wn(n,t,e,r,i,o){var s,a=t&Jn,c=t&Oi,p=t&mt;if(e&&(s=i?e(n,r,i,o):e(n)),s!==u)return s;if(!H(n))return n;var d=E(n);if(d){if(s=jl(n),!a)return rn(n,s)}else{var _=k(n),w=_==ge||_==Di;if(it(n))return tf(n,a);if(_==Gn||_==yt||w&&!i){if(s=c||w?{}:xf(n),!a)return c?Hl(n,cl(s,n)):$l(n,Pu(s,n))}else{if(!N[_])return i?n:{};s=nc(n,_,a)}}o||(o=new Cn);var y=o.get(n);if(y)return y;o.set(n,s),Qf(n)?n.forEach(function(C){s.add(wn(C,t,e,C,n,o))}):Yf(n)&&n.forEach(function(C,b){s.set(b,wn(C,t,e,b,n,o))});var L=p?c?ii:ri:c?fn:X,O=d?u:L(n);return _n(O||n,function(C,b){O&&(b=C,C=n[b]),kt(s,b,wn(C,t,e,b,n,o))}),s}function hl(n){var t=X(n);return function(e){return bu(e,n,t)}}function bu(n,t,e){var r=e.length;if(n==null)return!r;for(n=M(n);r--;){var i=e[r],o=t[i],s=n[i];if(s===u&&!(i in n)||!o(s))return!1}return!0}function Du(n,t,e){if(typeof n!=\"function\")throw new vn(I);return ue(function(){n.apply(u,e)},t)}function jt(n,t,e,r){var i=-1,o=me,s=!0,a=n.length,c=[],p=t.length;if(!a)return c;e&&(t=$(t,an(e))),r?(o=Tr,s=!1):t.length>=x&&(o=Zt,s=!1,t=new ct(t));n:for(;++ii?0:i+e),r=r===u||r>i?i:S(r),r<0&&(r+=i),r=e>r?0:kf(r);e0&&e(a)?t>1?Q(a,t-1,e,r,i):Qn(i,a):r||(i[i.length]=a)}return i}var Nr=sf(),Gu=sf(!0);function bn(n,t){return n&&Nr(n,t,X)}function Ur(n,t){return n&&Gu(n,t,X)}function Fe(n,t){return Xn(t,function(e){return Kn(n[e])})}function gt(n,t){t=et(t,n);for(var e=0,r=t.length;n!=null&&et}function dl(n,t){return n!=null&&F.call(n,t)}function _l(n,t){return n!=null&&t in M(n)}function vl(n,t,e){return n>=V(t,e)&&n=120&&d.length>=120)?new ct(s&&d):u}d=n[0];var _=-1,w=a[0];n:for(;++_-1;)a!==n&&Ee.call(a,c,1),Ee.call(n,c,1);return n}function Ju(n,t){for(var e=n?t.length:0,r=e-1;e--;){var i=t[e];if(e==r||i!==o){var o=i;Hn(i)?Ee.call(n,i,1):Qr(n,i)}}return n}function Jr(n,t){return n+Pe(Ru()*(t-n+1))}function Ol(n,t,e,r){for(var i=-1,o=J(Oe((t-n)/(e||1)),0),s=h(o);o--;)s[r?o:++i]=n,n+=e;return s}function Yr(n,t){var e=\"\";if(!n||t<1||t>Yn)return e;do t%2&&(e+=n),t=Pe(t/2),t&&(n+=n);while(t);return e}function P(n,t){return ci(Tf(n,t,on),n+\"\")}function Pl(n){return Ou(Gt(n))}function bl(n,t){var e=Gt(n);return Ye(e,ht(t,0,e.length))}function ee(n,t,e,r){if(!H(n))return n;t=et(t,n);for(var i=-1,o=t.length,s=o-1,a=n;a!=null&&++ii?0:i+t),e=e>i?i:e,e<0&&(e+=i),i=t>e?0:e-t>>>0,t>>>=0;for(var o=h(i);++r>>1,s=n[o];s!==null&&!cn(s)&&(e?s<=t:s=x){var p=t?null:Zl(n);if(p)return Ae(p);s=!1,i=Zt,c=new ct}else c=t?[]:a;n:for(;++r=r?n:An(n,t,e)}var nf=Ia||function(n){return Y.clearTimeout(n)};function tf(n,t){if(t)return n.slice();var e=n.length,r=yu?yu(e):new n.constructor(e);return n.copy(r),r}function ni(n){var t=new n.constructor(n.byteLength);return new Ce(t).set(new Ce(n)),t}function Fl(n,t){var e=t?ni(n.buffer):n.buffer;return new n.constructor(e,n.byteOffset,n.byteLength)}function Ml(n){var t=new n.constructor(n.source,Mi.exec(n));return t.lastIndex=n.lastIndex,t}function Nl(n){return Vt?M(Vt.call(n)):{}}function ef(n,t){var e=t?ni(n.buffer):n.buffer;return new n.constructor(e,n.byteOffset,n.length)}function rf(n,t){if(n!==t){var e=n!==u,r=n===null,i=n===n,o=cn(n),s=t!==u,a=t===null,c=t===t,p=cn(t);if(!a&&!p&&!o&&n>t||o&&s&&c&&!a&&!p||r&&s&&c||!e&&c||!i)return 1;if(!r&&!o&&!p&&n=a)return c;var p=e[r];return c*(p==\"desc\"?-1:1)}}return n.index-t.index}function uf(n,t,e,r){for(var i=-1,o=n.length,s=e.length,a=-1,c=t.length,p=J(o-s,0),d=h(c+p),_=!r;++a1?e[i-1]:u,s=i>2?e[2]:u;for(o=n.length>3&&typeof o==\"function\"?(i--,o):u,s&&tn(e[0],e[1],s)&&(o=i<3?u:o,i=1),t=M(t);++r-1?i[o?t[s]:s]:u}}function cf(n){return $n(function(t){var e=t.length,r=e,i=mn.prototype.thru;for(n&&t.reverse();r--;){var o=t[r];if(typeof o!=\"function\")throw new vn(I);if(i&&!s&&Ze(o)==\"wrapper\")var s=new mn([],!0)}for(r=s?r:e;++r1&&W.reverse(),d&&ca))return!1;var p=o.get(n),d=o.get(t);if(p&&d)return p==t&&d==n;var _=-1,w=!0,y=e&ae?new ct:u;for(o.set(n,t),o.set(t,n);++_1?\"& \":\"\")+t[r],t=t.join(e>2?\", \":\" \"),n.replace(rs,`{\n/* [wrapped with `+t+`] */\n`)}function ec(n){return E(n)||_t(n)||!!(Lu&&n&&n[Lu])}function Hn(n,t){var e=typeof n;return t=t??Yn,!!t&&(e==\"number\"||e!=\"symbol\"&&ps.test(n))&&n>-1&&n%1==0&&n0){if(++t>=Po)return arguments[0]}else t=0;return n.apply(u,arguments)}}function Ye(n,t){var e=-1,r=n.length,i=r-1;for(t=t===u?r:t;++e1?n[t-1]:u;return e=typeof e==\"function\"?(n.pop(),e):u,Gf(n,e)});function Ff(n){var t=f(n);return t.__chain__=!0,t}function gh(n,t){return t(n),n}function Xe(n,t){return t(n)}var ph=$n(function(n){var t=n.length,e=t?n[0]:0,r=this.__wrapped__,i=function(o){return Mr(o,n)};return t>1||this.__actions__.length||!(r instanceof D)||!Hn(e)?this.thru(i):(r=r.slice(e,+e+(t?1:0)),r.__actions__.push({func:Xe,args:[i],thisArg:u}),new mn(r,this.__chain__).thru(function(o){return t&&!o.length&&o.push(u),o}))});function dh(){return Ff(this)}function _h(){return new mn(this.value(),this.__chain__)}function vh(){this.__values__===u&&(this.__values__=Vf(this.value()));var n=this.__index__>=this.__values__.length,t=n?u:this.__values__[this.__index__++];return{done:n,value:t}}function mh(){return this}function wh(n){for(var t,e=this;e instanceof We;){var r=Of(e);r.__index__=0,r.__values__=u,t?i.__wrapped__=r:t=r;var i=r;e=e.__wrapped__}return i.__wrapped__=n,t}function Ah(){var n=this.__wrapped__;if(n instanceof D){var t=n;return this.__actions__.length&&(t=new D(this)),t=t.reverse(),t.__actions__.push({func:Xe,args:[hi],thisArg:u}),new mn(t,this.__chain__)}return this.thru(hi)}function xh(){return ku(this.__wrapped__,this.__actions__)}var yh=$e(function(n,t,e){F.call(n,e)?++n[e]:Nn(n,e,1)});function Ih(n,t,e){var r=E(n)?cu:gl;return e&&tn(n,t,e)&&(t=u),r(n,T(t,3))}function Th(n,t){var e=E(n)?Xn:Bu;return e(n,T(t,3))}var Lh=lf(Pf),Ch=lf(bf);function Rh(n,t){return Q(Qe(n,t),1)}function Eh(n,t){return Q(Qe(n,t),ot)}function Sh(n,t,e){return e=e===u?1:S(e),Q(Qe(n,t),e)}function Mf(n,t){var e=E(n)?_n:nt;return e(n,T(t,3))}function Nf(n,t){var e=E(n)?Ys:Wu;return e(n,T(t,3))}var Oh=$e(function(n,t,e){F.call(n,e)?n[e].push(t):Nn(n,e,[t])});function Ph(n,t,e,r){n=un(n)?n:Gt(n),e=e&&!r?S(e):0;var i=n.length;return e<0&&(e=J(i+e,0)),tr(n)?e<=i&&n.indexOf(t,e)>-1:!!i&&Lt(n,t,e)>-1}var bh=P(function(n,t,e){var r=-1,i=typeof t==\"function\",o=un(n)?h(n.length):[];return nt(n,function(s){o[++r]=i?sn(t,s,e):ne(s,t,e)}),o}),Dh=$e(function(n,t,e){Nn(n,e,t)});function Qe(n,t){var e=E(n)?$:$u;return e(n,T(t,3))}function Wh(n,t,e,r){return n==null?[]:(E(t)||(t=t==null?[]:[t]),e=r?u:e,E(e)||(e=e==null?[]:[e]),zu(n,t,e))}var Bh=$e(function(n,t,e){n[e?0:1].push(t)},function(){return[[],[]]});function Gh(n,t,e){var r=E(n)?Lr:du,i=arguments.length<3;return r(n,T(t,4),e,i,nt)}function Fh(n,t,e){var r=E(n)?Xs:du,i=arguments.length<3;return r(n,T(t,4),e,i,Wu)}function Mh(n,t){var e=E(n)?Xn:Bu;return e(n,je(T(t,3)))}function Nh(n){var t=E(n)?Ou:Pl;return t(n)}function Uh(n,t,e){(e?tn(n,t,e):t===u)?t=1:t=S(t);var r=E(n)?sl:bl;return r(n,t)}function $h(n){var t=E(n)?al:Wl;return t(n)}function Hh(n){if(n==null)return 0;if(un(n))return tr(n)?Rt(n):n.length;var t=k(n);return t==In||t==Tn?n.size:qr(n).length}function Kh(n,t,e){var r=E(n)?Cr:Bl;return e&&tn(n,t,e)&&(t=u),r(n,T(t,3))}var qh=P(function(n,t){if(n==null)return[];var e=t.length;return e>1&&tn(n,t[0],t[1])?t=[]:e>2&&tn(t[0],t[1],t[2])&&(t=[t[0]]),zu(n,Q(t,1),[])}),Ve=Ta||function(){return Y.Date.now()};function zh(n,t){if(typeof t!=\"function\")throw new vn(I);return n=S(n),function(){if(--n<1)return t.apply(this,arguments)}}function Uf(n,t,e){return t=e?u:t,t=n&&t==null?n.length:t,Un(n,Bn,u,u,u,u,t)}function $f(n,t){var e;if(typeof t!=\"function\")throw new vn(I);return n=S(n),function(){return--n>0&&(e=t.apply(this,arguments)),n<=1&&(t=u),e}}var pi=P(function(n,t,e){var r=pn;if(e.length){var i=Vn(e,Wt(pi));r|=On}return Un(n,r,t,e,i)}),Hf=P(function(n,t,e){var r=pn|ft;if(e.length){var i=Vn(e,Wt(Hf));r|=On}return Un(t,r,n,e,i)});function Kf(n,t,e){t=e?u:t;var r=Un(n,Sn,u,u,u,u,u,t);return r.placeholder=Kf.placeholder,r}function qf(n,t,e){t=e?u:t;var r=Un(n,At,u,u,u,u,u,t);return r.placeholder=qf.placeholder,r}function zf(n,t,e){var r,i,o,s,a,c,p=0,d=!1,_=!1,w=!0;if(typeof n!=\"function\")throw new vn(I);t=yn(t)||0,H(e)&&(d=!!e.leading,_=\"maxWait\"in e,o=_?J(yn(e.maxWait)||0,t):o,w=\"trailing\"in e?!!e.trailing:w);function y(z){var En=r,zn=i;return r=i=u,p=z,s=n.apply(zn,En),s}function L(z){return p=z,a=ue(b,t),d?y(z):s}function O(z){var En=z-c,zn=z-p,lo=t-En;return _?V(lo,o-zn):lo}function C(z){var En=z-c,zn=z-p;return c===u||En>=t||En<0||_&&zn>=o}function b(){var z=Ve();if(C(z))return W(z);a=ue(b,O(z))}function W(z){return a=u,w&&r?y(z):(r=i=u,s)}function hn(){a!==u&&nf(a),p=0,r=c=i=a=u}function en(){return a===u?s:W(Ve())}function gn(){var z=Ve(),En=C(z);if(r=arguments,i=this,c=z,En){if(a===u)return L(c);if(_)return nf(a),a=ue(b,t),y(c)}return a===u&&(a=ue(b,t)),s}return gn.cancel=hn,gn.flush=en,gn}var Zh=P(function(n,t){return Du(n,1,t)}),Jh=P(function(n,t,e){return Du(n,yn(t)||0,e)});function Yh(n){return Un(n,ur)}function ke(n,t){if(typeof n!=\"function\"||t!=null&&typeof t!=\"function\")throw new vn(I);var e=function(){var r=arguments,i=t?t.apply(this,r):r[0],o=e.cache;if(o.has(i))return o.get(i);var s=n.apply(this,r);return e.cache=o.set(i,s)||o,s};return e.cache=new(ke.Cache||Mn),e}ke.Cache=Mn;function je(n){if(typeof n!=\"function\")throw new vn(I);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function Xh(n){return $f(2,n)}var Qh=Gl(function(n,t){t=t.length==1&&E(t[0])?$(t[0],an(T())):$(Q(t,1),an(T()));var e=t.length;return P(function(r){for(var i=-1,o=V(r.length,e);++i=t}),_t=Mu(function(){return arguments}())?Mu:function(n){return K(n)&&F.call(n,\"callee\")&&!Tu.call(n,\"callee\")},E=h.isArray,cg=uu?an(uu):wl;function un(n){return n!=null&&nr(n.length)&&!Kn(n)}function q(n){return K(n)&&un(n)}function hg(n){return n===!0||n===!1||K(n)&&nn(n)==Nt}var it=Ca||Ci,gg=fu?an(fu):Al;function pg(n){return K(n)&&n.nodeType===1&&!fe(n)}function dg(n){if(n==null)return!0;if(un(n)&&(E(n)||typeof n==\"string\"||typeof n.splice==\"function\"||it(n)||Bt(n)||_t(n)))return!n.length;var t=k(n);if(t==In||t==Tn)return!n.size;if(ie(n))return!qr(n).length;for(var e in n)if(F.call(n,e))return!1;return!0}function _g(n,t){return te(n,t)}function vg(n,t,e){e=typeof e==\"function\"?e:u;var r=e?e(n,t):u;return r===u?te(n,t,u,e):!!r}function _i(n){if(!K(n))return!1;var t=nn(n);return t==he||t==Uo||typeof n.message==\"string\"&&typeof n.name==\"string\"&&!fe(n)}function mg(n){return typeof n==\"number\"&&Cu(n)}function Kn(n){if(!H(n))return!1;var t=nn(n);return t==ge||t==Di||t==No||t==Ho}function Jf(n){return typeof n==\"number\"&&n==S(n)}function nr(n){return typeof n==\"number\"&&n>-1&&n%1==0&&n<=Yn}function H(n){var t=typeof n;return n!=null&&(t==\"object\"||t==\"function\")}function K(n){return n!=null&&typeof n==\"object\"}var Yf=ou?an(ou):yl;function wg(n,t){return n===t||Kr(n,t,fi(t))}function Ag(n,t,e){return e=typeof e==\"function\"?e:u,Kr(n,t,fi(t),e)}function xg(n){return Xf(n)&&n!=+n}function yg(n){if(uc(n))throw new R(m);return Nu(n)}function Ig(n){return n===null}function Tg(n){return n==null}function Xf(n){return typeof n==\"number\"||K(n)&&nn(n)==$t}function fe(n){if(!K(n)||nn(n)!=Gn)return!1;var t=Re(n);if(t===null)return!0;var e=F.call(t,\"constructor\")&&t.constructor;return typeof e==\"function\"&&e instanceof e&&Ie.call(e)==Aa}var vi=su?an(su):Il;function Lg(n){return Jf(n)&&n>=-Yn&&n<=Yn}var Qf=au?an(au):Tl;function tr(n){return typeof n==\"string\"||!E(n)&&K(n)&&nn(n)==Kt}function cn(n){return typeof n==\"symbol\"||K(n)&&nn(n)==pe}var Bt=lu?an(lu):Ll;function Cg(n){return n===u}function Rg(n){return K(n)&&k(n)==qt}function Eg(n){return K(n)&&nn(n)==qo}var Sg=ze(zr),Og=ze(function(n,t){return n<=t});function Vf(n){if(!n)return[];if(un(n))return tr(n)?Ln(n):rn(n);if(Jt&&n[Jt])return sa(n[Jt]());var t=k(n),e=t==In?br:t==Tn?Ae:Gt;return e(n)}function qn(n){if(!n)return n===0?n:0;if(n=yn(n),n===ot||n===-ot){var t=n<0?-1:1;return t*Bo}return n===n?n:0}function S(n){var t=qn(n),e=t%1;return t===t?e?t-e:t:0}function kf(n){return n?ht(S(n),0,Pn):0}function yn(n){if(typeof n==\"number\")return n;if(cn(n))return le;if(H(n)){var t=typeof n.valueOf==\"function\"?n.valueOf():n;n=H(t)?t+\"\":t}if(typeof n!=\"string\")return n===0?n:+n;n=_u(n);var e=cs.test(n);return e||gs.test(n)?zs(n.slice(2),e?2:8):ls.test(n)?le:+n}function jf(n){return Dn(n,fn(n))}function Pg(n){return n?ht(S(n),-Yn,Yn):n===0?n:0}function G(n){return n==null?\"\":ln(n)}var bg=bt(function(n,t){if(ie(t)||un(t)){Dn(t,X(t),n);return}for(var e in t)F.call(t,e)&&kt(n,e,t[e])}),no=bt(function(n,t){Dn(t,fn(t),n)}),er=bt(function(n,t,e,r){Dn(t,fn(t),n,r)}),Dg=bt(function(n,t,e,r){Dn(t,X(t),n,r)}),Wg=$n(Mr);function Bg(n,t){var e=Pt(n);return t==null?e:Pu(e,t)}var Gg=P(function(n,t){n=M(n);var e=-1,r=t.length,i=r>2?t[2]:u;for(i&&tn(t[0],t[1],i)&&(r=1);++e1),o}),Dn(n,ii(n),e),r&&(e=wn(e,Jn|Oi|mt,Jl));for(var i=t.length;i--;)Qr(e,t[i]);return e});function np(n,t){return eo(n,je(T(t)))}var tp=$n(function(n,t){return n==null?{}:El(n,t)});function eo(n,t){if(n==null)return{};var e=$(ii(n),function(r){return[r]});return t=T(t),Zu(n,e,function(r,i){return t(r,i[0])})}function ep(n,t,e){t=et(t,n);var r=-1,i=t.length;for(i||(i=1,n=u);++rt){var r=n;n=t,t=r}if(e||n%1||t%1){var i=Ru();return V(n+i*(t-n+qs(\"1e-\"+((i+\"\").length-1))),t)}return Jr(n,t)}var gp=Dt(function(n,t,e){return t=t.toLowerCase(),n+(e?uo(t):t)});function uo(n){return Ai(G(n).toLowerCase())}function fo(n){return n=G(n),n&&n.replace(ds,ra).replace(Ws,\"\")}function pp(n,t,e){n=G(n),t=ln(t);var r=n.length;e=e===u?r:ht(S(e),0,r);var i=e;return e-=t.length,e>=0&&n.slice(e,i)==t}function dp(n){return n=G(n),n&&Xo.test(n)?n.replace(Gi,ia):n}function _p(n){return n=G(n),n&&ts.test(n)?n.replace(dr,\"\\\\$&\"):n}var vp=Dt(function(n,t,e){return n+(e?\"-\":\"\")+t.toLowerCase()}),mp=Dt(function(n,t,e){return n+(e?\" \":\"\")+t.toLowerCase()}),wp=af(\"toLowerCase\");function Ap(n,t,e){n=G(n),t=S(t);var r=t?Rt(n):0;if(!t||r>=t)return n;var i=(t-r)/2;return qe(Pe(i),e)+n+qe(Oe(i),e)}function xp(n,t,e){n=G(n),t=S(t);var r=t?Rt(n):0;return t&&r>>0,e?(n=G(n),n&&(typeof t==\"string\"||t!=null&&!vi(t))&&(t=ln(t),!t&&Ct(n))?rt(Ln(n),0,e):n.split(t,e)):[]}var Ep=Dt(function(n,t,e){return n+(e?\" \":\"\")+Ai(t)});function Sp(n,t,e){return n=G(n),e=e==null?0:ht(S(e),0,n.length),t=ln(t),n.slice(e,e+t.length)==t}function Op(n,t,e){var r=f.templateSettings;e&&tn(n,t,e)&&(t=u),n=G(n),t=er({},t,r,_f);var i=er({},t.imports,r.imports,_f),o=X(i),s=Pr(i,o),a,c,p=0,d=t.interpolate||de,_=\"__p += '\",w=Dr((t.escape||de).source+\"|\"+d.source+\"|\"+(d===Fi?as:de).source+\"|\"+(t.evaluate||de).source+\"|$\",\"g\"),y=\"//# sourceURL=\"+(F.call(t,\"sourceURL\")?(t.sourceURL+\"\").replace(/\\s/g,\" \"):\"lodash.templateSources[\"+ ++Ns+\"]\")+`\n`;n.replace(w,function(C,b,W,hn,en,gn){return W||(W=hn),_+=n.slice(p,gn).replace(_s,ua),b&&(a=!0,_+=`' +\n__e(`+b+`) +\n'`),en&&(c=!0,_+=`';\n`+en+`;\n__p += '`),W&&(_+=`' +\n((__t = (`+W+`)) == null ? '' : __t) +\n'`),p=gn+C.length,C}),_+=`';\n`;var L=F.call(t,\"variable\")&&t.variable;if(!L)_=`with (obj) {\n`+_+`\n}\n`;else if(os.test(L))throw new R(j);_=(c?_.replace(zo,\"\"):_).replace(Zo,\"$1\").replace(Jo,\"$1;\"),_=\"function(\"+(L||\"obj\")+`) {\n`+(L?\"\":`obj || (obj = {});\n`)+\"var __t, __p = ''\"+(a?\", __e = _.escape\":\"\")+(c?`, __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n`:`;\n`)+_+`return __p\n}`;var O=so(function(){return B(o,y+\"return \"+_).apply(u,s)});if(O.source=_,_i(O))throw O;return O}function Pp(n){return G(n).toLowerCase()}function bp(n){return G(n).toUpperCase()}function Dp(n,t,e){if(n=G(n),n&&(e||t===u))return _u(n);if(!n||!(t=ln(t)))return n;var r=Ln(n),i=Ln(t),o=vu(r,i),s=mu(r,i)+1;return rt(r,o,s).join(\"\")}function Wp(n,t,e){if(n=G(n),n&&(e||t===u))return n.slice(0,Au(n)+1);if(!n||!(t=ln(t)))return n;var r=Ln(n),i=mu(r,Ln(t))+1;return rt(r,0,i).join(\"\")}function Bp(n,t,e){if(n=G(n),n&&(e||t===u))return n.replace(_r,\"\");if(!n||!(t=ln(t)))return n;var r=Ln(n),i=vu(r,Ln(t));return rt(r,i).join(\"\")}function Gp(n,t){var e=So,r=Oo;if(H(t)){var i=\"separator\"in t?t.separator:i;e=\"length\"in t?S(t.length):e,r=\"omission\"in t?ln(t.omission):r}n=G(n);var o=n.length;if(Ct(n)){var s=Ln(n);o=s.length}if(e>=o)return n;var a=e-Rt(r);if(a<1)return r;var c=s?rt(s,0,a).join(\"\"):n.slice(0,a);if(i===u)return c+r;if(s&&(a+=c.length-a),vi(i)){if(n.slice(a).search(i)){var p,d=c;for(i.global||(i=Dr(i.source,G(Mi.exec(i))+\"g\")),i.lastIndex=0;p=i.exec(d);)var _=p.index;c=c.slice(0,_===u?a:_)}}else if(n.indexOf(ln(i),a)!=a){var w=c.lastIndexOf(i);w>-1&&(c=c.slice(0,w))}return c+r}function Fp(n){return n=G(n),n&&Yo.test(n)?n.replace(Bi,ha):n}var Mp=Dt(function(n,t,e){return n+(e?\" \":\"\")+t.toUpperCase()}),Ai=af(\"toUpperCase\");function oo(n,t,e){return n=G(n),t=e?u:t,t===u?oa(n)?da(n):ks(n):n.match(t)||[]}var so=P(function(n,t){try{return sn(n,u,t)}catch(e){return _i(e)?e:new R(e)}}),Np=$n(function(n,t){return _n(t,function(e){e=Wn(e),Nn(n,e,pi(n[e],n))}),n});function Up(n){var t=n==null?0:n.length,e=T();return n=t?$(n,function(r){if(typeof r[1]!=\"function\")throw new vn(I);return[e(r[0]),r[1]]}):[],P(function(r){for(var i=-1;++iYn)return[];var e=Pn,r=V(n,Pn);t=T(t),n-=Pn;for(var i=Or(r,t);++e0||t<0)?new D(e):(n<0?e=e.takeRight(-n):n&&(e=e.drop(n)),t!==u&&(t=S(t),e=t<0?e.dropRight(-t):e.take(t-n)),e)},D.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},D.prototype.toArray=function(){return this.take(Pn)},bn(D.prototype,function(n,t){var e=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),i=f[r?\"take\"+(t==\"last\"?\"Right\":\"\"):t],o=r||/^find/.test(t);i&&(f.prototype[t]=function(){var s=this.__wrapped__,a=r?[1]:arguments,c=s instanceof D,p=a[0],d=c||E(s),_=function(b){var W=i.apply(f,Qn([b],a));return r&&w?W[0]:W};d&&e&&typeof p==\"function\"&&p.length!=1&&(c=d=!1);var w=this.__chain__,y=!!this.__actions__.length,L=o&&!w,O=c&&!y;if(!o&&d){s=O?s:new D(this);var C=n.apply(s,a);return C.__actions__.push({func:Xe,args:[_],thisArg:u}),new mn(C,w)}return L&&O?n.apply(this,a):(C=this.thru(_),L?r?C.value()[0]:C.value():C)})}),_n([\"pop\",\"push\",\"shift\",\"sort\",\"splice\",\"unshift\"],function(n){var t=xe[n],e=/^(?:push|sort|unshift)$/.test(n)?\"tap\":\"thru\",r=/^(?:pop|shift)$/.test(n);f.prototype[n]=function(){var i=arguments;if(r&&!this.__chain__){var o=this.value();return t.apply(E(o)?o:[],i)}return this[e](function(s){return t.apply(E(s)?s:[],i)})}}),bn(D.prototype,function(n,t){var e=f[t];if(e){var r=e.name+\"\";F.call(Ot,r)||(Ot[r]=[]),Ot[r].push({name:t,func:e})}}),Ot[He(u,ft).name]=[{name:\"wrapper\",func:u}],D.prototype.clone=Fa,D.prototype.reverse=Ma,D.prototype.value=Na,f.prototype.at=ph,f.prototype.chain=dh,f.prototype.commit=_h,f.prototype.next=vh,f.prototype.plant=wh,f.prototype.reverse=Ah,f.prototype.toJSON=f.prototype.valueOf=f.prototype.value=xh,f.prototype.first=f.prototype.head,Jt&&(f.prototype[Jt]=mh),f},kn=_a();typeof define==\"function\"&&typeof define.amd==\"object\"&&define.amd?(Y._=kn,define(function(){return kn})):st?((st.exports=kn)._=kn,yr._=kn):Y._=kn}).call(Ft)});var jd={};Od(jd,{albIpMonitor:()=>Ao,albTargetRecordMonitor:()=>Ro});module.exports=Pd(jd);var go=ho(require(\"dns\")),po=require(\"@aws-sdk/lib-dynamodb\"),_o=require(\"@aws-sdk/client-dynamodb\"),vo=require(\"@aws-sdk/client-elastic-load-balancing-v2\"),Ri=process.env.LOOKUP_TABLE??\"\",mo=po.DynamoDBDocument.from(new _o.DynamoDB),wo=new vo.ElasticLoadBalancingV2({logger:console}),bd=async u=>{console.log(`Scanning route lookup table ${Ri}`);let v={TableName:u},x=[],m;do m=await mo.scan(v),m.Items?.forEach(I=>x.push(I)),v.ExclusiveStartKey=m.LastEvaluatedKey;while(typeof m.LastEvaluatedKey<\"u\");return x},Dd=async(u,v,x)=>{let m=v.map(j=>({Id:j,Port:x,AvailabilityZone:\"all\"})),I={TargetGroupArn:u,Targets:m};return wo.registerTargets(I)},Wd=async(u,v)=>{console.log(`Deregistering IP addresses ${JSON.stringify(v)} from target group ${u}`);let x=v.map(I=>({Id:I})),m={TargetGroupArn:u,Targets:x};return wo.deregisterTargets(m)},Bd=async u=>new Promise((v,x)=>{go.lookup(u,{all:!0,family:4},(m,I)=>{m?x(m):v(I.map(j=>j.address).sort())})}),Gd=(u,v)=>{let x=u.indexOf(v);return x>-1&&u.splice(x,1),u},Fd=async u=>{let v={TableName:Ri,Item:u};return mo.put(v)},Ao=async(u,v)=>{let x=await bd(Ri)??[];for(let m of x)try{m.dnsLookupIps=[];try{m.dnsLookupIps=await Bd(m.targetAlbDnsName)}catch(I){console.log(I)}m.ipAddList=m.dnsLookupIps?.filter(I=>!m.metadata?.targetGroupIpAddresses?.includes(I))??[],m.ipRemoveList=m.metadata?.targetGroupIpAddresses?.filter(I=>!m.dnsLookupIps?.includes(I))??[],m.ipAddList?.length>0?(console.log(`Registering new ips ${JSON.stringify(m.ipAddList)} to target ${m.metadata.targetGroupArn} with port ${m.targetGroupDestinationPort}`),await Dd(m.metadata.targetGroupArn,m.ipAddList,m.targetGroupDestinationPort),m.metadata.targetGroupIpAddresses.push(...m.ipAddList)):console.log(\"No new Ip addresses to register\"),m.ipRemoveList?.length>0?(console.log(`Deregistering old ip addresses ${JSON.stringify(m.ipRemoveList)} from target group targetGroupRecord.metadata.targetGroupArn`),await Wd(m.metadata.targetGroupArn,m.ipRemoveList),m.ipRemoveList?.forEach(I=>{console.log(m.metadata.targetGroupIpAddresses,I),m.metadata.targetGroupIpAddresses=Gd(m.metadata.targetGroupIpAddresses,I)})):console.log(\"No old ip addresses to deregister\"),delete m.ipAddList,delete m.ipRemoveList,delete m.dnsLookupIps,console.log(\"Writing record to DDB table \",JSON.stringify(m,null,4)),await Fd(m)}catch(I){console.log(\"There was a problem updating the record \",JSON.stringify(m,null,4)),console.log(I)}return\"Done\"};var yo=require(\"@aws-sdk/lib-dynamodb\"),Io=require(\"@aws-sdk/client-dynamodb\"),Ei=require(\"@aws-sdk/util-dynamodb\"),ir=require(\"@aws-sdk/client-elastic-load-balancing-v2\"),vt=ho(xo()),Zn=new ir.ElasticLoadBalancingV2,Md=yo.DynamoDBDocument.from(new Io.DynamoDB),Nd=process.env.LOOKUP_TABLE||\"\",Ud=u=>new Promise(v=>{setTimeout(v,u)}),$d=async(u,v,x,m)=>{let I={Name:u,Port:v,Protocol:m,VpcId:x,TargetType:ir.TargetTypeEnum.IP};return Zn.createTargetGroup(I)},Hd=async u=>{let v={Attributes:[{Key:\"stickiness.enabled\",Value:\"true\"}],TargetGroupArn:u};return Zn.modifyTargetGroupAttributes(v)},To=async(u,v)=>{let x={ListenerArn:v};return((await Zn.describeRules(x)).Rules?.filter(j=>j.Priority===u.toString())||[]).length===0},Lo=async u=>{try{let v={ListenerArns:[u]};return await Zn.describeListeners(v),Promise.resolve(!0)}catch(v){return console.log(v),Promise.resolve(!1)}},Kd=async(u,v,x,m,I)=>{console.log(\"trying to create listener rule\"),console.log(x,v,u,m,I);let j={Actions:[{TargetGroupArn:m,Type:\"forward\"}],ListenerArn:u,Priority:I,Conditions:[]};if(v?.length>0){let ut={Field:\"path-pattern\",Values:v};j.Conditions?.push(ut)}if(x?.length>0){let ut={Field:\"host-header\",Values:x};j.Conditions?.push(ut)}return Zn.createRule(j)},qd=async(u,v,x,m)=>{let I={Actions:[{TargetGroupArn:m,Type:\"forward\"}],RuleArn:u,Conditions:[]};if(v?.length>0){let j={Field:\"path-pattern\",Values:v};I?.Conditions?.push(j)}if(x?.length>0){let j={Field:\"host-header\",Values:x};I?.Conditions?.push(j)}return Zn.modifyRule(I)},zd=async u=>{let v={RuleArn:u};return Zn.deleteRule(v)},Zd=async u=>{let v={TargetGroupArn:u};return Zn.deleteTargetGroup(v)},Jd=async(u,v)=>{let x={RulePriorities:[{Priority:v,RuleArn:u}]};return Zn.setRulePriorities(x)},Yd=async(u,v)=>{let x={TableName:u,Item:v};return Md.put(x)},Xd=(u,v)=>{let x={vpcId:u.vpcId,destinationPort:u.targetGroupDestinationPort,protocol:u.targetGroupProtocol},m={vpcId:v.vpcId,destinationPort:v.targetGroupDestinationPort,protocol:v.targetGroupProtocol};return!vt.isEqual(x,m)},Qd=(u,v)=>{let x={sourceListenerArn:u.rule.sourceListenerArn,priority:u.rule.condition.priority,paths:u.rule.condition.paths?.sort(),hosts:u.rule.condition.hosts?.sort()},m={sourceListenerArn:v.rule.sourceListenerArn,priority:v.rule.condition.priority,paths:v.rule.condition.paths?.sort(),hosts:v.rule.condition.hosts?.sort()};return!vt.isEqual(x,m)},Vd=(u,v)=>{let x=u.rule.condition.priority,m=v.rule.condition.priority;return x!==m},Si=async u=>{console.log(\"Record creation detected.\");try{if(!await Lo(u.rule.sourceListenerArn))throw new Error(`The ALB Listener ARN: ${u.rule.sourceListenerArn} does not exist. Exiting`);if(console.log(\"Checking if priority is valid\"),!await To(u.rule.condition.priority,u.rule.sourceListenerArn))throw new Error(`The priority ${u.rule.condition.priority.toString()} matches an existing rule priority on the listener arn ${u.rule.sourceListenerArn}. Priorities must not match. Exiting`);let x=(await $d(u.id,u.targetGroupDestinationPort,u.vpcId,u.targetGroupProtocol))?.TargetGroups?.[0].TargetGroupArn??\"\";await Hd(x);let I=(await Kd(u.rule.sourceListenerArn,u.rule.condition.paths,u.rule.condition.hosts,x,u.rule.condition.priority))?.Rules?.[0].RuleArn??\"\";if(!x||!I)throw new Error(`There was an error getting the target group arn or listener rule arn. \nTarget Group Arn: ${x}\nRule Arn: ${I}`);return u.metadata={targetGroupArn:x,ruleArn:I,targetGroupIpAddresses:[]},await Yd(Nd,u),console.log(\"Added metadata to table\"),u}catch(v){throw console.log(\"There was a problem creating resources for the following record\",JSON.stringify(u,null,4)),v}},Co=async u=>{try{console.log(`Deleting listener rule and target group for ${u.id}`),await zd(u.metadata.ruleArn),console.log(\"Deleted listener rule.\")}catch(v){console.log(v),console.log(\"Could not delete listener rule for record. Continuing...\",JSON.stringify(u,null,4))}try{await Zd(u.metadata.targetGroupArn),console.log(\"Deleted target group\");return}catch(v){console.log(\"Could not delete target group for record\",JSON.stringify(u,null,4)),console.log(v)}},kd=async(u,v)=>{try{if(console.log(`The record with id ${u.id} was updated. Performing comparison.`),!await Lo(u.rule.sourceListenerArn))throw new Error(`The ALB Listener ARN: ${u.rule.sourceListenerArn} does not exist. Exiting`);let x=vt.cloneDeep(u),m=vt.cloneDeep(v);if(delete x.metadata,delete m.metadata,vt.isEqual(x,m)){console.log(`Update Record handler found no changes made for record with Id ${u.id}`);return}if(!v.metadata){console.log(\"No previous metadata detected for record. Creating metadata based off of new entry\"),await Si(u);return}if(Qd(v,u)&&(console.log(`Detected a listener rule change. Modifying rule ${u.metadata.ruleArn}`),await qd(u.metadata.ruleArn,u.rule.condition.paths,u.rule.condition.hosts,u.metadata.targetGroupArn)),Vd(v,u)){if(!await To(u.rule.condition.priority,u.rule.sourceListenerArn))throw new Error(`The priority ${u.rule.condition.priority.toString()} matches an existing rule priority on the listener arn ${u.rule.sourceListenerArn}. Priorities must not match.`);await Jd(u.metadata.ruleArn,u.rule.condition.priority)}Xd(v,u)&&(console.log(`Detected a target group change. deleting target group ${u.metadata.targetGroupArn} and creating a new target group`),await Co(u),await Ud(1e4),await Si(u))}catch(x){throw console.log(\"There was a problem updating a target group or listener rule for the records:\"),console.log(\"Old Record: \",JSON.stringify(v,null,4)),console.log(\"New Record: \",JSON.stringify(u,null,4)),x}},Ro=async(u,v)=>{console.log(JSON.stringify(u,null,2));let x=u.Records.map(m=>(m.dynamodb.OldImage&&(m.dynamodb.OldImage=(0,Ei.unmarshall)(m.dynamodb.OldImage)),m.dynamodb.NewImage&&(m.dynamodb.NewImage=(0,Ei.unmarshall)(m.dynamodb.NewImage)),m));for(let m of x)m.eventName===\"INSERT\"&&await Si(m.dynamodb.NewImage),m.eventName===\"MODIFY\"&&await kd(m.dynamodb.NewImage,m.dynamodb.OldImage),m.eventName===\"REMOVE\"&&await Co(m.dynamodb.OldImage)};0&&(module.exports={albIpMonitor,albTargetRecordMonitor});\n/*! Bundled license information:\n\nlodash/lodash.js:\n (**\n * @license\n * Lodash \n * Copyright OpenJS Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n *)\n*/\n" + }, + "Environment": { + "Variables": { + "LOOKUP_TABLE": { + "Ref": "AlbIpForwardingddbDNSFirewallTableDE7BAC6C" + } + } + }, + "Handler": "index.albIpMonitor", + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "AlbIpForwardingdnsFWLambdaServiceRoleE2550228", + "Arn" + ] + }, + "Runtime": "nodejs18.x", + "Timeout": 60 + }, + "DependsOn": [ + "AlbIpForwardingdnsFWLambdaServiceRoleDefaultPolicyF5FC440E", + "AlbIpForwardingdnsFWLambdaServiceRoleE2550228" + ], + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/dnsFWLambda/Resource", + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole" + }, + { + "id": "W89", + "reason": "This function supports infrastructure deployment and is not deployed inside a VPC." + }, + { + "id": "W92", + "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions." + } + ] + } + } + }, + "AlbIpForwardingdnsFWPolicyB74542DB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:RegisterTargets" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AlbIpForwardingdnsFWPolicyB74542DB", + "Roles": [ + { + "Ref": "AlbIpForwardingdnsFWLambdaServiceRoleE2550228" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda need to be able to work with any ELB in the account" + } + ] + } + } + }, + "AlbIpForwardingddbDnsRecordMonitorServiceRoleBDC0C08F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "RoleName": { + "Fn::Join": [ + "", + [ + { + "Ref": "acceleratorPrefix" + }, + "-ddbDnsRecordMonitor-", + { + "Ref": "vpcName" + } + ] + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "Names must be set explicitly to be protected by accelerator SCPs`" + } + ] + } + } + }, + "AlbIpForwardingddbDnsRecordMonitorServiceRoleDefaultPolicyBB5ECA75": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AlbIpForwardingalb2albKeyF92E9EA0", + "Arn" + ] + } + }, + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:ConditionCheckItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:PutItem", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:UpdateItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "AlbIpForwardingddbDNSFirewallTableDE7BAC6C", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AlbIpForwardingddbDNSFirewallTableDE7BAC6C", + "StreamArn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AlbIpForwardingddbDnsRecordMonitorServiceRoleDefaultPolicyBB5ECA75", + "Roles": [ + { + "Ref": "AlbIpForwardingddbDnsRecordMonitorServiceRoleBDC0C08F" + } + ] + }, + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/ddbDnsRecordMonitor/ServiceRole/DefaultPolicy/Resource" + } + }, + "AlbIpForwardingddbDnsRecordMonitor551C6C2F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\"use strict\";var Td=Object.create;var rr=Object.defineProperty;var Ld=Object.getOwnPropertyDescriptor;var Cd=Object.getOwnPropertyNames;var Rd=Object.getPrototypeOf,Ed=Object.prototype.hasOwnProperty;var Sd=(u,v)=>()=>(v||u((v={exports:{}}).exports,v),v.exports),Od=(u,v)=>{for(var x in v)rr(u,x,{get:v[x],enumerable:!0})},co=(u,v,x,m)=>{if(v&&typeof v==\"object\"||typeof v==\"function\")for(let I of Cd(v))!Ed.call(u,I)&&I!==x&&rr(u,I,{get:()=>v[I],enumerable:!(m=Ld(v,I))||m.enumerable});return u};var ho=(u,v,x)=>(x=u!=null?Td(Rd(u)):{},co(v||!u||!u.__esModule?rr(x,\"default\",{value:u,enumerable:!0}):x,u)),Pd=u=>co(rr({},\"__esModule\",{value:!0}),u);var xo=Sd((Ft,oe)=>{(function(){var u,v=\"4.17.21\",x=200,m=\"Unsupported core-js use. Try https://npms.io/search?q=ponyfill.\",I=\"Expected a function\",j=\"Invalid `variable` option passed into `_.template`\",ut=\"__lodash_hash_undefined__\",Eo=500,se=\"__lodash_placeholder__\",Jn=1,Oi=2,mt=4,wt=1,ae=2,pn=1,ft=2,Pi=4,Sn=8,At=16,On=32,xt=64,Bn=128,Mt=256,ur=512,So=30,Oo=\"...\",Po=800,bo=16,bi=1,Do=2,Wo=3,ot=1/0,Yn=9007199254740991,Bo=17976931348623157e292,le=0/0,Pn=4294967295,Go=Pn-1,Fo=Pn>>>1,Mo=[[\"ary\",Bn],[\"bind\",pn],[\"bindKey\",ft],[\"curry\",Sn],[\"curryRight\",At],[\"flip\",ur],[\"partial\",On],[\"partialRight\",xt],[\"rearg\",Mt]],yt=\"[object Arguments]\",ce=\"[object Array]\",No=\"[object AsyncFunction]\",Nt=\"[object Boolean]\",Ut=\"[object Date]\",Uo=\"[object DOMException]\",he=\"[object Error]\",ge=\"[object Function]\",Di=\"[object GeneratorFunction]\",In=\"[object Map]\",$t=\"[object Number]\",$o=\"[object Null]\",Gn=\"[object Object]\",Wi=\"[object Promise]\",Ho=\"[object Proxy]\",Ht=\"[object RegExp]\",Tn=\"[object Set]\",Kt=\"[object String]\",pe=\"[object Symbol]\",Ko=\"[object Undefined]\",qt=\"[object WeakMap]\",qo=\"[object WeakSet]\",zt=\"[object ArrayBuffer]\",It=\"[object DataView]\",fr=\"[object Float32Array]\",or=\"[object Float64Array]\",sr=\"[object Int8Array]\",ar=\"[object Int16Array]\",lr=\"[object Int32Array]\",cr=\"[object Uint8Array]\",hr=\"[object Uint8ClampedArray]\",gr=\"[object Uint16Array]\",pr=\"[object Uint32Array]\",zo=/\\b__p \\+= '';/g,Zo=/\\b(__p \\+=) '' \\+/g,Jo=/(__e\\(.*?\\)|\\b__t\\)) \\+\\n'';/g,Bi=/&(?:amp|lt|gt|quot|#39);/g,Gi=/[&<>\"']/g,Yo=RegExp(Bi.source),Xo=RegExp(Gi.source),Qo=/<%-([\\s\\S]+?)%>/g,Vo=/<%([\\s\\S]+?)%>/g,Fi=/<%=([\\s\\S]+?)%>/g,ko=/\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,jo=/^\\w*$/,ns=/[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,dr=/[\\\\^$.*+?()[\\]{}|]/g,ts=RegExp(dr.source),_r=/^\\s+/,es=/\\s/,rs=/\\{(?:\\n\\/\\* \\[wrapped with .+\\] \\*\\/)?\\n?/,is=/\\{\\n\\/\\* \\[wrapped with (.+)\\] \\*/,us=/,? & /,fs=/[^\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]+/g,os=/[()=,{}\\[\\]\\/\\s]/,ss=/\\\\(\\\\)?/g,as=/\\$\\{([^\\\\}]*(?:\\\\.[^\\\\}]*)*)\\}/g,Mi=/\\w*$/,ls=/^[-+]0x[0-9a-f]+$/i,cs=/^0b[01]+$/i,hs=/^\\[object .+?Constructor\\]$/,gs=/^0o[0-7]+$/i,ps=/^(?:0|[1-9]\\d*)$/,ds=/[\\xc0-\\xd6\\xd8-\\xf6\\xf8-\\xff\\u0100-\\u017f]/g,de=/($^)/,_s=/['\\n\\r\\u2028\\u2029\\\\]/g,_e=\"\\\\ud800-\\\\udfff\",vs=\"\\\\u0300-\\\\u036f\",ms=\"\\\\ufe20-\\\\ufe2f\",ws=\"\\\\u20d0-\\\\u20ff\",Ni=vs+ms+ws,Ui=\"\\\\u2700-\\\\u27bf\",$i=\"a-z\\\\xdf-\\\\xf6\\\\xf8-\\\\xff\",As=\"\\\\xac\\\\xb1\\\\xd7\\\\xf7\",xs=\"\\\\x00-\\\\x2f\\\\x3a-\\\\x40\\\\x5b-\\\\x60\\\\x7b-\\\\xbf\",ys=\"\\\\u2000-\\\\u206f\",Is=\" \\\\t\\\\x0b\\\\f\\\\xa0\\\\ufeff\\\\n\\\\r\\\\u2028\\\\u2029\\\\u1680\\\\u180e\\\\u2000\\\\u2001\\\\u2002\\\\u2003\\\\u2004\\\\u2005\\\\u2006\\\\u2007\\\\u2008\\\\u2009\\\\u200a\\\\u202f\\\\u205f\\\\u3000\",Hi=\"A-Z\\\\xc0-\\\\xd6\\\\xd8-\\\\xde\",Ki=\"\\\\ufe0e\\\\ufe0f\",qi=As+xs+ys+Is,vr=\"['\\u2019]\",Ts=\"[\"+_e+\"]\",zi=\"[\"+qi+\"]\",ve=\"[\"+Ni+\"]\",Zi=\"\\\\d+\",Ls=\"[\"+Ui+\"]\",Ji=\"[\"+$i+\"]\",Yi=\"[^\"+_e+qi+Zi+Ui+$i+Hi+\"]\",mr=\"\\\\ud83c[\\\\udffb-\\\\udfff]\",Cs=\"(?:\"+ve+\"|\"+mr+\")\",Xi=\"[^\"+_e+\"]\",wr=\"(?:\\\\ud83c[\\\\udde6-\\\\uddff]){2}\",Ar=\"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\",Tt=\"[\"+Hi+\"]\",Qi=\"\\\\u200d\",Vi=\"(?:\"+Ji+\"|\"+Yi+\")\",Rs=\"(?:\"+Tt+\"|\"+Yi+\")\",ki=\"(?:\"+vr+\"(?:d|ll|m|re|s|t|ve))?\",ji=\"(?:\"+vr+\"(?:D|LL|M|RE|S|T|VE))?\",nu=Cs+\"?\",tu=\"[\"+Ki+\"]?\",Es=\"(?:\"+Qi+\"(?:\"+[Xi,wr,Ar].join(\"|\")+\")\"+tu+nu+\")*\",Ss=\"\\\\d*(?:1st|2nd|3rd|(?![123])\\\\dth)(?=\\\\b|[A-Z_])\",Os=\"\\\\d*(?:1ST|2ND|3RD|(?![123])\\\\dTH)(?=\\\\b|[a-z_])\",eu=tu+nu+Es,Ps=\"(?:\"+[Ls,wr,Ar].join(\"|\")+\")\"+eu,bs=\"(?:\"+[Xi+ve+\"?\",ve,wr,Ar,Ts].join(\"|\")+\")\",Ds=RegExp(vr,\"g\"),Ws=RegExp(ve,\"g\"),xr=RegExp(mr+\"(?=\"+mr+\")|\"+bs+eu,\"g\"),Bs=RegExp([Tt+\"?\"+Ji+\"+\"+ki+\"(?=\"+[zi,Tt,\"$\"].join(\"|\")+\")\",Rs+\"+\"+ji+\"(?=\"+[zi,Tt+Vi,\"$\"].join(\"|\")+\")\",Tt+\"?\"+Vi+\"+\"+ki,Tt+\"+\"+ji,Os,Ss,Zi,Ps].join(\"|\"),\"g\"),Gs=RegExp(\"[\"+Qi+_e+Ni+Ki+\"]\"),Fs=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Ms=[\"Array\",\"Buffer\",\"DataView\",\"Date\",\"Error\",\"Float32Array\",\"Float64Array\",\"Function\",\"Int8Array\",\"Int16Array\",\"Int32Array\",\"Map\",\"Math\",\"Object\",\"Promise\",\"RegExp\",\"Set\",\"String\",\"Symbol\",\"TypeError\",\"Uint8Array\",\"Uint8ClampedArray\",\"Uint16Array\",\"Uint32Array\",\"WeakMap\",\"_\",\"clearTimeout\",\"isFinite\",\"parseInt\",\"setTimeout\"],Ns=-1,U={};U[fr]=U[or]=U[sr]=U[ar]=U[lr]=U[cr]=U[hr]=U[gr]=U[pr]=!0,U[yt]=U[ce]=U[zt]=U[Nt]=U[It]=U[Ut]=U[he]=U[ge]=U[In]=U[$t]=U[Gn]=U[Ht]=U[Tn]=U[Kt]=U[qt]=!1;var N={};N[yt]=N[ce]=N[zt]=N[It]=N[Nt]=N[Ut]=N[fr]=N[or]=N[sr]=N[ar]=N[lr]=N[In]=N[$t]=N[Gn]=N[Ht]=N[Tn]=N[Kt]=N[pe]=N[cr]=N[hr]=N[gr]=N[pr]=!0,N[he]=N[ge]=N[qt]=!1;var Us={\\u00C0:\"A\",\\u00C1:\"A\",\\u00C2:\"A\",\\u00C3:\"A\",\\u00C4:\"A\",\\u00C5:\"A\",\\u00E0:\"a\",\\u00E1:\"a\",\\u00E2:\"a\",\\u00E3:\"a\",\\u00E4:\"a\",\\u00E5:\"a\",\\u00C7:\"C\",\\u00E7:\"c\",\\u00D0:\"D\",\\u00F0:\"d\",\\u00C8:\"E\",\\u00C9:\"E\",\\u00CA:\"E\",\\u00CB:\"E\",\\u00E8:\"e\",\\u00E9:\"e\",\\u00EA:\"e\",\\u00EB:\"e\",\\u00CC:\"I\",\\u00CD:\"I\",\\u00CE:\"I\",\\u00CF:\"I\",\\u00EC:\"i\",\\u00ED:\"i\",\\u00EE:\"i\",\\u00EF:\"i\",\\u00D1:\"N\",\\u00F1:\"n\",\\u00D2:\"O\",\\u00D3:\"O\",\\u00D4:\"O\",\\u00D5:\"O\",\\u00D6:\"O\",\\u00D8:\"O\",\\u00F2:\"o\",\\u00F3:\"o\",\\u00F4:\"o\",\\u00F5:\"o\",\\u00F6:\"o\",\\u00F8:\"o\",\\u00D9:\"U\",\\u00DA:\"U\",\\u00DB:\"U\",\\u00DC:\"U\",\\u00F9:\"u\",\\u00FA:\"u\",\\u00FB:\"u\",\\u00FC:\"u\",\\u00DD:\"Y\",\\u00FD:\"y\",\\u00FF:\"y\",\\u00C6:\"Ae\",\\u00E6:\"ae\",\\u00DE:\"Th\",\\u00FE:\"th\",\\u00DF:\"ss\",\\u0100:\"A\",\\u0102:\"A\",\\u0104:\"A\",\\u0101:\"a\",\\u0103:\"a\",\\u0105:\"a\",\\u0106:\"C\",\\u0108:\"C\",\\u010A:\"C\",\\u010C:\"C\",\\u0107:\"c\",\\u0109:\"c\",\\u010B:\"c\",\\u010D:\"c\",\\u010E:\"D\",\\u0110:\"D\",\\u010F:\"d\",\\u0111:\"d\",\\u0112:\"E\",\\u0114:\"E\",\\u0116:\"E\",\\u0118:\"E\",\\u011A:\"E\",\\u0113:\"e\",\\u0115:\"e\",\\u0117:\"e\",\\u0119:\"e\",\\u011B:\"e\",\\u011C:\"G\",\\u011E:\"G\",\\u0120:\"G\",\\u0122:\"G\",\\u011D:\"g\",\\u011F:\"g\",\\u0121:\"g\",\\u0123:\"g\",\\u0124:\"H\",\\u0126:\"H\",\\u0125:\"h\",\\u0127:\"h\",\\u0128:\"I\",\\u012A:\"I\",\\u012C:\"I\",\\u012E:\"I\",\\u0130:\"I\",\\u0129:\"i\",\\u012B:\"i\",\\u012D:\"i\",\\u012F:\"i\",\\u0131:\"i\",\\u0134:\"J\",\\u0135:\"j\",\\u0136:\"K\",\\u0137:\"k\",\\u0138:\"k\",\\u0139:\"L\",\\u013B:\"L\",\\u013D:\"L\",\\u013F:\"L\",\\u0141:\"L\",\\u013A:\"l\",\\u013C:\"l\",\\u013E:\"l\",\\u0140:\"l\",\\u0142:\"l\",\\u0143:\"N\",\\u0145:\"N\",\\u0147:\"N\",\\u014A:\"N\",\\u0144:\"n\",\\u0146:\"n\",\\u0148:\"n\",\\u014B:\"n\",\\u014C:\"O\",\\u014E:\"O\",\\u0150:\"O\",\\u014D:\"o\",\\u014F:\"o\",\\u0151:\"o\",\\u0154:\"R\",\\u0156:\"R\",\\u0158:\"R\",\\u0155:\"r\",\\u0157:\"r\",\\u0159:\"r\",\\u015A:\"S\",\\u015C:\"S\",\\u015E:\"S\",\\u0160:\"S\",\\u015B:\"s\",\\u015D:\"s\",\\u015F:\"s\",\\u0161:\"s\",\\u0162:\"T\",\\u0164:\"T\",\\u0166:\"T\",\\u0163:\"t\",\\u0165:\"t\",\\u0167:\"t\",\\u0168:\"U\",\\u016A:\"U\",\\u016C:\"U\",\\u016E:\"U\",\\u0170:\"U\",\\u0172:\"U\",\\u0169:\"u\",\\u016B:\"u\",\\u016D:\"u\",\\u016F:\"u\",\\u0171:\"u\",\\u0173:\"u\",\\u0174:\"W\",\\u0175:\"w\",\\u0176:\"Y\",\\u0177:\"y\",\\u0178:\"Y\",\\u0179:\"Z\",\\u017B:\"Z\",\\u017D:\"Z\",\\u017A:\"z\",\\u017C:\"z\",\\u017E:\"z\",\\u0132:\"IJ\",\\u0133:\"ij\",\\u0152:\"Oe\",\\u0153:\"oe\",\\u0149:\"'n\",\\u017F:\"s\"},$s={\"&\":\"&\",\"<\":\"<\",\">\":\">\",'\"':\""\",\"'\":\"'\"},Hs={\"&\":\"&\",\"<\":\"<\",\">\":\">\",\""\":'\"',\"'\":\"'\"},Ks={\"\\\\\":\"\\\\\",\"'\":\"'\",\"\\n\":\"n\",\"\\r\":\"r\",\"\\u2028\":\"u2028\",\"\\u2029\":\"u2029\"},qs=parseFloat,zs=parseInt,ru=typeof global==\"object\"&&global&&global.Object===Object&&global,Zs=typeof self==\"object\"&&self&&self.Object===Object&&self,Y=ru||Zs||Function(\"return this\")(),yr=typeof Ft==\"object\"&&Ft&&!Ft.nodeType&&Ft,st=yr&&typeof oe==\"object\"&&oe&&!oe.nodeType&&oe,iu=st&&st.exports===yr,Ir=iu&&ru.process,dn=function(){try{var l=st&&st.require&&st.require(\"util\").types;return l||Ir&&Ir.binding&&Ir.binding(\"util\")}catch{}}(),uu=dn&&dn.isArrayBuffer,fu=dn&&dn.isDate,ou=dn&&dn.isMap,su=dn&&dn.isRegExp,au=dn&&dn.isSet,lu=dn&&dn.isTypedArray;function sn(l,g,h){switch(h.length){case 0:return l.call(g);case 1:return l.call(g,h[0]);case 2:return l.call(g,h[0],h[1]);case 3:return l.call(g,h[0],h[1],h[2])}return l.apply(g,h)}function Js(l,g,h,A){for(var R=-1,B=l==null?0:l.length;++R-1}function Tr(l,g,h){for(var A=-1,R=l==null?0:l.length;++A-1;);return h}function mu(l,g){for(var h=l.length;h--&&Lt(g,l[h],0)>-1;);return h}function ea(l,g){for(var h=l.length,A=0;h--;)l[h]===g&&++A;return A}var ra=Er(Us),ia=Er($s);function ua(l){return\"\\\\\"+Ks[l]}function fa(l,g){return l==null?u:l[g]}function Ct(l){return Gs.test(l)}function oa(l){return Fs.test(l)}function sa(l){for(var g,h=[];!(g=l.next()).done;)h.push(g.value);return h}function br(l){var g=-1,h=Array(l.size);return l.forEach(function(A,R){h[++g]=[R,A]}),h}function wu(l,g){return function(h){return l(g(h))}}function Vn(l,g){for(var h=-1,A=l.length,R=0,B=[];++h-1}function Xa(n,t){var e=this.__data__,r=Be(e,n);return r<0?(++this.size,e.push([n,t])):e[r][1]=t,this}Fn.prototype.clear=za,Fn.prototype.delete=Za,Fn.prototype.get=Ja,Fn.prototype.has=Ya,Fn.prototype.set=Xa;function Mn(n){var t=-1,e=n==null?0:n.length;for(this.clear();++t=t?n:t)),n}function wn(n,t,e,r,i,o){var s,a=t&Jn,c=t&Oi,p=t&mt;if(e&&(s=i?e(n,r,i,o):e(n)),s!==u)return s;if(!H(n))return n;var d=E(n);if(d){if(s=jl(n),!a)return rn(n,s)}else{var _=k(n),w=_==ge||_==Di;if(it(n))return tf(n,a);if(_==Gn||_==yt||w&&!i){if(s=c||w?{}:xf(n),!a)return c?Hl(n,cl(s,n)):$l(n,Pu(s,n))}else{if(!N[_])return i?n:{};s=nc(n,_,a)}}o||(o=new Cn);var y=o.get(n);if(y)return y;o.set(n,s),Qf(n)?n.forEach(function(C){s.add(wn(C,t,e,C,n,o))}):Yf(n)&&n.forEach(function(C,b){s.set(b,wn(C,t,e,b,n,o))});var L=p?c?ii:ri:c?fn:X,O=d?u:L(n);return _n(O||n,function(C,b){O&&(b=C,C=n[b]),kt(s,b,wn(C,t,e,b,n,o))}),s}function hl(n){var t=X(n);return function(e){return bu(e,n,t)}}function bu(n,t,e){var r=e.length;if(n==null)return!r;for(n=M(n);r--;){var i=e[r],o=t[i],s=n[i];if(s===u&&!(i in n)||!o(s))return!1}return!0}function Du(n,t,e){if(typeof n!=\"function\")throw new vn(I);return ue(function(){n.apply(u,e)},t)}function jt(n,t,e,r){var i=-1,o=me,s=!0,a=n.length,c=[],p=t.length;if(!a)return c;e&&(t=$(t,an(e))),r?(o=Tr,s=!1):t.length>=x&&(o=Zt,s=!1,t=new ct(t));n:for(;++ii?0:i+e),r=r===u||r>i?i:S(r),r<0&&(r+=i),r=e>r?0:kf(r);e0&&e(a)?t>1?Q(a,t-1,e,r,i):Qn(i,a):r||(i[i.length]=a)}return i}var Nr=sf(),Gu=sf(!0);function bn(n,t){return n&&Nr(n,t,X)}function Ur(n,t){return n&&Gu(n,t,X)}function Fe(n,t){return Xn(t,function(e){return Kn(n[e])})}function gt(n,t){t=et(t,n);for(var e=0,r=t.length;n!=null&&et}function dl(n,t){return n!=null&&F.call(n,t)}function _l(n,t){return n!=null&&t in M(n)}function vl(n,t,e){return n>=V(t,e)&&n=120&&d.length>=120)?new ct(s&&d):u}d=n[0];var _=-1,w=a[0];n:for(;++_-1;)a!==n&&Ee.call(a,c,1),Ee.call(n,c,1);return n}function Ju(n,t){for(var e=n?t.length:0,r=e-1;e--;){var i=t[e];if(e==r||i!==o){var o=i;Hn(i)?Ee.call(n,i,1):Qr(n,i)}}return n}function Jr(n,t){return n+Pe(Ru()*(t-n+1))}function Ol(n,t,e,r){for(var i=-1,o=J(Oe((t-n)/(e||1)),0),s=h(o);o--;)s[r?o:++i]=n,n+=e;return s}function Yr(n,t){var e=\"\";if(!n||t<1||t>Yn)return e;do t%2&&(e+=n),t=Pe(t/2),t&&(n+=n);while(t);return e}function P(n,t){return ci(Tf(n,t,on),n+\"\")}function Pl(n){return Ou(Gt(n))}function bl(n,t){var e=Gt(n);return Ye(e,ht(t,0,e.length))}function ee(n,t,e,r){if(!H(n))return n;t=et(t,n);for(var i=-1,o=t.length,s=o-1,a=n;a!=null&&++ii?0:i+t),e=e>i?i:e,e<0&&(e+=i),i=t>e?0:e-t>>>0,t>>>=0;for(var o=h(i);++r>>1,s=n[o];s!==null&&!cn(s)&&(e?s<=t:s=x){var p=t?null:Zl(n);if(p)return Ae(p);s=!1,i=Zt,c=new ct}else c=t?[]:a;n:for(;++r=r?n:An(n,t,e)}var nf=Ia||function(n){return Y.clearTimeout(n)};function tf(n,t){if(t)return n.slice();var e=n.length,r=yu?yu(e):new n.constructor(e);return n.copy(r),r}function ni(n){var t=new n.constructor(n.byteLength);return new Ce(t).set(new Ce(n)),t}function Fl(n,t){var e=t?ni(n.buffer):n.buffer;return new n.constructor(e,n.byteOffset,n.byteLength)}function Ml(n){var t=new n.constructor(n.source,Mi.exec(n));return t.lastIndex=n.lastIndex,t}function Nl(n){return Vt?M(Vt.call(n)):{}}function ef(n,t){var e=t?ni(n.buffer):n.buffer;return new n.constructor(e,n.byteOffset,n.length)}function rf(n,t){if(n!==t){var e=n!==u,r=n===null,i=n===n,o=cn(n),s=t!==u,a=t===null,c=t===t,p=cn(t);if(!a&&!p&&!o&&n>t||o&&s&&c&&!a&&!p||r&&s&&c||!e&&c||!i)return 1;if(!r&&!o&&!p&&n=a)return c;var p=e[r];return c*(p==\"desc\"?-1:1)}}return n.index-t.index}function uf(n,t,e,r){for(var i=-1,o=n.length,s=e.length,a=-1,c=t.length,p=J(o-s,0),d=h(c+p),_=!r;++a1?e[i-1]:u,s=i>2?e[2]:u;for(o=n.length>3&&typeof o==\"function\"?(i--,o):u,s&&tn(e[0],e[1],s)&&(o=i<3?u:o,i=1),t=M(t);++r-1?i[o?t[s]:s]:u}}function cf(n){return $n(function(t){var e=t.length,r=e,i=mn.prototype.thru;for(n&&t.reverse();r--;){var o=t[r];if(typeof o!=\"function\")throw new vn(I);if(i&&!s&&Ze(o)==\"wrapper\")var s=new mn([],!0)}for(r=s?r:e;++r1&&W.reverse(),d&&ca))return!1;var p=o.get(n),d=o.get(t);if(p&&d)return p==t&&d==n;var _=-1,w=!0,y=e&ae?new ct:u;for(o.set(n,t),o.set(t,n);++_1?\"& \":\"\")+t[r],t=t.join(e>2?\", \":\" \"),n.replace(rs,`{\n/* [wrapped with `+t+`] */\n`)}function ec(n){return E(n)||_t(n)||!!(Lu&&n&&n[Lu])}function Hn(n,t){var e=typeof n;return t=t??Yn,!!t&&(e==\"number\"||e!=\"symbol\"&&ps.test(n))&&n>-1&&n%1==0&&n0){if(++t>=Po)return arguments[0]}else t=0;return n.apply(u,arguments)}}function Ye(n,t){var e=-1,r=n.length,i=r-1;for(t=t===u?r:t;++e1?n[t-1]:u;return e=typeof e==\"function\"?(n.pop(),e):u,Gf(n,e)});function Ff(n){var t=f(n);return t.__chain__=!0,t}function gh(n,t){return t(n),n}function Xe(n,t){return t(n)}var ph=$n(function(n){var t=n.length,e=t?n[0]:0,r=this.__wrapped__,i=function(o){return Mr(o,n)};return t>1||this.__actions__.length||!(r instanceof D)||!Hn(e)?this.thru(i):(r=r.slice(e,+e+(t?1:0)),r.__actions__.push({func:Xe,args:[i],thisArg:u}),new mn(r,this.__chain__).thru(function(o){return t&&!o.length&&o.push(u),o}))});function dh(){return Ff(this)}function _h(){return new mn(this.value(),this.__chain__)}function vh(){this.__values__===u&&(this.__values__=Vf(this.value()));var n=this.__index__>=this.__values__.length,t=n?u:this.__values__[this.__index__++];return{done:n,value:t}}function mh(){return this}function wh(n){for(var t,e=this;e instanceof We;){var r=Of(e);r.__index__=0,r.__values__=u,t?i.__wrapped__=r:t=r;var i=r;e=e.__wrapped__}return i.__wrapped__=n,t}function Ah(){var n=this.__wrapped__;if(n instanceof D){var t=n;return this.__actions__.length&&(t=new D(this)),t=t.reverse(),t.__actions__.push({func:Xe,args:[hi],thisArg:u}),new mn(t,this.__chain__)}return this.thru(hi)}function xh(){return ku(this.__wrapped__,this.__actions__)}var yh=$e(function(n,t,e){F.call(n,e)?++n[e]:Nn(n,e,1)});function Ih(n,t,e){var r=E(n)?cu:gl;return e&&tn(n,t,e)&&(t=u),r(n,T(t,3))}function Th(n,t){var e=E(n)?Xn:Bu;return e(n,T(t,3))}var Lh=lf(Pf),Ch=lf(bf);function Rh(n,t){return Q(Qe(n,t),1)}function Eh(n,t){return Q(Qe(n,t),ot)}function Sh(n,t,e){return e=e===u?1:S(e),Q(Qe(n,t),e)}function Mf(n,t){var e=E(n)?_n:nt;return e(n,T(t,3))}function Nf(n,t){var e=E(n)?Ys:Wu;return e(n,T(t,3))}var Oh=$e(function(n,t,e){F.call(n,e)?n[e].push(t):Nn(n,e,[t])});function Ph(n,t,e,r){n=un(n)?n:Gt(n),e=e&&!r?S(e):0;var i=n.length;return e<0&&(e=J(i+e,0)),tr(n)?e<=i&&n.indexOf(t,e)>-1:!!i&&Lt(n,t,e)>-1}var bh=P(function(n,t,e){var r=-1,i=typeof t==\"function\",o=un(n)?h(n.length):[];return nt(n,function(s){o[++r]=i?sn(t,s,e):ne(s,t,e)}),o}),Dh=$e(function(n,t,e){Nn(n,e,t)});function Qe(n,t){var e=E(n)?$:$u;return e(n,T(t,3))}function Wh(n,t,e,r){return n==null?[]:(E(t)||(t=t==null?[]:[t]),e=r?u:e,E(e)||(e=e==null?[]:[e]),zu(n,t,e))}var Bh=$e(function(n,t,e){n[e?0:1].push(t)},function(){return[[],[]]});function Gh(n,t,e){var r=E(n)?Lr:du,i=arguments.length<3;return r(n,T(t,4),e,i,nt)}function Fh(n,t,e){var r=E(n)?Xs:du,i=arguments.length<3;return r(n,T(t,4),e,i,Wu)}function Mh(n,t){var e=E(n)?Xn:Bu;return e(n,je(T(t,3)))}function Nh(n){var t=E(n)?Ou:Pl;return t(n)}function Uh(n,t,e){(e?tn(n,t,e):t===u)?t=1:t=S(t);var r=E(n)?sl:bl;return r(n,t)}function $h(n){var t=E(n)?al:Wl;return t(n)}function Hh(n){if(n==null)return 0;if(un(n))return tr(n)?Rt(n):n.length;var t=k(n);return t==In||t==Tn?n.size:qr(n).length}function Kh(n,t,e){var r=E(n)?Cr:Bl;return e&&tn(n,t,e)&&(t=u),r(n,T(t,3))}var qh=P(function(n,t){if(n==null)return[];var e=t.length;return e>1&&tn(n,t[0],t[1])?t=[]:e>2&&tn(t[0],t[1],t[2])&&(t=[t[0]]),zu(n,Q(t,1),[])}),Ve=Ta||function(){return Y.Date.now()};function zh(n,t){if(typeof t!=\"function\")throw new vn(I);return n=S(n),function(){if(--n<1)return t.apply(this,arguments)}}function Uf(n,t,e){return t=e?u:t,t=n&&t==null?n.length:t,Un(n,Bn,u,u,u,u,t)}function $f(n,t){var e;if(typeof t!=\"function\")throw new vn(I);return n=S(n),function(){return--n>0&&(e=t.apply(this,arguments)),n<=1&&(t=u),e}}var pi=P(function(n,t,e){var r=pn;if(e.length){var i=Vn(e,Wt(pi));r|=On}return Un(n,r,t,e,i)}),Hf=P(function(n,t,e){var r=pn|ft;if(e.length){var i=Vn(e,Wt(Hf));r|=On}return Un(t,r,n,e,i)});function Kf(n,t,e){t=e?u:t;var r=Un(n,Sn,u,u,u,u,u,t);return r.placeholder=Kf.placeholder,r}function qf(n,t,e){t=e?u:t;var r=Un(n,At,u,u,u,u,u,t);return r.placeholder=qf.placeholder,r}function zf(n,t,e){var r,i,o,s,a,c,p=0,d=!1,_=!1,w=!0;if(typeof n!=\"function\")throw new vn(I);t=yn(t)||0,H(e)&&(d=!!e.leading,_=\"maxWait\"in e,o=_?J(yn(e.maxWait)||0,t):o,w=\"trailing\"in e?!!e.trailing:w);function y(z){var En=r,zn=i;return r=i=u,p=z,s=n.apply(zn,En),s}function L(z){return p=z,a=ue(b,t),d?y(z):s}function O(z){var En=z-c,zn=z-p,lo=t-En;return _?V(lo,o-zn):lo}function C(z){var En=z-c,zn=z-p;return c===u||En>=t||En<0||_&&zn>=o}function b(){var z=Ve();if(C(z))return W(z);a=ue(b,O(z))}function W(z){return a=u,w&&r?y(z):(r=i=u,s)}function hn(){a!==u&&nf(a),p=0,r=c=i=a=u}function en(){return a===u?s:W(Ve())}function gn(){var z=Ve(),En=C(z);if(r=arguments,i=this,c=z,En){if(a===u)return L(c);if(_)return nf(a),a=ue(b,t),y(c)}return a===u&&(a=ue(b,t)),s}return gn.cancel=hn,gn.flush=en,gn}var Zh=P(function(n,t){return Du(n,1,t)}),Jh=P(function(n,t,e){return Du(n,yn(t)||0,e)});function Yh(n){return Un(n,ur)}function ke(n,t){if(typeof n!=\"function\"||t!=null&&typeof t!=\"function\")throw new vn(I);var e=function(){var r=arguments,i=t?t.apply(this,r):r[0],o=e.cache;if(o.has(i))return o.get(i);var s=n.apply(this,r);return e.cache=o.set(i,s)||o,s};return e.cache=new(ke.Cache||Mn),e}ke.Cache=Mn;function je(n){if(typeof n!=\"function\")throw new vn(I);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function Xh(n){return $f(2,n)}var Qh=Gl(function(n,t){t=t.length==1&&E(t[0])?$(t[0],an(T())):$(Q(t,1),an(T()));var e=t.length;return P(function(r){for(var i=-1,o=V(r.length,e);++i=t}),_t=Mu(function(){return arguments}())?Mu:function(n){return K(n)&&F.call(n,\"callee\")&&!Tu.call(n,\"callee\")},E=h.isArray,cg=uu?an(uu):wl;function un(n){return n!=null&&nr(n.length)&&!Kn(n)}function q(n){return K(n)&&un(n)}function hg(n){return n===!0||n===!1||K(n)&&nn(n)==Nt}var it=Ca||Ci,gg=fu?an(fu):Al;function pg(n){return K(n)&&n.nodeType===1&&!fe(n)}function dg(n){if(n==null)return!0;if(un(n)&&(E(n)||typeof n==\"string\"||typeof n.splice==\"function\"||it(n)||Bt(n)||_t(n)))return!n.length;var t=k(n);if(t==In||t==Tn)return!n.size;if(ie(n))return!qr(n).length;for(var e in n)if(F.call(n,e))return!1;return!0}function _g(n,t){return te(n,t)}function vg(n,t,e){e=typeof e==\"function\"?e:u;var r=e?e(n,t):u;return r===u?te(n,t,u,e):!!r}function _i(n){if(!K(n))return!1;var t=nn(n);return t==he||t==Uo||typeof n.message==\"string\"&&typeof n.name==\"string\"&&!fe(n)}function mg(n){return typeof n==\"number\"&&Cu(n)}function Kn(n){if(!H(n))return!1;var t=nn(n);return t==ge||t==Di||t==No||t==Ho}function Jf(n){return typeof n==\"number\"&&n==S(n)}function nr(n){return typeof n==\"number\"&&n>-1&&n%1==0&&n<=Yn}function H(n){var t=typeof n;return n!=null&&(t==\"object\"||t==\"function\")}function K(n){return n!=null&&typeof n==\"object\"}var Yf=ou?an(ou):yl;function wg(n,t){return n===t||Kr(n,t,fi(t))}function Ag(n,t,e){return e=typeof e==\"function\"?e:u,Kr(n,t,fi(t),e)}function xg(n){return Xf(n)&&n!=+n}function yg(n){if(uc(n))throw new R(m);return Nu(n)}function Ig(n){return n===null}function Tg(n){return n==null}function Xf(n){return typeof n==\"number\"||K(n)&&nn(n)==$t}function fe(n){if(!K(n)||nn(n)!=Gn)return!1;var t=Re(n);if(t===null)return!0;var e=F.call(t,\"constructor\")&&t.constructor;return typeof e==\"function\"&&e instanceof e&&Ie.call(e)==Aa}var vi=su?an(su):Il;function Lg(n){return Jf(n)&&n>=-Yn&&n<=Yn}var Qf=au?an(au):Tl;function tr(n){return typeof n==\"string\"||!E(n)&&K(n)&&nn(n)==Kt}function cn(n){return typeof n==\"symbol\"||K(n)&&nn(n)==pe}var Bt=lu?an(lu):Ll;function Cg(n){return n===u}function Rg(n){return K(n)&&k(n)==qt}function Eg(n){return K(n)&&nn(n)==qo}var Sg=ze(zr),Og=ze(function(n,t){return n<=t});function Vf(n){if(!n)return[];if(un(n))return tr(n)?Ln(n):rn(n);if(Jt&&n[Jt])return sa(n[Jt]());var t=k(n),e=t==In?br:t==Tn?Ae:Gt;return e(n)}function qn(n){if(!n)return n===0?n:0;if(n=yn(n),n===ot||n===-ot){var t=n<0?-1:1;return t*Bo}return n===n?n:0}function S(n){var t=qn(n),e=t%1;return t===t?e?t-e:t:0}function kf(n){return n?ht(S(n),0,Pn):0}function yn(n){if(typeof n==\"number\")return n;if(cn(n))return le;if(H(n)){var t=typeof n.valueOf==\"function\"?n.valueOf():n;n=H(t)?t+\"\":t}if(typeof n!=\"string\")return n===0?n:+n;n=_u(n);var e=cs.test(n);return e||gs.test(n)?zs(n.slice(2),e?2:8):ls.test(n)?le:+n}function jf(n){return Dn(n,fn(n))}function Pg(n){return n?ht(S(n),-Yn,Yn):n===0?n:0}function G(n){return n==null?\"\":ln(n)}var bg=bt(function(n,t){if(ie(t)||un(t)){Dn(t,X(t),n);return}for(var e in t)F.call(t,e)&&kt(n,e,t[e])}),no=bt(function(n,t){Dn(t,fn(t),n)}),er=bt(function(n,t,e,r){Dn(t,fn(t),n,r)}),Dg=bt(function(n,t,e,r){Dn(t,X(t),n,r)}),Wg=$n(Mr);function Bg(n,t){var e=Pt(n);return t==null?e:Pu(e,t)}var Gg=P(function(n,t){n=M(n);var e=-1,r=t.length,i=r>2?t[2]:u;for(i&&tn(t[0],t[1],i)&&(r=1);++e1),o}),Dn(n,ii(n),e),r&&(e=wn(e,Jn|Oi|mt,Jl));for(var i=t.length;i--;)Qr(e,t[i]);return e});function np(n,t){return eo(n,je(T(t)))}var tp=$n(function(n,t){return n==null?{}:El(n,t)});function eo(n,t){if(n==null)return{};var e=$(ii(n),function(r){return[r]});return t=T(t),Zu(n,e,function(r,i){return t(r,i[0])})}function ep(n,t,e){t=et(t,n);var r=-1,i=t.length;for(i||(i=1,n=u);++rt){var r=n;n=t,t=r}if(e||n%1||t%1){var i=Ru();return V(n+i*(t-n+qs(\"1e-\"+((i+\"\").length-1))),t)}return Jr(n,t)}var gp=Dt(function(n,t,e){return t=t.toLowerCase(),n+(e?uo(t):t)});function uo(n){return Ai(G(n).toLowerCase())}function fo(n){return n=G(n),n&&n.replace(ds,ra).replace(Ws,\"\")}function pp(n,t,e){n=G(n),t=ln(t);var r=n.length;e=e===u?r:ht(S(e),0,r);var i=e;return e-=t.length,e>=0&&n.slice(e,i)==t}function dp(n){return n=G(n),n&&Xo.test(n)?n.replace(Gi,ia):n}function _p(n){return n=G(n),n&&ts.test(n)?n.replace(dr,\"\\\\$&\"):n}var vp=Dt(function(n,t,e){return n+(e?\"-\":\"\")+t.toLowerCase()}),mp=Dt(function(n,t,e){return n+(e?\" \":\"\")+t.toLowerCase()}),wp=af(\"toLowerCase\");function Ap(n,t,e){n=G(n),t=S(t);var r=t?Rt(n):0;if(!t||r>=t)return n;var i=(t-r)/2;return qe(Pe(i),e)+n+qe(Oe(i),e)}function xp(n,t,e){n=G(n),t=S(t);var r=t?Rt(n):0;return t&&r>>0,e?(n=G(n),n&&(typeof t==\"string\"||t!=null&&!vi(t))&&(t=ln(t),!t&&Ct(n))?rt(Ln(n),0,e):n.split(t,e)):[]}var Ep=Dt(function(n,t,e){return n+(e?\" \":\"\")+Ai(t)});function Sp(n,t,e){return n=G(n),e=e==null?0:ht(S(e),0,n.length),t=ln(t),n.slice(e,e+t.length)==t}function Op(n,t,e){var r=f.templateSettings;e&&tn(n,t,e)&&(t=u),n=G(n),t=er({},t,r,_f);var i=er({},t.imports,r.imports,_f),o=X(i),s=Pr(i,o),a,c,p=0,d=t.interpolate||de,_=\"__p += '\",w=Dr((t.escape||de).source+\"|\"+d.source+\"|\"+(d===Fi?as:de).source+\"|\"+(t.evaluate||de).source+\"|$\",\"g\"),y=\"//# sourceURL=\"+(F.call(t,\"sourceURL\")?(t.sourceURL+\"\").replace(/\\s/g,\" \"):\"lodash.templateSources[\"+ ++Ns+\"]\")+`\n`;n.replace(w,function(C,b,W,hn,en,gn){return W||(W=hn),_+=n.slice(p,gn).replace(_s,ua),b&&(a=!0,_+=`' +\n__e(`+b+`) +\n'`),en&&(c=!0,_+=`';\n`+en+`;\n__p += '`),W&&(_+=`' +\n((__t = (`+W+`)) == null ? '' : __t) +\n'`),p=gn+C.length,C}),_+=`';\n`;var L=F.call(t,\"variable\")&&t.variable;if(!L)_=`with (obj) {\n`+_+`\n}\n`;else if(os.test(L))throw new R(j);_=(c?_.replace(zo,\"\"):_).replace(Zo,\"$1\").replace(Jo,\"$1;\"),_=\"function(\"+(L||\"obj\")+`) {\n`+(L?\"\":`obj || (obj = {});\n`)+\"var __t, __p = ''\"+(a?\", __e = _.escape\":\"\")+(c?`, __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n`:`;\n`)+_+`return __p\n}`;var O=so(function(){return B(o,y+\"return \"+_).apply(u,s)});if(O.source=_,_i(O))throw O;return O}function Pp(n){return G(n).toLowerCase()}function bp(n){return G(n).toUpperCase()}function Dp(n,t,e){if(n=G(n),n&&(e||t===u))return _u(n);if(!n||!(t=ln(t)))return n;var r=Ln(n),i=Ln(t),o=vu(r,i),s=mu(r,i)+1;return rt(r,o,s).join(\"\")}function Wp(n,t,e){if(n=G(n),n&&(e||t===u))return n.slice(0,Au(n)+1);if(!n||!(t=ln(t)))return n;var r=Ln(n),i=mu(r,Ln(t))+1;return rt(r,0,i).join(\"\")}function Bp(n,t,e){if(n=G(n),n&&(e||t===u))return n.replace(_r,\"\");if(!n||!(t=ln(t)))return n;var r=Ln(n),i=vu(r,Ln(t));return rt(r,i).join(\"\")}function Gp(n,t){var e=So,r=Oo;if(H(t)){var i=\"separator\"in t?t.separator:i;e=\"length\"in t?S(t.length):e,r=\"omission\"in t?ln(t.omission):r}n=G(n);var o=n.length;if(Ct(n)){var s=Ln(n);o=s.length}if(e>=o)return n;var a=e-Rt(r);if(a<1)return r;var c=s?rt(s,0,a).join(\"\"):n.slice(0,a);if(i===u)return c+r;if(s&&(a+=c.length-a),vi(i)){if(n.slice(a).search(i)){var p,d=c;for(i.global||(i=Dr(i.source,G(Mi.exec(i))+\"g\")),i.lastIndex=0;p=i.exec(d);)var _=p.index;c=c.slice(0,_===u?a:_)}}else if(n.indexOf(ln(i),a)!=a){var w=c.lastIndexOf(i);w>-1&&(c=c.slice(0,w))}return c+r}function Fp(n){return n=G(n),n&&Yo.test(n)?n.replace(Bi,ha):n}var Mp=Dt(function(n,t,e){return n+(e?\" \":\"\")+t.toUpperCase()}),Ai=af(\"toUpperCase\");function oo(n,t,e){return n=G(n),t=e?u:t,t===u?oa(n)?da(n):ks(n):n.match(t)||[]}var so=P(function(n,t){try{return sn(n,u,t)}catch(e){return _i(e)?e:new R(e)}}),Np=$n(function(n,t){return _n(t,function(e){e=Wn(e),Nn(n,e,pi(n[e],n))}),n});function Up(n){var t=n==null?0:n.length,e=T();return n=t?$(n,function(r){if(typeof r[1]!=\"function\")throw new vn(I);return[e(r[0]),r[1]]}):[],P(function(r){for(var i=-1;++iYn)return[];var e=Pn,r=V(n,Pn);t=T(t),n-=Pn;for(var i=Or(r,t);++e0||t<0)?new D(e):(n<0?e=e.takeRight(-n):n&&(e=e.drop(n)),t!==u&&(t=S(t),e=t<0?e.dropRight(-t):e.take(t-n)),e)},D.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},D.prototype.toArray=function(){return this.take(Pn)},bn(D.prototype,function(n,t){var e=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),i=f[r?\"take\"+(t==\"last\"?\"Right\":\"\"):t],o=r||/^find/.test(t);i&&(f.prototype[t]=function(){var s=this.__wrapped__,a=r?[1]:arguments,c=s instanceof D,p=a[0],d=c||E(s),_=function(b){var W=i.apply(f,Qn([b],a));return r&&w?W[0]:W};d&&e&&typeof p==\"function\"&&p.length!=1&&(c=d=!1);var w=this.__chain__,y=!!this.__actions__.length,L=o&&!w,O=c&&!y;if(!o&&d){s=O?s:new D(this);var C=n.apply(s,a);return C.__actions__.push({func:Xe,args:[_],thisArg:u}),new mn(C,w)}return L&&O?n.apply(this,a):(C=this.thru(_),L?r?C.value()[0]:C.value():C)})}),_n([\"pop\",\"push\",\"shift\",\"sort\",\"splice\",\"unshift\"],function(n){var t=xe[n],e=/^(?:push|sort|unshift)$/.test(n)?\"tap\":\"thru\",r=/^(?:pop|shift)$/.test(n);f.prototype[n]=function(){var i=arguments;if(r&&!this.__chain__){var o=this.value();return t.apply(E(o)?o:[],i)}return this[e](function(s){return t.apply(E(s)?s:[],i)})}}),bn(D.prototype,function(n,t){var e=f[t];if(e){var r=e.name+\"\";F.call(Ot,r)||(Ot[r]=[]),Ot[r].push({name:t,func:e})}}),Ot[He(u,ft).name]=[{name:\"wrapper\",func:u}],D.prototype.clone=Fa,D.prototype.reverse=Ma,D.prototype.value=Na,f.prototype.at=ph,f.prototype.chain=dh,f.prototype.commit=_h,f.prototype.next=vh,f.prototype.plant=wh,f.prototype.reverse=Ah,f.prototype.toJSON=f.prototype.valueOf=f.prototype.value=xh,f.prototype.first=f.prototype.head,Jt&&(f.prototype[Jt]=mh),f},kn=_a();typeof define==\"function\"&&typeof define.amd==\"object\"&&define.amd?(Y._=kn,define(function(){return kn})):st?((st.exports=kn)._=kn,yr._=kn):Y._=kn}).call(Ft)});var jd={};Od(jd,{albIpMonitor:()=>Ao,albTargetRecordMonitor:()=>Ro});module.exports=Pd(jd);var go=ho(require(\"dns\")),po=require(\"@aws-sdk/lib-dynamodb\"),_o=require(\"@aws-sdk/client-dynamodb\"),vo=require(\"@aws-sdk/client-elastic-load-balancing-v2\"),Ri=process.env.LOOKUP_TABLE??\"\",mo=po.DynamoDBDocument.from(new _o.DynamoDB),wo=new vo.ElasticLoadBalancingV2({logger:console}),bd=async u=>{console.log(`Scanning route lookup table ${Ri}`);let v={TableName:u},x=[],m;do m=await mo.scan(v),m.Items?.forEach(I=>x.push(I)),v.ExclusiveStartKey=m.LastEvaluatedKey;while(typeof m.LastEvaluatedKey<\"u\");return x},Dd=async(u,v,x)=>{let m=v.map(j=>({Id:j,Port:x,AvailabilityZone:\"all\"})),I={TargetGroupArn:u,Targets:m};return wo.registerTargets(I)},Wd=async(u,v)=>{console.log(`Deregistering IP addresses ${JSON.stringify(v)} from target group ${u}`);let x=v.map(I=>({Id:I})),m={TargetGroupArn:u,Targets:x};return wo.deregisterTargets(m)},Bd=async u=>new Promise((v,x)=>{go.lookup(u,{all:!0,family:4},(m,I)=>{m?x(m):v(I.map(j=>j.address).sort())})}),Gd=(u,v)=>{let x=u.indexOf(v);return x>-1&&u.splice(x,1),u},Fd=async u=>{let v={TableName:Ri,Item:u};return mo.put(v)},Ao=async(u,v)=>{let x=await bd(Ri)??[];for(let m of x)try{m.dnsLookupIps=[];try{m.dnsLookupIps=await Bd(m.targetAlbDnsName)}catch(I){console.log(I)}m.ipAddList=m.dnsLookupIps?.filter(I=>!m.metadata?.targetGroupIpAddresses?.includes(I))??[],m.ipRemoveList=m.metadata?.targetGroupIpAddresses?.filter(I=>!m.dnsLookupIps?.includes(I))??[],m.ipAddList?.length>0?(console.log(`Registering new ips ${JSON.stringify(m.ipAddList)} to target ${m.metadata.targetGroupArn} with port ${m.targetGroupDestinationPort}`),await Dd(m.metadata.targetGroupArn,m.ipAddList,m.targetGroupDestinationPort),m.metadata.targetGroupIpAddresses.push(...m.ipAddList)):console.log(\"No new Ip addresses to register\"),m.ipRemoveList?.length>0?(console.log(`Deregistering old ip addresses ${JSON.stringify(m.ipRemoveList)} from target group targetGroupRecord.metadata.targetGroupArn`),await Wd(m.metadata.targetGroupArn,m.ipRemoveList),m.ipRemoveList?.forEach(I=>{console.log(m.metadata.targetGroupIpAddresses,I),m.metadata.targetGroupIpAddresses=Gd(m.metadata.targetGroupIpAddresses,I)})):console.log(\"No old ip addresses to deregister\"),delete m.ipAddList,delete m.ipRemoveList,delete m.dnsLookupIps,console.log(\"Writing record to DDB table \",JSON.stringify(m,null,4)),await Fd(m)}catch(I){console.log(\"There was a problem updating the record \",JSON.stringify(m,null,4)),console.log(I)}return\"Done\"};var yo=require(\"@aws-sdk/lib-dynamodb\"),Io=require(\"@aws-sdk/client-dynamodb\"),Ei=require(\"@aws-sdk/util-dynamodb\"),ir=require(\"@aws-sdk/client-elastic-load-balancing-v2\"),vt=ho(xo()),Zn=new ir.ElasticLoadBalancingV2,Md=yo.DynamoDBDocument.from(new Io.DynamoDB),Nd=process.env.LOOKUP_TABLE||\"\",Ud=u=>new Promise(v=>{setTimeout(v,u)}),$d=async(u,v,x,m)=>{let I={Name:u,Port:v,Protocol:m,VpcId:x,TargetType:ir.TargetTypeEnum.IP};return Zn.createTargetGroup(I)},Hd=async u=>{let v={Attributes:[{Key:\"stickiness.enabled\",Value:\"true\"}],TargetGroupArn:u};return Zn.modifyTargetGroupAttributes(v)},To=async(u,v)=>{let x={ListenerArn:v};return((await Zn.describeRules(x)).Rules?.filter(j=>j.Priority===u.toString())||[]).length===0},Lo=async u=>{try{let v={ListenerArns:[u]};return await Zn.describeListeners(v),Promise.resolve(!0)}catch(v){return console.log(v),Promise.resolve(!1)}},Kd=async(u,v,x,m,I)=>{console.log(\"trying to create listener rule\"),console.log(x,v,u,m,I);let j={Actions:[{TargetGroupArn:m,Type:\"forward\"}],ListenerArn:u,Priority:I,Conditions:[]};if(v?.length>0){let ut={Field:\"path-pattern\",Values:v};j.Conditions?.push(ut)}if(x?.length>0){let ut={Field:\"host-header\",Values:x};j.Conditions?.push(ut)}return Zn.createRule(j)},qd=async(u,v,x,m)=>{let I={Actions:[{TargetGroupArn:m,Type:\"forward\"}],RuleArn:u,Conditions:[]};if(v?.length>0){let j={Field:\"path-pattern\",Values:v};I?.Conditions?.push(j)}if(x?.length>0){let j={Field:\"host-header\",Values:x};I?.Conditions?.push(j)}return Zn.modifyRule(I)},zd=async u=>{let v={RuleArn:u};return Zn.deleteRule(v)},Zd=async u=>{let v={TargetGroupArn:u};return Zn.deleteTargetGroup(v)},Jd=async(u,v)=>{let x={RulePriorities:[{Priority:v,RuleArn:u}]};return Zn.setRulePriorities(x)},Yd=async(u,v)=>{let x={TableName:u,Item:v};return Md.put(x)},Xd=(u,v)=>{let x={vpcId:u.vpcId,destinationPort:u.targetGroupDestinationPort,protocol:u.targetGroupProtocol},m={vpcId:v.vpcId,destinationPort:v.targetGroupDestinationPort,protocol:v.targetGroupProtocol};return!vt.isEqual(x,m)},Qd=(u,v)=>{let x={sourceListenerArn:u.rule.sourceListenerArn,priority:u.rule.condition.priority,paths:u.rule.condition.paths?.sort(),hosts:u.rule.condition.hosts?.sort()},m={sourceListenerArn:v.rule.sourceListenerArn,priority:v.rule.condition.priority,paths:v.rule.condition.paths?.sort(),hosts:v.rule.condition.hosts?.sort()};return!vt.isEqual(x,m)},Vd=(u,v)=>{let x=u.rule.condition.priority,m=v.rule.condition.priority;return x!==m},Si=async u=>{console.log(\"Record creation detected.\");try{if(!await Lo(u.rule.sourceListenerArn))throw new Error(`The ALB Listener ARN: ${u.rule.sourceListenerArn} does not exist. Exiting`);if(console.log(\"Checking if priority is valid\"),!await To(u.rule.condition.priority,u.rule.sourceListenerArn))throw new Error(`The priority ${u.rule.condition.priority.toString()} matches an existing rule priority on the listener arn ${u.rule.sourceListenerArn}. Priorities must not match. Exiting`);let x=(await $d(u.id,u.targetGroupDestinationPort,u.vpcId,u.targetGroupProtocol))?.TargetGroups?.[0].TargetGroupArn??\"\";await Hd(x);let I=(await Kd(u.rule.sourceListenerArn,u.rule.condition.paths,u.rule.condition.hosts,x,u.rule.condition.priority))?.Rules?.[0].RuleArn??\"\";if(!x||!I)throw new Error(`There was an error getting the target group arn or listener rule arn. \nTarget Group Arn: ${x}\nRule Arn: ${I}`);return u.metadata={targetGroupArn:x,ruleArn:I,targetGroupIpAddresses:[]},await Yd(Nd,u),console.log(\"Added metadata to table\"),u}catch(v){throw console.log(\"There was a problem creating resources for the following record\",JSON.stringify(u,null,4)),v}},Co=async u=>{try{console.log(`Deleting listener rule and target group for ${u.id}`),await zd(u.metadata.ruleArn),console.log(\"Deleted listener rule.\")}catch(v){console.log(v),console.log(\"Could not delete listener rule for record. Continuing...\",JSON.stringify(u,null,4))}try{await Zd(u.metadata.targetGroupArn),console.log(\"Deleted target group\");return}catch(v){console.log(\"Could not delete target group for record\",JSON.stringify(u,null,4)),console.log(v)}},kd=async(u,v)=>{try{if(console.log(`The record with id ${u.id} was updated. Performing comparison.`),!await Lo(u.rule.sourceListenerArn))throw new Error(`The ALB Listener ARN: ${u.rule.sourceListenerArn} does not exist. Exiting`);let x=vt.cloneDeep(u),m=vt.cloneDeep(v);if(delete x.metadata,delete m.metadata,vt.isEqual(x,m)){console.log(`Update Record handler found no changes made for record with Id ${u.id}`);return}if(!v.metadata){console.log(\"No previous metadata detected for record. Creating metadata based off of new entry\"),await Si(u);return}if(Qd(v,u)&&(console.log(`Detected a listener rule change. Modifying rule ${u.metadata.ruleArn}`),await qd(u.metadata.ruleArn,u.rule.condition.paths,u.rule.condition.hosts,u.metadata.targetGroupArn)),Vd(v,u)){if(!await To(u.rule.condition.priority,u.rule.sourceListenerArn))throw new Error(`The priority ${u.rule.condition.priority.toString()} matches an existing rule priority on the listener arn ${u.rule.sourceListenerArn}. Priorities must not match.`);await Jd(u.metadata.ruleArn,u.rule.condition.priority)}Xd(v,u)&&(console.log(`Detected a target group change. deleting target group ${u.metadata.targetGroupArn} and creating a new target group`),await Co(u),await Ud(1e4),await Si(u))}catch(x){throw console.log(\"There was a problem updating a target group or listener rule for the records:\"),console.log(\"Old Record: \",JSON.stringify(v,null,4)),console.log(\"New Record: \",JSON.stringify(u,null,4)),x}},Ro=async(u,v)=>{console.log(JSON.stringify(u,null,2));let x=u.Records.map(m=>(m.dynamodb.OldImage&&(m.dynamodb.OldImage=(0,Ei.unmarshall)(m.dynamodb.OldImage)),m.dynamodb.NewImage&&(m.dynamodb.NewImage=(0,Ei.unmarshall)(m.dynamodb.NewImage)),m));for(let m of x)m.eventName===\"INSERT\"&&await Si(m.dynamodb.NewImage),m.eventName===\"MODIFY\"&&await kd(m.dynamodb.NewImage,m.dynamodb.OldImage),m.eventName===\"REMOVE\"&&await Co(m.dynamodb.OldImage)};0&&(module.exports={albIpMonitor,albTargetRecordMonitor});\n/*! Bundled license information:\n\nlodash/lodash.js:\n (**\n * @license\n * Lodash \n * Copyright OpenJS Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n *)\n*/\n" + }, + "Environment": { + "Variables": { + "LOOKUP_TABLE": { + "Ref": "AlbIpForwardingddbDNSFirewallTableDE7BAC6C" + } + } + }, + "Handler": "index.albTargetRecordMonitor", + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "AlbIpForwardingddbDnsRecordMonitorServiceRoleBDC0C08F", + "Arn" + ] + }, + "Runtime": "nodejs18.x", + "Timeout": 60 + }, + "DependsOn": [ + "AlbIpForwardingddbDnsRecordMonitorServiceRoleDefaultPolicyBB5ECA75", + "AlbIpForwardingddbDnsRecordMonitorServiceRoleBDC0C08F" + ], + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/ddbDnsRecordMonitor/Resource", + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole" + }, + { + "id": "W89", + "reason": "This function supports infrastructure deployment and is not deployed inside a VPC." + }, + { + "id": "W92", + "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions." + } + ] + } + } + }, + "AlbIpForwardingddbDnsRecordMonitorDynamoDBEventSourceAlbIpForwardingStackAlbIpForwardingddbDNSFirewallTable6FC8CBEEB4643A78": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 100, + "EventSourceArn": { + "Fn::GetAtt": [ + "AlbIpForwardingddbDNSFirewallTableDE7BAC6C", + "StreamArn" + ] + }, + "FunctionName": { + "Ref": "AlbIpForwardingddbDnsRecordMonitor551C6C2F" + }, + "MaximumRetryAttempts": 0, + "StartingPosition": "TRIM_HORIZON" + }, + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/ddbDnsRecordMonitor/DynamoDBEventSource:AlbIpForwardingStackAlbIpForwardingddbDNSFirewallTable6FC8CBEE/Resource" + } + }, + "AlbIpForwardingddbDnsRecordMonitorPolicyF716B7E4": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:DeleteRule", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:SetRulePriorities" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AlbIpForwardingddbDnsRecordMonitorPolicyF716B7E4", + "Roles": [ + { + "Ref": "AlbIpForwardingddbDnsRecordMonitorServiceRoleBDC0C08F" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda need to be able to work with any ELB in the account" + } + ] + } + } + }, + "AlbIpForwardingcwruleBF5444E5": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "AlbIpForwardingdnsFWLambdaCDFE4DA7", + "Arn" + ] + }, + "Id": "Target0" + } + ] + }, + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/cwrule/Resource" + } + }, + "AlbIpForwardingcwruleAllowEventRuleAlbIpForwardingStackAlbIpForwardingdnsFWLambda3E8E784BCF0ACCC6": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AlbIpForwardingdnsFWLambdaCDFE4DA7", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "AlbIpForwardingcwruleBF5444E5", + "Arn" + ] + } + }, + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/AlbIpForwarding/cwrule/AllowEventRuleAlbIpForwardingStackAlbIpForwardingdnsFWLambda3E8E784B" + } + }, + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/22OzWrDMBCEnyV3eev4kgco7SUUjJN7WEvbsrF+gn4SjNC7F8khp5zmY2aZnQH2wwH6HT5CJ9XSaZ4hnyLKRXz+2hE9GorkBT7CJS8mQD7SWqMjrUWo1aJxaoZ8xllT9RsUodHMCiF/JysjO1ujF3/dycaTS17SD95ubP9q/N4dyRsOgZ0tgtFAntz2qOnoNMu2Z6MiqLYEyFN6niVNpYiJQmtu1pOLsE4RXMPHfehh30O/uwbmzicb2RBMm/4DO1vN1iMBAAA=" + }, + "Metadata": { + "aws:cdk:path": "AlbIpForwardingStack/CDKMetadata/Default" + } + } + } +} \ No newline at end of file diff --git a/config/global-config.yaml b/config/global-config.yaml index 6e01a39..759f917 100644 --- a/config/global-config.yaml +++ b/config/global-config.yaml @@ -1,7 +1,9 @@ homeRegion: &HOME_REGION ca-central-1 -configVersion: 1.6.1-a +configVersion: 1.7.0-a enabledRegions: - *HOME_REGION + # It is recommended to enable additional regions once the initial installation is complete in the home region. + # See the post-deployment documentation for more information. # - "ap-northeast-1" # - "ap-northeast-2" # - "ap-northeast-3" @@ -28,23 +30,33 @@ cdkOptions: forceBootstrap: true snsTopics: deploymentTargets: - organizationalUnits: - - Root + accounts: + - Management + - Audit topics: - name: SecurityHigh emailAddresses: - - @example.com # <----- UPDATE EMAIL ADDRESS + - "{{ SecurityHigh }}" - name: SecurityMedium emailAddresses: - - @example.com # <----- UPDATE EMAIL ADDRESS + - "{{ SecurityMedium }}" - name: SecurityLow emailAddresses: - - @example.com # <----- UPDATE EMAIL ADDRESS + - "{{ SecurityLow }}" - name: SecurityIgnore emailAddresses: - - @example.com # <----- UPDATE EMAIL ADDRESS + - "{{ SecurityIgnore }}" controlTower: enable: false # UPDATE if using Control Tower, set to true + # UPDATE If using ControlTower, uncomment the following block and set the version to ControlTower latest available version + # landingZone: + # version: '3.3' + # logging: + # loggingBucketRetentionDays: 365 + # accessLoggingBucketRetentionDays: 3650 + # organizationTrail: true + # security: + # enableIdentityCenterAccess: true logging: account: LogArchive cloudtrail: @@ -62,7 +74,7 @@ logging: accountTrails: - name: AccountTrail regions: - - *HOME_REGION + - "{{ AcceleratorHomeRegion }}" deploymentTargets: accounts: [] organizationalUnits: [] @@ -85,9 +97,66 @@ logging: noncurrentVersionExpiration: 730 attachPolicyToIamRoles: - EC2-Default-SSM-AD-Role - - AWSAccelerator-RDGW-Role + - "{{ AcceleratorPrefix }}-RDGW-Role" + excludeRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 + excludeAccounts: + - Management + - LogArchive + - Audit cloudwatchLogs: dynamicPartitioning: dynamic-partitioning/log-filters.json + exclusions: + - accounts: + - Management + logGroupNames: + - aws-accelerator-cloudtrail-logs + encryption: + useCMK: true + deploymentTargets: + organizationalUnits: + - Security + - Infrastructure + - Central + - Dev + - Test + - Prod + - UnClass + - Sandbox + accounts: + - Management + excludedRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 accessLogBucket: lifecycleRules: - enabled: true @@ -106,6 +175,70 @@ logging: abortIncompleteMultipartUpload: 7 expiration: 730 noncurrentVersionExpiration: 730 +s3: + encryption: + createCMK: true + deploymentTargets: + organizationalUnits: + - Security + - Infrastructure + - Central + - Dev + - Test + - Prod + - UnClass + - Sandbox + accounts: + - Management + excludedRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 +lambda: + encryption: + useCMK: true + deploymentTargets: + organizationalUnits: + - Security + - Infrastructure + - Central + - Dev + - Test + - Prod + - UnClass + - Sandbox + accounts: + - Management + excludedRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 ssmInventory: enable: true deploymentTargets: @@ -162,25 +295,25 @@ reports: threshold: 100 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 90 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 75 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 50 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - deploymentTargets: accounts: - Perimeter @@ -206,31 +339,31 @@ reports: threshold: 100 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 90 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 80 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 75 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 50 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - deploymentTargets: accounts: - Management @@ -256,31 +389,31 @@ reports: threshold: 100 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 90 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 80 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 75 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 50 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - deploymentTargets: organizationalUnits: - Security @@ -313,31 +446,31 @@ reports: threshold: 100 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 90 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 80 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 75 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" - type: ACTUAL thresholdType: PERCENTAGE threshold: 50 comparisonOperator: GREATER_THAN subscriptionType: EMAIL - address: @example.com # <----- UPDATE EMAIL ADDRESS + address: "{{ BudgetEmail }}" limits: - serviceCode: vpc quotaCode: L-29B6F2EB diff --git a/config/iam-config.yaml b/config/iam-config.yaml index 32855ce..336ee4f 100644 --- a/config/iam-config.yaml +++ b/config/iam-config.yaml @@ -1,4 +1,4 @@ -homeRegion: &HOME_REGION ca-central-1 +homeRegion: {{ AcceleratorHomeRegion }} ###### # Version 1.6.1-a of the configuration introduced this change. If you are upgrading from a previous version, # review the Identity Center section of the FAQ in this repository before applying this change. @@ -11,13 +11,13 @@ policySets: organizationalUnits: - Root policies: - - name: AWSAccelerator-Default-Boundary-Policy + - name: "{{ AcceleratorPrefix }}-Default-Boundary-Policy" policy: iam-policies/boundary-policy.json - deploymentTargets: accounts: - Management policies: - - name: AWSAccelerator-IAM-User-Boundary-Policy + - name: "{{ AcceleratorPrefix }}-IAM-User-Boundary-Policy" policy: iam-policies/iam-user-boundary-policy.json roleSets: - deploymentTargets: @@ -34,8 +34,8 @@ roleSets: - AmazonSSMManagedInstanceCore - AmazonSSMDirectoryServiceAccess - CloudWatchAgentServerPolicy - boundaryPolicy: AWSAccelerator-Default-Boundary-Policy - - name: AWSAccelerator-RDGW-Role + boundaryPolicy: "{{ AcceleratorPrefix }}-Default-Boundary-Policy" + - name: "{{ AcceleratorPrefix }}-RDGW-Role" instanceProfile: true assumedBy: - type: service @@ -45,8 +45,8 @@ roleSets: - AmazonSSMManagedInstanceCore - AmazonSSMDirectoryServiceAccess - CloudWatchAgentServerPolicy - boundaryPolicy: AWSAccelerator-Default-Boundary-Policy - - name: AWSAccelerator-Rsyslog-Role + boundaryPolicy: "{{ AcceleratorPrefix }}-Default-Boundary-Policy" + - name: "{{ AcceleratorPrefix }}-Rsyslog-Role" instanceProfile: true assumedBy: - type: service @@ -56,7 +56,7 @@ roleSets: - AmazonSSMManagedInstanceCore - CloudWatchAgentServerPolicy - AmazonS3ReadOnlyAccess - boundaryPolicy: AWSAccelerator-Default-Boundary-Policy + boundaryPolicy: "{{ AcceleratorPrefix }}-Default-Boundary-Policy" groupSets: - deploymentTargets: accounts: @@ -77,15 +77,15 @@ userSets: users: - username: breakGlassUser01 group: BreakGlassAdmins - boundaryPolicy: AWSAccelerator-IAM-User-Boundary-Policy + boundaryPolicy: "{{ AcceleratorPrefix }}-IAM-User-Boundary-Policy" - username: breakGlassUser02 group: BreakGlassAdmins - boundaryPolicy: AWSAccelerator-IAM-User-Boundary-Policy + boundaryPolicy: "{{ AcceleratorPrefix }}-IAM-User-Boundary-Policy" managedActiveDirectories: - - name: AWSAcceleratorManagedActiveDirectory + - name: "{{ AcceleratorPrefix }}ManagedActiveDirectory" type: AWS Managed Microsoft AD account: Operations - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} dnsName: {{ MadDnsName }} netBiosDomainName: {{ MadNetbiosDomainName }} description: This directory is a) shared to most accounts in the organization to provide centralized Windows and Linux authentication for cloud workloads, b) used as an identity source for AWS SSO, c) used to inter-connect with on-premises directory services, and d) provides a single identities source for instance and AWS console access. @@ -95,10 +95,10 @@ managedActiveDirectories: subnets: - Central-App-A - Central-App-B - resolverRuleName: AWSAccelerator-Endpoint-mad-example-local + resolverRuleName: "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" secretConfig: account: Operations - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} adminSecretName: my-admin-001 sharedOrganizationalUnits: organizationalUnits: @@ -117,7 +117,7 @@ managedActiveDirectories: imagePath: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base securityGroupInboundSources: - 10.1.0.0/16 - instanceRole: AWSAccelerator-RDGW-Role + instanceRole: "{{ AcceleratorPrefix }}-RDGW-Role" userDataScripts: - scriptName: JoinDomain scriptFilePath: ad-config-scripts/Join-Domain.ps1 @@ -155,11 +155,11 @@ managedActiveDirectories: lockoutAttemptsReset: 30 adUsers: - name: adconnector-user - email: example-adconnector-user@example.com # <----- UPDATE EMAIL ADDRESS + email: {{ ActiveDirectoryConnectorEmail }} groups: - ADConnector-grp - name: User1 - email: example-user1@example.com # <----- UPDATE EMAIL ADDRESS + email: {{ ActiveDirectoryUserEmail }} groups: - aws-Provisioning - "*-View" @@ -167,6 +167,6 @@ managedActiveDirectories: - "*-PowerUser" - AWS Delegated Administrators - name: User2 - email: example-user2@example.com # <----- UPDATE EMAIL ADDRESS + email: {{ ActiveDirectoryReadonlyUserEmail }} groups: - "*-View" diff --git a/config/network-config.yaml b/config/network-config.yaml index 66e203a..3fbfeb6 100644 --- a/config/network-config.yaml +++ b/config/network-config.yaml @@ -1,11 +1,10 @@ -homeRegion: &HOME_REGION ca-central-1 defaultVpc: delete: true excludeAccounts: [] transitGateways: - name: Network-Main account: Network - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} shareTargets: organizationalUnits: - Infrastructure @@ -42,14 +41,14 @@ centralNetworkServices: delegatedAdminAccount: Network ipams: - name: accelerator-ipam - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} description: Accelerator IPAM operatingRegions: - - *HOME_REGION + - {{ AcceleratorHomeRegion }} pools: - name: home-region-sandbox-pool description: Pool for sandbox environment - locale: *HOME_REGION + locale: {{ AcceleratorHomeRegion }} provisionedCidrs: - 10.0.0.0/8 shareTargets: @@ -57,9 +56,9 @@ centralNetworkServices: - Sandbox networkFirewall: firewalls: - - name: AWSAccelerator-firewall - region: *HOME_REGION - firewallPolicy: AWSAccelerator-policy + - name: "{{ AcceleratorPrefix }}-firewall" + region: {{ AcceleratorHomeRegion }} + firewallPolicy: "{{ AcceleratorPrefix }}-policy" subnets: - Perimeter-A - Perimeter-B @@ -70,22 +69,22 @@ centralNetworkServices: - destination: cloud-watch-logs type: FLOW policies: - - name: AWSAccelerator-policy + - name: "{{ AcceleratorPrefix }}-policy" regions: - - *HOME_REGION + - {{ AcceleratorHomeRegion }} firewallPolicy: statelessDefaultActions: ["aws:forward_to_sfe"] statelessFragmentDefaultActions: ["aws:forward_to_sfe"] statefulRuleGroups: - - name: AWSAccelerator-rule-group - - name: AWSAccelerator-domain-list-group + - name: "{{ AcceleratorPrefix }}-rule-group" + - name: "{{ AcceleratorPrefix }}-domain-list-group" shareTargets: accounts: - Perimeter rules: - - name: AWSAccelerator-rule-group + - name: "{{ AcceleratorPrefix }}-rule-group" regions: - - *HOME_REGION + - {{ AcceleratorHomeRegion }} capacity: 100 type: STATEFUL ruleGroup: @@ -114,9 +113,9 @@ centralNetworkServices: ruleOptions: - keyword: sid settings: ["200"] - - name: AWSAccelerator-domain-list-group + - name: "{{ AcceleratorPrefix }}-domain-list-group" regions: - - *HOME_REGION + - {{ AcceleratorHomeRegion }} capacity: 10 type: STATEFUL ruleGroup: @@ -150,7 +149,7 @@ centralNetworkServices: - Endpoint-A - Endpoint-B rules: - - name: AWSAccelerator-Endpoint-mad-example-local + - name: "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" domainName: {{ MadDnsName }} targetIps: - ip: 1.1.1.1 @@ -170,7 +169,7 @@ centralNetworkServices: firewallRuleGroups: - name: accelerator-block-group regions: - - *HOME_REGION + - {{ AcceleratorHomeRegion }} rules: - name: managed-rule action: BLOCK @@ -193,6 +192,28 @@ prefixLists: maxEntries: 1 entries: - 10.1.0.1/32 +################################################################################### +# Self signed certificates are deployed as an example. # +# Replace by your own certificate or request them with Amazon Certificate Manager # +################################################################################### +certificates: + - name: PerimSelf-SignedCert + type: import + privKey: certs/example1-cert.key + cert: certs/example1-cert.crt + deploymentTargets: + accounts: + - Perimeter + - name: DevSelf-SignedCert + type: import + privKey: certs/example1-cert.key + cert: certs/example1-cert.crt + deploymentTargets: + organizationalUnits: + - Dev + accounts: + - Network +################################################################################### endpointPolicies: - name: Default document: vpc-endpoint-policies/default.json @@ -204,7 +225,7 @@ vpcs: - key: Name value: Endpoint account: Network - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} cidrs: - {{ VpcEndpointCidr }} defaultSecurityGroupRulesDeletion: true @@ -331,13 +352,13 @@ vpcs: # - service: transfer.server # - service: workspaces resolverRules: - - AWSAccelerator-Endpoint-mad-example-local + - "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" - name: Perimeter tags: - key: Name value: Perimeter account: Perimeter - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} cidrs: - {{ VpcPerimeterCidr }} defaultSecurityGroupRulesDeletion: true @@ -348,34 +369,34 @@ vpcs: - name: NfwRoute destination: 0.0.0.0/0 type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: a - name: NfwRouteToNatA destination: 10.7.7.176/28 type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: a - name: NfwRouteToNatB destination: 10.7.7.192/28 type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: b - name: Perimeter-Tgw-B routes: - name: NfwRoute destination: 0.0.0.0/0 type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: b - name: NfwRouteToNatA destination: 10.7.7.176/28 type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: a - name: NfwRouteToNatB destination: 10.7.7.192/28 type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: b - name: Perimeter-A routes: @@ -414,7 +435,7 @@ vpcs: - name: NfwNatRoute destination: {{ AwsRangeCidr }} type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: a - name: IgwRoute destination: 0.0.0.0/0 @@ -425,7 +446,7 @@ vpcs: - name: NfwNatRoute destination: {{ AwsRangeCidr }} type: networkFirewall - target: AWSAccelerator-firewall + target: "{{ AcceleratorPrefix }}-firewall" targetAvailabilityZone: b - name: IgwRoute destination: 0.0.0.0/0 @@ -461,6 +482,35 @@ vpcs: subnet: PerimeterNat-A - name: Nat-Perimeter-B subnet: PerimeterNat-B + securityGroups: + - name: "Public-Prod-ALB" + description: "Perimeter ALB Security Group for prod" + inboundRules: + - description: "HTTPS Traffic Inbound" + types: + - HTTPS + sources: + - 0.0.0.0/0 + outboundRules: + - description: "All Outbound" + types: + - ALL + sources: + - 0.0.0.0/0 + - name: "Public-DevTest-ALB" + description: "Perimeter ALB Security Group for dev-test" + inboundRules: + - description: "HTTPS Traffic Inbound" + types: + - HTTPS + sources: + - 0.0.0.0/0 + outboundRules: + - description: "All Outbound" + types: + - ALL + sources: + - 0.0.0.0/0 transitGatewayAttachments: - name: Perimeter transitGateway: @@ -480,12 +530,59 @@ vpcs: - service: s3 - service: dynamodb useCentralEndpoints: true + loadBalancers: + applicationLoadBalancers: + - name: Public-Prod + scheme: internet-facing + subnets: + - PerimeterNat-A + - PerimeterNat-B + securityGroups: + - Public-Prod-ALB + listeners: + - name: alb-listener + port: 443 + protocol: HTTPS + targetGroup: Public-Prod-tg + type: forward + certificate: PerimSelf-SignedCert + sslPolicy: ELBSecurityPolicy-FS-1-2-Res-2019-08 + - name: Public-DevTest + scheme: internet-facing + subnets: + - PerimeterNat-A + - PerimeterNat-B + securityGroups: + - Public-DevTest-ALB + listeners: + - name: alb-listener + port: 443 + protocol: HTTPS + targetGroup: Public-DevTest-tg + type: forward + certificate: PerimSelf-SignedCert + sslPolicy: ELBSecurityPolicy-FS-1-2-Res-2019-08 + targetGroups: + - name: Public-Prod-tg + port: 443 + protocol: HTTPS + type: instance + connectionTermination: true + preserveClientIp: true + proxyProtocolV2: true + - name: Public-DevTest-tg + port: 443 + protocol: HTTPS + type: instance + connectionTermination: true + preserveClientIp: true + proxyProtocolV2: true - name: Dev tags: - key: Name value: Dev account: Network - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} cidrs: - {{ VpcDevCidr }} defaultSecurityGroupRulesDeletion: true @@ -794,13 +891,49 @@ vpcs: - service: dynamodb useCentralEndpoints: true resolverRules: - - AWSAccelerator-Endpoint-mad-example-local + - "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" + ####################################################################### + # Example config to deploy backend ALB to multiple workload accounts # + ####################################################################### + # loadBalancers: + # applicationLoadBalancers: + # - name: core-dev-alb + # scheme: internal + # subnets: + # - Dev-Web-A + # - Dev-Web-B + # securityGroups: + # - Web + # listeners: + # - name: appA-listener-1 + # port: 443 + # protocol: HTTPS + # order: 1 + # type: forward + # targetGroup: core-dev-tg + # certificate: DevSelf-SignedCert + # sslPolicy: ELBSecurityPolicy-FS-1-2-Res-2019-08 + # shareTargets: + # organizationalUnits: + # - Dev + # targetGroups: + # - name: core-dev-tg + # port: 443 + # protocol: HTTPS + # type: instance + # connectionTermination: true + # preserveClientIp: true + # proxyProtocolV2: true + # shareTargets: + # organizationalUnits: + # - Dev + ########################################### - name: Central tags: - key: Name value: Central account: Network - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} cidrs: - {{ VpcCentralCidr }} defaultSecurityGroupRulesDeletion: true @@ -1060,29 +1193,6 @@ vpcs: availabilityZone: b routeTable: Central ipv4CidrBlock: 10.1.208.0/21 - - ## App2 subnets are shared only with Operations account. These are used - ## to deploy Managed AD instances, or other centrally managed, routable, resources. - - name: Central-App2-A - tags: - - key: Name - value: Central-App2-A - availabilityZone: a - routeTable: Central - ipv4CidrBlock: 10.1.0.128/25 - shareTargets: - accounts: - - Operations - - name: Central-App2-B - tags: - - key: Name - value: Central-App2-B - availabilityZone: b - routeTable: Central - ipv4CidrBlock: 10.1.1.0/25 - shareTargets: - accounts: - - Operations transitGatewayAttachments: - name: Central-Main transitGateway: @@ -1104,13 +1214,13 @@ vpcs: - service: dynamodb useCentralEndpoints: true resolverRules: - - AWSAccelerator-Endpoint-mad-example-local + - "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" - name: Test tags: - key: Name value: Test account: Network - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} cidrs: - {{ VpcTestCidr }} defaultSecurityGroupRulesDeletion: true @@ -1417,13 +1527,13 @@ vpcs: - service: dynamodb useCentralEndpoints: true resolverRules: - - AWSAccelerator-Endpoint-mad-example-local + - "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" - name: Prod tags: - key: Name value: Prod account: Network - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} cidrs: - {{ VpcProdCidr }} defaultSecurityGroupRulesDeletion: true @@ -1730,10 +1840,10 @@ vpcs: - service: dynamodb useCentralEndpoints: true resolverRules: - - AWSAccelerator-Endpoint-mad-example-local + - "{{ AcceleratorPrefix }}-Endpoint-mad-example-local" vpcTemplates: - name: Sandbox-Template - region: *HOME_REGION + region: {{ AcceleratorHomeRegion }} deploymentTargets: organizationalUnits: - Sandbox diff --git a/config/organization-config.yaml b/config/organization-config.yaml index 0ffe8a0..a3a78f2 100644 --- a/config/organization-config.yaml +++ b/config/organization-config.yaml @@ -77,7 +77,7 @@ serviceControlPolicies: # - Audit # - LogArchive ## END - - name: AWSAccelerator-Guardrails-Unclass + - name: "{{ AcceleratorPrefix }}-Guardrails-Unclass" description: > LZA Guardrails Unclassified Environment Specific policy: service-control-policies/LZA-Guardrails-Unclass.json @@ -85,7 +85,7 @@ serviceControlPolicies: deploymentTargets: organizationalUnits: - UnClass - - name: AWSAccelerator-Guardrails-Sandbox + - name: "{{ AcceleratorPrefix }}-Guardrails-Sandbox" description: > LZA Guardrails Sandbox Environment Specific policy: service-control-policies/LZA-Guardrails-Sandbox.json @@ -93,14 +93,14 @@ serviceControlPolicies: deploymentTargets: organizationalUnits: - Sandbox - - name: AWSAccelerator-Quarantine-New-Object + - name: "{{ AcceleratorPrefix }}-Quarantine-New-Object" description: > LZA Quarantine policy - Apply to ACCOUNTS that need to be quarantined policy: service-control-policies/Quarantine-New-Object.json type: customerManaged deploymentTargets: organizationalUnits: [] - - name: AWSAccelerator-Guardrails-Part-0-Core + - name: "{{ AcceleratorPrefix }}-Guardrails-Part-0-Core" description: > LZA Guardrails Part 0 Core Accounts policy: service-control-policies/LZA-Guardrails-Part0-CoreOUs.json diff --git a/config/replacements-config.yaml b/config/replacements-config.yaml index 347ddc8..fd87eae 100644 --- a/config/replacements-config.yaml +++ b/config/replacements-config.yaml @@ -6,19 +6,53 @@ ## https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/latest/classes/_aws_accelerator_config.ParameterReplacementConfigV2.html ###### globalReplacements: -# Name of CloudWatch log group that centralizes CloudTrail logs. Referenced by CloudWatch metrics and alarms + # Accelerator Prefix + - key: AcceleratorPrefix + type: String + value: AWSAccelerator + # Home Region + - key: AcceleratorHomeRegion + type: String + value: ca-central-1 + # Security notification emails + - key: SecurityHigh + type: String + value: @example.com + - key: SecurityMedium + type: String + value: @example.com + - key: SecurityLow + type: String + value: @example.com + - key: SecurityIgnore + type: String + value: @example.com + # Budget notification emails + - key: BudgetEmail + type: String + value: @example.com + # Name of CloudWatch log group that centralizes CloudTrail logs. Referenced by CloudWatch metrics and alarms - key: CloudTrailLogGroup type: String value: aws-accelerator-cloudtrail-logs # UPDATE If using ControlTower change this to 'aws-controltower/CloudTrailLogs' -# Domain name for Managed Active Directory + # Managed Active Directory settings - key: MadDnsName type: String value: example.local - key: MadNetbiosDomainName type: String value: example -###### -# VPC CIDR definition. If you change these values you also need to update individual subnets range in network-config.yaml + - key: ActiveDirectoryConnectorEmail + type: String + value: example-adconnector-user@example.com + - key: ActiveDirectoryUserEmail + type: String + value: example-user1@example.com + - key: ActiveDirectoryReadonlyUserEmail + type: String + value: example-user2@example.com + ###### + # VPC CIDR definition. If you change these values you also need to update individual subnets range in network-config.yaml - key: VpcCentralCidr type: String value: 10.1.0.0/16 diff --git a/config/security-config.yaml b/config/security-config.yaml index 4f17eee..7eee24c 100644 --- a/config/security-config.yaml +++ b/config/security-config.yaml @@ -1,9 +1,24 @@ -homeRegion: &HOME_REGION ca-central-1 centralSecurityServices: delegatedAdminAccount: Audit ebsDefaultVolumeEncryption: enable: true - excludeRegions: [] + excludeRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 scpRevertChangesConfig: enable: true snsTopicName: SecurityHigh @@ -12,7 +27,8 @@ centralSecurityServices: excludeAccounts: [] macie: enable: true - excludeRegions: [] + excludeRegions: + - ca-west-1 # does not support macie policyFindingsPublishingFrequency: FIFTEEN_MINUTES publishSensitiveDataFindings: true guardduty: @@ -30,7 +46,8 @@ centralSecurityServices: exportFrequency: FIFTEEN_MINUTES auditManager: enable: false - excludeRegions: [] + excludeRegions: + - ca-west-1 # Does not support Audit Manager defaultReportsConfiguration: enable: true destinationType: S3 @@ -67,6 +84,23 @@ centralSecurityServices: cloudWatch: enable: true ssmAutomation: + excludeRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 documentSets: - shareTargets: organizationalUnits: @@ -82,19 +116,19 @@ centralSecurityServices: - Management documents: # Calls the AWS CLI to enable access logs on a specified ELB - - name: AWSAccelerator-SSM-ELB-Enable-Logging + - name: "{{ AcceleratorPrefix }}-SSM-ELB-Enable-Logging" template: ssm-documents/ssm-elb-enable-logging.yaml # Enables S3 encryption using KMS - - name: AWSAccelerator-Put-S3-Encryption + - name: "{{ AcceleratorPrefix }}-Put-S3-Encryption" template: ssm-documents/s3-encryption.yaml # Attaches instance profiles to an EC2 instance - - name: AWSAccelerator-Attach-IAM-Instance-Profile + - name: "{{ AcceleratorPrefix }}-Attach-IAM-Instance-Profile" template: ssm-documents/attach-iam-instance-profile.yaml # Attaches Aws IAM Managed Policy to IAM Role - - name: AWSAccelerator-Attach-IAM-Role-Policy + - name: "{{ AcceleratorPrefix }}-Attach-IAM-Role-Policy" template: ssm-documents/attach-iam-role-policy.yaml # Enforces HTTPS on S3 Buckets - - name: AWSAccelerator-S3-Enforce-HTTPS + - name: "{{ AcceleratorPrefix }}-S3-Enforce-HTTPS" template: ssm-documents/s3-enforce-https.yaml accessAnalyzer: enable: true @@ -119,7 +153,8 @@ awsConfig: ## END ruleSets: - deploymentTargets: - ## GLOBAL Section for config rules across all OUs + Management Account + ## GLOBAL Section for config rules across all OUs + Management Account. + ## Applicable to all regions with workloads organizationalUnits: - Security - Infrastructure @@ -131,15 +166,32 @@ awsConfig: - Sandbox accounts: - Management + excludedRegions: + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ap-south-1 + - ap-southeast-1 + - ap-southeast-2 + - eu-central-1 + - eu-north-1 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - sa-east-1 + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 rules: - - name: AWSAccelerator-s3-bucket-server-side-encryption-enabled + - name: "{{ AcceleratorPrefix }}-s3-bucket-server-side-encryption-enabled" identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED complianceResourceTypes: - AWS::S3::Bucket remediation: rolePolicyFile: custom-config-rules/bucket-sse-enabled-remediation-role.json automatic: true - targetId: AWSAccelerator-Put-S3-Encryption + targetId: "{{ AcceleratorPrefix }}-Put-S3-Encryption" retryAttemptSeconds: 60 maximumAutomaticAttempts: 5 parameters: @@ -149,34 +201,21 @@ awsConfig: - name: KMSMasterKey value: ${ACCEL_LOOKUP::KMS} type: StringList - - name: AWSAccelerator-s3-bucket-enforce-https + - name: "{{ AcceleratorPrefix }}-s3-bucket-enforce-https" identifier: S3_BUCKET_SSL_REQUESTS_ONLY complianceResourceTypes: - AWS::S3::Bucket remediation: rolePolicyFile: custom-config-rules/bucket-enforce-https-remediation-role.json automatic: true - targetId: AWSAccelerator-S3-Enforce-HTTPS + targetId: "{{ AcceleratorPrefix }}-S3-Enforce-HTTPS" retryAttemptSeconds: 60 maximumAutomaticAttempts: 5 parameters: - name: BucketName value: RESOURCE_ID type: String - - deploymentTargets: - ## Section for config rules across all OUs except Sandbox + Management Account - organizationalUnits: - - Security - - Infrastructure - - Central - - Dev - - Test - - Prod - - UnClass - accounts: - - Management - rules: - - name: AWSAccelerator-attach-ec2-instance-profile + - name: "{{ AcceleratorPrefix }}-attach-ec2-instance-profile" type: Custom description: Custom rule for checking EC2 instance IAM profile attachment inputParameters: @@ -184,7 +223,7 @@ awsConfig: lambda: sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip handler: index.handler - runtime: nodejs16.x + runtime: nodejs18.x rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json periodic: true maximumExecutionFrequency: Six_Hours @@ -197,7 +236,7 @@ awsConfig: remediation: rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json automatic: true - targetId: AWSAccelerator-Attach-IAM-Instance-Profile + targetId: "{{ AcceleratorPrefix }}-Attach-IAM-Instance-Profile" retryAttemptSeconds: 60 maximumAutomaticAttempts: 5 parameters: @@ -207,7 +246,7 @@ awsConfig: - name: IamInstanceProfile value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} type: StringList - - name: AWSAccelerator-ec2-instance-profile-permission + - name: "{{ AcceleratorPrefix }}-ec2-instance-profile-permission" type: Custom description: Custom role to remediate EC2 instance profile permission inputParameters: @@ -217,7 +256,7 @@ awsConfig: lambda: sourceFilePath: custom-config-rules/ec2-instance-profile-permissions.zip handler: index.handler - runtime: nodejs16.x + runtime: nodejs18.x rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-detection-role.json periodic: true maximumExecutionFrequency: Six_Hours @@ -230,7 +269,7 @@ awsConfig: remediation: rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-remediation-role.json automatic: true - targetId: AWSAccelerator-Attach-IAM-Role-Policy + targetId: "{{ AcceleratorPrefix }}-Attach-IAM-Role-Policy" targetAccountName: Audit retryAttemptSeconds: 60 maximumAutomaticAttempts: 5 @@ -241,7 +280,7 @@ awsConfig: - name: AWSManagedPolicies value: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy type: StringList - - name: AWSAccelerator-elb-logging-enabled + - name: "{{ AcceleratorPrefix }}-elb-logging-enabled" identifier: ELB_LOGGING_ENABLED complianceResourceTypes: - AWS::ElasticLoadBalancing::LoadBalancer @@ -251,7 +290,7 @@ awsConfig: remediation: rolePolicyFile: custom-config-rules/elb-logging-enabled-remediation-role.json automatic: true - targetId: AWSAccelerator-SSM-ELB-Enable-Logging + targetId: "{{ AcceleratorPrefix }}-SSM-ELB-Enable-Logging" retryAttemptSeconds: 60 maximumAutomaticAttempts: 5 parameters: @@ -261,23 +300,78 @@ awsConfig: - name: LogDestination value: ${ACCEL_LOOKUP::Bucket:elbLogs} type: StringList - - name: AWSAccelerator-acm-certificate-expiration-check + - deploymentTargets: + ## Section for config rules across all OUs except Sandbox + Management Account + ## Rules not supported in ap-northeast-3 AND ca-west-1 + organizationalUnits: + - Security + - Infrastructure + - Central + - Dev + - Test + - Prod + - UnClass + accounts: + - Management + excludedRegions: + - ap-northeast-3 + - ca-west-1 + rules: + - name: "{{ AcceleratorPrefix }}-sagemaker-notebook-instance-kms-key-configured" + identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED + - name: "{{ AcceleratorPrefix }}-wafv2-logging-enabled" + identifier: WAFV2_LOGGING_ENABLED + - name: "{{ AcceleratorPrefix }}-dynamodb-in-backup-plan" + identifier: DYNAMODB_IN_BACKUP_PLAN + - name: "{{ AcceleratorPrefix }}-sagemaker-endpoint-configuration-kms-key-configured" + identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED + - name: "{{ AcceleratorPrefix }}-ebs-in-backup-plan" + identifier: EBS_IN_BACKUP_PLAN + - name: "{{ AcceleratorPrefix }}-rds-in-backup-plan" + identifier: RDS_IN_BACKUP_PLAN + - name: "{{ AcceleratorPrefix }}-elb-acm-certificate-required" + complianceResourceTypes: + - AWS::ElasticLoadBalancing::LoadBalancer + identifier: ELB_ACM_CERTIFICATE_REQUIRED + - name: "{{ AcceleratorPrefix }}-securityhub-enabled" + identifier: SECURITYHUB_ENABLED + - name: "{{ AcceleratorPrefix }}-ec2-managedinstance-patch-compliance-status-check" + identifier: EC2_MANAGEDINSTANCE_PATCH_COMPLIANCE_STATUS_CHECK + - name: "{{ AcceleratorPrefix }}-internet-gateway-authorized-vpc-only" + complianceResourceTypes: + - AWS::EC2::InternetGateway + identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY + - name: "{{ AcceleratorPrefix }}-dynamodb-table-encrypted-kms" + complianceResourceTypes: + - AWS::DynamoDB::Table + identifier: DYNAMODB_TABLE_ENCRYPTED_KMS + - name: "{{ AcceleratorPrefix }}-acm-certificate-expiration-check" inputParameters: daysToExpiration: "90" identifier: ACM_CERTIFICATE_EXPIRATION_CHECK - - name: AWSAccelerator-alb-waf-enabled + - name: "{{ AcceleratorPrefix }}-alb-waf-enabled" identifier: ALB_WAF_ENABLED - - name: AWSAccelerator-api-gw-cache-enabled-and-encrypted - complianceResourceTypes: - - AWS::ApiGateway::Stage - identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED - - name: AWSAccelerator-cloudtrail-enabled + - deploymentTargets: + ## Section for config rules across all OUs except Sandbox + Management Account + ## Rules not supported in ap-northeast-3 ONLY + organizationalUnits: + - Security + - Infrastructure + - Central + - Dev + - Test + - Prod + - UnClass + accounts: + - Management + excludedRegions: + - ap-northeast-3 + rules: + - name: "{{ AcceleratorPrefix }}-cloudtrail-enabled" identifier: CLOUD_TRAIL_ENABLED - - name: AWSAccelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: AWSAccelerator-cloudtrail-security-trail-enabled + - name: "{{ AcceleratorPrefix }}-cloudtrail-security-trail-enabled" identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: AWSAccelerator-cloudwatch-alarm-action-check + - name: "{{ AcceleratorPrefix }}-cloudwatch-alarm-action-check" complianceResourceTypes: - AWS::CloudWatch::Alarm inputParameters: @@ -285,76 +379,31 @@ awsConfig: insufficientDataActionRequired: "TRUE" okActionRequired: "FALSE" identifier: CLOUDWATCH_ALARM_ACTION_CHECK - - name: AWSAccelerator-cw-loggroup-retention-period-check + - name: "{{ AcceleratorPrefix }}-cw-loggroup-retention-period-check" identifier: CW_LOGGROUP_RETENTION_PERIOD_CHECK - - name: AWSAccelerator-db-instance-backup-enabled + - name: "{{ AcceleratorPrefix }}-db-instance-backup-enabled" identifier: DB_INSTANCE_BACKUP_ENABLED - - name: AWSAccelerator-dynamodb-in-backup-plan - identifier: DYNAMODB_IN_BACKUP_PLAN - - name: AWSAccelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - name: AWSAccelerator-ebs-in-backup-plan - identifier: EBS_IN_BACKUP_PLAN - - name: AWSAccelerator-ec2-instance-detailed-monitoring-enabled + - name: "{{ AcceleratorPrefix }}-ec2-instance-detailed-monitoring-enabled" complianceResourceTypes: - AWS::EC2::Instance identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: AWSAccelerator-ec2-managedinstance-patch-compliance-status-check - identifier: EC2_MANAGEDINSTANCE_PATCH_COMPLIANCE_STATUS_CHECK - - name: AWSAccelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: "TRUE" - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: AWSAccelerator-elasticache-redis-cluster-automatic-backup-check - identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - - name: AWSAccelerator-elb-acm-certificate-required - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_ACM_CERTIFICATE_REQUIRED - - name: AWSAccelerator-elb-cross-zone-load-balancing-enabled + - name: "{{ AcceleratorPrefix }}-elb-cross-zone-load-balancing-enabled" complianceResourceTypes: - AWS::ElasticLoadBalancing::LoadBalancer identifier: ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED - - name: AWSAccelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: AWSAccelerator-guardduty-non-archived-findings + - name: "{{ AcceleratorPrefix }}-guardduty-non-archived-findings" inputParameters: daysHighSev: "1" daysLowSev: "30" daysMediumSev: "7" identifier: GUARDDUTY_NON_ARCHIVED_FINDINGS - - name: AWSAccelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: AWSAccelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: AWSAccelerator-incoming-ssh-disabled + - name: "{{ AcceleratorPrefix }}-incoming-ssh-disabled" identifier: INCOMING_SSH_DISABLED - - name: AWSAccelerator-ec2-instances-in-vpc + - name: "{{ AcceleratorPrefix }}-ec2-instances-in-vpc" complianceResourceTypes: - AWS::EC2::Instance identifier: INSTANCES_IN_VPC - - name: AWSAccelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: AWSAccelerator-rds-in-backup-plan - identifier: RDS_IN_BACKUP_PLAN - - name: AWSAccelerator-redshift-cluster-configuration-check - inputParameters: - clusterDbEncrypted: "TRUE" - loggingEnabled: "TRUE" - complianceResourceTypes: - - AWS::Redshift::Cluster - identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK - - name: AWSAccelerator-restricted-incoming-traffic + - name: "{{ AcceleratorPrefix }}-restricted-incoming-traffic" inputParameters: blockedPort1: "20" blockedPort2: "21" @@ -362,179 +411,218 @@ awsConfig: blockedPort4: "3306" blockedPort5: "4333" identifier: RESTRICTED_INCOMING_TRAFFIC - - name: AWSAccelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - - name: AWSAccelerator-s3-bucket-versioning-enabled + - name: "{{ AcceleratorPrefix }}-s3-bucket-versioning-enabled" complianceResourceTypes: - AWS::S3::Bucket identifier: S3_BUCKET_VERSIONING_ENABLED - - name: AWSAccelerator-sagemaker-endpoint-configuration-kms-key-configured - identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED - - name: AWSAccelerator-sagemaker-notebook-instance-kms-key-configured - identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED - - name: AWSAccelerator-securityhub-enabled - identifier: SECURITYHUB_ENABLED - - name: AWSAccelerator-vpc-sg-open-only-to-authorized-ports + - name: "{{ AcceleratorPrefix }}-vpc-sg-open-only-to-authorized-ports" inputParameters: authorizedTcpPorts: "443" authorizedUdpPorts: "1020-1025" complianceResourceTypes: - AWS::EC2::SecurityGroup identifier: VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS - - name: AWSAccelerator-wafv2-logging-enabled - identifier: WAFV2_LOGGING_ENABLED + - deploymentTargets: + ## Section for config rules across all OUs except Sandbox + Management Account + ## Rules not supported in ca-west-1 ONLY + organizationalUnits: + - UnClass + - Security + - Prod + - Dev + - Test + - Central + - Infrastructure + accounts: + - Management + excludedRegions: + - ca-west-1 + rules: + - name: "{{ AcceleratorPrefix }}-redshift-cluster-configuration-check" + inputParameters: + clusterDbEncrypted: "TRUE" + loggingEnabled: "TRUE" + complianceResourceTypes: + - AWS::Redshift::Cluster + identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK + - name: "{{ AcceleratorPrefix }}-s3-bucket-policy-grantee-check" + complianceResourceTypes: + - AWS::S3::Bucket + identifier: S3_BUCKET_POLICY_GRANTEE_CHECK + - name: "{{ AcceleratorPrefix }}-iam-user-group-membership-check" + complianceResourceTypes: + - AWS::IAM::User + identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK + - name: "{{ AcceleratorPrefix }}-cloudtrail-s3-dataevents-enabled" + identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED + - name: "{{ AcceleratorPrefix }}-iam-group-has-users-check" + complianceResourceTypes: + - AWS::IAM::Group + identifier: IAM_GROUP_HAS_USERS_CHECK + - name: "{{ AcceleratorPrefix }}-api-gw-cache-enabled-and-encrypted" + complianceResourceTypes: + - AWS::ApiGateway::Stage + identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED + - name: "{{ AcceleratorPrefix }}-ec2-volume-inuse-check" + inputParameters: + deleteOnTermination: "TRUE" + complianceResourceTypes: + - AWS::EC2::Volume + identifier: EC2_VOLUME_INUSE_CHECK + - name: "{{ AcceleratorPrefix }}-elasticache-redis-cluster-automatic-backup-check" + identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK + - name: "{{ AcceleratorPrefix }}-emr-kerberos-enabled" + identifier: EMR_KERBEROS_ENABLED cloudWatch: metricSets: - regions: - - *HOME_REGION + - "{{AcceleratorHomeRegion}}" deploymentTargets: accounts: - Management metrics: # CIS 1.1 – Avoid the use of the "root" account - - filterName: AWSAccelerator-RootAccountMetricFilter + - filterName: "{{ AcceleratorPrefix }}-RootAccountMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' metricNamespace: CloudTrailMetrics metricName: RootAccount metricValue: "1" # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - filterName: AWSAccelerator-UnauthorizedAPICallsMetricFilter + - filterName: "{{ AcceleratorPrefix }}-UnauthorizedAPICallsMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}' metricNamespace: CloudTrailMetrics metricName: UnauthorizedAPICalls metricValue: "1" # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - filterName: AWSAccelerator-ConsoleSigninWithoutMFAMetricFilter + - filterName: "{{ AcceleratorPrefix }}-ConsoleSigninWithoutMFAMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: '{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}' metricNamespace: CloudTrailMetrics metricName: ConsoleSigninWithoutMFA metricValue: "1" # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - # - filterName: AWSAccelerator-MetricFilter + # - filterName: {{ AcceleratorPrefix }}-MetricFilter # logGroupName: {{ CloudTrailLogGroup }} # filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' # metricNamespace: CloudTrailMetrics # metricName: RootAccountUsage # metricValue: "1" # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: AWSAccelerator-IAMPolicyChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-IAMPolicyChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}" metricNamespace: CloudTrailMetrics metricName: IAMPolicyChanges metricValue: "1" # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: AWSAccelerator-CloudTrailChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-CloudTrailChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}" metricNamespace: CloudTrailMetrics metricName: CloudTrailChanges metricValue: "1" # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: AWSAccelerator-ConsoleAuthenticationFailureMetricFilter + - filterName: "{{ AcceleratorPrefix }}-ConsoleAuthenticationFailureMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' metricNamespace: CloudTrailMetrics metricName: ConsoleAuthenticationFailure metricValue: "1" # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: AWSAccelerator-DisableOrDeleteCMKMetricFilter + - filterName: "{{ AcceleratorPrefix }}-DisableOrDeleteCMKMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}" metricNamespace: CloudTrailMetrics metricName: DisableOrDeleteCMK metricValue: "1" # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: AWSAccelerator-S3BucketPolicyChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-S3BucketPolicyChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}" metricNamespace: CloudTrailMetrics metricName: S3BucketPolicyChanges metricValue: "1" # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSAccelerator-AWSConfigChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-AWSConfigChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}" metricNamespace: CloudTrailMetrics metricName: AWSConfigChanges metricValue: "1" # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: AWSAccelerator-SecurityGroupChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-SecurityGroupChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}" metricNamespace: CloudTrailMetrics metricName: SecurityGroupChanges metricValue: "1" # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: AWSAccelerator-NetworkACLChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-NetworkACLChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}" metricNamespace: CloudTrailMetrics metricName: NetworkACLChanges metricValue: "1" # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: AWSAccelerator-NetworkGatewayChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-NetworkGatewayChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}" metricNamespace: CloudTrailMetrics metricName: NetworkGatewayChanges metricValue: "1" # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: AWSAccelerator-RouteTableChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-RouteTableChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}" metricNamespace: CloudTrailMetrics metricName: RouteTableChanges metricValue: "1" # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: AWSAccelerator-VPCChangesMetricFilter + - filterName: "{{ AcceleratorPrefix }}-VPCChangesMetricFilter" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}" metricNamespace: CloudTrailMetrics metricName: VPCChanges metricValue: "1" - - filterName: AWSAccelerator-Ec2InstanceChangeMetric + - filterName: "{{ AcceleratorPrefix }}-Ec2InstanceChangeMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{ ($.eventName = RunInstances) || ($.eventName = RebootInstances)|| ($.eventName = StartInstances) || ($.eventName = StopInstances) || ($.eventName= TerminateInstances) }" metricNamespace: CloudTrailMetrics metricName: EC2InstanceEventCount metricValue: "1" - - filterName: AWSAccelerator-Ec2LargeInstanceChangeMetric + - filterName: "{{ AcceleratorPrefix }}-Ec2LargeInstanceChangeMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{ (($.eventName = RunInstances) || ($.eventName = RebootInstances)|| ($.eventName = StartInstances) || ($.eventName = StopInstances) || ($.eventName= TerminateInstances)) && (($.requestParameters.instanceType= *.32xlarge) || ($.requestParameters.instanceType= *.24xlarge) || ($.requestParameters.instanceType= *.18xlarge) || ($.requestParameters.instanceType= *.16xlarge) || ($.requestParameters.instanceType= *.12xlarge) || ($.requestParameters.instanceType= *.10xlarge) || ($.requestParameters.instanceType= *.9xlarge) || ($.requestParameters.instanceType= *.8xlarge) || ($.requestParameters.instanceType = *.4xlarge)) }" metricNamespace: CloudTrailMetrics metricName: EC2LargeInstanceEventCount metricValue: "1" - - filterName: AWSAccelerator-SSOAuthUnapprovedIPMetric + - filterName: "{{ AcceleratorPrefix }}-SSOAuthUnapprovedIPMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{ ($.eventSource=sso.amazonaws.com) && ($.eventName=Authenticate) && ($.sourceIPAddress != 10.10.10.*) }" # Needs Updating metricNamespace: CloudTrailMetrics metricName: SSOAuthUnapprovedIPCount metricValue: "1" - - filterName: AWSAccelerator-IAMAuthUnapprovedIPMetric + - filterName: "{{ AcceleratorPrefix }}-IAMAuthUnapprovedIPMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{ ($.eventName=ConsoleLogin) && ($.userIdentity.type=IAMUser) && ($.sourceIPAddress != 10.10.10.*) }" # Needs Updating metricNamespace: CloudTrailMetrics metricName: IAMAuthUnapprovedIPCount metricValue: "1" - - filterName: AWSAccelerator-UnencryptedFilesystemCreatedMetric + - filterName: "{{ AcceleratorPrefix }}-UnencryptedFilesystemCreatedMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{ ($.eventName = CreateFileSystem) && ($.responseElements.encrypted IS FALSE) }" metricNamespace: CloudTrailMetrics metricName: UnencryptedFilesystemCreatedCount metricValue: "1" - - filterName: AWSAccelerator-IgnoreAuthorizationFailureMetric + - filterName: "{{ AcceleratorPrefix }}-IgnoreAuthorizationFailureMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.errorCode=\"*UnauthorizedOperation\") || ($.errorCode=\"AccessDenied*\")}" metricNamespace: CloudTrailMetrics metricName: IgnoreAuthorizationFailureCount metricValue: "1" - - filterName: AWSAccelerator-IgnoreConsoleSignInWithoutMfaMetric + - filterName: "{{ AcceleratorPrefix }}-IgnoreConsoleSignInWithoutMfaMetric" logGroupName: {{ CloudTrailLogGroup }} filterPattern: "{($.eventName=\"ConsoleLogin\") && ($.additionalEventData.MFAUsed !=\"Yes\")}" metricNamespace: CloudTrailMetrics @@ -542,13 +630,13 @@ cloudWatch: metricValue: "1" alarmSets: - regions: - - *HOME_REGION + - {{ AcceleratorHomeRegion }} deploymentTargets: accounts: - Management alarms: # CIS 1.1 – Avoid the use of the "root" account - - alarmName: AWSAccelerator-CIS-1.1-RootAccountUsage + - alarmName: "{{ AcceleratorPrefix }}-CIS-1.1-RootAccountUsage" alarmDescription: Alarm for usage of "root" account snsTopicName: SecurityLow metricName: RootAccountUsage @@ -560,7 +648,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - alarmName: AWSAccelerator-CIS-3.1-UnauthorizedAPICalls + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.1-UnauthorizedAPICalls" alarmDescription: Alarm for unauthorized API calls snsTopicName: SecurityLow metricName: UnauthorizedAPICalls @@ -572,7 +660,7 @@ cloudWatch: threshold: 5 treatMissingData: notBreaching # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - alarmName: AWSAccelerator-CIS-3.2-ConsoleSigninWithoutMFA + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.2-ConsoleSigninWithoutMFA" alarmDescription: Alarm for AWS Management Console sign-in without MFA snsTopicName: SecurityHigh metricName: ConsoleSigninWithoutMFA @@ -597,7 +685,7 @@ cloudWatch: # threshold: 1 # treatMissingData: notBreaching # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: AWSAccelerator-CIS-3.4-IAMPolicyChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.4-IAMPolicyChanges" alarmDescription: Alarm for IAM policy changes snsTopicName: SecurityMedium metricName: IAMPolicyChanges @@ -609,7 +697,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: AWSAccelerator-CIS-3.5-CloudTrailChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.5-CloudTrailChanges" alarmDescription: Alarm for CloudTrail configuration changes snsTopicName: SecurityHigh metricName: CloudTrailChanges @@ -621,7 +709,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: AWSAccelerator-CIS-3.6-ConsoleAuthenticationFailure + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.6-ConsoleAuthenticationFailure" alarmDescription: Alarm exist for AWS Management Console authentication failures snsTopicName: SecurityLow metricName: ConsoleAuthenticationFailure @@ -633,7 +721,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: AWSAccelerator-CIS-3.7-DisableOrDeleteCMK + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.7-DisableOrDeleteCMK" alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs snsTopicName: SecurityHigh metricName: DisableOrDeleteCMK @@ -645,7 +733,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: AWSAccelerator-CIS-3.8-S3BucketPolicyChanges. + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.8-S3BucketPolicyChanges" alarmDescription: Alarm for S3 bucket policy changes snsTopicName: SecurityMedium metricName: S3BucketPolicyChanges @@ -657,7 +745,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: AWSAccelerator-CIS-3.9-AWSConfigChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.9-AWSConfigChanges" alarmDescription: Alarm for AWS Config configuration changes snsTopicName: SecurityHigh metricName: AWSConfigChanges @@ -669,7 +757,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: AWSAccelerator-CIS-3.10-SecurityGroupChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.10-SecurityGroupChanges" alarmDescription: Alarm for security group changes snsTopicName: SecurityLow metricName: SecurityGroupChanges @@ -681,7 +769,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: AWSAccelerator-CIS-3.11-NetworkACLChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.11-NetworkACLChanges" alarmDescription: Alarm for changes to Network Access Control Lists (NACL) snsTopicName: SecurityMedium metricName: NetworkACLChanges @@ -693,7 +781,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: AWSAccelerator-CIS-3.12-NetworkGatewayChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.12-NetworkGatewayChanges" alarmDescription: Alarm for changes to network gateways snsTopicName: SecurityMedium metricName: NetworkGatewayChanges @@ -705,7 +793,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: AWSAccelerator-CIS-3.13-RouteTableChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.13-RouteTableChanges" alarmDescription: Alarm for route table changes snsTopicName: SecurityMedium metricName: RouteTableChanges @@ -717,7 +805,7 @@ cloudWatch: threshold: 1 treatMissingData: notBreaching # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: AWSAccelerator-CIS-3.14-VPCChanges + - alarmName: "{{ AcceleratorPrefix }}-CIS-3.14-VPCChanges" alarmDescription: Alarm for VPC changes snsTopicName: SecurityMedium metricName: VPCChanges @@ -728,7 +816,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-EC2-Instances-Changed + - alarmName: "{{ AcceleratorPrefix }}-AWS-EC2-Instances-Changed" alarmDescription: Alarms when one or more API calls are made to create, terminate, start, stop or reboot any EC2 instance (in any account, any region of your AWS Organization). snsTopicName: SecurityLow metricName: EC2InstanceEventCount @@ -739,7 +827,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-EC2-Large-Instance-Changed + - alarmName: "{{ AcceleratorPrefix }}-AWS-EC2-Large-Instance-Changed" alarmDescription: Alarms when one or more API calls are made to create, terminate, start, stop or reboot a 4x, 8x, 9x, 10x, 12x, 16x, 18x, 24x, 32x-large EC2 instance (in any account, any region of your AWS Organization). snsTopicName: SecurityMedium metricName: EC2LargeInstanceEventCount @@ -750,7 +838,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-SSO-Authentication-From-Unapproved-IP + - alarmName: "{{ AcceleratorPrefix }}-AWS-SSO-Authentication-From-Unapproved-IP" alarmDescription: Alarms when someone authenticates using AWS SSO from an unauthorized IP address range. snsTopicName: SecurityHigh metricName: SSOAuthUnapprovedIPCount @@ -761,7 +849,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-IAM-Authentication-From-Unapproved-IP + - alarmName: "{{ AcceleratorPrefix }}-AWS-IAM-Authentication-From-Unapproved-IP" alarmDescription: Alarms when someone authenticates using AWS IAM from an unauthorized IP address range. snsTopicName: SecurityHigh metricName: IAMAuthUnapprovedIPCount @@ -772,7 +860,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-Unencrypted-Filesystem-Created + - alarmName: "{{ AcceleratorPrefix }}-AWS-Unencrypted-Filesystem-Created" alarmDescription: Alarms when one or more API calls are made to create an Unencrypted filesystem (i.e. EFS) (in any account, any region of your AWS Organization). snsTopicName: SecurityHigh metricName: UnencryptedFilesystemCreatedCount @@ -783,7 +871,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-IGNORE-AWS-Authorization-Failure + - alarmName: "{{ AcceleratorPrefix }}-IGNORE-AWS-Authorization-Failure" alarmDescription: Alarms when one or more unauthorized API calls are made (in any account, any region of your AWS Organization). snsTopicName: SecurityIgnore metricName: IgnoreAuthorizationFailureCount @@ -794,7 +882,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-IGNORE-AWS-Console-SignIn-Without-MFA + - alarmName: "{{ AcceleratorPrefix }}-IGNORE-AWS-Console-SignIn-Without-MFA" alarmDescription: Alarms when MFA is NOT used to sign into the console with IAM (in any account, any region of your AWS Organization). snsTopicName: SecurityIgnore metricName: IgnoreConsoleSignInWithoutMfaCount @@ -805,7 +893,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-Console-SignIn-Failure + - alarmName: "{{ AcceleratorPrefix }}-AWS-Console-SignIn-Failure" alarmDescription: Alarms when one or more unauthenticated API calls are made to sign into the console (in any account, any region of your AWS Organization). snsTopicName: SecurityHigh metricName: ConsoleSignInFailureCount @@ -816,7 +904,7 @@ cloudWatch: statistic: Sum threshold: 3 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-Authorization-Failure + - alarmName: "{{ AcceleratorPrefix }}-AWS-Authorization-Failure" alarmDescription: Alarms when one or more unauthorized API calls are made (in any account, any region of your AWS Organization). snsTopicName: SecurityLow metricName: AuthorizationFailureCount @@ -827,7 +915,7 @@ cloudWatch: statistic: Sum threshold: 1 treatMissingData: notBreaching - - alarmName: AWSAccelerator-AWS-Root-Login + - alarmName: "{{ AcceleratorPrefix }}-AWS-Root-Login" alarmDescription: Alarms when the root user successfully logs in one or more times (in any account, any region of your AWS Organization). snsTopicName: SecurityHigh metricName: RootLoginEventCount diff --git a/config/ssm-documents/s3-enforce-https.yaml b/config/ssm-documents/s3-enforce-https.yaml index 564f014..40ccc7c 100644 --- a/config/ssm-documents/s3-enforce-https.yaml +++ b/config/ssm-documents/s3-enforce-https.yaml @@ -97,7 +97,7 @@ mainSteps: s3_client = boto3.client("s3") bucket_name = event["BucketName"] region = os.environ['AWS_REGION'] - + bucket_policy = get_bucket_policy(s3_client, bucket_name) policy_sid = generate_random_policy_statement_id() partition = get_partition(region) diff --git a/documentation/FAQ.md b/documentation/FAQ.md index b0adc60..f88c186 100644 --- a/documentation/FAQ.md +++ b/documentation/FAQ.md @@ -28,9 +28,64 @@ For existing deployments, before making this change in your configuration and ru If you didn't previously used or enabled Identity Center, you need to enable it in your management account before running the LZA pipeline. -**If you are already operating Identity Center from the Audit account with provisionned permission sets and assignements you should carefully review the impact of this change. Making this change can remove all existing Identity Center configurations**. To continue using the Audit Account as the delegated adminstrator for Identity Center you can use the following configuration instead: +**If you are already operating Identity Center from the Audit account with provisionned permission sets and assignements you should carefully review the impact of this change. Making this change can remove all existing Identity Center configurations**. To continue using the Audit Account as the delegated administrator for Identity Center you can use the following configuration instead: ``` identityCenter: name: OrgIdentityCenter delegatedAdminAccount: Audit ``` + +## AWS Regions + +### What is the Home Region? + +The [homeRegion](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/v1.6.0/classes/_aws_accelerator_config.GlobalConfig.html#homeRegion) is where the accelerator pipeline is deployed and the region in which you will most often operate in. This reference architecture deploys the networking resources in that region by default. + +### What are Enabled Regions? +[enabledRegions](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/v1.6.0/classes/_aws_accelerator_config.GlobalConfig.html#enabledRegions) is the list of regions where accelerator resources will be deployed. Home region must be part of this list. + +### Why should all regions be enabled in my configuration even if I don't intend to deploy workloads outside the Home Region? + +We highly recommend to add all AWS regions that are enabled by default to the list of `enabledRegions` in LZA to deploy all the guardrails and detective controls configured by this reference architecture. Service Control Policies are in place to prevent users to deploy resources outside of the home region, the deployment of detective controls in every region serves as a defense in depth measure. Multiple `excludeRegions:` statements are included in the reference configuration to avoid deploying unnecessary resources to all enabled regions and limit cost. + +### What if I want to deploy workloads in another region than the home region? + +The exact steps required to enable an additional region within the reference architecture may vary based on the type of workload and use of the region (e.g. used for disaster recovery only are as the main region for specific applications). The high-level steps are: + +1. Update the accelerator Service Control Policies to allow the use of the additional region. More precisely the `GBL1` and `GBL2` statements that denies the use of most services outside the Home Region. +2. Remove the additional region from the `excludeRegions:` directives throughout the configuration files. +3. Deploy the appropriate networking and other supporting resources in the additional region. + +### What about deploying workloads to an opt-in region? + +AWS Regions added after March 20, 2019 are disabled by default and need to be enabled before resources can be created in those regions. In addition to the steps from the previous section you will need to enable the opt-in region by following the steps in [Enable or disable a Region in your organization](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-regions.html#manage-acct-regions-enable-organization) + +## Application Load Balancers Forwarding + +Since the `1.7.0-a` release of the configuration, Application Load Balancers are deployed in the Perimeter VPC. Sample configuration is also provided to automate the deployment of Application Load Balancers in workload accounts. AWS ALBs are published using DNS names which resolve to backing IPs which could silently change at any time due to a scaling event, maintenance, or a hardware failure. While published as a DNS name, ALBs can only target IP addresses. This presents a challenge as we need the ALBs in the perimeter account to target ALB's in the various back-end workload accounts. + +ALB Forwarding solves this problem by executing a small snippet of code every 60 seconds which updates managed ALB listeners with any IP changes, ensuring any managed flows do not go offline. This removes the requirement to leverage a 3rd party appliance to perform NAT to a DNS name. + +See the [post-deployment](../post-deployment.md) instructions for more details on how to configure the ALB forwarding rules + +### Why was a target group not created for an ALB Forwarding entry? + +When an entry is added to the ALB Forwarding table, a lookup is performed to validate and configure the target group. Additional information is then inserted to the DynamoDB table, specifically to the newly added entry with the key `metadata`. If this `metadata` key is not added and populated and/or a target group is not added, this could indicate an error in the parameters provided for the entry. + +To troubleshoot ALB Forwarding target group creation, consult the CloudWatch logs for the ALB Forwarding Lambda functions. The CloudWatch Log Groups are named `-AlbIPForwa-*`. If necessary, remove the affected entry from the DynamoDB table and re-add, ensuring that any information provided is correct. + +### Why can I not reach resources behind my workload ALB through the external ALB? + +The reachability of resources in workload accounts relies on several factors: + +1. The ALB targets in the workload account are healthy. + +Since the external ALB in the Perimeter account relies on health checks against the internal ALB in workload accounts, verify that the targets attached to the workload internal ALB are healthy. + +2. Routing from the Perimeter ALBs to the workload ALBs, and return traffic, through the Transit Gateway. + +Verify that the Transit Gateway is configured in the Landing Zone Accelerator to route traffic between the Perimeter and workload VPCs, and that the corresponding Transit Gateway attachments are present in the Network account. + +3. Network security configuration permitting the required traffic to the workload ALBs and resources. + +Verify that any Network ACLs (NACLS) and security groups are configured to permit the required traffic from the ALB Forwarding resources in the Perimeter account. \ No newline at end of file diff --git a/install-controltower.md b/install-controltower.md index 487a99e..2e329b2 100644 --- a/install-controltower.md +++ b/install-controltower.md @@ -5,22 +5,9 @@ _Note: Government of Canada customers are required to skip this step and [deploy ## 2.1 Configure AWS Control Tower -You should first configure AWS Control Tower in your home region using the documentation from [Getting started with AWS Control Tower](https://docs.aws.amazon.com/controltower/latest/userguide/getting-started-with-control-tower.html) - -- Be sure you've correctly designated the AWS Region that you select for your home Region. After you've deployed AWS Control Tower, you can't change the home Region. -- Leave the Region deny setting set to Not enabled - the Accelerator needs a customized region deny policy. -- Select all regions for Additional AWS Regions for governance -- For the Foundational OU, leave the default value Security -- For the Additional OU provide the value Infrastructure, click Next -- When configuring the shared accounts, keep the default names, use the email addresses defined in the prerequisites: - * **Management Account:** Use the "Management Account" email defined in the prerequisites - * **Log Archive Account:** Use the "Log Archive Account" email defined in the prerequisites - * **Audit Account:** Use the "Security Account" email defined in the prerequisites -- Select Enabled for AWS CloudTrail configuration - -After Control Tower deployment you should have a Security and Infrastructure OU as well as the three mandatory accounts. Go to Control Tower Account Factory and edit the Network configuration - - Set the Maximum number of private subnets to 0 - - Uncheck all regions for VPC creations (VPC creation will be handled by the accelerator) +Starting with version v1.7.0 of Landing Zone Accelerator, Control Tower can be setup as part of the LZA installation by setting the appropriate parameters when deploying the CloudFormation stack in the next step. + +You first need to [enable AWS Organizations in your home Region](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_create.html) by going to the [AWS Organizations console](https://console.aws.amazon.com/organizations/v2) and choosing **Create an organization**. ## 2.2 Deploy the installer CloudFormation stack Click the **Launch Solution** button on [Step 1. Launch the stack](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-1.-launch-the-stack.html) page. **Ensure the Region is set to your desired home Region, as it typically defaults to US East (N. Virginia)** @@ -42,9 +29,18 @@ Leave all other values as default, unless you have specific reasons to customize - Wait for the successful completion of the `AWSAccelerator-Pipeline` pipeline. +## 2.4 Verify Control Tower deployment and update configuration + +After Control Tower deployment you should have a Security and Infrastructure OU as well as the three mandatory accounts. + +### 2.4.1 Disable Control Tower VPC creation +Go to Control Tower Account Factory and edit the Network configuration + - Set the Maximum number of private subnets to 0 + - Uncheck all regions for VPC creations (VPC creation will be handled by the accelerator) + # 3. Deploy the reference architecture -The Landing Zone Accelerator on AWS solution deploys an AWS CodeCommit repository called `aws-accelerator-config`, along with six customizable YAML configuration files. The YAML files are pre-populated with a minimal configuration for the solution. The configuration files found in this directory should replace the files in the default AWS CodeCommit repository after adjusting environment specific configurations. +The Landing Zone Accelerator on AWS solution deploys an AWS CodeCommit repository called `aws-accelerator-config`, along with six customizable YAML configuration files. The YAML files are pre-populated with a minimal configuration for the solution. The configuration files found in this repo's '[config](./config/)' should replace the files in the default AWS CodeCommit repository after adjusting environment specific configurations. We recommend you read the LZA guidance on [using the configuration files](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/using-configuration-files.html), before continuing with the deployment of the reference architecture. @@ -56,30 +52,38 @@ We recommend you go through every configuration file and confirm the default val 2. Clone this repository (`landing-zone-accelerator-on-aws-for-cccs-medium`) 3. Copy the contents from the `config` folder in the repository `landing-zone-accelerator-on-aws-for-cccs-medium` to your local `aws-accelerator-config` repo. You may be prompted to overwrite duplicate configs, such as accounts-config.yaml. -## 3.2 Create and register additional organizational units +## 3.2 Mandatory customization + +Using the IDE of your choice, in your local `aws-accelerator-config` repo, update the following values: +- replacements-config.yaml - This file contains global variables that can be referenced from all other configuration files. Review the value of each variable to confirm it is appropriate to your deployment. **Note: ** the passwords for the active directory accounts will be available via [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). +- accounts-config.yaml - Update the config email addresses to match the email addresses you assigned in the prerequisites section. -Every Organization Unit defined in the `organization-config.yaml` file needs to be registered in Control Tower. Follow the steps to [create a new OU](https://docs.aws.amazon.com/controltower/latest/userguide/create-new-ou.html) for each OU defined in your configuration. +### 3.2.1 Changing the home Region -When using this reference architecture, the OUs are: -- Security: Already created when you setup Control Tower -- Infrastructure: Already created when you setup Control Tower -- Central -- Dev -- Test -- Prod -- UnClass -- Sandbox +If you are changing the home region from *ca-central-1* to different region, you need to make the following configuration file modifications. -## 3.3 Mandatory customization +- global-config.yaml - **homeRegion: &HOME_REGION ca-central-1** must be updated from *ca-central-1* to the region you are using as your home region, e.g. *homeRegion: &HOME_REGION eu-west-2* +- global-config.yaml - all references to your home region in any **excludeRegions** blocks must be deleted and *ca-central-1* must be added. -Using the IDE of your choice, in your local `aws-accelerator-config` repo, update the following values: -- replacements-config.yaml - This file contains global variables that can be referenced from all other configuration files. Review the value of each variable to confirm it is appropriate to your deployment. -- accounts-config.yaml - Replace all the AWS Account email addresses with valid emails for the deployment. These are used to create AWS Accounts. -- global-config.yaml - Replace all emails used for AWS Budgets and security notifications to match the email you allocated in the prerequisites. -- iam-config.yaml - Replace the groupName and Active Directory user account details with those specified in the prerequisites. **Note: ** the passwords for these accounts will be available via [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). -- This sample configuration is built using the **ca-central-1** AWS Region as the home or installation Region. If installing to a different home Region, then the five references to **ca-central-1** must be updated to reference your desired home Region in the following four configuration files (global-config, iam-config, network-config, security-config). +### 3.2.2 Changing the accelerator prefix + +If you changed the [accelerator prefix](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-1.-launch-the-stack.html) from **AWSAccelerator** during the LZA deployment, you need to make the following configuration file modifications. -### 3.3.1 Customizations specific to ControlTower +- global-config.yaml - update the **cdkOptions/customDeploymentRole** to *-PipelineRole* e.g. *ExamplePrefix-PipelineRole*. +- iam-config.yaml - update the **managedActiveDirectories/logs/groupName** to *-/MAD/{{MadDnsName}}* e.g. */ExamplePrefix/MAD/{{MadDnsName}}*. +- **dynamic-partitioning/log-filters.json** - update the acceleratorPrefix to **. For example if your prefix is *TSEProd*, the config file should look like the following: +``` +[ + { "logGroupPattern": "/TSEProd/MAD", "s3Prefix": "managed-ad" }, + { "logGroupPattern": "/TSEProd/rql", "s3Prefix": "rql" }, + { "logGroupPattern": "/TSEProd-SecurityHub", "s3Prefix": "security-hub" }, + { "logGroupPattern": "TSEProdFirewallFlowLogGroup", "s3Prefix": "nfw" }, + { "logGroupPattern": "/TSEProd/rsyslog", "s3Prefix": "rsyslog" }, + { "logGroupPattern": "TSEProd-sessionmanager-logs", "s3Prefix": "ssm" } +] +``` + +### 3.2.3 Customizations specific to ControlTower Some configuration elements need to be updated when using ControlTower @@ -88,6 +92,7 @@ Some configuration elements need to be updated when using ControlTower - global-config.yaml * Update `managementAccountAccessRole` value to `AWSControlTowerExecution * Update `controlTower` to `enable: true` + * Uncomment the `landingZone` block * Under `logging/cloudtrail/organizationTrailSettings` set `managementEvents` to `false`, an Organizational Trail was already setup by CloudTrail. - organization-config.yaml * Uncomment the proper configuration block under the `AWSAccelerator-Guardrails-Sensitive-Part-1` configuration to have the following configuration @@ -113,13 +118,13 @@ Some configuration elements need to be updated when using ControlTower If you are deploying a demo environment for experimentation purposes, and don't need to perform any specific customization such as defining specific CIDR ranges that don't overlap with on-premises networks, you may wish to skip to the section on running the pipeline. -## 3.4 Network customization +## 3.3 Network customization It is common for customers to want to assert control over their networking, based on existing on-premises requirements, such as CIDR ranges and the specific workload requirements, e.g. a VPN to integrate with on-premises services. By default reference architecture deploys a fully working shared network, isolated between development, test and production environments. The following section describes how to modify the CIDR ranges for the shared networking if necessary. -### 3.4.1 Customizing the shared network +### 3.3.1 Customizing the shared network The shared network makes use of a contiguous CIDR. The is currently specified as `10.0.0.0/8`. This is subdivided into @@ -135,6 +140,20 @@ The shared network makes use of a contiguous CIDR. The is currently specified as You can choose to customize these ranges in the `replacements-config.yaml`, however take careful note when updating the config to also update subnet range, NACLs, Security Groups, Firewall rules and routing appropriately in `network-config.yaml`. +## 3.4 Copy assets to assets buckets + +The sample configuration file uses self-signed certificates to attach to Application Load Balancers. Valid certificates need to be copied to the S3 assets bucket of your management account. (e.g. `aws-accelerator-assets--`) + +The `network-config.yaml` references certificates used by the Application Load Balancers (ALB), but the sample certificates must be generated locally. Follow these instructions to generate sample certificates for the initial deployment and demonstration purposes. Ideally you would generate real certificates using your existing certificate authority. Note that the config references the sample certs in a `certs` folder, therefore, the sample certs must be in uploaded into a `certs` folder in the S3 bucket. + +``` +Example1: +openssl req -newkey rsa:2048 -nodes -keyout example1-cert.key -out example1-cert.csr -subj "/C=CA/ST=Ontario/L=Ottawa/O=AnyCompany/CN=*.example.ca" +openssl x509 -signkey example1-cert.key -in example1-cert.csr -req -days 1095 -out example1-cert.crt +``` + +You can also update the configuration to automatically request certificates from Amazon Certificate Manager (ACM). See the [CertificateConfig](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/v1.6.2/classes/_aws_accelerator_config.CertificateConfig.html) documentation from LZA. + ## 3.5 Running the pipeline - Commit and push all your change to the `aws-accelerator-config` AWS CodeCommit repository. diff --git a/install-organizations.md b/install-organizations.md index c518480..d80e4b3 100644 --- a/install-organizations.md +++ b/install-organizations.md @@ -36,13 +36,22 @@ Leave all other values as default, unless you have specific reasons to customize - Wait for the successful completion of the `AWSAccelerator-Pipeline` pipeline. +## 2.4 Enable IAM Identity Center in the management account + +1. Login into the management account +2. Make sure the region in the console is set to your home AWS Region +3. Follow the guidance on [enabling AWS IAM Identity Center](https://docs.aws.amazon.com/singlesignon/latest/userguide/get-set-up-for-idc.html) + +> **Note:** +> Don't configure delegated administration, this will be done by the LZA pipeline in the next steps. + # 3. Deploy the reference architecture -The Landing Zone Accelerator on AWS solution deploys an AWS CodeCommit repository called `aws-accelerator-config`, along with six customizable YAML configuration files. The YAML files are pre-populated with a minimal configuration for the solution. The configuration files found in this directory should replace the files in the default AWS CodeCommit repository after adjusting environment specific configurations. +The Landing Zone Accelerator on AWS solution deploys an AWS CodeCommit repository called `aws-accelerator-config`, along with six customizable YAML configuration files. The YAML files are pre-populated with a minimal configuration for the solution. The configuration files found in this repo's '[config](./config/)' should replace the files in the default AWS CodeCommit repository after adjusting environment specific configurations. We recommend you read the LZA guidance on [using the configuration files](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/using-configuration-files.html), before continuing with the deployment of the reference architecture. -We recommend you go through every configuration file and confirm the default values correspond to your needs. Pay careful attention to any comments provided in the configuration files. To facilitate futur updates of the reference configuration, we suggest you keep the same file structure and comment out parts that you don't need instead of removing them. +We recommend you go through every configuration file and confirm the default values correspond to your needs. Pay careful attention to any comments provided in the configuration files. To facilitate future updates of the reference configuration, we suggest you keep the same file structure and comment out parts that you don't need instead of removing them. ## 3.1 Prepare the reference architecture configuration files @@ -63,11 +72,33 @@ You can run the [`setup-organizational-units`](./reference-artifacts/organizatio ## 3.3 Mandatory customization Using the IDE of your choice, in your local `aws-accelerator-config` repo, update the following values: -- replacements-config.yaml - This file contains global variables that can be referenced from all other configuration files. Review the value of each variable to confirm it is appropriate to your deployment. -- accounts-config.yaml - Replace all the AWS Account email addresses with valid emails for the deployment. These are used to create AWS Accounts. -- global-config.yaml - Replace all emails used for AWS Budgets and security notifications to match the email you allocated in the prerequisites. -- iam-config.yaml - Replace the groupName and Active Directory user account details with those specified in the prerequisites. **Note: ** the passwords for these accounts will be available via [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). -- This sample configuration is built using the **ca-central-1** AWS Region as the home or installation Region. If installing to a different home Region, then the five references to **ca-central-1** must be updated to reference your desired home Region in the following four configuration files (global-config, iam-config, network-config, security-config). +- replacements-config.yaml - This file contains global variables that can be referenced from all other configuration files. Review the value of each variable to confirm it is appropriate to your deployment. **Note: ** the passwords for the active directory accounts will be available via [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). +- accounts-config.yaml - Update the config email addresses to match the email addresses you assigned in the prerequisites section. + +### 3.3.1 Changing the home Region + +If you are changing the home region from *ca-central-1* to different region, you need to make the following configuration file modifications. + +- global-config.yaml - **homeRegion: &HOME_REGION ca-central-1** must be updated from *ca-central-1* to the region you are using as your home region, e.g. *homeRegion: &HOME_REGION eu-west-2* +- global-config.yaml - all references to your home region in any **excludeRegions** blocks must be deleted and *ca-central-1* must be added. + +### 3.3.2 Changing the accelerator prefix + +If you changed the [accelerator prefix](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-1.-launch-the-stack.html) from **AWSAccelerator** during the LZA deployment, you need to make the following configuration file modifications. + +- global-config.yaml - update the **cdkOptions/customDeploymentRole** to *-PipelineRole* e.g. *ExamplePrefix-PipelineRole*. +- iam-config.yaml - update the **managedActiveDirectories/logs/groupName** to *-/MAD/{{MadDnsName}}* e.g. */ExamplePrefix/MAD/{{MadDnsName}}*. +- **dynamic-partitioning/log-filters.json** - update the acceleratorPrefix to **. For example if your prefix is *TSEProd*, the config file should look like the following: +``` +[ + { "logGroupPattern": "/TSEProd/MAD", "s3Prefix": "managed-ad" }, + { "logGroupPattern": "/TSEProd/rql", "s3Prefix": "rql" }, + { "logGroupPattern": "/TSEProd-SecurityHub", "s3Prefix": "security-hub" }, + { "logGroupPattern": "TSEProdFirewallFlowLogGroup", "s3Prefix": "nfw" }, + { "logGroupPattern": "/TSEProd/rsyslog", "s3Prefix": "rsyslog" }, + { "logGroupPattern": "TSEProd-sessionmanager-logs", "s3Prefix": "ssm" } +] +``` If you are deploying a demo environment for experimentation purposes, and don't need to perform any specific customization such as defining specific CIDR ranges that don't overlap with on-premises networks, you may wish to skip to the section on running the pipeline. @@ -93,7 +124,21 @@ The shared network makes use of a contiguous CIDR. The is currently specified as You can choose to customize these ranges in the `replacements-config.yaml`, however take careful note when updating the config to also update subnet range, NACLs, Security Groups, Firewall rules and routing appropriately in `network-config.yaml`. -## 3.5 Running the pipeline +## 3.5 Copy assets to assets buckets + +The sample configuration file uses self-signed certificates to attach to Application Load Balancers. Valid certificates need to be copied to the S3 assets bucket of your management account. (e.g. `aws-accelerator-assets--`) + +The `network-config.yaml` references certificates used by the Application Load Balancers (ALB), but the sample certificates must be generated locally. Follow these instructions to generate sample certificates for the initial deployment and demonstration purposes. Ideally you would generate real certificates using your existing certificate authority. Note that the config references the sample certs in a `certs` folder, therefore, the sample certs must be in uploaded into a `certs` folder in the S3 bucket. + +``` +Example1: +openssl req -newkey rsa:2048 -nodes -keyout example1-cert.key -out example1-cert.csr -subj "/C=CA/ST=Ontario/L=Ottawa/O=AnyCompany/CN=*.example.ca" +openssl x509 -signkey example1-cert.key -in example1-cert.csr -req -days 1095 -out example1-cert.crt +``` + +You can also update the configuration to automatically request certificates from Amazon Certificate Manager (ACM). See the [CertificateConfig](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/v1.6.2/classes/_aws_accelerator_config.CertificateConfig.html) documentation from LZA. + +## 3.6 Running the pipeline - Commit and push all your change to the `aws-accelerator-config` AWS CodeCommit repository. - Release a change manually to the `AWSAccelerator-Pipeline` pipeline. diff --git a/install.md b/install.md index ed070b5..c10b900 100644 --- a/install.md +++ b/install.md @@ -98,12 +98,6 @@ Following the guidance on [enabling AWS Cost Explorer](https://docs.aws.amazon.c If you are using the GitHub source for the LZA code, you will need to follow the prerequisite step to [store a github token in secrets manager](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/prerequisites.html#create-a-github-personal-access-token-and-store-in-secrets-manager) -### 1.5.12 Enable IAM Identity Center in the management account - -1. Login into the management account -2. Make sure the region in the console is set to your home AWS Region -3. Follow the guidance on [enabling AWS IAM Identity Center](https://docs.aws.amazon.com/singlesignon/latest/userguide/get-set-up-for-idc.html) - # 2. Deploy LZA We recommend you first read the [LZA guidance on troubleshooting and known issues](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/troubleshooting.html) prior to running the installation. diff --git a/post-deployment.md b/post-deployment.md index 1116790..f988788 100644 --- a/post-deployment.md +++ b/post-deployment.md @@ -2,8 +2,8 @@ ## 4.1 Access AWS IAM Identity Centre and configure your identity source -1. Log into the Operations account that is the delegated adminisration account for AWS IAM Identity Center. -2. If you plan to use the AWS Directory that the reference architecture deploys follow the [IAM Identity centre guidance to configure AD](https://docs.aws.amazon.com/singlesignon/latest/userguide/connectawsad.html). If you plan to use an external IDP follow the IAM Identity centre guidance to configure an external identity provider](https://docs.aws.amazon.com/singlesignon/latest/userguide/manage-your-identity-source-idp.html). +1. Log into the Operations account that is the delegated administration account for AWS IAM Identity Center. +2. If you plan to use the AWS Directory that the reference architecture deploys follow the [IAM Identity centre guidance to configure AD](https://docs.aws.amazon.com/singlesignon/latest/userguide/connectawsad.html). If you plan to use an external IDP follow the [IAM Identity centre guidance to configure an external identity provider](https://docs.aws.amazon.com/singlesignon/latest/userguide/manage-your-identity-source-idp.html). ## 4.2 Configure Multi-factor authentication for IAM Identity Centre: @@ -20,3 +20,82 @@ We recommend the following minimum settings: The breakglass users are highly privileged user accounts. Login to the management and follow the [AWS IAM documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable.html) to configure MFA on both breakglass accounts. We recommend that you use hardware MFA for these accounts. + +## 4.4 Configure Application Load Balancer forwarding + +Since the `1.7.0-a` release of the configuration, Application Load Balancers are deployed in the Perimeter VPC. Sample configuration is also provided to automate the deployment of Application Load Balancers in workload accounts. AWS ALBs are published using DNS names which resolve to backing IPs which could silently change at any time due to a scaling event, maintenance, or a hardware failure. While published as a DNS name, ALBs can only target IP addresses. This presents a challenge as we need the ALBs in the perimeter account to target ALB's in the various back-end workload accounts. + +ALB Forwarding solves this problem by executing a small snippet of code every 60 seconds which updates managed ALB listeners with any IP changes, ensuring any managed flows do not go offline. This removes the requirement to leverage a 3rd party appliance to perform NAT to a DNS name. + +### Architecture Overview + +![ALB Forwarding Architecture](./architecture-doc/images/alb-forwarding-architecture.png "ALB Forwarding Architecture") + +### Deploying ALB Forwarding + +The CloudFormation stack to deploy the ALB forwarding is provided in `customizations/AlbIpForwardingStack.template.json`. The configuration to deploy this stack to the Perimeter account is provided in `customizations-config.yaml`. This stack creates a new DynamoDB table named `-Alb-Ip-Forwarding-vpc-*` in the Perimeter account. + +### How do I configure an ALB Forwarding Rule? + +When using the default configuration file, an external ALB is already provisioned in the Perimeter account with a listener on port 443. For each application that needs to be published, a record needs to be added to the DynamoDB table, see sample below. + +Records can be added to the table for any ALB in the account running the ALB Forwarding component (by default, the Perimeter account). Records can be added at any time. DynamoDB change logs will trigger the initial creation of the appropriate target group(s) and IP addresses will be verified and updated every 60 seconds thereafter. + +#### Sample JSON to add an entry to the ALB Forwarding table + +__Note__: The sample below is in standard JSON format, not DynamoDB JSON. When adding an entry via the console, ensure that __JSON view__ is selected and that __View DynamoDB JSON__ is disabled. + +```json +{ + "id": "App1", + "targetAlbDnsName": "internal-Core-mydevacct1-alb-123456789.ca-central-1.elb.amazonaws.com", + "targetGroupDestinationPort": 443, + "targetGroupProtocol": "HTTPS", + "vpcId": "vpc-0a6f44a80514daaaf", + "rule": { + "sourceListenerArn": "arn:aws:elasticloadbalancing:ca-central-1:123456789012:listener/app/Public-DevTest/b1b12e7a0c412bf3/ef9b022a4fdd8bdf", + "condition": { + "paths": ["/img/*", "/myApp2"], + "hosts": ["aws.amazon.com"], + "priority": 30 + } + } +} +``` + +- `id` is any unique text +- `targetAlbDnsName` is the DNS address for the internal ALB for this application (in workload account) +- `vpcId` is the vpc ID containing the external ALB (in perimeter account) +- `sourceListenerArn` is the ARN of the listener of the external ALB (in Perimeter account) +- `paths` and `hosts` are both optional, but one of the two must be supplied +- `priority` must be unique and is used to order the listener rules. Priorities should be spaced at least 40 apart to allow for easy insertion of new applications and forwarder rules. +- the provided `targetAlbDnsName` must resolve to addresses within a [supported](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html) IP address space. + +### Troubleshooting ALB forwarding +For tips on troubleshooting issues with ALB forwarding rules see the [FAQ about Application Load Balancers Forwarding](./documentation/FAQ.md#Application-Load-Balancers-Forwarding) + +## 4.5 Enable security controls in every region + +During the initial installation only the home region was enabled. We highly recommend guardrail deployment for all AWS regions that are enabled by default. For more details see the [FAQ about AWS Regions](./documentation/FAQ.md#aws-regions). + +Edit the `global-config.yaml` file and un-comment all regions under the `enabledRegions` property. Commit and push your change to your CodeCommit repository and release the Accelerator pipeline. +``` +enabledRegions: + - *HOME_REGION + - "ap-northeast-1" + - "ap-northeast-2" + - "ap-northeast-3" + - "ap-south-1" + - "ap-southeast-1" + - "ap-southeast-2" + - "eu-central-1" + - "eu-north-1" + - "eu-west-1" + - "eu-west-2" + - "eu-west-3" + - "sa-east-1" + - "us-east-1" + - "us-east-2" + - "us-west-1" + - "us-west-2" +``` diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..7221a80 --- /dev/null +++ b/readme.md @@ -0,0 +1,24 @@ +# Canadian Centre for Cyber Security (CCCS) Cloud Medium (CCCS Medium) + +## Overview +**The Landing Zone Accelerator on AWS (LZA)** for _Canadian Centre for Cyber Security (CCCS) Cloud Medium_ is an industry specific deployment of the [Landing Zone Accelerator on AWS](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) solution designed in collaboration with our national security; defence; national law enforcement; and federal, provincial, and municipal government customers to accelerate compliance with their strict and unique security and compliance requirements. The _Canadian Centre for Cyber Security Cloud Medium (CCCS Medium) Reference Architecture_ is a comprehensive, multi-account AWS cloud architecture targeting sensitive level workloads. It was designed to help customers address central identity and access management, governance, data security, comprehensive logging, and network design/segmentation in alignment with security frameworks such as NIST 800-53, ITSG-33, FEDRAMP Moderate, CCCS-Medium, IRAP, and other [sensitive][sensitive] or medium level security profiles. + +Please refer to the CCCS Medium [Reference Architecture document](./architecture-doc/readme.md) for the full detailed design. + +## Deployment overview +AWS developed the sample config files herein for use with the Landing Zone Accelerator on AWS (LZA) solution. Using these sample config files with LZA will automate the deployment of [CCCS Medium](https://www.canada.ca/en/government/system/digital-government/digital-government-innovations/cloud-services/government-canada-security-control-profile-cloud-based-it-services.html) (formerly PBMM) security controls. + +LZA will deploy an opinionated architecture that has been designed in consultation with CCCS and Government of Canada’s Treasury Board Secretariat. Inheriting the controls from the [CCCS assessment of AWS](https://aws.amazon.com/compliance/services-in-scope/CCCS/) and deploying additional controls using LZA with the sample config files allow customers to meet up to 70% of the controls that have a technical element. This reduces security control implementation time, allowing customers to focus on operational capabilities and the evidentiary exercise in a [Security Assessment and Authorization](https://www.cyber.gc.ca/en/guidance/guidance-cloud-security-assessment-and-authorization-itsp50105) (SA&A) process like that used by the Government of Canada. + +The sample config files define a log retention period of 2 years based on [guidance](https://www.canada.ca/en/government/system/digital-government/online-security-privacy/event-logging-guidance.html) provided by the Treasury Board Secretariat. Customers are encouraged to consider defining longer retention periods, such as 10 years, so that you'll have the data you need to investigate and reconstruct events long after they occur. + +Customers are encouraged to work with their local AWS Account Teams to learn more about customizing this configuration, to learn more about the CCCS-Medium reference architecture, and the Landing Zone Accelerator on AWS solution. + +**NOTE: The initial release of the CCCS-Medium LZA sample configuration files included as part of LZA v1.3 do not yet fully automate the delivery of this architecture. This will be resolved in subsequent LZA releases.** + +- [Configuration files and installation instructions](./install.md) +- [Instructions for version updates](./update-instructions.md) +- [FAQ](./documentation/FAQ.md) + + +[sensitive]: https://www.canada.ca/en/government/system/digital-government/modern-emerging-technologies/cloud-services/government-canada-security-control-profile-cloud-based-it-services.html#toc4 \ No newline at end of file diff --git a/reference-artifacts/organizations-setup/setup-prerequisites.yaml b/reference-artifacts/organizations-setup/setup-prerequisites.yaml index c3c31ca..7681923 100644 --- a/reference-artifacts/organizations-setup/setup-prerequisites.yaml +++ b/reference-artifacts/organizations-setup/setup-prerequisites.yaml @@ -47,7 +47,7 @@ Resources: Properties: AccountName: Audit Email: !Ref SecurityAccountEmail - ParentIds: + ParentIds: - !Ref SecurityOU RoleName: OrganizationAccountAccessRole @@ -57,7 +57,7 @@ Resources: Properties: AccountName: LogArchive Email: !Ref LogArchiveAccountEmail - ParentIds: + ParentIds: - !Ref SecurityOU RoleName: OrganizationAccountAccessRole @@ -67,7 +67,7 @@ Resources: # Properties: # AccountName: LandingZoneDeployment # Email: !Ref LandingZoneDeploymentAccountEmail - # ParentIds: + # ParentIds: # - !Ref LandingZoneDeploymentOU # RoleName: OrganizationAccountAccessRole @@ -75,10 +75,10 @@ Outputs: OrgId: Description: Organisation ID Value: !Ref AWSOrganization - Export: + Export: Name: OrgId OrgRootId: Description: Organisation ID Value: !GetAtt AWSOrganization.RootId - Export: + Export: Name: OrgRootId \ No newline at end of file diff --git a/source/alb-forwarder/alb-ip-monitor.ts b/source/alb-forwarder/alb-ip-monitor.ts new file mode 100644 index 0000000..d87b6f7 --- /dev/null +++ b/source/alb-forwarder/alb-ip-monitor.ts @@ -0,0 +1,211 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import * as dns from 'dns'; + +import { DynamoDBDocument, PutCommandInput, ScanCommandInput } from '@aws-sdk/lib-dynamodb'; +import { DynamoDB } from '@aws-sdk/client-dynamodb'; + +import { + DeregisterTargetsCommandInput, + DeregisterTargetsCommandOutput, + ElasticLoadBalancingV2, + RegisterTargetsCommandInput, + RegisterTargetsCommandOutput, +} from '@aws-sdk/client-elastic-load-balancing-v2'; + + +const routeLookupTable = process.env['LOOKUP_TABLE'] ?? ''; + +const docClient = DynamoDBDocument.from(new DynamoDB()); +const elbv2 = new ElasticLoadBalancingV2({ + logger: console, +}); + +export interface dnsForwardItem { + id: string; + ipAddList?: string[]; + ipRemoveList?: string[]; + dnsLookupIps?: string[]; + targetAlbDnsName: string; + targetGroupDestinationPort: number; + metadata: { + targetGroupArn: string; + targetGroupIpAddresses: string[]; + }; +} + +const scanTable = async (tableName: string): Promise => { + console.log(`Scanning route lookup table ${routeLookupTable}`); + const scanParams: ScanCommandInput = { + TableName: tableName, + }; + const scanResults: Record[] = []; + let results; + do { + results = await docClient.scan(scanParams); + results.Items?.forEach(item => scanResults.push(item)); + scanParams.ExclusiveStartKey = results.LastEvaluatedKey; + } while (typeof results.LastEvaluatedKey != 'undefined'); + + return scanResults as dnsForwardItem[]; +}; + +const registerTargets = async ( + targetGroupArn: string, + ips: string[], + port: number, +): Promise => { + const targets = ips.map(ip => { + return { + Id: ip, + Port: port, + AvailabilityZone: 'all', + }; + }); + + const registerTargetsParams: RegisterTargetsCommandInput = { + TargetGroupArn: targetGroupArn, + Targets: targets, + }; + + return elbv2.registerTargets(registerTargetsParams); +}; + +const deregisterTargets = async (targetGroupArn: string, ips: string[]): Promise => { + console.log(`Deregistering IP addresses ${JSON.stringify(ips)} from target group ${targetGroupArn}`); + const targets = ips.map(ip => { + return { + Id: ip, + }; + }); + + const deregisterTargetsParams: DeregisterTargetsCommandInput = { + TargetGroupArn: targetGroupArn, + Targets: targets, + }; + + return elbv2.deregisterTargets(deregisterTargetsParams); +}; + +const dnslookup = async (host: string): Promise => { + return new Promise((resolve, reject) => { + dns.lookup(host, { all: true, family: 4 }, (err, addresses) => { + if (err) { + reject(err); + } else { + resolve( + addresses + .map(item => { + return item.address; + }) + .sort(), + ); + } + }); + }); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const removeItem = (arr: any[], item: any) => { + const index = arr.indexOf(item); + if (index > -1) { + arr.splice(index, 1); + } + return arr; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const putRecord = async (record: any) => { + const putParams: PutCommandInput = { + TableName: routeLookupTable, + Item: record, + }; + return docClient.put(putParams); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars +export const handler = async (_event: any, _context: any) => { + const targetGroupRecords = (await scanTable(routeLookupTable)) ?? []; + + for (const targetGroupRecord of targetGroupRecords) { + try { + // Get Hostname Lookup + targetGroupRecord.dnsLookupIps = []; + try { + targetGroupRecord.dnsLookupIps = await dnslookup(targetGroupRecord.targetAlbDnsName); + } catch (err) { + console.log(err); + } + // Get Ip Addresses to add to current IP List + targetGroupRecord.ipAddList = + targetGroupRecord.dnsLookupIps?.filter(ip => { + return !targetGroupRecord.metadata?.targetGroupIpAddresses?.includes(ip); + }) ?? []; + // Get Ip addresses to remove from current list + targetGroupRecord.ipRemoveList = + targetGroupRecord.metadata?.targetGroupIpAddresses?.filter(ip => { + return !targetGroupRecord.dnsLookupIps?.includes(ip); + }) ?? []; + + if (targetGroupRecord.ipAddList?.length > 0) { + // Register new ips + console.log( + `Registering new ips ${JSON.stringify(targetGroupRecord.ipAddList)} to target ${ + targetGroupRecord.metadata.targetGroupArn + } with port ${targetGroupRecord.targetGroupDestinationPort}`, + ); + await registerTargets( + targetGroupRecord.metadata.targetGroupArn, + targetGroupRecord.ipAddList, + targetGroupRecord.targetGroupDestinationPort, + ); + // Add new ips to record + targetGroupRecord.metadata.targetGroupIpAddresses.push(...targetGroupRecord.ipAddList); + } else { + console.log('No new Ip addresses to register'); + } + if (targetGroupRecord.ipRemoveList?.length > 0) { + // Deregister old ips + console.log( + `Deregistering old ip addresses ${JSON.stringify( + targetGroupRecord.ipRemoveList, + )} from target group targetGroupRecord.metadata.targetGroupArn`, + ); + await deregisterTargets(targetGroupRecord.metadata.targetGroupArn, targetGroupRecord.ipRemoveList); + // Remove old ips from record + targetGroupRecord.ipRemoveList?.forEach(ip => { + console.log(targetGroupRecord.metadata.targetGroupIpAddresses, ip); + targetGroupRecord.metadata.targetGroupIpAddresses = removeItem( + targetGroupRecord.metadata.targetGroupIpAddresses, + ip, + ); + }); + } else { + console.log('No old ip addresses to deregister'); + } + + // Delete add, remove, and dnslookup list before writing to table + delete targetGroupRecord.ipAddList; + delete targetGroupRecord.ipRemoveList; + delete targetGroupRecord.dnsLookupIps; + console.log('Writing record to DDB table ', JSON.stringify(targetGroupRecord, null, 4)); + await putRecord(targetGroupRecord); + } catch (err) { + console.log('There was a problem updating the record ', JSON.stringify(targetGroupRecord, null, 4)); + + console.log(err); + } + } + return 'Done'; +}; diff --git a/source/alb-forwarder/alb-target-record-monitor.ts b/source/alb-forwarder/alb-target-record-monitor.ts new file mode 100644 index 0000000..0c19725 --- /dev/null +++ b/source/alb-forwarder/alb-target-record-monitor.ts @@ -0,0 +1,396 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + + +import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'; +import { DynamoDB } from '@aws-sdk/client-dynamodb'; +import { unmarshall } from '@aws-sdk/util-dynamodb'; + +import { + CreateRuleCommandInput, + DescribeListenersCommandInput, + ElasticLoadBalancingV2, + ModifyRuleCommandInput, + ProtocolEnum, + TargetTypeEnum, +} from '@aws-sdk/client-elastic-load-balancing-v2'; + +import * as _ from 'lodash'; + +const elbv2 = new ElasticLoadBalancingV2(); +const docClient = DynamoDBDocument.from(new DynamoDB()); +const ddbTable = process.env['LOOKUP_TABLE'] || ''; + +const sleep = (ms: number) => { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +}; + +const createTargetGroup = async (name: string, port: number, vpcId: string, protocol: ProtocolEnum) => { + const targetGroupParams = { + Name: name, + Port: port, + Protocol: protocol, + VpcId: vpcId, + TargetType: TargetTypeEnum.IP, + }; + + return elbv2.createTargetGroup(targetGroupParams); +}; + +const enableTargetStickyness = async (targetGroupArn: string) => { + const targetGroupAttributesParams = { + Attributes: [{ Key: 'stickiness.enabled', Value: 'true' }], + TargetGroupArn: targetGroupArn, + }; + + return elbv2.modifyTargetGroupAttributes(targetGroupAttributesParams); +}; + +const isValidPriority = async (priority: number, listenerArn: string) => { + const ruleParams = { + ListenerArn: listenerArn, + }; + + const ruleList = await elbv2.describeRules(ruleParams); + const priorityExists = + ruleList.Rules?.filter(rule => { + return rule.Priority === priority.toString(); + }) || []; + return priorityExists.length === 0; +}; + +const listenerExists = async (listenerArn: string): Promise => { + try { + const listenerParams: DescribeListenersCommandInput = { + ListenerArns: [listenerArn], + }; + await elbv2.describeListeners(listenerParams); + return Promise.resolve(true); + } catch (err) { + console.log(err); + return Promise.resolve(false); + } +}; + +const createListenerRule = async ( + listenerArn: string, + paths: string[], + hosts: string[], + targetGroupArn: string, + priority: number, +) => { + console.log('trying to create listener rule'); + console.log(hosts, paths, listenerArn, targetGroupArn, priority); + const ruleParams: CreateRuleCommandInput = { + Actions: [ + { + TargetGroupArn: targetGroupArn, + Type: 'forward', + }, + ], + ListenerArn: listenerArn, + Priority: priority, + Conditions: [], + }; + + if (paths?.length > 0) { + const pathConfig = { + Field: 'path-pattern', + Values: paths, + }; + ruleParams.Conditions?.push(pathConfig); + } + + if (hosts?.length > 0) { + const hostConfig = { + Field: 'host-header', + Values: hosts, + }; + ruleParams.Conditions?.push(hostConfig); + } + + return elbv2.createRule(ruleParams); +}; + +const updateListenerRule = async (ruleArn: string, paths: string[], hosts: string[], targetGroupArn: string) => { + const ruleParams: ModifyRuleCommandInput = { + Actions: [ + { + TargetGroupArn: targetGroupArn, + Type: 'forward', + }, + ], + RuleArn: ruleArn, + Conditions: [], + }; + + if (paths?.length > 0) { + const pathConfig = { + Field: 'path-pattern', + Values: paths, + }; + ruleParams?.Conditions?.push(pathConfig); + } + + if (hosts?.length > 0) { + const hostConfig = { + Field: 'host-header', + Values: hosts, + }; + ruleParams?.Conditions?.push(hostConfig); + } + + return elbv2.modifyRule(ruleParams); +}; + +const deleteListenerRule = async (ruleArn: string) => { + const ruleParams = { + RuleArn: ruleArn, + }; + + return elbv2.deleteRule(ruleParams); +}; + +const deleteTargetGroup = async (targetGroupArn: string) => { + const targetGroupParams = { + TargetGroupArn: targetGroupArn, + }; + + return elbv2.deleteTargetGroup(targetGroupParams); +}; + +const updateRulePriority = async (ruleArn: string, priority: number) => { + const rulePriorityParams = { + RulePriorities: [ + { + Priority: priority, + RuleArn: ruleArn, + }, + ], + }; + return elbv2.setRulePriorities(rulePriorityParams); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const putRecord = async (table: string, record: any) => { + const putParams = { + TableName: table, + Item: record, + }; + return docClient.put(putParams); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const targetGroupChange = (oldRecord: any, newRecord: any) => { + const oldTargetGroupAttributes = { + vpcId: oldRecord.vpcId, + destinationPort: oldRecord.targetGroupDestinationPort, + protocol: oldRecord.targetGroupProtocol, + }; + + const newTargetGroupAttributes = { + vpcId: newRecord.vpcId, + destinationPort: newRecord.targetGroupDestinationPort, + protocol: newRecord.targetGroupProtocol, + }; + return !_.isEqual(oldTargetGroupAttributes, newTargetGroupAttributes); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const listenerRulesChange = (oldRecord: any, newRecord: any) => { + const oldListenerRules = { + sourceListenerArn: oldRecord.rule.sourceListenerArn, + priority: oldRecord.rule.condition.priority, + paths: oldRecord.rule.condition.paths?.sort(), + hosts: oldRecord.rule.condition.hosts?.sort(), + }; + + const newListenerRules = { + sourceListenerArn: newRecord.rule.sourceListenerArn, + priority: newRecord.rule.condition.priority, + paths: newRecord.rule.condition.paths?.sort(), + hosts: newRecord.rule.condition.hosts?.sort(), + }; + + return !_.isEqual(oldListenerRules, newListenerRules); +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const priorityChange = (oldRecord: any, newRecord: any) => { + const oldPriority = oldRecord.rule.condition.priority; + const newPriority = newRecord.rule.condition.priority; + + return !(oldPriority === newPriority); +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const createRecordHandler = async (record: any) => { + console.log('Record creation detected.'); + try { + if (!(await listenerExists(record.rule.sourceListenerArn))) { + throw new Error(`The ALB Listener ARN: ${record.rule.sourceListenerArn} does not exist. Exiting`); + } + + console.log('Checking if priority is valid'); + if (!(await isValidPriority(record.rule.condition.priority, record.rule.sourceListenerArn))) { + throw new Error( + `The priority ${record.rule.condition.priority.toString()} matches an existing rule priority on the listener arn ${ + record.rule.sourceListenerArn + }. Priorities must not match. Exiting`, + ); + } + const targetGroup = await createTargetGroup( + record.id, + record.targetGroupDestinationPort, + record.vpcId, + record.targetGroupProtocol, + ); + + const targetGroupArn = targetGroup?.TargetGroups?.[0].TargetGroupArn ?? ''; + + await enableTargetStickyness(targetGroupArn); + + const rule = await createListenerRule( + record.rule.sourceListenerArn, + record.rule.condition.paths, + record.rule.condition.hosts, + targetGroupArn, + record.rule.condition.priority, + ); + const ruleArn = rule?.Rules?.[0].RuleArn ?? ''; + if (!targetGroupArn || !ruleArn) { + throw new Error( + `There was an error getting the target group arn or listener rule arn. \nTarget Group Arn: ${targetGroupArn}\nRule Arn: ${ruleArn}`, + ); + } + record.metadata = { + targetGroupArn, + ruleArn, + targetGroupIpAddresses: [], + }; + await putRecord(ddbTable, record); + console.log('Added metadata to table'); + return record; + } catch (err) { + console.log('There was a problem creating resources for the following record', JSON.stringify(record, null, 4)); + throw err; + } +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const deleteRecordHandler = async (record: any) => { + try { + console.log(`Deleting listener rule and target group for ${record.id}`); + + await deleteListenerRule(record.metadata.ruleArn); + console.log('Deleted listener rule.'); + } catch (err) { + console.log(err); + console.log('Could not delete listener rule for record. Continuing...', JSON.stringify(record, null, 4)); + } + try { + await deleteTargetGroup(record.metadata.targetGroupArn); + console.log('Deleted target group'); + return; + } catch (err) { + console.log('Could not delete target group for record', JSON.stringify(record, null, 4)); + console.log(err); + } +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const updateRecordHandler = async (newRecord: any, oldRecord: any) => { + try { + console.log(`The record with id ${newRecord.id} was updated. Performing comparison.`); + if (!(await listenerExists(newRecord.rule.sourceListenerArn))) { + throw new Error(`The ALB Listener ARN: ${newRecord.rule.sourceListenerArn} does not exist. Exiting`); + } + + const newRecordClone = _.cloneDeep(newRecord); + const oldRecordClone = _.cloneDeep(oldRecord); + delete newRecordClone.metadata; + delete oldRecordClone.metadata; + + if (_.isEqual(newRecordClone, oldRecordClone)) { + console.log(`Update Record handler found no changes made for record with Id ${newRecord.id}`); + return; + } + + if (!oldRecord.metadata) { + console.log('No previous metadata detected for record. Creating metadata based off of new entry'); + await createRecordHandler(newRecord); + return; + } + + if (listenerRulesChange(oldRecord, newRecord)) { + console.log(`Detected a listener rule change. Modifying rule ${newRecord.metadata.ruleArn}`); + await updateListenerRule( + newRecord.metadata.ruleArn, + newRecord.rule.condition.paths, + newRecord.rule.condition.hosts, + newRecord.metadata.targetGroupArn, + ); + } + if (priorityChange(oldRecord, newRecord)) { + if (!(await isValidPriority(newRecord.rule.condition.priority, newRecord.rule.sourceListenerArn))) { + throw new Error( + `The priority ${newRecord.rule.condition.priority.toString()} matches an existing rule priority on the listener arn ${ + newRecord.rule.sourceListenerArn + }. Priorities must not match.`, + ); + } + await updateRulePriority(newRecord.metadata.ruleArn, newRecord.rule.condition.priority); + } + if (targetGroupChange(oldRecord, newRecord)) { + console.log( + `Detected a target group change. deleting target group ${newRecord.metadata.targetGroupArn} and creating a new target group`, + ); + await deleteRecordHandler(newRecord); + await sleep(10000); + await createRecordHandler(newRecord); + } + } catch (err) { + console.log('There was a problem updating a target group or listener rule for the records:'); + console.log('Old Record: ', JSON.stringify(oldRecord, null, 4)); + console.log('New Record: ', JSON.stringify(newRecord, null, 4)); + throw err; + } +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars +export const handler = async (event: any, _context: any) => { + console.log(JSON.stringify(event, null, 2)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const records = event.Records.map((record: any) => { + if (record.dynamodb.OldImage) { + record.dynamodb.OldImage = unmarshall(record.dynamodb.OldImage); + } + if (record.dynamodb.NewImage) { + record.dynamodb.NewImage = unmarshall(record.dynamodb.NewImage); + } + return record; + }); + + for (const record of records) { + if (record.eventName === 'INSERT') { + await createRecordHandler(record.dynamodb.NewImage); + } + if (record.eventName === 'MODIFY') { + await updateRecordHandler(record.dynamodb.NewImage, record.dynamodb.OldImage); + } + + if (record.eventName === 'REMOVE') { + await deleteRecordHandler(record.dynamodb.OldImage); + } + } +}; diff --git a/source/alb-forwarder/index.ts b/source/alb-forwarder/index.ts new file mode 100644 index 0000000..da8eb92 --- /dev/null +++ b/source/alb-forwarder/index.ts @@ -0,0 +1,15 @@ +/** + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +export { handler as albIpMonitor } from './alb-ip-monitor'; +export { handler as albTargetRecordMonitor } from './alb-target-record-monitor'; diff --git a/source/custom-config-rules/attach-ec2-instance-profile/index.js b/source/custom-config-rules/attach-ec2-instance-profile/index.js index 1955774..2075d4b 100644 --- a/source/custom-config-rules/attach-ec2-instance-profile/index.js +++ b/source/custom-config-rules/attach-ec2-instance-profile/index.js @@ -1,7 +1,5 @@ -const AWS = require('aws-sdk'); -AWS.config.logger = console; - -const config = new AWS.ConfigService(); +const { ConfigServiceClient, PutEvaluationsCommand } = require("@aws-sdk/client-config-service"); +const client = new ConfigServiceClient(); const APPLICABLE_RESOURCES = ['AWS::EC2::Instance']; @@ -23,18 +21,20 @@ exports.handler = async function(event, context) { console.debug(`Evaluation`); console.debug(JSON.stringify(evaluation, null, 2)); - await config.putEvaluations({ + const payload = { ResultToken: event.resultToken, Evaluations: [ { ComplianceResourceId: configurationItem.resourceId, ComplianceResourceType: configurationItem.resourceType, ComplianceType: evaluation.complianceType, - OrderingTimestamp: configurationItem.configurationItemCaptureTime, + OrderingTimestamp: new Date(configurationItem.configurationItemCaptureTime), Annotation: evaluation.annotation, }, ], - }).promise(); + }; + const putEvaluationsCommand = new PutEvaluationsCommand(payload); + await client.send(putEvaluationsCommand); }; async function evaluateCompliance(props) { diff --git a/source/custom-config-rules/ec2-instance-profile-permissions/index.js b/source/custom-config-rules/ec2-instance-profile-permissions/index.js index fd88ba2..a143e89 100644 --- a/source/custom-config-rules/ec2-instance-profile-permissions/index.js +++ b/source/custom-config-rules/ec2-instance-profile-permissions/index.js @@ -1,12 +1,10 @@ -const AWS = require('aws-sdk'); -AWS.config.logger = console; - -const config = new AWS.ConfigService(); +const { ConfigServiceClient, PutEvaluationsCommand } = require("@aws-sdk/client-config-service"); +const client = new ConfigServiceClient(); const APPLICABLE_RESOURCES = ['AWS::IAM::Role']; exports.handler = async function (event, context) { - console.log(`Custom Rule for checking policies attached to IAM role used under Instance Profile...`); + console.log(`Custom Rule for checking Policies attached to IAM role used under Instance Profile...`); console.log(JSON.stringify(event, null, 2)); const invokingEvent = JSON.parse(event.invokingEvent); @@ -28,25 +26,24 @@ exports.handler = async function (event, context) { console.debug(`Evaluation`); console.debug(JSON.stringify(evaluation, null, 2)); - await config - .putEvaluations({ - ResultToken: event.resultToken, - Evaluations: [ - { - ComplianceResourceId: configurationItem.resourceId, - ComplianceResourceType: configurationItem.resourceType, - ComplianceType: evaluation.complianceType, - OrderingTimestamp: configurationItem.configurationItemCaptureTime, - Annotation: evaluation.annotation, - }, - ], - }) - .promise(); + const payload = { + ResultToken: event.resultToken, + Evaluations: [ + { + ComplianceResourceId: configurationItem.resourceId, + ComplianceResourceType: configurationItem.resourceType, + ComplianceType: evaluation.complianceType, + OrderingTimestamp: new Date(configurationItem.configurationItemCaptureTime), + Annotation: evaluation.annotation, + }, + ], + }; + const putEvaluationsCommand = new PutEvaluationsCommand(payload); + await client.send(putEvaluationsCommand); }; async function evaluateCompliance(props) { const { configurationItem, ruleParams } = props; - if (!APPLICABLE_RESOURCES.includes(configurationItem.resourceType)) { return { complianceType: 'NOT_APPLICABLE', @@ -57,6 +54,11 @@ async function evaluateCompliance(props) { complianceType: 'NOT_APPLICABLE', annotation: 'The configuration item was deleted and could not be validated', }; + } else if (configurationItem.configurationItemStatus === 'ResourceNotRecorded' || configurationItem.configurationItemStatus === 'ResourceDeletedNotRecorded') { + return { + complianceType: 'NOT_APPLICABLE', + annotation: 'The configuration item is not recorded in this region and need not be validated', + }; } if (configurationItem.configuration && !configurationItem.configuration.instanceProfileList) { @@ -112,4 +114,4 @@ async function evaluateCompliance(props) { complianceType: 'NON_COMPLIANT', annotation: 'The resource logging destination is incorrect', }; -} +} \ No newline at end of file diff --git a/update-instructions.md b/update-instructions.md index 51db77f..d12d517 100644 --- a/update-instructions.md +++ b/update-instructions.md @@ -2,23 +2,30 @@ To upgrade your LZA to the latest version you should follow the [update instructions from the LZA implementation guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/update-the-solution.html). The current page contains additional instructions specific to this reference configuration. +Landing Zone Accelerator and reference configurations have their own release cycle and versioning scheme. +- Versions of the configuration files (this repository) aligns with the Landing Zone Accelerator semantic version scheme +- An alphabetic suffix is added to the configuration version when more than one release aligns with the same LZA version (i.e. 1.6.1-a, 1.6.1-b) +- Git Tags are used to track the versions of the configuration files +- Only the main branch is maintained and customers should always install the latest version of LZA and configurations +- For example `1.6.0-a` is the first release of the configuration after the LZA v1.6.0 release. It has been tested with LZA v1.6.0 and must be applied on a LZA environment with v1.6.0 as a minimum + ### Update Preparation Before proceeding with the update you should carefully review the release notes for every version and identify any configuration changes that are mandatory or recommended. - Review the [LZA release notes](https://github.com/awslabs/landing-zone-accelerator-on-aws/releases) -- Review new configuration items from the LZA release notes, assess the new defaults and integrate them into your configuration - Review configuration changes to the [default configuration files](./config/) and determine which change you need to apply to your configuration - Review this configuration [CHANGELOG](CHANGELOG.md) +**Tip** To facilitate the identification of configuration changes between two releases of the configuration files, you can use the built-in comparison of GitHub. e.g. This link will show differences between the `v1.6.0-a` and `v1.6.1-a` tags: https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-cccs-medium/compare/release/v1.6.0-a...release/v1.6.1-a + ### General update steps 1. Login to your Organization Management (root) AWS account with administrative privileges 2. Either: a) Ensure a valid Github token is stored in secrets manager ([per the installation guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/prerequisites.html#create-a-github-personal-access-token-and-store-in-secrets-manager)), or b) Ensure the latest release is in a valid branch of CodeCommit in the Organization Management account -3. Before updating: Run the pipeline with the current version and confirm a sucessful execution +3. Before updating: Run the pipeline with the current version and confirm a successful execution 4. Review and implement any relevant tasks noted in the **Update Preparation** section above 5. Update the configuration files in the `aws-accelerator-config` **CodeCommit** repository as outlined in the **Update Preparation** section above 6. Sign in to the AWS CloudFormation console, select your existing Landing Zone Accelerator on AWS CloudFormation stack and Update the stack with the latest template available from the release page. ([refer to the LZA implementation guide for detailed steps](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/update-the-solution.html)) 7. When reviewing the Stack Parameters, make sure to update the `RepositoryBranchName` value to point to the branch of the latest release (i.e. release/v.X.Y.Z) 8. Wait for successful execution of the Landing Zone Accelerator stack update and the `AWSAccelerator-Installer` and `AWSAccelerator-Pipeline` pipelines -