Automated EC2 Malware Scanning with AWS GuardDuty and SAM

This guide details how to automate on-demand malware scans for your Amazon EC2 instances using AWS GuardDuty, orchestrated via a simple AWS Serverless Application Model (SAM) template and a Python Lambda function. By following these steps, you can establish a robust, scheduled security check for your running instances.

Essential Prerequisites

Before deploying the automation, ensure the following conditions are met:

  • Encrypted EC2 Volumes: All EC2 instances targeted for scanning must have their EBS volumes encrypted with an AWS Key Management Service (KMS) Customer Managed Key (CMK). If your existing EBS volumes are not encrypted or you need to change their encryption key, consult AWS documentation on modifying EBS encryption settings.
  • IAM Permissions: You will require appropriate AWS Identity and Access Management (IAM) permissions to deploy AWS SAM applications within your account.

Step-by-Step Implementation

The automation relies on an AWS Lambda function, defined and deployed using an AWS SAM template, which will trigger GuardDuty scans on a predefined schedule.

1. Defining the Lambda Function with AWS SAM

Begin by creating a new SAM template file (e.g., template.yaml). This file will define your Lambda function, including its runtime, execution schedule, and necessary permissions.

# Simplified representation of the SAM template structure
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  Automates GuardDuty on-demand malware scans for EC2 instances.

Resources:
  EC2MalwareScan:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: ec2-malware-scan-weekly
      Description: Initiates GuardDuty on-demand malware scans for running EC2 instances
      PackageType: Zip
      Runtime: python3.13
      Handler: ec2_malware_scan_guardduty.ec2_malware_scan
      CodeUri: lambdas/infrastructure/ec2_malware_scan/
      Timeout: 60
      MemorySize: 256
      Tracing: Active
      LoggingConfig:
        LogFormat: JSON
      Architectures:
        - x86_64
      Events:
        WeeklySchedule:
          Type: ScheduleV2
          Properties:
            ScheduleExpression: 'cron(0 6 ? * MON *)' # Runs every Monday at 6 AM UTC
            Name: WeeklyEC2MalwareScan
            Description: Weekly EC2 malware scan
            State: ENABLED
            RetryPolicy: # Optional retry configuration
              MaximumEventAgeInSeconds: 3600
              MaximumRetryAttempts: 2
      Policies: # IAM permissions for the Lambda function
        - Statement:
            - Sid: Ec2Describe
              Effect: Allow
              Action: ec2:DescribeInstances
              Resource: "*"
            - Sid: GuardDutyScanOnly
              Effect: Allow
              Action:
                - guardduty:ListDetectors
                - guardduty:GetDetector
                - guardduty:StartMalwareScan
              Resource: "*"
            - Sid: IAMPermissions
              Effect: Allow
              Action:
                - iam:GetRole
                - iam:PassRole
              Resource: "arn:aws:iam::*:role/aws-service-role/malware-protection.guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDutyMalwareProtection"
            - Sid: StsCaller
              Effect: Allow
              Action: sts:GetCallerIdentity
              Resource: "*"
      Environment:
        Variables:
          EXCLUDED_INSTANCES: "" # Comma-separated list of Instance IDs to exclude

Key components of this SAM template:

  • Lambda Function (EC2MalwareScan): Configured with Python 3.13 runtime, a 60-second timeout, and 256MB memory.
  • Scheduled Execution: The WeeklySchedule EventBridge rule triggers the Lambda function every Monday at 6 AM UTC. This schedule can be customized to fit your operational needs.
  • IAM Permissions: The function is granted specific permissions:
    • ec2:DescribeInstances: To discover running EC2 instances.
    • guardduty:ListDetectors, guardduty:GetDetector, guardduty:StartMalwareScan: To interact with GuardDuty for detector management and scan initiation.
    • iam:GetRole, iam:PassRole: To assume the GuardDuty service-linked role required for malware protection.
    • sts:GetCallerIdentity: For retrieving AWS account identity, useful for logging and context.
  • Environment Variables: The EXCLUDED_INSTANCES variable allows you to specify a comma-separated list of EC2 instance IDs that should be skipped during the scan.

