Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- AWSTemplateFormatVersion: 2010-09-09
- Description: S3 VirusScan
- Metadata:
- 'AWS::CloudFormation::Interface':
- ParameterGroups:
- - Label:
- default: Integrations
- Parameters:
- - SecurityHubIntegration
- - OpsCenterIntegration
- - CloudWatchIntegration
- - Label:
- default: Scan Parameters
- Parameters:
- - DeleteInfectedFiles
- - TagFiles
- - TagKey
- - ReportCleanFiles
- - Label:
- default: Auto Scaling Group Parameters
- Parameters:
- - AutoScalingMinSize
- - AutoScalingMaxSize
- - Label:
- default: EC2 Parameters
- Parameters:
- - KeyName
- - SSHIngressCidrIp
- - InstanceType
- - LogsRetentionInDays
- - VolumeSize
- - Label:
- default: Permissions Parameters
- Parameters:
- - S3BucketRestriction
- - S3ObjectRestriction
- Parameters:
- SecurityHubIntegration:
- Description: >-
- Forward infected files and scan failures to AWS Security Hub (Security Hub
- needs to be enabled in the region).
- Type: String
- Default: 'false'
- AllowedValues:
- - 'true'
- - 'false'
- OpsCenterIntegration:
- Description: Forward infected files and scan failures to AWS Systems Manager OpsCenter.
- Type: String
- Default: 'false'
- AllowedValues:
- - 'true'
- - 'false'
- CloudWatchIntegration:
- Description: Generate CloudWatch Metrics with scan results.
- Type: String
- Default: 'true'
- AllowedValues:
- - 'true'
- - 'false'
- DeleteInfectedFiles:
- Description: Automatically delete infected files.
- Type: String
- Default: 'true'
- AllowedValues:
- - 'true'
- - 'false'
- ReportCleanFiles:
- Description: Report clean files to the SNS topic.
- Type: String
- Default: 'true'
- AllowedValues:
- - 'true'
- - 'false'
- TagFiles:
- Description: >-
- Tag S3 object upon successful scan accordingly with values of "clean",
- "infected", or "no" (infected only works if DeleteInfectedFiles != true)
- using the tag key specified by TagKey.
- Type: String
- Default: 'true'
- AllowedValues:
- - 'true'
- - 'false'
- TagKey:
- Description: 'S3 object tag key used to specify values of "clean", "infected", or "no".'
- Type: String
- Default: s3-virusscan
- AutoScalingMinSize:
- Description: Minimum number of EC2 instances scanning files.
- Type: Number
- Default: 1
- ConstraintDescription: Must be >= 1
- MinValue: 1
- AutoScalingMaxSize:
- Description: Maximum number of EC2 instances scanning files.
- Type: Number
- Default: 1
- ConstraintDescription: Must be >= 1
- MinValue: 1
- KeyName:
- Description: 'Name of the EC2 key pair to log in via SSH (username: ec2-user).'
- Type: 'AWS::EC2::KeyPair::KeyName'
- InstanceType:
- Description: Specifies the instance type of the EC2 instance
- Type: String
- Default: m5.large
- AllowedValues:
- - t3a.small
- - t3a.medium
- - t3a.large
- - t3a.xlarge
- - t3a.2xlarge
- - 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
- - m4.large
- - m4.xlarge
- - m4.2xlarge
- - m4.4xlarge
- - m4.10xlarge
- - m4.16xlarge
- LogsRetentionInDays:
- Description: Specifies the number of days you want to retain log events.
- Type: Number
- Default: 14
- AllowedValues:
- - 1
- - 3
- - 5
- - 7
- - 14
- - 30
- - 60
- - 90
- - 120
- - 150
- - 180
- - 365
- - 400
- - 545
- - 731
- - 1827
- - 3653
- VolumeSize:
- Description: >-
- The size of the volume, in gibibytes (GiBs). You can only scan files that
- are smaller than helf of this value.
- Type: Number
- Default: 8
- ConstraintDescription: 'Must be in the range [8-1024]'
- MinValue: 8
- MaxValue: 1024
- SSHIngressCidrIp:
- Description: >-
- Optional ingress rule allows SSH access from this IP address range (e.g.,
- access from anywhere: 0.0.0.0/0, from single IP address 91.45.138.21/32)
- Type: String
- Default: ''
- S3BucketRestriction:
- Description: >-
- Restrict access to specific S3 buckets (e.g.
- arn:aws:s3:::s3-virusscan-a,arn:aws:s3:::s3-virusscan-b or * to allow
- access to all S3 buckets).
- Type: CommaDelimitedList
- Default: '*'
- S3ObjectRestriction:
- Description: >-
- Restrict access to specific S3 objects (e.g.
- arn:aws:s3:::s3-virusscan-a/*,arn:aws:s3:::s3-virusscan-b/* or * to allow
- access to all S3 objects).
- Type: CommaDelimitedList
- Default: '*'
- Conditions:
- HasSecurityHubIntegration: !<!Equals>
- - !<!Ref> SecurityHubIntegration
- - 'true'
- HasOpsCenterIntegration: !<!Equals>
- - !<!Ref> OpsCenterIntegration
- - 'true'
- HasCloudWatchIntegration: !<!Equals>
- - !<!Ref> CloudWatchIntegration
- - 'true'
- HasDeleteInfectedFiles: !<!Equals>
- - !<!Ref> DeleteInfectedFiles
- - 'true'
- HasTagFiles: !<!Equals>
- - !<!Ref> TagFiles
- - 'true'
- HasSSHIngressCidrIp: !<!Not>
- - !<!Equals>
- - !<!Ref> SSHIngressCidrIp
- - ''
- Mappings:
- RegionMap:
- us-east-1:
- AMI: ami-090c254a59e5700e7
- us-east-2:
- AMI: ami-0d5b903b4f3cd7118
- us-west-1:
- AMI: ami-0256db1fa2dd0d0d8
- us-west-2:
- AMI: ami-083bca78f3e03c51e
- eu-west-1:
- AMI: ami-0a5f5ac4b648932e3
- eu-west-2:
- AMI: ami-0540b01de123f44bf
- eu-west-3:
- AMI: ami-095dffd5b3ff2a971
- eu-central-1:
- AMI: ami-0d49abfd983e4c175
- ca-central-1:
- AMI: ami-0497ff4107c2c00a0
- ap-southeast-1:
- AMI: ami-0d597b1ce1659b16b
- ap-northeast-1:
- AMI: ami-0f4b3d8ba2212daff
- ap-northeast-2:
- AMI: ami-0b073992f76339934
- sa-east-1:
- AMI: ami-043ae1117d1d3229e
- ap-southeast-2:
- AMI: ami-0ed4704ab99d8c6cd
- ap-south-1:
- AMI: ami-050d9f3486d9a18fb
- eu-north-1:
- AMI: ami-b21c97cc
- Resources:
- VPC:
- Type: 'AWS::EC2::VPC'
- Properties:
- CidrBlock: 10.0.0.0/16
- EnableDnsSupport: true
- EnableDnsHostnames: true
- InstanceTenancy: default
- Tags:
- - Key: Name
- Value: !<!Ref> 'AWS::StackName'
- InternetGateway:
- Type: 'AWS::EC2::InternetGateway'
- Properties:
- Tags:
- - Key: Name
- Value: !<!Ref> 'AWS::StackName'
- VPCGatewayAttachment:
- Type: 'AWS::EC2::VPCGatewayAttachment'
- Properties:
- VpcId: !<!Ref> VPC
- InternetGatewayId: !<!Ref> InternetGateway
- SubnetAPublic:
- Type: 'AWS::EC2::Subnet'
- Properties:
- AvailabilityZone: !<!Select>
- - 0
- - !<!GetAZs> ''
- CidrBlock: 10.0.0.0/20
- MapPublicIpOnLaunch: true
- VpcId: !<!Ref> VPC
- Tags:
- - Key: Name
- Value: !<!Sub> '${AWS::StackName}: A public'
- SubnetBPublic:
- Type: 'AWS::EC2::Subnet'
- Properties:
- AvailabilityZone: !<!Select>
- - 1
- - !<!GetAZs> ''
- CidrBlock: 10.0.32.0/20
- MapPublicIpOnLaunch: true
- VpcId: !<!Ref> VPC
- Tags:
- - Key: Name
- Value: !<!Sub> '${AWS::StackName}: B public'
- RouteTableAPublic:
- Type: 'AWS::EC2::RouteTable'
- Properties:
- VpcId: !<!Ref> VPC
- Tags:
- - Key: Name
- Value: !<!Sub> '${AWS::StackName}: A Public'
- RouteTableBPublic:
- Type: 'AWS::EC2::RouteTable'
- Properties:
- VpcId: !<!Ref> VPC
- Tags:
- - Key: Name
- Value: !<!Sub> '${AWS::StackName}: B Public'
- RouteTableAssociationAPublic:
- Type: 'AWS::EC2::SubnetRouteTableAssociation'
- Properties:
- SubnetId: !<!Ref> SubnetAPublic
- RouteTableId: !<!Ref> RouteTableAPublic
- RouteTableAssociationBPublic:
- Type: 'AWS::EC2::SubnetRouteTableAssociation'
- Properties:
- SubnetId: !<!Ref> SubnetBPublic
- RouteTableId: !<!Ref> RouteTableBPublic
- RouteTableAPublicInternetRoute:
- Type: 'AWS::EC2::Route'
- DependsOn: VPCGatewayAttachment
- Properties:
- RouteTableId: !<!Ref> RouteTableAPublic
- DestinationCidrBlock: 0.0.0.0/0
- GatewayId: !<!Ref> InternetGateway
- RouteTableBPublicInternetRoute:
- Type: 'AWS::EC2::Route'
- DependsOn: VPCGatewayAttachment
- Properties:
- RouteTableId: !<!Ref> RouteTableBPublic
- DestinationCidrBlock: 0.0.0.0/0
- GatewayId: !<!Ref> InternetGateway
- NetworkAclPublic:
- Type: 'AWS::EC2::NetworkAcl'
- Properties:
- VpcId: !<!Ref> VPC
- Tags:
- - Key: Name
- Value: !<!Sub> '${AWS::StackName}: Public'
- SubnetNetworkAclAssociationAPublic:
- Type: 'AWS::EC2::SubnetNetworkAclAssociation'
- Properties:
- SubnetId: !<!Ref> SubnetAPublic
- NetworkAclId: !<!Ref> NetworkAclPublic
- SubnetNetworkAclAssociationBPublic:
- Type: 'AWS::EC2::SubnetNetworkAclAssociation'
- Properties:
- SubnetId: !<!Ref> SubnetBPublic
- NetworkAclId: !<!Ref> NetworkAclPublic
- NetworkAclEntryInPublicAllowAll:
- Type: 'AWS::EC2::NetworkAclEntry'
- Properties:
- NetworkAclId: !<!Ref> NetworkAclPublic
- RuleNumber: 99
- Protocol: -1
- RuleAction: allow
- Egress: false
- CidrBlock: 0.0.0.0/0
- NetworkAclEntryOutPublicAllowAll:
- Type: 'AWS::EC2::NetworkAclEntry'
- Properties:
- NetworkAclId: !<!Ref> NetworkAclPublic
- RuleNumber: 99
- Protocol: -1
- RuleAction: allow
- Egress: true
- CidrBlock: 0.0.0.0/0
- FindingsTopic:
- Type: 'AWS::SNS::Topic'
- Properties: {}
- ScanQueue:
- Type: 'AWS::SQS::Queue'
- Properties:
- VisibilityTimeout: 300
- RedrivePolicy:
- deadLetterTargetArn: !<!GetAtt> DeadLetterQueue.Arn
- maxReceiveCount: 3
- ScanQueueFullAlarm:
- Type: 'AWS::CloudWatch::Alarm'
- Properties:
- AlarmActions:
- - !<!Ref> ScanScaleUp
- ComparisonOperator: GreaterThanThreshold
- Dimensions:
- - Name: QueueName
- Value: !<!GetAtt> ScanQueue.QueueName
- EvaluationPeriods: 1
- MetricName: ApproximateNumberOfMessagesVisible
- Namespace: AWS/SQS
- Period: 300
- Statistic: Maximum
- Threshold: 0
- TreatMissingData: notBreaching
- ScanQueueEmptyAlarm:
- Type: 'AWS::CloudWatch::Alarm'
- Properties:
- AlarmActions:
- - !<!Ref> ScanScaleDown
- ComparisonOperator: LessThanOrEqualToThreshold
- Dimensions:
- - Name: QueueName
- Value: !<!GetAtt> ScanQueue.QueueName
- EvaluationPeriods: 1
- MetricName: ApproximateNumberOfMessagesVisible
- Namespace: AWS/SQS
- Period: 300
- Statistic: Maximum
- Threshold: 0
- TreatMissingData: notBreaching
- ScanQueuePolicy:
- Type: 'AWS::SQS::QueuePolicy'
- Properties:
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Principal:
- AWS: '*'
- Action: 'SQS:SendMessage'
- Resource: !<!GetAtt> ScanQueue.Arn
- Queues:
- - !<!Ref> ScanQueue
- DeadLetterQueue:
- Type: 'AWS::SQS::Queue'
- Properties:
- MessageRetentionPeriod: 1209600
- DeadLetterQueueAlarm:
- Type: 'AWS::CloudWatch::Alarm'
- Properties:
- AlarmDescription: Alarm if dead letter queue has messages
- Namespace: AWS/SQS
- MetricName: ApproximateNumberOfMessagesVisible
- Dimensions:
- - Name: QueueName
- Value: !<!GetAtt> DeadLetterQueue.QueueName
- Statistic: Sum
- Period: 60
- EvaluationPeriods: 1
- Threshold: 1
- ComparisonOperator: GreaterThanOrEqualToThreshold
- AlarmActions:
- - !<!Ref> FindingsTopic
- TreatMissingData: notBreaching
- ScanQueueOldMessagesAlarm:
- Type: 'AWS::CloudWatch::Alarm'
- Properties:
- AlarmDescription: Alarm if scan queue contains a message older than 1 hour
- Namespace: AWS/SQS
- MetricName: ApproximateAgeOfOldestMessage
- Dimensions:
- - Name: QueueName
- Value: !<!GetAtt> ScanQueue.QueueName
- Statistic: Maximum
- Period: 60
- EvaluationPeriods: 1
- Threshold: 3600
- ComparisonOperator: GreaterThanOrEqualToThreshold
- AlarmActions:
- - !<!Ref> FindingsTopic
- TreatMissingData: notBreaching
- ScanAutoScalingGroup:
- DependsOn: VPCGatewayAttachment
- Type: 'AWS::AutoScaling::AutoScalingGroup'
- Properties:
- LaunchConfigurationName: !<!Ref> ScanLaunchConfiguration
- MetricsCollection:
- - Granularity: 1Minute
- Metrics:
- - GroupTotalInstances
- - GroupMaxSize
- MaxSize: !<!Ref> AutoScalingMaxSize
- MinSize: !<!Ref> AutoScalingMinSize
- VPCZoneIdentifier:
- - !<!Ref> SubnetAPublic
- - !<!Ref> SubnetBPublic
- Tags:
- - Key: Name
- Value: !<!Ref> 'AWS::StackName'
- PropagateAtLaunch: true
- CreationPolicy:
- ResourceSignal:
- Timeout: PT10M
- UpdatePolicy:
- AutoScalingRollingUpdate:
- PauseTime: PT10M
- WaitOnResourceSignals: true
- ScanScaleUp:
- Type: 'AWS::AutoScaling::ScalingPolicy'
- Properties:
- AdjustmentType: ChangeInCapacity
- AutoScalingGroupName: !<!Ref> ScanAutoScalingGroup
- EstimatedInstanceWarmup: 300
- MetricAggregationType: Maximum
- PolicyType: StepScaling
- StepAdjustments:
- - MetricIntervalLowerBound: 0
- MetricIntervalUpperBound: 25
- ScalingAdjustment: 1
- - MetricIntervalLowerBound: 25
- MetricIntervalUpperBound: 100
- ScalingAdjustment: 2
- - MetricIntervalLowerBound: 100
- MetricIntervalUpperBound: 400
- ScalingAdjustment: 4
- - MetricIntervalLowerBound: 400
- MetricIntervalUpperBound: 1600
- ScalingAdjustment: 8
- - MetricIntervalLowerBound: 1600
- MetricIntervalUpperBound: 6400
- ScalingAdjustment: 16
- - MetricIntervalLowerBound: 6400
- MetricIntervalUpperBound: 25600
- ScalingAdjustment: 32
- - MetricIntervalLowerBound: 25600
- ScalingAdjustment: 64
- ScanScaleDown:
- Type: 'AWS::AutoScaling::ScalingPolicy'
- Properties:
- AdjustmentType: PercentChangeInCapacity
- AutoScalingGroupName: !<!Ref> ScanAutoScalingGroup
- Cooldown: '300'
- MinAdjustmentMagnitude: 1
- PolicyType: SimpleScaling
- ScalingAdjustment: -25
- ScanInstanceProfile:
- Type: 'AWS::IAM::InstanceProfile'
- Properties:
- Roles:
- - !<!Ref> ScanIAMRole
- ScanIAMRole:
- Type: 'AWS::IAM::Role'
- Properties:
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Principal:
- Service: ec2.amazonaws.com
- Action: 'sts:AssumeRole'
- Policies:
- - PolicyName: s3read
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Action:
- - 's3:GetObject*'
- Resource: !<!Ref> S3ObjectRestriction
- - Effect: Allow
- Action:
- - 's3:ListBucket*'
- Resource: !<!Ref> S3BucketRestriction
- - PolicyName: sqs
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Action:
- - 'sqs:DeleteMessage'
- - 'sqs:ReceiveMessage'
- Resource: !<!GetAtt> ScanQueue.Arn
- - PolicyName: sns
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Action: 'sns:Publish'
- Resource: !<!Ref> FindingsTopic
- - PolicyName: logs
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Action:
- - 'logs:CreateLogGroup'
- - 'logs:CreateLogStream'
- - 'logs:PutLogEvents'
- - 'logs:DescribeLogStreams'
- Resource: 'arn:aws:logs:*:*:*'
- DeletePolicy:
- Type: 'AWS::IAM::Policy'
- Condition: HasDeleteInfectedFiles
- Properties:
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Action:
- - 's3:DeleteObject*'
- Resource: !<!Ref> S3ObjectRestriction
- PolicyName: s3delete
- Roles:
- - !<!Ref> ScanIAMRole
- TagObjectPolicy:
- Type: 'AWS::IAM::Policy'
- Condition: HasTagFiles
- Properties:
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Action:
- - 's3:PutObjectTagging'
- - 's3:PutObjectVersionTagging'
- Resource: !<!Ref> S3ObjectRestriction
- Condition:
- 'ForAllValues:StringLike':
- 's3:RequestObjectTagKeys': !<!Ref> TagKey
- PolicyName: s3objecttag
- Roles:
- - !<!Ref> ScanIAMRole
- Logs:
- Type: 'AWS::Logs::LogGroup'
- Properties:
- RetentionInDays: !<!Ref> LogsRetentionInDays
- ScanLaunchConfiguration:
- Type: 'AWS::AutoScaling::LaunchConfiguration'
- Metadata:
- 'AWS::CloudFormation::Init':
- config:
- files:
- /etc/awslogs/awscli.conf:
- content: !<!Sub> |
- [default]
- region = ${AWS::Region}
- [plugins]
- cwlogs = cwlogs
- mode: '000644'
- owner: root
- group: root
- /etc/awslogs/config/files.conf:
- content: !<!Sub> |
- [/var/log/audit/audit.log]
- file = /var/log/audit/audit.log
- log_stream_name = {instance_id}/var/log/audit/audit.log
- log_group_name = ${Logs}
- [/var/log/awslogs.log]
- datetime_format = %Y-%m-%d %H:%M:%S
- file = /var/log/awslogs.log
- log_stream_name = {instance_id}/var/log/awslogs.log
- log_group_name = ${Logs}
- [/var/log/boot.log]
- file = /var/log/boot.log
- log_stream_name = {instance_id}/var/log/boot.log
- log_group_name = ${Logs}
- [/var/log/cfn-hup.log]
- datetime_format = %Y-%m-%d %H:%M:%S
- file = /var/log/cfn-hup.log
- log_stream_name = {instance_id}/var/log/cfn-hup.log
- log_group_name = ${Logs}
- [/var/log/cfn-init-cmd.log]
- datetime_format = %Y-%m-%d %H:%M:%S
- file = /var/log/cfn-init-cmd.log
- log_stream_name = {instance_id}/var/log/cfn-init-cmd.log
- log_group_name = ${Logs}
- [/var/log/cfn-init.log]
- datetime_format = %Y-%m-%d %H:%M:%S
- file = /var/log/cfn-init.log
- log_stream_name = {instance_id}/var/log/cfn-init.log
- log_group_name = ${Logs}
- [/var/log/cfn-wire.log]
- datetime_format = %Y-%m-%d %H:%M:%S
- file = /var/log/cfn-wire.log
- log_stream_name = {instance_id}/var/log/cfn-wire.log
- log_group_name = ${Logs}
- [/var/log/cloud-init-output.log]
- file = /var/log/cloud-init-output.log
- log_stream_name = {instance_id}/var/log/cloud-init-output.log
- log_group_name = ${Logs}
- [/var/log/cloud-init.log]
- datetime_format = %b %d %H:%M:%S
- file = /var/log/cloud-init.log
- log_stream_name = {instance_id}/var/log/cloud-init.log
- log_group_name = ${Logs}
- [/var/log/cron]
- datetime_format = %b %d %H:%M:%S
- file = /var/log/cron
- log_stream_name = {instance_id}/var/log/cron
- log_group_name = ${Logs}
- [/var/log/dmesg]
- file = /var/log/dmesg
- log_stream_name = {instance_id}/var/log/dmesg
- log_group_name = ${Logs}
- [/var/log/freshclam.log]
- file = /var/log/freshclam.log
- log_stream_name = {instance_id}/var/log/freshclam.log
- log_group_name = ${Logs}
- [/var/log/grubby_prune_debug]
- file = /var/log/grubby_prune_debug
- log_stream_name = {instance_id}/var/log/grubby_prune_debug
- log_group_name = ${Logs}
- [/var/log/maillog]
- datetime_format = %b %d %H:%M:%S
- file = /var/log/maillog
- log_stream_name = {instance_id}/var/log/maillog
- log_group_name = ${Logs}
- [/var/log/messages]
- datetime_format = %b %d %H:%M:%S
- file = /var/log/messages
- log_stream_name = {instance_id}/var/log/messages
- log_group_name = ${Logs}
- [/var/log/secure]
- datetime_format = %b %d %H:%M:%S
- file = /var/log/secure
- log_stream_name = {instance_id}/var/log/secure
- log_group_name = ${Logs}
- [/var/log/yum.log]
- datetime_format = %b %d %H:%M:%S
- file = /var/log/yum.log
- log_stream_name = {instance_id}/var/log/yum.log
- log_group_name = ${Logs}
- mode: '000644'
- owner: root
- group: root
- /opt/s3-virusscan/s3-virusscan.conf:
- content: !<!Sub> |
- delete: ${DeleteInfectedFiles}
- report_clean: ${ReportCleanFiles}
- tag_files: ${TagFiles}
- tag_key: ${TagKey}
- region: ${AWS::Region}
- queue: ${ScanQueue}
- topic: ${FindingsTopic}
- volume_size: ${VolumeSize}
- mode: '000644'
- owner: ec2-user
- group: ec2-user
- /etc/cfn/cfn-hup.conf:
- content: !<!Sub> |
- [main]
- stack=${AWS::StackId}
- region=${AWS::Region}
- interval=1
- mode: '000400'
- owner: root
- group: root
- /etc/cfn/hooks.d/cfn-auto-reloader.conf:
- content: !<!Sub> >
- [cfn-auto-reloader-hook]
- triggers=post.update
- path=Resources.ScanLaunchConfiguration.Metadata.AWS::CloudFormation::Init
- action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName}
- --resource ScanLaunchConfiguration --region ${AWS::Region}
- runas=root
- services:
- sysvinit:
- awslogsd:
- enabled: true
- ensureRunning: true
- files:
- - /etc/awslogs/awscli.conf
- - /etc/awslogs/config/files.conf
- cfn-hup:
- enabled: true
- ensureRunning: true
- files:
- - /etc/cfn/cfn-hup.conf
- - /etc/cfn/hooks.d/cfn-auto-reloader.conf
- s3-virusscan:
- enabled: true
- ensureRunning: true
- files:
- - /opt/s3-virusscan/s3-virusscan.conf
- Properties:
- KeyName: !<!Ref> KeyName
- AssociatePublicIpAddress: true
- EbsOptimized: false
- BlockDeviceMappings:
- - DeviceName: /dev/xvda
- Ebs:
- VolumeSize: !<!Ref> VolumeSize
- VolumeType: gp2
- IamInstanceProfile: !<!Ref> ScanInstanceProfile
- ImageId: !<!FindInMap>
- - RegionMap
- - !<!Ref> 'AWS::Region'
- - AMI
- InstanceType: !<!Ref> InstanceType
- SecurityGroups:
- - !<!Ref> ScanSecurityGroup
- UserData: !<!Base64>
- 'Fn::Sub': >
- #!/bin/bash -ex
- trap '/opt/aws/bin/cfn-signal -e 1 --stack ${AWS::StackName}
- --resource ScanAutoScalingGroup --region ${AWS::Region}' ERR
- /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource
- ScanLaunchConfiguration --region ${AWS::Region}
- /opt/aws/bin/cfn-signal -e 0 --stack ${AWS::StackName} --resource
- ScanAutoScalingGroup --region ${AWS::Region}
- ScanSecurityGroup:
- Type: 'AWS::EC2::SecurityGroup'
- Properties:
- GroupDescription: !<!Ref> 'AWS::StackName'
- VpcId: !<!Ref> VPC
- ScanSecurityGroupInSSH:
- Condition: HasSSHIngressCidrIp
- Type: 'AWS::EC2::SecurityGroupIngress'
- Properties:
- GroupId: !<!Ref> ScanSecurityGroup
- IpProtocol: tcp
- FromPort: 22
- ToPort: 22
- CidrIp: !<!Ref> SSHIngressCidrIp
- Dashboard:
- Type: 'AWS::CloudWatch::Dashboard'
- Properties:
- DashboardBody: !<!Sub> |
- {
- "widgets": [{
- "type": "metric",
- "x": 0,
- "y": 0,
- "width": 6,
- "height": 6,
- "properties": {
- "metrics": [
- [ "${AWS::StackName}", "clean", { "period": 300, "stat": "Sum", "label": "Clean" } ],
- [ ".", "infected", { "period": 300, "stat": "Sum", "color": "#d62728", "label": "Infected" } ],
- [ ".", "no", { "period": 300, "stat": "Sum", "color": "#ff7f0e", "label": "No" } ]
- ],
- "view": "timeSeries",
- "stacked": true,
- "region": "${AWS::Region}",
- "title": "Scan results (requires CloudWatch Integration)",
- "period": 300
- }
- }, {
- "type": "metric",
- "x": 6,
- "y": 0,
- "width": 6,
- "height": 6,
- "properties": {
- "metrics": [
- [ "AWS/SQS", "ApproximateNumberOfMessagesVisible", "QueueName", "${ScanQueue.QueueName}", { "label": "Length" } ]
- ],
- "view": "timeSeries",
- "stacked": false,
- "region": "${AWS::Region}",
- "title": "Scan Queue Length",
- "period": 300
- }
- }, {
- "type": "metric",
- "x": 12,
- "y": 0,
- "width": 6,
- "height": 6,
- "properties": {
- "metrics": [
- [ "AWS/AutoScaling", "GroupTotalInstances", "AutoScalingGroupName", "${ScanAutoScalingGroup}", { "stat": "Average", "label": "Running Instances" } ],
- [ ".", "GroupMaxSize", ".", ".", { "label": "Max Instances", "color": "#d62728" } ]
- ],
- "view": "timeSeries",
- "stacked": false,
- "region": "${AWS::Region}",
- "title": "Scan Fleet",
- "period": 300
- }
- }, {
- "type": "log",
- "x": 0,
- "y": 6,
- "width": 24,
- "height": 6,
- "properties": {
- "query": "SOURCE '${Logs}' | fields @timestamp, @message\n| filter @logStream like /messages/ and @message like /s3-virusscan/\n| sort @timestamp desc\n| limit 100",
- "region": "${AWS::Region}",
- "stacked": false,
- "title": "Scan Logs",
- "view": "table"
- }
- }, {
- "type": "log",
- "x": 0,
- "y": 12,
- "width": 24,
- "height": 6,
- "properties": {
- "query": "SOURCE '${Logs}' | fields @timestamp, @message\n| sort @timestamp desc\n| limit 100",
- "region": "${AWS::Region}",
- "stacked": false,
- "title": "System Logs",
- "view": "table"
- }
- }]
- }
- DashboardName: !<!Ref> 'AWS::StackName'
- SecurityHubIntegrationSubscription:
- Condition: HasSecurityHubIntegration
- DependsOn:
- - SecurityHubIntegrationLambdaPermission
- - SecurityHubIntegrationLambdaPolicy
- Type: 'AWS::SNS::Subscription'
- Properties:
- Endpoint: !<!GetAtt> SecurityHubIntegrationLambdaFunction.Arn
- FilterPolicy:
- status:
- - infected
- - 'no'
- Protocol: lambda
- TopicArn: !<!Ref> FindingsTopic
- SecurityHubIntegrationLambdaPermission:
- Condition: HasSecurityHubIntegration
- Type: 'AWS::Lambda::Permission'
- Properties:
- Action: 'lambda:InvokeFunction'
- FunctionName: !<!Ref> SecurityHubIntegrationLambdaFunction
- Principal: sns.amazonaws.com
- SourceArn: !<!Ref> FindingsTopic
- SecurityHubIntegrationLambdaRole:
- Condition: HasSecurityHubIntegration
- Type: 'AWS::IAM::Role'
- Properties:
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Principal:
- Service: lambda.amazonaws.com
- Action: 'sts:AssumeRole'
- Policies:
- - PolicyName: securityhub
- PolicyDocument:
- Statement:
- - Effect: Allow
- Action: 'securityhub:BatchImportFindings'
- Resource: '*'
- Condition:
- StringEquals:
- 'securityhub:TargetAccount': !<!Ref> 'AWS::AccountId'
- SecurityHubIntegrationLambdaPolicy:
- Condition: HasSecurityHubIntegration
- Type: 'AWS::IAM::Policy'
- Properties:
- Roles:
- - !<!Ref> SecurityHubIntegrationLambdaRole
- PolicyName: logs
- PolicyDocument:
- Statement:
- - Effect: Allow
- Action:
- - 'logs:CreateLogStream'
- - 'logs:PutLogEvents'
- Resource: !<!GetAtt> SecurityHubIntegrationLambdaLogGroup.Arn
- SecurityHubIntegrationLambdaFunction:
- Condition: HasSecurityHubIntegration
- Type: 'AWS::Lambda::Function'
- Properties:
- Code:
- ZipFile: !<!Sub> |
- 'use strict';
- const AWS = require('aws-sdk');
- const securityhub = new AWS.SecurityHub({apiVersion: '2018-10-26'});
- function generateTitle(record) {
- if (record.Sns.MessageAttributes.status.Value === 'no') {
- return 'File scan failed';
- } else if (record.Sns.MessageAttributes.status.Value === 'infected') {
- return 'Infected file found';
- } else {
- throw new Error(`unsupported status: ${!record.Sns.MessageAttributes.status.Value}`);
- }
- }
- function generateDescription(record) {
- if (record.Sns.MessageAttributes.status.Value === 'no') {
- if ('version' in record.Sns.MessageAttributes) {
- return `File in S3 bucket ${!record.Sns.MessageAttributes.bucket.Value} with key ${!record.Sns.MessageAttributes.key.Value} and version ${!record.Sns.MessageAttributes.version.Value} could not be scanned, ${!record.Sns.MessageAttributes.action.Value} action executed`;
- } else {
- return `File in S3 bucket ${!record.Sns.MessageAttributes.bucket.Value} with key ${!record.Sns.MessageAttributes.key.Value} could not be scanned, ${!record.Sns.MessageAttributes.action.Value} action executed`;
- }
- } else if (record.Sns.MessageAttributes.status.Value === 'infected') {
- if ('version' in record.Sns.MessageAttributes) {
- return `Infected file found in S3 bucket ${!record.Sns.MessageAttributes.bucket.Value} with key ${!record.Sns.MessageAttributes.key.Value} and version ${!record.Sns.MessageAttributes.version.Value}, ${!record.Sns.MessageAttributes.action.Value} action executed`;
- } else {
- return `Infected file found in S3 bucket ${!record.Sns.MessageAttributes.bucket.Value} with key ${!record.Sns.MessageAttributes.key.Value}, ${!record.Sns.MessageAttributes.action.Value} action executed`;
- }
- } else {
- throw new Error(`unsupported status: ${!record.Sns.MessageAttributes.status.Value}`);
- }
- }
- exports.handler = async (event) => {
- console.log(`Invoke: ${!JSON.stringify(event)}`);
- try {
- await securityhub.batchImportFindings({
- Findings: event.Records.map(record => {
- const finding = {
- SchemaVersion: '2018-10-08',
- Id: record.Sns.MessageId,
- ProductArn: 'arn:${AWS::Partition}:securityhub:${AWS::Region}:${AWS::AccountId}:product/${AWS::AccountId}/default',
- GeneratorId: 'clamav',
- AwsAccountId: '${AWS::AccountId}',
- Types: ['Unusual Behaviors/Data'],
- CreatedAt: record.Sns.Timestamp,
- UpdatedAt: record.Sns.Timestamp,
- Severity: {
- Product: 1,
- Normalized: 70
- },
- Confidence: 100,
- Title: generateTitle(record),
- Description: generateDescription(record),
- Resources: [{
- Type: 'AwsS3Bucket',
- Id: `arn:aws:s3:::${!record.Sns.MessageAttributes.bucket.Value}`
- }]
- };
- return finding;
- })
- }).promise();
- } catch (err) {
- if (err.code === 'AccessDeniedException') { // Security Hub not enabled
- return false;
- } else {
- throw err;
- }
- }
- return true;
- };
- Handler: index.handler
- MemorySize: 128
- Role: !<!GetAtt> SecurityHubIntegrationLambdaRole.Arn
- Runtime: nodejs8.10
- Timeout: 60
- SecurityHubIntegrationLambdaLogGroup:
- Condition: HasSecurityHubIntegration
- Type: 'AWS::Logs::LogGroup'
- Properties:
- LogGroupName: !<!Sub> '/aws/lambda/${SecurityHubIntegrationLambdaFunction}'
- RetentionInDays: !<!Ref> LogsRetentionInDays
- OpsCenterIntegrationSubscription:
- Condition: HasOpsCenterIntegration
- DependsOn:
- - OpsCenterIntegrationLambdaPermission
- - OpsCenterIntegrationLambdaPolicy
- Type: 'AWS::SNS::Subscription'
- Properties:
- Endpoint: !<!GetAtt> OpsCenterIntegrationLambdaFunction.Arn
- FilterPolicy:
- status:
- - infected
- - 'no'
- Protocol: lambda
- TopicArn: !<!Ref> FindingsTopic
- OpsCenterIntegrationLambdaPermission:
- Condition: HasOpsCenterIntegration
- Type: 'AWS::Lambda::Permission'
- Properties:
- Action: 'lambda:InvokeFunction'
- FunctionName: !<!Ref> OpsCenterIntegrationLambdaFunction
- Principal: sns.amazonaws.com
- SourceArn: !<!Ref> FindingsTopic
- OpsCenterIntegrationLambdaRole:
- Condition: HasOpsCenterIntegration
- Type: 'AWS::IAM::Role'
- Properties:
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Principal:
- Service: lambda.amazonaws.com
- Action: 'sts:AssumeRole'
- Policies:
- - PolicyName: ssm
- PolicyDocument:
- Statement:
- - Effect: Allow
- Action: 'ssm:CreateOpsItem'
- Resource: '*'
- OpsCenterIntegrationLambdaPolicy:
- Condition: HasOpsCenterIntegration
- Type: 'AWS::IAM::Policy'
- Properties:
- Roles:
- - !<!Ref> OpsCenterIntegrationLambdaRole
- PolicyName: logs
- PolicyDocument:
- Statement:
- - Effect: Allow
- Action:
- - 'logs:CreateLogStream'
- - 'logs:PutLogEvents'
- Resource: !<!GetAtt> OpsCenterIntegrationLambdaLogGroup.Arn
- OpsCenterIntegrationLambdaFunction:
- Condition: HasOpsCenterIntegration
- Type: 'AWS::Lambda::Function'
- Properties:
- Code:
- ZipFile: |
- 'use strict';
- const AWS = require('aws-sdk');
- const ssm = new AWS.SSM({apiVersion: '2018-10-26'});
- function generateTitle(record) {
- if (record.Sns.MessageAttributes.status.Value === 'no') {
- return 'File scan failed';
- } else if (record.Sns.MessageAttributes.status.Value === 'infected') {
- return 'Infected file found';
- } else {
- throw new Error(`unsupported status: ${record.Sns.MessageAttributes.status.Value}`);
- }
- }
- function generateDescription(record) {
- if (record.Sns.MessageAttributes.status.Value === 'no') {
- if ('version' in record.Sns.MessageAttributes) {
- return `File in S3 bucket ${record.Sns.MessageAttributes.bucket.Value} with key ${record.Sns.MessageAttributes.key.Value} and version ${record.Sns.MessageAttributes.version.Value} could not be scanned, ${record.Sns.MessageAttributes.action.Value} action executed`;
- } else {
- return `File in S3 bucket ${record.Sns.MessageAttributes.bucket.Value} with key ${record.Sns.MessageAttributes.key.Value} could not be scanned, ${record.Sns.MessageAttributes.action.Value} action executed`;
- }
- } else if (record.Sns.MessageAttributes.status.Value === 'infected') {
- if ('version' in record.Sns.MessageAttributes) {
- return `Infected file found in S3 bucket ${record.Sns.MessageAttributes.bucket.Value} with key ${record.Sns.MessageAttributes.key.Value} and version ${record.Sns.MessageAttributes.version.Value}, ${record.Sns.MessageAttributes.action.Value} action executed`;
- } else {
- return `Infected file found in S3 bucket ${record.Sns.MessageAttributes.bucket.Value} with key ${record.Sns.MessageAttributes.key.Value}, ${record.Sns.MessageAttributes.action.Value} action executed`;
- }
- } else {
- throw new Error(`unsupported status: ${record.Sns.MessageAttributes.status.Value}`);
- }
- }
- exports.handler = async (event) => {
- console.log(`Invoke: ${JSON.stringify(event)}`);
- await Promise.all(event.Records.map(record => {
- return ssm.createOpsItem({
- Description: generateDescription(record),
- OperationalData: {
- '/aws/resources': {
- Value: `[{"arn": "arn:aws:s3:::${record.Sns.MessageAttributes.bucket.Value}"}]`,
- Type: 'SearchableString'
- },
- 'bucket': {
- Value: record.Sns.MessageAttributes.bucket.Value,
- Type: 'SearchableString'
- },
- 'key': {
- Value: record.Sns.MessageAttributes.bucket.Value,
- Type: 'SearchableString'
- }
- },
- Priority: 4,
- Source: 's3-virusscan',
- Title: generateTitle(record),
- }).promise();
- }));
- return true;
- };
- Handler: index.handler
- MemorySize: 128
- Role: !<!GetAtt> OpsCenterIntegrationLambdaRole.Arn
- Runtime: nodejs8.10
- Timeout: 60
- OpsCenterIntegrationLambdaLogGroup:
- Condition: HasOpsCenterIntegration
- Type: 'AWS::Logs::LogGroup'
- Properties:
- LogGroupName: !<!Sub> '/aws/lambda/${OpsCenterIntegrationLambdaFunction}'
- RetentionInDays: !<!Ref> LogsRetentionInDays
- CloudWatchIntegrationSubscription:
- Condition: HasCloudWatchIntegration
- DependsOn:
- - CloudWatchIntegrationLambdaPermission
- - CloudWatchIntegrationLambdaPolicy
- Type: 'AWS::SNS::Subscription'
- Properties:
- Endpoint: !<!GetAtt> CloudWatchIntegrationLambdaFunction.Arn
- Protocol: lambda
- TopicArn: !<!Ref> FindingsTopic
- CloudWatchIntegrationLambdaPermission:
- Condition: HasCloudWatchIntegration
- Type: 'AWS::Lambda::Permission'
- Properties:
- Action: 'lambda:InvokeFunction'
- FunctionName: !<!Ref> CloudWatchIntegrationLambdaFunction
- Principal: sns.amazonaws.com
- SourceArn: !<!Ref> FindingsTopic
- CloudWatchIntegrationLambdaRole:
- Condition: HasCloudWatchIntegration
- Type: 'AWS::IAM::Role'
- Properties:
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- - Effect: Allow
- Principal:
- Service: lambda.amazonaws.com
- Action: 'sts:AssumeRole'
- Policies:
- - PolicyName: ssm
- PolicyDocument:
- Statement:
- - Effect: Allow
- Action: 'cloudwatch:PutMetricData'
- Resource: '*'
- CloudWatchIntegrationLambdaPolicy:
- Condition: HasCloudWatchIntegration
- Type: 'AWS::IAM::Policy'
- Properties:
- Roles:
- - !<!Ref> CloudWatchIntegrationLambdaRole
- PolicyName: logs
- PolicyDocument:
- Statement:
- - Effect: Allow
- Action:
- - 'logs:CreateLogStream'
- - 'logs:PutLogEvents'
- Resource: !<!GetAtt> CloudWatchIntegrationLambdaLogGroup.Arn
- CloudWatchIntegrationLambdaFunction:
- Condition: HasCloudWatchIntegration
- Type: 'AWS::Lambda::Function'
- Properties:
- Code:
- ZipFile: |
- 'use strict';
- const AWS = require('aws-sdk');
- const cloudwatch = new AWS.CloudWatch({apiVersion: '2010-08-01'});
- exports.handler = async (event) => {
- console.log(`Invoke: ${JSON.stringify(event)}`);
- const no = event.Records.filter(record => record.Sns.MessageAttributes.status.Value === 'no').length;
- const clean = event.Records.filter(record => record.Sns.MessageAttributes.status.Value === 'clean').length;
- const infected = event.Records.filter(record => record.Sns.MessageAttributes.status.Value === 'infected').length;
- await cloudwatch.putMetricData({
- Namespace: process.env.NAMESPACE,
- MetricData: [{
- MetricName: 'no',
- Value: no,
- Unit: 'Count'
- }, {
- MetricName: 'clean',
- Value: clean,
- Unit: 'Count'
- }, {
- MetricName: 'infected',
- Value: infected,
- Unit: 'Count'
- }]
- }).promise();
- return true;
- };
- Environment:
- Variables:
- NAMESPACE: !<!Ref> 'AWS::StackName'
- Handler: index.handler
- MemorySize: 128
- Role: !<!GetAtt> CloudWatchIntegrationLambdaRole.Arn
- Runtime: nodejs8.10
- Timeout: 60
- CloudWatchIntegrationLambdaLogGroup:
- Condition: HasCloudWatchIntegration
- Type: 'AWS::Logs::LogGroup'
- Properties:
- LogGroupName: !<!Sub> '/aws/lambda/${CloudWatchIntegrationLambdaFunction}'
- RetentionInDays: !<!Ref> LogsRetentionInDays
- Outputs:
- Version:
- Description: S3 VirusScan version.
- Value: 1.0.0
- StackName:
- Description: Stack name.
- Value: !<!Sub> '${AWS::StackName}'
- ScanQueueArn:
- Description: The ARN of the Scan Queue.
- Value: !<!GetAtt> ScanQueue.Arn
- Export:
- Name: !<!Sub> '${AWS::StackName}-ScanQueueArn'
- ScanQueueName:
- Description: The name of the Scan Queue.
- Value: !<!GetAtt> ScanQueue.QueueName
- Export:
- Name: !<!Sub> '${AWS::StackName}-ScanQueueName'
- ScanQueueUrl:
- Description: The URL of the Scan Queue.
- Value: !<!Ref> ScanQueue
- Export:
- Name: !<!Sub> '${AWS::StackName}-ScanQueueUrl'
- FindingsTopicArn:
- Description: The ARN of the Findings Topic.
- Value: !<!Ref> FindingsTopic
- Export:
- Name: !<!Sub> '${AWS::StackName}-FindingsTopicArn'
- FindingsTopicName:
- Description: The name of the Findings Topic.
- Value: !<!GetAtt> FindingsTopic.TopicName
- Export:
- Name: !<!Sub> '${AWS::StackName}-FindingsTopicName'
Add Comment
Please, Sign In to add comment