---
# Copyright widdix GmbH
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
# OLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
Description: bucketAV Synchronous HTTPS API Add-On for Amazon S3 powered by Sophos (dedicated VPC; scanners run in public subnets)
AWSTemplateFormatVersion: "2010-09-09"
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Required Parameters
        Parameters:
          - BucketAVStackName
          - KeyName
          - ApiKeys
          - DnsConfiguration
      - Label:
          default: DNS Parameters
        Parameters:
          - HostedZoneId
          - DomainName
          - CertificateArn
      - Label:
          default: API Parameters
        Parameters:
          - AccessLogsBucketName
          - AccessLogsPrefix
      - Label:
          default: Scan Parameters
        Parameters:
          - SophosLiveProtectionCloudLookups
      - Label:
          default: VPC Parameters
        Parameters:
          - VpcCidrBlock
          - VpcSubnetCidrBits
          - SSHIngressCidrIp
          - FlowLogRetentionInDays
          - ApiIngressCidrIp
      - Label:
          default: EC2 Parameters
        Parameters:
          - InstanceType
          - VolumeSize
          - VolumeIops
          - VolumeThroughput
          - LogsRetentionInDays
          - SystemsManagerAccess
      - Label:
          default: Auto Scaling Group Parameters
        Parameters:
          - AutoScalingMinSize
          - AutoScalingMaxSize
      - Label:
          default: Permissions Parameters
        Parameters:
          - ManagedPolicyArns
          - S3BucketRestriction
          - S3ObjectRestriction
          - KMSKeyRestriction
          - PermissionsBoundary
      - Label:
          default: Lambda Parameters
        Parameters:
          - AutoScalingGroupCalculatorFunctionReservedConcurrentExecutions
Parameters:
  BucketAVStackName:
    Type: String
    Description: CloudFormation stack name of bucketAV (if you followed our docs, the name is bucketav, or s3-virusscan for older installations)
  ApiKeys:
    Type: String
    Description: API keys for authenticating client requests using Bearer Authentication. Separate API keys with a comma (e.g., key1,key2).
    NoEcho: true
  DnsConfiguration:
    Type: String
    Default: AUTO_ROUTE_53
    AllowedValues:
      - AUTO_ROUTE_53
      - MANUAL
    Description: Do you want to configure Route 53 and the Certificate Manager public certificate automatically or do you prefer a manual approach?
  HostedZoneId:
    Type: String
    Default: ""
    Description: Route 53 public hosted zone ID (required if DnsConfiguration:=AUTO_ROUTE_53).
  DomainName:
    Type: String
    Default: ""
    Description: Domain name added to Route 53 public hosted zone and Certificate Manager public certificate (required if DnsConfiguration:=AUTO_ROUTE_53).
  CertificateArn:
    Type: String
    Default: ""
    Description: ACM public certificate ARN (required if DnsConfiguration:=MANUAL).
  AccessLogsBucketName:
    Type: String
    Default: ""
    Description: Optional bucket name to store access logs (leave empty to disable; bucket polic must allow access for log delivery as described here https://docs.aws.amazon.com/elasticloadbalancing/latest/application/enable-access-logging.html#attach-bucket-policy).
  AccessLogsPrefix:
    Type: String
    Default: ""
    Description: Optional access logs prefix.
  AutoScalingMinSize:
    Type: Number
    Default: 2
    ConstraintDescription: Must be >= 2
    Description: Minimum number of EC2 instances scanning files.
    MinValue: 2
  AutoScalingMaxSize:
    Type: Number
    Default: 2
    ConstraintDescription: Must be >= 2
    Description: Maximum number of EC2 instances scanning files.
    MinValue: 2
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: "Name of the EC2 key pair to log in via SSH (username: ec2-user)."
  LogsRetentionInDays:
    Type: Number
    Default: 14
    AllowedValues:
      - 1
      - 3
      - 5
      - 7
      - 14
      - 30
      - 60
      - 90
      - 120
      - 150
      - 180
      - 365
      - 400
      - 545
      - 731
      - 1827
      - 3653
    Description: Specifies the number of days you want to retain log events.
  SystemsManagerAccess:
    Type: String
    Default: "false"
    AllowedValues:
      - "true"
      - "false"
    Description: Enable AWS Systems Manager Session Manager to connect to the EC2 instances. To fully enable SSM, add arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore to the ManagedPolicyArns configuration parameter as well.
  ManagedPolicyArns:
    Type: String
    Default: ""
    Description: Optional comma-delimited list of IAM managed policy ARNs to attach to the IAM role of the EC2 instances.
  S3BucketRestriction:
    Type: CommaDelimitedList
    Default: "*"
    Description: Restrict access to specific S3 buckets (e.g. arn:aws:s3:::bucket-a,arn:aws:s3:::bucket-b or * to allow access to all S3 buckets).
  S3ObjectRestriction:
    Type: CommaDelimitedList
    Default: "*"
    Description: Restrict access to specific S3 objects (e.g. arn:aws:s3:::bucket-a/*,arn:aws:s3:::bucket-b/* or * to allow access to all S3 objects).
  KMSKeyRestriction:
    Type: CommaDelimitedList
    Default: "*"
    Description: Restrict access to specific KMS keys (e.g. arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab,arn:aws:kms:us-east-1:111122223333:key/0987dcba-09fe-87dc-65ba-ab0987654321 or * to allow access to all KMS keys).
  PermissionsBoundary:
    Type: String
    Default: ""
    Description: Optional IAM policy ARN that will be used as the permissions boundary for all roles.
  AutoScalingGroupCalculatorFunctionReservedConcurrentExecutions:
    Type: Number
    Default: 0
    Description: Maximum number of execution environment instances for the Lambda function (set to 0 to disable; Check out the CloudWatch metric ConcurrentExecutions to get the maximum concurrent invocations of the past).
    MinValue: 0
  SSHIngressCidrIp:
    Type: String
    Default: ""
    Description: "Optional ingress rule allows SSH access from this IP address range (e.g., access from anywhere: 0.0.0.0/0, from single public IP address 91.45.138.21/32)."
  VpcCidrBlock:
    Type: String
    Default: 10.0.0.0/16
    Description: The IPv4 network range for the VPC, in CIDR notation (e.g., 10.0.0.0/16).
  ApiIngressCidrIp:
    Type: String
    Default: 0.0.0.0/0
    Description: "Ingress rule allows HTTP(S) access from this IP address range (e.g., access from anywhere: 0.0.0.0/0, from single public IP address 91.45.138.21/32)."
  VpcSubnetCidrBits:
    Type: Number
    Default: 12
    Description: The number of subnet bits for the CIDR (e.g., a value 8 will create a CIDR with a mask of /24).
    MaxValue: 14
    MinValue: 6
  FlowLogRetentionInDays:
    Type: Number
    Default: 14
    AllowedValues:
      - 0
      - 1
      - 3
      - 5
      - 7
      - 14
      - 30
      - 60
      - 90
      - 120
      - 150
      - 180
      - 365
      - 400
      - 545
      - 731
      - 1827
      - 3653
    Description: Specifies the number of days you want to retain VPC Flow Log events (set to 0 to disable).
  InstanceType:
    Type: String
    Default: m5.large
    AllowedValues:
      - t3a.nano
      - t3a.micro
      - t3a.small
      - t3a.medium
      - t3a.large
      - t3a.xlarge
      - t3a.2xlarge
      - t3.nano
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large
      - t3.xlarge
      - t3.2xlarge
      - m5a.large
      - m5a.xlarge
      - m5a.2xlarge
      - m5a.4xlarge
      - m5a.8xlarge
      - m5a.12xlarge
      - m5a.16xlarge
      - m5a.24xlarge
      - m5.large
      - m5.xlarge
      - m5.2xlarge
      - m5.4xlarge
      - m5.8xlarge
      - m5.12xlarge
      - m5.16xlarge
      - m5.24xlarge
      - m6i.large
      - m6i.xlarge
      - m6i.2xlarge
      - m6i.4xlarge
      - m6i.8xlarge
      - m6i.12xlarge
      - m6i.16xlarge
      - m6i.24xlarge
      - m6i.32xlarge
      - m6a.large
      - m6a.xlarge
      - m6a.2xlarge
      - m6a.4xlarge
      - m6a.8xlarge
      - m6a.12xlarge
      - m6a.16xlarge
      - m6a.24xlarge
      - m6a.32xlarge
      - m6a.48xlarge
      - m7i.large
      - m7i.xlarge
      - m7i.2xlarge
      - m7i.4xlarge
      - m7i.8xlarge
      - m7i.12xlarge
      - m7i.16xlarge
      - m7i.24xlarge
      - m7i.48xlarge
      - m7a.medium
      - m7a.large
      - m7a.xlarge
      - m7a.2xlarge
      - m7a.4xlarge
      - m7a.8xlarge
      - m7a.12xlarge
      - m7a.16xlarge
      - m7a.24xlarge
      - m7a.32xlarge
      - m7a.48xlarge
      - c5.large
      - c5.xlarge
      - c5.2xlarge
      - c5.4xlarge
      - c5.9xlarge
      - c5.12xlarge
      - c5.18xlarge
      - c5.24xlarge
      - c5a.large
      - c5a.xlarge
      - c5a.2xlarge
      - c5a.4xlarge
      - c5a.8xlarge
      - c5a.12xlarge
      - c5a.16xlarge
      - c5a.24xlarge
      - c6i.large
      - c6i.xlarge
      - c6i.2xlarge
      - c6i.4xlarge
      - c6i.8xlarge
      - c6i.12xlarge
      - c6i.16xlarge
      - c6i.24xlarge
      - c6i.32xlarge
      - c6a.large
      - c6a.xlarge
      - c6a.2xlarge
      - c6a.4xlarge
      - c6a.8xlarge
      - c6a.12xlarge
      - c6a.16xlarge
      - c6a.24xlarge
      - c6a.32xlarge
      - c6a.48xlarge
      - c7i.large
      - c7i.xlarge
      - c7i.2xlarge
      - c7i.4xlarge
      - c7i.8xlarge
      - c7i.12xlarge
      - c7i.16xlarge
      - c7i.24xlarge
      - c7i.48xlarge
      - c7a.medium
      - c7a.large
      - c7a.xlarge
      - c7a.2xlarge
      - c7a.4xlarge
      - c7a.8xlarge
      - c7a.12xlarge
      - c7a.16xlarge
      - c7a.24xlarge
      - c7a.32xlarge
      - c7a.48xlarge
      - m5zn.large
      - m5zn.xlarge
      - m5zn.2xlarge
      - m5zn.3xlarge
      - m5zn.6xlarge
      - m5zn.12xlarge
    Description: Specifies the instance type of the EC2 instance.
  SophosLiveProtectionCloudLookups:
    Type: String
    Default: "false"
    AllowedValues:
      - "true"
      - "false"
    Description: Live Protection cloud lookups use Sophos' SXL technology and infrastructure to enable the antivirus engine to determine whether a suspicious file is malicious or clean by querying Sophos's extensive database of both malware and clean files. SXL improves detection rates and lowers false-positives. The file hash is shared with Sophos if you enable this feature!
  VolumeSize:
    Type: Number
    Default: 32
    ConstraintDescription: Must be in the range [32-5136]
    Description: The size of the EBS volume, in gibibytes (GiB). You can only scan files that are smaller than VolumeSize-16. Max S3 file size 5120 GiB.
    MaxValue: 5136
    MinValue: 32
  VolumeIops:
    Type: Number
    Default: 3000
    ConstraintDescription: Must be in the range [3000-16000]
    Description: The provisioned I/O operations per second (IOPS)
    MaxValue: 16000
    MinValue: 3000
  VolumeThroughput:
    Type: Number
    Default: 125
    ConstraintDescription: Must be in the range [125-1000]
    Description: The provisioned throughput per second in MiB.
    MaxValue: 1000
    MinValue: 125
Conditions:
  HasDnsConfigurationAutoRoute53:
    Fn::Equals:
      - Ref: DnsConfiguration
      - AUTO_ROUTE_53
  HasDnsConfigurationManual:
    Fn::Equals:
      - Ref: DnsConfiguration
      - MANUAL
  HasAccessLogsBucketName:
    Fn::Not:
      - Fn::Equals:
          - Ref: AccessLogsBucketName
          - ""
  HasAccessLogsPrefix:
    Fn::Not:
      - Fn::Equals:
          - Ref: AccessLogsPrefix
          - ""
  HasAccessLogsBucketNameAndPrefix:
    Fn::And:
      - Condition: HasAccessLogsBucketName
      - Condition: HasAccessLogsPrefix
  HasManagedPolicyArns:
    Fn::Not:
      - Fn::Equals:
          - Ref: ManagedPolicyArns
          - ""
  HasSystemsManagerAccess:
    Fn::Equals:
      - Ref: SystemsManagerAccess
      - "true"
  HasPermissionsBoundary:
    Fn::Not:
      - Fn::Equals:
          - Ref: PermissionsBoundary
          - ""
  HasAutoScalingGroupCalculatorFunctionReservedConcurrentExecutions:
    Fn::Not:
      - Fn::Equals:
          - Ref: AutoScalingGroupCalculatorFunctionReservedConcurrentExecutions
          - 0
  HasSSHIngressCidrIp:
    Fn::Not:
      - Fn::Equals:
          - Ref: SSHIngressCidrIp
          - ""
  HasFlowLogs:
    Fn::Not:
      - Fn::Equals:
          - Ref: FlowLogRetentionInDays
          - "0"
Resources:
  ApiKeysParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Description: bucketAV managed value. DO NOT CHANGE!
      Name:
        Fn::Join:
          - ""
          - - /bucketAV/
            - Ref: BucketAVStackName
            - /AddOn/api-sync/
            - Ref: AWS::StackName
            - /ApiKeys
      Type: StringList
      Value:
        Ref: ApiKeys
  SignaturesAgeAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Fn::ImportValue:
            Fn::Join:
              - ""
              - - Ref: BucketAVStackName
                - -InfrastructureAlarmsTopicArn
      AlarmDescription: Signatures are older than 7 days. Are signature updates working? Please follow https://bucketav.com/help/operations/monitoring-alerting.html#signaturesagealarm
      ComparisonOperator: GreaterThanThreshold
      EvaluationPeriods: 1
      MetricName: signatures_age
      Namespace:
        Ref: AWS::StackName
      Period: 600
      Statistic: Maximum
      Threshold: 604800
      TreatMissingData: notBreaching
  Logs:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays:
        Ref: LogsRetentionInDays
  AutoScalingGroupCalculatorRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      PermissionsBoundary:
        Fn::If:
          - HasPermissionsBoundary
          - Ref: PermissionsBoundary
          - Ref: AWS::NoValue
  AutoScalingGroupCalculatorFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          var __defProp = Object.defineProperty;
          var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
          var __getOwnPropNames = Object.getOwnPropertyNames;
          var __hasOwnProp = Object.prototype.hasOwnProperty;
          var __export = (target, all) => {
            for (var name in all)
              __defProp(target, name, { get: all[name], enumerable: true });
          };
          var __copyProps = (to, from, except, desc) => {
            if (from && typeof from === "object" || typeof from === "function") {
              for (let key of __getOwnPropNames(from))
                if (!__hasOwnProp.call(to, key) && key !== except)
                  __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
            }
            return to;
          };
          var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

          // lambda/asg-calculator.js
          var asg_calculator_exports = {};
          __export(asg_calculator_exports, {
            calculate: () => calculate,
            handler: () => handler
          });
          module.exports = __toCommonJS(asg_calculator_exports);

          // lambda/lib.js
          var import_client_cloudformation = require("@aws-sdk/client-cloudformation");
          var import_client_s3 = require("@aws-sdk/client-s3");
          var import_client_sns = require("@aws-sdk/client-sns");
          var import_client_eventbridge = require("@aws-sdk/client-eventbridge");
          var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
          var import_client_s32 = require("@aws-sdk/client-s3");
          var import_client_ssm = require("@aws-sdk/client-ssm");
          var import_client_secrets_manager = require("@aws-sdk/client-secrets-manager");
          var import_client_organizations = require("@aws-sdk/client-organizations");
          var import_credential_providers = require("@aws-sdk/credential-providers");

          // lambda/node_modules/@smithy/types/dist-es/auth/auth.js
          var HttpAuthLocation;
          (function(HttpAuthLocation2) {
            HttpAuthLocation2["HEADER"] = "header";
            HttpAuthLocation2["QUERY"] = "query";
          })(HttpAuthLocation || (HttpAuthLocation = {}));

          // lambda/node_modules/@smithy/types/dist-es/auth/HttpApiKeyAuth.js
          var HttpApiKeyAuthLocation;
          (function(HttpApiKeyAuthLocation2) {
            HttpApiKeyAuthLocation2["HEADER"] = "header";
            HttpApiKeyAuthLocation2["QUERY"] = "query";
          })(HttpApiKeyAuthLocation || (HttpApiKeyAuthLocation = {}));

          // lambda/node_modules/@smithy/types/dist-es/endpoint.js
          var EndpointURLScheme;
          (function(EndpointURLScheme2) {
            EndpointURLScheme2["HTTP"] = "http";
            EndpointURLScheme2["HTTPS"] = "https";
          })(EndpointURLScheme || (EndpointURLScheme = {}));

          // lambda/node_modules/@smithy/types/dist-es/extensions/checksum.js
          var AlgorithmId;
          (function(AlgorithmId2) {
            AlgorithmId2["MD5"] = "md5";
            AlgorithmId2["CRC32"] = "crc32";
            AlgorithmId2["CRC32C"] = "crc32c";
            AlgorithmId2["SHA1"] = "sha1";
            AlgorithmId2["SHA256"] = "sha256";
          })(AlgorithmId || (AlgorithmId = {}));

          // lambda/node_modules/@smithy/types/dist-es/http.js
          var FieldPosition;
          (function(FieldPosition2) {
            FieldPosition2[FieldPosition2["HEADER"] = 0] = "HEADER";
            FieldPosition2[FieldPosition2["TRAILER"] = 1] = "TRAILER";
          })(FieldPosition || (FieldPosition = {}));

          // lambda/node_modules/@smithy/types/dist-es/profile.js
          var IniSectionType;
          (function(IniSectionType2) {
            IniSectionType2["PROFILE"] = "profile";
            IniSectionType2["SSO_SESSION"] = "sso-session";
            IniSectionType2["SERVICES"] = "services";
          })(IniSectionType || (IniSectionType = {}));

          // lambda/node_modules/@smithy/types/dist-es/transfer.js
          var RequestHandlerProtocol;
          (function(RequestHandlerProtocol2) {
            RequestHandlerProtocol2["HTTP_0_9"] = "http/0.9";
            RequestHandlerProtocol2["HTTP_1_0"] = "http/1.0";
            RequestHandlerProtocol2["TDS_8_0"] = "tds/8.0";
          })(RequestHandlerProtocol || (RequestHandlerProtocol = {}));

          // lambda/node_modules/@smithy/middleware-serde/dist-es/serdePlugin.js
          var serializerMiddlewareOption = {
            name: "serializerMiddleware",
            step: "serialize",
            tags: ["SERIALIZER"],
            override: true
          };

          // lambda/node_modules/@smithy/core/dist-es/middleware-http-auth-scheme/getHttpAuthSchemePlugin.js
          var httpAuthSchemeMiddlewareOptions = {
            step: "serialize",
            tags: ["HTTP_AUTH_SCHEME"],
            name: "httpAuthSchemeMiddleware",
            override: true,
            relation: "before",
            toMiddleware: serializerMiddlewareOption.name
          };

          // lambda/node_modules/@smithy/core/dist-es/pagination/createPaginator.js
          var makePagedClientRequest = async (CommandCtor, client, input, withCommand = (_) => _, ...args) => {
            let command = new CommandCtor(input);
            command = withCommand(command) ?? command;
            return await client.send(command, ...args);
          };
          function createPaginator(ClientCtor, CommandCtor, inputTokenName, outputTokenName, pageSizeTokenName) {
            return async function* paginateOperation(config, input, ...additionalArguments) {
              const _input = input;
              let token = config.startingToken ?? _input[inputTokenName];
              let hasNext = true;
              let page;
              while (hasNext) {
                _input[inputTokenName] = token;
                if (pageSizeTokenName) {
                  _input[pageSizeTokenName] = _input[pageSizeTokenName] ?? config.pageSize;
                }
                if (config.client instanceof ClientCtor) {
                  page = await makePagedClientRequest(CommandCtor, config.client, input, config.withCommand, ...additionalArguments);
                } else {
                  throw new Error(`Invalid client, expected instance of ${ClientCtor.name}`);
                }
                yield page;
                const prevToken = token;
                token = get(page, outputTokenName);
                hasNext = !!(token && (!config.stopOnSameToken || token !== prevToken));
              }
              return void 0;
            };
          }
          var get = (fromObject, path) => {
            let cursor = fromObject;
            const pathComponents = path.split(".");
            for (const step of pathComponents) {
              if (!cursor || typeof cursor !== "object") {
                return void 0;
              }
              cursor = cursor[step];
            }
            return cursor;
          };

          // lambda/node_modules/@smithy/util-hex-encoding/dist-es/index.js
          var SHORT_TO_HEX = {};
          var HEX_TO_SHORT = {};
          for (let i = 0; i < 256; i++) {
            let encodedByte = i.toString(16).toLowerCase();
            if (encodedByte.length === 1) {
              encodedByte = `0${encodedByte}`;
            }
            SHORT_TO_HEX[i] = encodedByte;
            HEX_TO_SHORT[encodedByte] = i;
          }

          // lambda/node_modules/@smithy/core/dist-es/util-identity-and-auth/memoizeIdentityProvider.js
          var createIsIdentityExpiredFunction = (expirationMs) => (identity) => doesIdentityRequireRefresh(identity) && identity.expiration.getTime() - Date.now() < expirationMs;
          var EXPIRATION_MS = 3e5;
          var isIdentityExpired = createIsIdentityExpiredFunction(EXPIRATION_MS);
          var doesIdentityRequireRefresh = (identity) => identity.expiration !== void 0;

          // lambda/lib.js
          var paginateListRuleNamesByTarget = createPaginator(import_client_eventbridge.EventBridgeClient, import_client_eventbridge.ListRuleNamesByTargetCommand, "NextToken", "NextToken", "Limit");
          var MAX_S3_COPY_SIZE = 5 * 1024 * 1024 * 1024;
          async function cfnCustomResourceSuccess(event, physicalResourceId, optionalData) {
            const response = await fetch(event.ResponseURL, {
              method: "PUT",
              headers: {
                "Content-Type": "application/json"
              },
              body: JSON.stringify({
                Status: "SUCCESS",
                PhysicalResourceId: physicalResourceId,
                StackId: event.StackId,
                RequestId: event.RequestId,
                LogicalResourceId: event.LogicalResourceId,
                ...optionalData !== void 0 && { Data: optionalData }
              })
            });
            if (response.status !== 200) {
              console.log("response status", response.status);
              console.log("response", await response.text());
              throw new Error("unexpected status code");
            }
          }

          // lambda/asg-calculator.js
          function calculate(minSize, maxSize) {
            return {
              MaxBatchSize: Math.max(1, Math.floor(minSize / 2)),
              MinInstancesInService: maxSize === minSize ? Math.max(0, minSize - 1) : minSize
            };
          }
          async function handler(event) {
            console.log(`Invoke: ${JSON.stringify(event)}`);
            if (event.RequestType === "Create" || event.RequestType === "Update" || event.RequestType === "Delete") {
              const minSize = parseInt(event.ResourceProperties.MinSize, 10);
              const maxSize = parseInt(event.ResourceProperties.MaxSize, 10);
              await cfnCustomResourceSuccess(event, "asg-calculator", calculate(minSize, maxSize));
            } else {
              throw new Error("unsupported request type");
            }
          }
          // Annotate the CommonJS export names for ESM import in node:
          0 && (module.exports = {
            calculate,
            handler
          });
      Handler: index.handler
      MemorySize: 1769
      ReservedConcurrentExecutions:
        Fn::If:
          - HasAutoScalingGroupCalculatorFunctionReservedConcurrentExecutions
          - Ref: AutoScalingGroupCalculatorFunctionReservedConcurrentExecutions
          - Ref: AWS::NoValue
      Role:
        Fn::GetAtt:
          - AutoScalingGroupCalculatorRole
          - Arn
      Runtime: nodejs22.x
      Timeout: 30
  AutoScalingGroupCalculatorLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName:
        Fn::Join:
          - ""
          - - /aws/lambda/
            - Ref: AutoScalingGroupCalculatorFunction
      RetentionInDays:
        Ref: LogsRetentionInDays
  AutoScalingGroupCalculatorPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource:
              Fn::GetAtt:
                - AutoScalingGroupCalculatorLogGroup
                - Arn
      PolicyName: lambda
      Roles:
        - Ref: AutoScalingGroupCalculatorRole
  AutoScalingGroupCalculator:
    Type: Custom::AutoScalingGroupCalculator
    Properties:
      ServiceToken:
        Fn::GetAtt:
          - AutoScalingGroupCalculatorFunction
          - Arn
      Version: 2.0.0
      MinSize:
        Ref: AutoScalingMinSize
      MaxSize:
        Ref: AutoScalingMaxSize
    DependsOn:
      - AutoScalingGroupCalculatorPolicy
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
  ScanIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        Fn::If:
          - HasManagedPolicyArns
          - Fn::Split:
              - ","
              - Ref: ManagedPolicyArns
          - Ref: AWS::NoValue
      PermissionsBoundary:
        Fn::If:
          - HasPermissionsBoundary
          - Ref: PermissionsBoundary
          - Ref: AWS::NoValue
      Policies:
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: s3:GetObject*
                Resource:
                  Ref: S3ObjectRestriction
              - Effect: Allow
                Action: s3:ListBucket*
                Resource:
                  Ref: S3BucketRestriction
              - Effect: Allow
                Action: kms:Decrypt
                Resource:
                  Ref: KMSKeyRestriction
          PolicyName: s3read
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: ssm:GetParameter
                Resource:
                  Fn::Join:
                    - ""
                    - - "arn:"
                      - Ref: AWS::Partition
                      - ":ssm:"
                      - Ref: AWS::Region
                      - ":"
                      - Ref: AWS::AccountId
                      - :parameter
                      - Ref: ApiKeysParameter
          PolicyName: ssmparam
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: cloudwatch:PutMetricData
                Resource: "*"
                Condition:
                  StringEquals:
                    cloudwatch:namespace:
                      Ref: AWS::StackName
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogStreams
                Resource:
                  Fn::GetAtt:
                    - Logs
                    - Arn
              - Effect: Allow
                Action: logs:DescribeLogGroups
                Resource: "*"
          PolicyName: cloudwatch
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: aws-marketplace:MeterUsage
                Resource: "*"
          PolicyName: awsmp
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: autoscaling:DescribeAutoScalingInstances
                Resource: "*"
              - Effect: Allow
                Action:
                  - autoscaling:CompleteLifecycleAction
                  - autoscaling:RecordLifecycleActionHeartbeat
                Resource: "*"
                Condition:
                  StringEquals:
                    autoscaling:ResourceTag/aws:cloudformation:stack-id:
                      Ref: AWS::StackId
          PolicyName: asg
        - Fn::If:
            - HasSystemsManagerAccess
            - PolicyName: ssmv2
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Action:
                      - ssm:UpdateInstanceInformation
                      - ssm:ListAssociations
                      - ssm:ListInstanceAssociations
                      - ssmmessages:CreateControlChannel
                      - ssmmessages:CreateDataChannel
                      - ssmmessages:OpenControlChannel
                      - ssmmessages:OpenDataChannel
                      - ec2messages:AcknowledgeMessage
                      - ec2messages:DeleteMessage
                      - ec2messages:FailMessage
                      - ec2messages:GetEndpoint
                      - ec2messages:GetMessages
                      - ec2messages:SendReply
                    Resource: "*"
            - Ref: AWS::NoValue
  ScanInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - Ref: ScanIAMRole
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock:
        Ref: VpcCidrBlock
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value:
            Ref: AWS::StackName
  FlowLogLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays:
        Ref: FlowLogRetentionInDays
    Condition: HasFlowLogs
  FlowLogRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: vpc-flow-logs.amazonaws.com
            Action: sts:AssumeRole
      PermissionsBoundary:
        Fn::If:
          - HasPermissionsBoundary
          - Ref: PermissionsBoundary
          - Ref: AWS::NoValue
      Policies:
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogGroups
                  - logs:DescribeLogStreams
                Resource:
                  Fn::GetAtt:
                    - FlowLogLogGroup
                    - Arn
          PolicyName: flowlogs-policy
    Condition: HasFlowLogs
  FlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      DeliverLogsPermissionArn:
        Fn::GetAtt:
          - FlowLogRole
          - Arn
      LogGroupName:
        Ref: FlowLogLogGroup
      ResourceId:
        Ref: VPC
      ResourceType: VPC
      TrafficType: ALL
    Condition: HasFlowLogs
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value:
            Ref: AWS::StackName
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId:
        Ref: InternetGateway
      VpcId:
        Ref: VPC
  SubnetAPublic:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      CidrBlock:
        Fn::Select:
          - 0
          - Fn::Cidr:
              - Fn::GetAtt:
                  - VPC
                  - CidrBlock
              - 4
              - Ref: VpcSubnetCidrBits
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value:
            Fn::Join:
              - ""
              - - Ref: AWS::StackName
                - ": A public"
      VpcId:
        Ref: VPC
  SubnetBPublic:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      CidrBlock:
        Fn::Select:
          - 2
          - Fn::Cidr:
              - Fn::GetAtt:
                  - VPC
                  - CidrBlock
              - 4
              - Ref: VpcSubnetCidrBits
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value:
            Fn::Join:
              - ""
              - - Ref: AWS::StackName
                - ": B public"
      VpcId:
        Ref: VPC
  RouteTableAPublic:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value:
            Fn::Join:
              - ""
              - - Ref: AWS::StackName
                - ": A public"
      VpcId:
        Ref: VPC
  RouteTableBPublic:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value:
            Fn::Join:
              - ""
              - - Ref: AWS::StackName
                - ": B public"
      VpcId:
        Ref: VPC
  RouteTableAssociationAPublic:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: RouteTableAPublic
      SubnetId:
        Ref: SubnetAPublic
  RouteTableAssociationBPublic:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: RouteTableBPublic
      SubnetId:
        Ref: SubnetBPublic
  RouteTableAPublicInternetRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
      RouteTableId:
        Ref: RouteTableAPublic
    DependsOn:
      - VPCGatewayAttachment
  RouteTableBPublicInternetRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
      RouteTableId:
        Ref: RouteTableBPublic
    DependsOn:
      - VPCGatewayAttachment
  NetworkAclPublic:
    Type: AWS::EC2::NetworkAcl
    Properties:
      Tags:
        - Key: Name
          Value:
            Fn::Join:
              - ""
              - - Ref: AWS::StackName
                - ": public"
      VpcId:
        Ref: VPC
  SubnetNetworkAclAssociationAPublic:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId:
        Ref: NetworkAclPublic
      SubnetId:
        Ref: SubnetAPublic
  SubnetNetworkAclAssociationBPublic:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId:
        Ref: NetworkAclPublic
      SubnetId:
        Ref: SubnetBPublic
  NetworkAclEntryInPublicAllowEphemeral:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 1024
        To: 65535
      Protocol: 6
      RuleAction: allow
      RuleNumber: 50
  NetworkAclEntryInPublicAllowSSH:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Ref: SSHIngressCidrIp
      Egress: false
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 22
        To: 22
      Protocol: 6
      RuleAction: allow
      RuleNumber: 60
    Condition: HasSSHIngressCidrIp
  NetworkAclEntryOutPublicAllowEphemeral:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Ref: SSHIngressCidrIp
      Egress: true
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 32768
        To: 65535
      Protocol: 6
      RuleAction: allow
      RuleNumber: 50
    Condition: HasSSHIngressCidrIp
  NetworkAclEntryOutPublicAllowHTTP:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 80
        To: 80
      Protocol: 6
      RuleAction: allow
      RuleNumber: 60
  NetworkAclEntryOutPublicAllowHTTPS:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 443
        To: 443
      Protocol: 6
      RuleAction: allow
      RuleNumber: 61
  NetworkAclEntryInPublicAllowHTTPS:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Ref: ApiIngressCidrIp
      Egress: false
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 443
        To: 443
      Protocol: 6
      RuleAction: allow
      RuleNumber: 70
  NetworkAclEntryOutPublicAllowEphemeral2:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Ref: ApiIngressCidrIp
      Egress: true
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 1024
        To: 65535
      Protocol: 6
      RuleAction: allow
      RuleNumber: 70
  NetworkAclEntryOutPublicAllow8080:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Fn::GetAtt:
          - VPC
          - CidrBlock
      Egress: true
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 8080
        To: 8080
      Protocol: 6
      RuleAction: allow
      RuleNumber: 71
  NetworkAclEntryInPublicAllow8080:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Fn::GetAtt:
          - VPC
          - CidrBlock
      Egress: false
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 8080
        To: 8080
      Protocol: 6
      RuleAction: allow
      RuleNumber: 71
  NetworkAclEntryOutPublicAllowEphemeral3:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock:
        Fn::GetAtt:
          - VPC
          - CidrBlock
      Egress: true
      NetworkAclId:
        Ref: NetworkAclPublic
      PortRange:
        From: 1024
        To: 65535
      Protocol: 6
      RuleAction: allow
      RuleNumber: 72
  ScanSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription:
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - -scan
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Accessing Amazon Linux repos.
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
        - CidrIp: 0.0.0.0/0
          Description: Accessing AWS APIs and fetching virus database updates.
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      VpcId:
        Ref: VPC
  ScanSecurityGroupInSSH:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      CidrIp:
        Ref: SSHIngressCidrIp
      FromPort: 22
      GroupId:
        Ref: ScanSecurityGroup
      IpProtocol: tcp
      ToPort: 22
    Condition: HasSSHIngressCidrIp
  ApiSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription:
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - -https-api
      SecurityGroupEgress:
        - DestinationSecurityGroupId:
            Ref: ScanSecurityGroup
          FromPort: 8080
          IpProtocol: tcp
          ToPort: 8080
      VpcId:
        Ref: VPC
  ScanSecurityGroupInApiSecurityGroup:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      FromPort: 8080
      GroupId:
        Ref: ScanSecurityGroup
      IpProtocol: tcp
      SourceSecurityGroupId:
        Ref: ApiSecurityGroup
      ToPort: 8080
  ApiSecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      CidrIp:
        Ref: ApiIngressCidrIp
      FromPort: 443
      GroupId:
        Ref: ApiSecurityGroup
      IpProtocol: tcp
      ToPort: 443
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 15
      HealthCheckPath: /healthz
      HealthCheckPort: "8080"
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 10
      HealthyThresholdCount: 2
      Matcher:
        HttpCode: "204"
      Port: 8080
      Protocol: HTTP
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: "0"
        - Key: load_balancing.algorithm.type
          Value: least_outstanding_requests
      UnhealthyThresholdCount: 2
      VpcId:
        Ref: VPC
  ScanLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        BlockDeviceMappings:
          - DeviceName: /dev/xvda
            Ebs:
              Encrypted: true
              Iops:
                Ref: VolumeIops
              Throughput:
                Ref: VolumeThroughput
              VolumeSize:
                Ref: VolumeSize
              VolumeType: gp3
        IamInstanceProfile:
          Name:
            Ref: ScanInstanceProfile
        ImageId:
          Fn::FindInMap:
            - RegionMap
            - Ref: AWS::Region
            - AMI
        InstanceType:
          Ref: InstanceType
        KeyName:
          Ref: KeyName
        MetadataOptions:
          HttpTokens: required
        NetworkInterfaces:
          - AssociatePublicIpAddress: true
            DeviceIndex: 0
            Groups:
              - Ref: ScanSecurityGroup
        TagSpecifications:
          - ResourceType: volume
            Tags:
              - Key: bucketav:cloudformation:logical-id
                Value: ScanLaunchTemplate
              - Key: bucketav:cloudformation:stack-id
                Value:
                  Ref: AWS::StackId
              - Key: bucketav:cloudformation:stack-name
                Value:
                  Ref: AWS::StackName
              - Key: Name
                Value:
                  Ref: AWS::StackName
          - ResourceType: network-interface
            Tags:
              - Key: bucketav:cloudformation:logical-id
                Value: ScanLaunchTemplate
              - Key: bucketav:cloudformation:stack-id
                Value:
                  Ref: AWS::StackId
              - Key: bucketav:cloudformation:stack-name
                Value:
                  Ref: AWS::StackName
              - Key: Name
                Value:
                  Ref: AWS::StackName
        UserData:
          Fn::Base64:
            Fn::Join:
              - ""
              - - |-
                  #!/bin/bash -ex
                  trap '/opt/aws/bin/cfn-signal -e 1 --stack 
                - Ref: AWS::StackName
                - " --resource ScanAutoScalingGroup --region "
                - Ref: AWS::Region
                - |-
                  ' ERR
                  sed -e 's/__REGION__/
                - Ref: AWS::Region
                - |-
                  /g' -i /etc/vector/vector.yaml
                  sed -e 's/__LOG_GROUP_NAME__/
                - Ref: Logs
                - |-
                  /g' -i /etc/vector/vector.yaml
                  sed -e "s/__INSTANCE_ID__/$(ec2-metadata -i --quiet)/g" -i /etc/vector/vector.yaml
                  systemctl enable vector.service
                  systemctl --no-block start vector.service
                  sed -e 's/__LOG_GROUP_NAME__/
                - Ref: Logs
                - |-
                  /g' -i /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
                  sed -e 's/__NAMESPACE__/
                - Ref: AWS::StackName
                - |-
                  /g' -i /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
                  systemctl enable amazon-cloudwatch-agent.service
                  systemctl --no-block start amazon-cloudwatch-agent.service
                  if [[ "
                - Ref: SystemsManagerAccess
                - |-
                  " == "true" ]]; then
                    systemctl enable amazon-ssm-agent.service
                    systemctl --no-block start amazon-ssm-agent.service
                  fi
                  cat <<"EOF" | tee /opt/bucketav/bucketav.conf > /dev/null
                  mode: server
                  region: '
                - Ref: AWS::Region
                - |-
                  '
                  volume_size: 
                - Ref: VolumeSize
                - |-
                  
                  stack_name: '
                - Ref: AWS::StackName
                - |-
                  '
                  core_stack_name: '
                - Ref: BucketAVStackName
                - |-
                  '
                  addon_stack_name: '
                - Ref: AWS::StackName
                - |-
                  '
                  product: 'bucketav'
                  debug: false
                  EOF
                  chown ec2-user:ec2-user /opt/bucketav/bucketav.conf
                  echo "threadcount: $(CONFIG_FILE=/opt/bucketav/bucketav.conf /opt/bucketav/bucketav --savdi-threadcount)" >> /usr/local/savdi/savdid.conf
                  systemctl enable savdi.service
                  systemctl start savdi.service
                  if [[ "
                - Ref: SophosLiveProtectionCloudLookups
                - |-
                  " == "true" ]]; then
                  sed -e 's/BUCKETAV_PLACEHOLDER_SXLLIVEPROTECTION/1/g' -i /usr/local/savdi/savdid.conf
                  sed -e "s/BUCKETAV_PLACEHOLDER_SXLDNSIP1/$(CONFIG_FILE=/opt/bucketav/bucketav.conf /opt/bucketav/bucketav --savdi-dns1)/g" -i /usr/local/savdi/savdid.conf
                  sed -e "s/BUCKETAV_PLACEHOLDER_SXLHEXIDMACHINE/$(openssl rand -hex 16)/g" -i /usr/local/savdi/savdid.conf
                  else
                  sed -e 's/BUCKETAV_PLACEHOLDER_SXLLIVEPROTECTION/0/g' -i /usr/local/savdi/savdid.conf
                  sed -e 's/BUCKETAV_PLACEHOLDER_SXLDNSIP1/169.254.169.253/g' -i /usr/local/savdi/savdid.conf
                  sed -e "s/BUCKETAV_PLACEHOLDER_SXLHEXIDMACHINE/$(openssl rand -hex 16)/g" -i /usr/local/savdi/savdid.conf
                  fi
                  systemctl enable bucketav.service
                  systemctl start bucketav.service
                  /opt/aws/bin/cfn-signal -e 0 --stack 
                - Ref: AWS::StackName
                - " --resource ScanAutoScalingGroup --region "
                - Ref: AWS::Region
                - "\n"
  ScanAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      HealthCheckGracePeriod: 300
      HealthCheckType: ELB
      LaunchTemplate:
        LaunchTemplateId:
          Ref: ScanLaunchTemplate
        Version:
          Fn::GetAtt:
            - ScanLaunchTemplate
            - LatestVersionNumber
      LifecycleHookSpecificationList:
        - DefaultResult: CONTINUE
          HeartbeatTimeout: 300
          LifecycleHookName: bucketav_ready
          LifecycleTransition: autoscaling:EC2_INSTANCE_LAUNCHING
        - DefaultResult: CONTINUE
          HeartbeatTimeout: 432
          LifecycleHookName: bucketav_terminate_gracefully
          LifecycleTransition: autoscaling:EC2_INSTANCE_TERMINATING
      MaxSize:
        Ref: AutoScalingMaxSize
      MetricsCollection:
        - Granularity: 1Minute
          Metrics:
            - GroupInServiceInstances
            - GroupDesiredCapacity
      MinSize:
        Ref: AutoScalingMinSize
      Tags:
        - Key: Name
          PropagateAtLaunch: true
          Value:
            Ref: AWS::StackName
      TargetGroupARNs:
        - Ref: TargetGroup
      VPCZoneIdentifier:
        - Ref: SubnetAPublic
        - Ref: SubnetBPublic
    DependsOn:
      - NetworkAclEntryInPublicAllow8080
      - NetworkAclEntryInPublicAllowEphemeral
      - NetworkAclEntryOutPublicAllow8080
      - NetworkAclEntryOutPublicAllowEphemeral3
      - NetworkAclEntryOutPublicAllowHTTP
      - NetworkAclEntryOutPublicAllowHTTPS
      - RouteTableAPublicInternetRoute
      - RouteTableAssociationAPublic
      - RouteTableAssociationBPublic
      - RouteTableBPublicInternetRoute
      - SubnetNetworkAclAssociationAPublic
      - SubnetNetworkAclAssociationBPublic
      - VPCGatewayAttachment
    UpdatePolicy:
      AutoScalingRollingUpdate:
        PauseTime: PT300S
        MaxBatchSize:
          Fn::GetAtt:
            - AutoScalingGroupCalculator
            - MaxBatchSize
        MinInstancesInService:
          Fn::GetAtt:
            - AutoScalingGroupCalculator
            - MinInstancesInService
        SuspendProcesses:
          - HealthCheck
          - ReplaceUnhealthy
          - AZRebalance
          - AlarmNotification
          - ScheduledActions
        WaitOnResourceSignals: true
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: "60"
        - Key: routing.http2.enabled
          Value: "true"
        - Key: access_logs.s3.enabled
          Value:
            Fn::If:
              - HasAccessLogsBucketName
              - "true"
              - "false"
        - Fn::If:
            - HasAccessLogsBucketNameAndPrefix
            - Key: access_logs.s3.prefix
              Value:
                Ref: AccessLogsPrefix
            - Ref: AWS::NoValue
        - Fn::If:
            - HasAccessLogsBucketName
            - Key: access_logs.s3.bucket
              Value:
                Ref: AccessLogsBucketName
            - Ref: AWS::NoValue
      Scheme: internet-facing
      SecurityGroups:
        - Ref: ApiSecurityGroup
      Subnets:
        - Ref: SubnetAPublic
        - Ref: SubnetBPublic
      Type: application
    DependsOn:
      - NetworkAclEntryInPublicAllow8080
      - NetworkAclEntryInPublicAllowEphemeral
      - NetworkAclEntryInPublicAllowHTTPS
      - NetworkAclEntryOutPublicAllow8080
      - NetworkAclEntryOutPublicAllowEphemeral2
      - NetworkAclEntryOutPublicAllowEphemeral3
      - ScanSecurityGroupInApiSecurityGroup
  HTTPCodeELB5XXTooHighAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Fn::ImportValue:
            Fn::Join:
              - ""
              - - Ref: BucketAVStackName
                - -InfrastructureAlarmsTopicArn
      AlarmDescription: Application load balancer returns 5XX HTTP status codes
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: LoadBalancer
          Value:
            Fn::GetAtt:
              - LoadBalancer
              - LoadBalancerFullName
      EvaluationPeriods: 1
      MetricName: HTTPCode_ELB_5XX_Count
      Namespace: AWS/ApplicationELB
      Period: 60
      Statistic: Sum
      Threshold: 0
      TreatMissingData: notBreaching
  RejectedConnectionCountTooHighAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Fn::ImportValue:
            Fn::Join:
              - ""
              - - Ref: BucketAVStackName
                - -InfrastructureAlarmsTopicArn
      AlarmDescription: Application load balancer rejected connections because the load balancer had reached its maximum number of connections
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: LoadBalancer
          Value:
            Fn::GetAtt:
              - LoadBalancer
              - LoadBalancerFullName
      EvaluationPeriods: 1
      MetricName: RejectedConnectionCount
      Namespace: AWS/ApplicationELB
      Period: 60
      Statistic: Sum
      Threshold: 0
      TreatMissingData: notBreaching
  HTTPCodeTarget5XXTooHighAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Fn::ImportValue:
            Fn::Join:
              - ""
              - - Ref: BucketAVStackName
                - -InfrastructureAlarmsTopicArn
      AlarmDescription: Application load balancer receives 5XX HTTP status codes from targets
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: LoadBalancer
          Value:
            Fn::GetAtt:
              - LoadBalancer
              - LoadBalancerFullName
        - Name: TargetGroup
          Value:
            Fn::GetAtt:
              - TargetGroup
              - TargetGroupFullName
      EvaluationPeriods: 1
      MetricName: HTTPCode_Target_5XX_Count
      Namespace: AWS/ApplicationELB
      Period: 60
      Statistic: Sum
      Threshold: 0
      TreatMissingData: notBreaching
  TargetConnectionErrorCountTooHighAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Fn::ImportValue:
            Fn::Join:
              - ""
              - - Ref: BucketAVStackName
                - -InfrastructureAlarmsTopicArn
      AlarmDescription: Application load balancer could not connect to targets
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: LoadBalancer
          Value:
            Fn::GetAtt:
              - LoadBalancer
              - LoadBalancerFullName
        - Name: TargetGroup
          Value:
            Fn::GetAtt:
              - TargetGroup
              - TargetGroupFullName
      EvaluationPeriods: 1
      MetricName: TargetConnectionErrorCount
      Namespace: AWS/ApplicationELB
      Period: 60
      Statistic: Sum
      Threshold: 0
      TreatMissingData: notBreaching
  Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      CertificateTransparencyLoggingPreference: DISABLED
      DomainName:
        Ref: DomainName
      DomainValidationOptions:
        - DomainName:
            Ref: DomainName
          HostedZoneId:
            Ref: HostedZoneId
      ValidationMethod: DNS
    Condition: HasDnsConfigurationAutoRoute53
  RecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName:
          Fn::Join:
            - ""
            - - dualstack.
              - Fn::GetAtt:
                  - LoadBalancer
                  - DNSName
        HostedZoneId:
          Fn::GetAtt:
            - LoadBalancer
            - CanonicalHostedZoneID
      HostedZoneId:
        Ref: HostedZoneId
      Name:
        Ref: DomainName
      Type: A
    Condition: HasDnsConfigurationAutoRoute53
  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn:
            Fn::If:
              - HasDnsConfigurationManual
              - Ref: CertificateArn
              - Ref: Certificate
      DefaultActions:
        - FixedResponseConfig:
            ContentType: application/json
            MessageBody: '{"statusCode":404,"error":"Not Found"}'
            StatusCode: "404"
          Order: 1
          Type: fixed-response
      LoadBalancerArn:
        Ref: LoadBalancer
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06
  ListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Order: 1
          TargetGroupArn:
            Ref: TargetGroup
          Type: forward
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - /api/v1/scan/sync/download
              - /api/v1/scan/sync/s3
              - /api/v1/scan/sync/form
              - /api/v1/scan/sync/binary
      ListenerArn:
        Ref: Listener
      Priority: 100
  ScanScaleUp:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: PercentChangeInCapacity
      AutoScalingGroupName:
        Ref: ScanAutoScalingGroup
      Cooldown: "180"
      MinAdjustmentMagnitude: 1
      PolicyType: SimpleScaling
      ScalingAdjustment: 20
  ScanScaleDown:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: PercentChangeInCapacity
      AutoScalingGroupName:
        Ref: ScanAutoScalingGroup
      Cooldown: "180"
      MinAdjustmentMagnitude: 1
      PolicyType: SimpleScaling
      ScalingAdjustment: -10
  TargetsBusyAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Ref: ScanScaleUp
      AlarmDescription: Don't be worried about this alarm. It is used to trigger auto-scaling policies.
      ComparisonOperator: GreaterThanThreshold
      EvaluationPeriods: 1
      MetricName: http_api_concurrently_accepted
      Namespace:
        Ref: AWS::StackName
      Period: 120
      Statistic: Average
      Threshold:
        Fn::FindInMap:
          - InstanceTypeMap
          - Ref: InstanceType
          - TargetsBusyThreshold
      TreatMissingData: notBreaching
  TargetsIdlingAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
        - Ref: ScanScaleDown
      AlarmDescription: Don't be worried about this alarm. It is used to trigger auto-scaling policies.
      ComparisonOperator: LessThanThreshold
      EvaluationPeriods: 1
      MetricName: http_api_concurrently_accepted
      Namespace:
        Ref: AWS::StackName
      Period: 120
      Statistic: Average
      Threshold:
        Fn::FindInMap:
          - InstanceTypeMap
          - Ref: InstanceType
          - TargetsIdlingThreshold
      TreatMissingData: notBreaching
  Dashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardBody:
        Fn::Join:
          - ""
          - - '{"start":"-PT24H","widgets":[{"type":"metric","x":0,"y":0,"height":4,"width":18,"properties":{"sparkline":false,"view":"singleValue","metrics":[["AWS/ApplicationELB","RequestCount","LoadBalancer","'
            - Fn::GetAtt:
                - LoadBalancer
                - LoadBalancerFullName
            - '",{"yAxis":"right","stat":"Sum","label":"API requests"}],["'
            - Ref: AWS::StackName
            - '","clean",{"stat":"Sum","label":"Clean"}],[".","infected",{"stat":"Sum","color":"#d62728","label":"Infected"}],[".","no",{"stat":"Sum","color":"#ff7f0e","label":"Unscannable (too big, access denied)"}],[".","scanned_data",{"stat":"Sum","label":"Scanned data (GB)"}],[".","billed_scanned_data",{"stat":"Sum","label":"Billed data (GB)"}]],"region":"'
            - Ref: AWS::Region
            - '","title":"bucketAV API (sync) powered by Sophos (dedicated-public-vpc) - Overview","liveData":true,"setPeriodToTimeRange":true,"trend":false,"singleValueFullPrecision":false}},{"type":"text","x":18,"y":0,"width":6,"height":4,"properties":{"markdown":""}},{"type":"metric","x":0,"y":4,"width":6,"height":6,"properties":{"metrics":[["'
            - Ref: AWS::StackName
            - '","clean",{"stat":"Sum","label":"Clean"}],[".","infected",{"stat":"Sum","color":"#d62728","label":"Infected"}],[".","no",{"stat":"Sum","color":"#ff7f0e","label":"Unscannable (too big, access denied)"}]],"view":"timeSeries","stacked":true,"region":"'
            - Ref: AWS::Region
            - '","title":"Scan Results","period":60,"liveData":true}},{"type":"metric","x":6,"y":4,"width":6,"height":6,"properties":{"metrics":[["AWS/ApplicationELB","HTTPCode_Target_2XX_Count","LoadBalancer","'
            - Fn::GetAtt:
                - LoadBalancer
                - LoadBalancerFullName
            - '","TargetGroup","'
            - Fn::GetAtt:
                - TargetGroup
                - TargetGroupFullName
            - '",{"stat":"Sum","label":"API 2xx responses"}],[".","HTTPCode_Target_3XX_Count",".",".",".",".",{"stat":"Sum","label":"API 3xx responses"}],[".","HTTPCode_Target_4XX_Count",".",".",".",".",{"stat":"Sum","label":"API 4xx responses"}],[".","HTTPCode_Target_5XX_Count",".",".",".",".",{"stat":"Sum","label":"API 5xx responses"}],[".","HTTPCode_ELB_3XX_Count","LoadBalancer","'
            - Fn::GetAtt:
                - LoadBalancer
                - LoadBalancerFullName
            - '",{"yAxis":"right","stat":"Sum","label":"ALB 3xx responses"}],[".","HTTPCode_ELB_4XX_Count","LoadBalancer","'
            - Fn::GetAtt:
                - LoadBalancer
                - LoadBalancerFullName
            - '",{"yAxis":"right","stat":"Sum","label":"ALB 4xx responses"}],[".","HTTPCode_ELB_5XX_Count","LoadBalancer","'
            - Fn::GetAtt:
                - LoadBalancer
                - LoadBalancerFullName
            - '",{"yAxis":"right","stat":"Sum","label":"ALB 5xx responses"}]],"view":"timeSeries","stacked":true,"region":"'
            - Ref: AWS::Region
            - '","title":"Load Balancer","period":60,"liveData":true}},{"type":"metric","x":12,"y":4,"width":6,"height":6,"properties":{"metrics":[["AWS/AutoScaling","GroupInServiceInstances","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"stat":"Average","label":"Running Instances"}]],"view":"timeSeries","stacked":false,"region":"'
            - Ref: AWS::Region
            - '","title":"Scan Fleet","period":60,"liveData":true,"annotations":{"horizontal":[{"label":"AutoScalingMinSize","value":'
            - Ref: AutoScalingMinSize
            - '},{"label":"AutoScalingMaxSize","value":'
            - Ref: AutoScalingMaxSize
            - '}]}}},{"type":"alarm","x":18,"y":4,"width":6,"height":6,"properties":{"title":"Infrastructure Alarms","alarms":["'
            - Fn::GetAtt:
                - SignaturesAgeAlarm
                - Arn
            - '","'
            - Fn::GetAtt:
                - HTTPCodeELB5XXTooHighAlarm
                - Arn
            - '","'
            - Fn::GetAtt:
                - RejectedConnectionCountTooHighAlarm
                - Arn
            - '","'
            - Fn::GetAtt:
                - HTTPCodeTarget5XXTooHighAlarm
                - Arn
            - '","'
            - Fn::GetAtt:
                - TargetConnectionErrorCountTooHighAlarm
                - Arn
            - "\"]}},{\"type\":\"log\",\"x\":0,\"y\":10,\"width\":8,\"height\":8,\"properties\":{\"query\":\"SOURCE '"
            - Ref: Logs
            - "' | fields @timestamp, @message | filter @logStream like \\\"/journald/bucketav.service\\\" | sort @timestamp desc | limit 1000\",\"region\":\""
            - Ref: AWS::Region
            - "\",\"stacked\":false,\"title\":\"Scan Logs\",\"view\":\"table\"}},{\"type\":\"log\",\"x\":8,\"y\":10,\"width\":8,\"height\":8,\"properties\":{\"query\":\"SOURCE '"
            - Ref: Logs
            - "' | fields @timestamp, @message | filter not(@logStream like \\\"/journald/bucketav.service\\\") | sort @timestamp desc | limit 1000\",\"region\":\""
            - Ref: AWS::Region
            - '","stacked":false,"title":"System Logs","view":"table"}},{"type":"text","x":16,"y":10,"width":8,"height":8,"properties":{"markdown":""}},{"type":"metric","x":0,"y":18,"width":6,"height":6,"properties":{"view":"timeSeries","stacked":false,"metrics":[["AWS/EC2","CPUUtilization","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"label":"Utilization"}]],"region":"'
            - Ref: AWS::Region
            - '","title":"CPU","period":300,"liveData":true,"yAxis":{"left":{"min":0,"max":100}}}},{"type":"metric","x":6,"y":18,"width":6,"height":6,"properties":{"view":"timeSeries","stacked":false,"metrics":[["'
            - Ref: AWS::StackName
            - '","mem_used_percent",{"label":"Memory utilization"}],["'
            - Ref: AWS::StackName
            - '","swap_used_percent",{"label":"Swap utilization"}]],"region":"'
            - Ref: AWS::Region
            - '","title":"Memory","period":300,"liveData":true,"yAxis":{"left":{"min":0,"max":100}}}},{"type":"metric","x":12,"y":18,"width":6,"height":6,"properties":{"view":"timeSeries","stacked":false,"metrics":[["'
            - Ref: AWS::StackName
            - '","disk_used_percent","path","/","fstype","xfs",{"label":"Storage utilization"}],[{"expression":"(wops+rops)/300","label":"IOPS","id":"e1","yAxis":"right"}],["AWS/EC2","EBSWriteOps","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"id":"wops","stat":"Sum","visible":false}],["AWS/EC2","EBSReadOps","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"id":"rops","stat":"Sum","visible":false}],[{"expression":"(w+r)/300/1024/1024","label":"Throughput (MiB/s)","id":"e2","yAxis":"right"}],["AWS/EC2","EBSWriteBytes","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"id":"w","stat":"Sum","visible":false}],["AWS/EC2","EBSReadBytes","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"id":"r","stat":"Sum","visible":false}]],"region":"'
            - Ref: AWS::Region
            - '","title":"Disk","period":300,"liveData":true,"yAxis":{"left":{"min":0,"max":100}}}},{"type":"metric","x":18,"y":18,"width":6,"height":6,"properties":{"view":"timeSeries","stacked":true,"metrics":[[{"expression":"(in+out)/300*8/1000/1000/1000","label":"Throughput (Gbit/s)","id":"e1"}],["AWS/EC2","NetworkIn","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"id":"in","stat":"Sum","visible":false}],["AWS/EC2","NetworkOut","AutoScalingGroupName","'
            - Ref: ScanAutoScalingGroup
            - '",{"id":"out","stat":"Sum","visible":false}]],"region":"'
            - Ref: AWS::Region
            - '","title":"Network","period":300,"liveData":true,"yAxis":{"left":{"showUnits":false}}}}]}'
      DashboardName:
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - "-"
            - Ref: AWS::Region
  ServiceDiscoveryVersion:
    Type: AWS::SSM::Parameter
    Properties:
      Description: bucketAV managed value. DO NOT CHANGE!
      Name:
        Fn::Join:
          - ""
          - - /bucketAV/
            - Ref: BucketAVStackName
            - /AddOn/api-sync/
            - Ref: AWS::StackName
            - /Version
      Type: String
      Value: 2.11.0
Rules:
  InstanceTypeMalaysia:
    RuleCondition:
      Fn::Equals:
        - Ref: AWS::Region
        - ap-southeast-5
    Assertions:
      - Assert:
          Fn::Contains:
            - - t3.nano
              - t3.micro
              - t3.small
              - t3.medium
              - t3.large
              - t3.xlarge
              - t3.2xlarge
              - m6i.large
              - m6i.xlarge
              - m6i.2xlarge
              - m6i.4xlarge
              - m6i.8xlarge
              - m6i.12xlarge
              - m6i.16xlarge
              - m6i.24xlarge
              - m6i.32xlarge
              - m7i.large
              - m7i.xlarge
              - m7i.2xlarge
              - m7i.4xlarge
              - m7i.8xlarge
              - m7i.12xlarge
              - m7i.16xlarge
              - m7i.24xlarge
              - m7i.48xlarge
              - c6i.large
              - c6i.xlarge
              - c6i.2xlarge
              - c6i.4xlarge
              - c6i.8xlarge
              - c6i.12xlarge
              - c6i.16xlarge
              - c6i.24xlarge
              - c6i.32xlarge
              - c7i.large
              - c7i.xlarge
              - c7i.2xlarge
              - c7i.4xlarge
              - c7i.8xlarge
              - c7i.12xlarge
              - c7i.16xlarge
              - c7i.24xlarge
              - c7i.48xlarge
            - Ref: InstanceType
        AssertDescription: Region ap-southeast-5 supports t3.nano, t3.micro, t3.small, t3.medium, t3.large, t3.xlarge, t3.2xlarge, m6i.large, m6i.xlarge, m6i.2xlarge, m6i.4xlarge, m6i.8xlarge, m6i.12xlarge, m6i.16xlarge, m6i.24xlarge, m6i.32xlarge, m7i.large, m7i.xlarge, m7i.2xlarge, m7i.4xlarge, m7i.8xlarge, m7i.12xlarge, m7i.16xlarge, m7i.24xlarge, m7i.48xlarge, c6i.large, c6i.xlarge, c6i.2xlarge, c6i.4xlarge, c6i.8xlarge, c6i.12xlarge, c6i.16xlarge, c6i.24xlarge, c6i.32xlarge, c7i.large, c7i.xlarge, c7i.2xlarge, c7i.4xlarge, c7i.8xlarge, c7i.12xlarge, c7i.16xlarge, c7i.24xlarge, c7i.48xlarge
Mappings:
  RegionMap:
    us-east-1:
      AMI: ami-08ac9d73292f3d768
    us-west-2:
      AMI: ami-0220b7816e5ca8801
    us-west-1:
      AMI: ami-074fd1bafe2ccc1ec
    us-east-2:
      AMI: ami-07232d5c143b63540
    sa-east-1:
      AMI: ami-0f284a555226f1870
    me-south-1:
      AMI: ami-0acb5854d1924387a
    me-central-1:
      AMI: ami-0b65dc19adfe89e9d
    il-central-1:
      AMI: ami-0fb5bf37320632ba7
    eu-west-3:
      AMI: ami-0843d1bebe2fac778
    eu-west-2:
      AMI: ami-05dd3f2784ae5225a
    eu-west-1:
      AMI: ami-0b820b784505dcf1a
    eu-south-2:
      AMI: ami-08626505d511430a8
    eu-south-1:
      AMI: ami-0f318418e19c54cbd
    eu-north-1:
      AMI: ami-089c0d67a26244224
    eu-central-2:
      AMI: ami-0abe7d9c80d5ff7d3
    eu-central-1:
      AMI: ami-0dd0783fba93880d8
    ca-central-1:
      AMI: ami-0794bf43465685e8e
    ap-southeast-4:
      AMI: ami-038cdd401f6c4c496
    ap-southeast-3:
      AMI: ami-0f644a0ad00718e78
    ap-southeast-2:
      AMI: ami-06d62ffbc502c50d7
    ap-southeast-1:
      AMI: ami-0902fbb9b8730f099
    ap-south-2:
      AMI: ami-048ea0400fff1fca2
    ap-south-1:
      AMI: ami-036c68f65abf94f7c
    ap-northeast-3:
      AMI: ami-0d54f92b66227968d
    ap-northeast-2:
      AMI: ami-03e409e73a7c74f39
    ap-northeast-1:
      AMI: ami-0fca157fdf61826ac
    ap-east-1:
      AMI: ami-03b56cea9a253a935
    af-south-1:
      AMI: ami-0d6b5438009b2a11d
  InstanceTypeMap:
    t3a.nano:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3a.micro:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3a.small:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3a.medium:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    t3a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    t3.nano:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3.micro:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3.small:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3.medium:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    t3.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    t3.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m5a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m5a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m5a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m5a.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    m5a.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    m5a.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    m5a.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    m5a.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    m5.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m5.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m5.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m5.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    m5.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    m5.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    m5.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    m5.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    m6i.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m6i.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m6i.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m6i.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    m6i.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    m6i.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    m6i.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    m6i.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    m6i.32xlarge:
      TargetsBusyThreshold: "614"
      TargetsIdlingThreshold: "307"
    m6a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m6a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m6a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m6a.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    m6a.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    m6a.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    m6a.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    m6a.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    m6a.32xlarge:
      TargetsBusyThreshold: "614"
      TargetsIdlingThreshold: "307"
    m6a.48xlarge:
      TargetsBusyThreshold: "922"
      TargetsIdlingThreshold: "461"
    m7i.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m7i.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m7i.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m7i.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    m7i.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    m7i.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    m7i.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    m7i.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    m7i.48xlarge:
      TargetsBusyThreshold: "922"
      TargetsIdlingThreshold: "461"
    m7a.medium:
      TargetsBusyThreshold: "5"
      TargetsIdlingThreshold: "2"
    m7a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m7a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m7a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m7a.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    m7a.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    m7a.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    m7a.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    m7a.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    m7a.32xlarge:
      TargetsBusyThreshold: "614"
      TargetsIdlingThreshold: "307"
    m7a.48xlarge:
      TargetsBusyThreshold: "922"
      TargetsIdlingThreshold: "461"
    c5.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    c5.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    c5.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    c5.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    c5.9xlarge:
      TargetsBusyThreshold: "173"
      TargetsIdlingThreshold: "86"
    c5.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    c5.18xlarge:
      TargetsBusyThreshold: "346"
      TargetsIdlingThreshold: "173"
    c5.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    c5a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    c5a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    c5a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    c5a.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    c5a.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    c5a.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    c5a.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    c5a.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    c6i.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    c6i.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    c6i.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    c6i.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    c6i.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    c6i.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    c6i.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    c6i.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    c6i.32xlarge:
      TargetsBusyThreshold: "614"
      TargetsIdlingThreshold: "307"
    c6a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    c6a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    c6a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    c6a.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    c6a.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    c6a.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    c6a.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    c6a.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    c6a.32xlarge:
      TargetsBusyThreshold: "614"
      TargetsIdlingThreshold: "307"
    c6a.48xlarge:
      TargetsBusyThreshold: "922"
      TargetsIdlingThreshold: "461"
    c7i.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    c7i.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    c7i.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    c7i.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    c7i.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    c7i.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    c7i.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    c7i.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    c7i.48xlarge:
      TargetsBusyThreshold: "922"
      TargetsIdlingThreshold: "461"
    c7a.medium:
      TargetsBusyThreshold: "5"
      TargetsIdlingThreshold: "2"
    c7a.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    c7a.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    c7a.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    c7a.4xlarge:
      TargetsBusyThreshold: "77"
      TargetsIdlingThreshold: "38"
    c7a.8xlarge:
      TargetsBusyThreshold: "154"
      TargetsIdlingThreshold: "77"
    c7a.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
    c7a.16xlarge:
      TargetsBusyThreshold: "307"
      TargetsIdlingThreshold: "154"
    c7a.24xlarge:
      TargetsBusyThreshold: "461"
      TargetsIdlingThreshold: "230"
    c7a.32xlarge:
      TargetsBusyThreshold: "614"
      TargetsIdlingThreshold: "307"
    c7a.48xlarge:
      TargetsBusyThreshold: "922"
      TargetsIdlingThreshold: "461"
    m5zn.large:
      TargetsBusyThreshold: "10"
      TargetsIdlingThreshold: "5"
    m5zn.xlarge:
      TargetsBusyThreshold: "19"
      TargetsIdlingThreshold: "10"
    m5zn.2xlarge:
      TargetsBusyThreshold: "38"
      TargetsIdlingThreshold: "19"
    m5zn.3xlarge:
      TargetsBusyThreshold: "58"
      TargetsIdlingThreshold: "29"
    m5zn.6xlarge:
      TargetsBusyThreshold: "115"
      TargetsIdlingThreshold: "58"
    m5zn.12xlarge:
      TargetsBusyThreshold: "230"
      TargetsIdlingThreshold: "115"
Outputs:
  RoleArn:
    Description: The ARN of the IAM Role attached to the EC2 instances powering the API.
    Value:
      Fn::GetAtt:
        - ScanIAMRole
        - Arn
    Export:
      Name:
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - -RoleArn
  AutoScalingGroupName:
    Description: Name of the ASG powering the API.
    Value:
      Ref: ScanAutoScalingGroup
  LoadBalancerDnsName:
    Description: DNS name of the Application Load Balancer required to configure Route 53 Alias record.
    Value:
      Fn::GetAtt:
        - LoadBalancer
        - DNSName
  AddOn:
    Description: bucketAV add-on.
    Value: api-sync
  Platform:
    Description: bucketAV platform.
    Value: aws
  Engine:
    Description: bucketAV engine.
    Value: sophos
  FulfillmentOption:
    Description: Fulfillment option.
    Value: dedicated-public-vpc
  Version:
    Description: bucketAV add-on version.
    Value: 2.11.0
  StackName:
    Description: Stack name.
    Value:
      Ref: AWS::StackName