2. Developing the Python Scan Logic

Next, create the Python script that will be executed by your Lambda function. This script, typically named ec2_malware_scan.py, should reside in the path specified by CodeUri in your SAM template (e.g., lambdas/infrastructure/ec2_malware_scan/).

The Python code utilizes the boto3 AWS SDK to interact with EC2, GuardDuty, and STS services.

# Outline of the Python script (ec2_malware_scan.py)
import boto3
import json
import logging
import os

# Initialize AWS clients
ec2 = boto3.client('ec2')
guardduty = boto3.client('guardduty')
sts = boto3.client('sts')
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def _get_detector_id() -> str:
    """Retrieves the GuardDuty detector ID for the current region."""
    # ... logic to list and return the first detector ID ...

def _malware_protection_enabled() -> bool:
    """Checks if GuardDuty Malware Protection for EBS volumes is enabled."""
    # ... logic to get detector details and check status ...

def _running_instances() -> list:
    """Fetches a list of all running EC2 instances with a 'Name' tag."""
    # ... logic to paginate describe_instances calls and filter by 'running' state and 'tag:Name' ...

def _start_scan(instance_arn: str) -> dict:
    """Initiates an on-demand GuardDuty malware scan for a given instance ARN."""
    # ... logic to call guardduty.start_malware_scan ...

def ec2_malware_scan(event, context):
    """
    Lambda handler function to orchestrate the weekly EC2 malware scans.
    """
    logger.info("Initiating Weekly GuardDuty EC2 malware scan.")

    # 1. Verify GuardDuty Malware Protection is enabled
    if not _malware_protection_enabled():
        return {
            'statusCode': 400,
            'body': json.dumps({'error': 'GuardDuty Malware Protection is not enabled'})
        }

    # 2. Get all running EC2 instances
    instances = _running_instances()

    # 3. Filter out excluded instances based on the EXCLUDED_INSTANCES environment variable
    excluded_ids = {x.strip() for x in os.environ.get('EXCLUDED_INSTANCES', '').split(',') if x.strip()}
    instances_to_scan = [i for i in instances if i['InstanceId'] not in excluded_ids]

    logger.info(f"Excluded {len(instances) - len(instances_to_scan)} instance(s); starting scans for {len(instances_to_scan)}.")

    # 4. Trigger scans for the identified instances
    scan_results = [_start_scan(instance['Arn']) for instance in instances_to_scan]

    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': 'On-demand malware scans initiated',
            'instances_scanned': len(scan_results),
            'instances_excluded': len(instances) - len(instances_to_scan),
            'results': scan_results
        })
    }

Key functions within the Python script:

  • _get_detector_id(): Locates the active GuardDuty detector in the current region.
  • _malware_protection_enabled(): Confirms that GuardDuty’s Malware Protection feature for EBS volumes is active.
  • _running_instances(): Gathers a list of all currently running EC2 instances, specifically those with a Name tag.
  • _start_scan(): Invokes the GuardDuty API to initiate a malware scan for a given EC2 instance ARN.
  • ec2_malware_scan(event, context): The main Lambda handler function that:
    1. Verifies GuardDuty Malware Protection is enabled.
    2. Retrieves all running EC2 instances.
    3. Filters out any instances specified in the EXCLUDED_INSTANCES environment variable.
    4. Iterates through the remaining instances and triggers an on-demand malware scan for each.

3. Deployment and Monitoring

After defining your SAM template and Python code, deploy the application using the AWS SAM CLI. Once deployed, the Lambda function will execute according to its schedule (e.g., weekly on Monday mornings), automatically initiating GuardDuty malware scans for your running EC2 instances.

You can monitor the status and results of these on-demand scans directly within the AWS GuardDuty console, under the “Scans” section. This will provide visibility into any potential malware findings and the overall security posture of your EC2 fleet.

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed