Skip to main content

Check if EC2 Instances Managed by Systems Manager Are Compliant with Patching Requirements

Overview

This check verifies that EC2 instances managed by AWS Systems Manager (SSM) have all required security patches installed according to your patch baseline. Proper patch management ensures your servers stay protected against known vulnerabilities.

Risk

Unpatched instances are vulnerable to cyberattacks. Even well-designed software cannot anticipate every future threat. Key risks include:

  • Remote code execution - Attackers can exploit known vulnerabilities to run malicious code
  • Ransomware attacks - Unpatched systems are prime targets for ransomware
  • Data breaches - Vulnerabilities may allow unauthorized access to sensitive data
  • Compliance violations - Many regulatory frameworks require timely patching

Remediation Steps

Prerequisites

  • AWS account access with permissions to use Systems Manager
  • EC2 instances must be managed by SSM (SSM Agent installed and IAM role configured)
Setting up SSM Agent on EC2 instances

If your instances are not yet managed by SSM, you need to:

  1. Ensure SSM Agent is installed - Most Amazon Linux, Ubuntu, and Windows AMIs have it pre-installed
  2. Attach an IAM instance profile with the AmazonSSMManagedInstanceCore policy
  3. Verify network connectivity - Instances need outbound access to SSM endpoints (or VPC endpoints)

To check if an instance is managed by SSM:

aws ssm describe-instance-information \
--region us-east-1 \
--query "InstanceInformationList[*].[InstanceId,PingStatus]" \
--output table

AWS Console Method

  1. Open Systems Manager in the AWS Console
  2. Go to Node Management > Run Command
  3. Click Run command
  4. In the search box, find and select AWS-RunPatchBaseline
  5. Under Command parameters:
    • Set Operation to Install
    • Set Reboot Option to RebootIfNeeded (or NoReboot if you prefer manual reboots)
  6. Under Target selection, choose your non-compliant instances:
    • Select Choose instances manually and pick the specific instances, OR
    • Select Choose instances by tag and use the PatchGroup tag
  7. Click Run
  8. Monitor the command status until it shows Success

For ongoing compliance, set up automated patching:

  1. Go to Node Management > Patch Manager
  2. Click Configure patching
  3. Select your instances or patch groups
  4. Choose a patching schedule (e.g., weekly maintenance window)
  5. Select Scan and install for the patching operation
  6. Complete the wizard to enable automated patching
AWS CLI (optional)

Install patches on a specific instance:

aws ssm send-command \
--region us-east-1 \
--document-name "AWS-RunPatchBaseline" \
--instance-ids "i-0123456789abcdef0" \
--parameters "Operation=Install,RebootOption=RebootIfNeeded"

Install patches on multiple instances by tag:

aws ssm send-command \
--region us-east-1 \
--document-name "AWS-RunPatchBaseline" \
--targets "Key=tag:PatchGroup,Values=production-servers" \
--parameters "Operation=Install,RebootOption=RebootIfNeeded" \
--max-concurrency "50%" \
--max-errors "25%"

Check patch compliance status for instances:

aws ssm describe-instance-patch-states \
--region us-east-1 \
--instance-ids "i-0123456789abcdef0" \
--query "InstancePatchStates[*].[InstanceId,PatchGroup,InstalledCount,MissingCount,FailedCount]" \
--output table

List all patch baselines:

aws ssm describe-patch-baselines \
--region us-east-1 \
--query "BaselineIdentities[*].[BaselineId,BaselineName,OperatingSystem,DefaultBaseline]" \
--output table
CloudFormation (optional)

This template creates a complete patch management setup with a custom patch baseline and automated maintenance window:

AWSTemplateFormatVersion: '2010-09-09'
Description: SSM Patch Manager configuration for EC2 instance patching compliance

Parameters:
PatchGroupName:
Type: String
Default: 'production-servers'
Description: Name of the patch group to target

MaintenanceWindowName:
Type: String
Default: 'weekly-patching-window'
Description: Name for the maintenance window

MaintenanceWindowSchedule:
Type: String
Default: 'cron(0 2 ? * SUN *)'
Description: Cron expression for maintenance window (default - Sundays at 2 AM UTC)

Resources:
PatchBaseline:
Type: AWS::SSM::PatchBaseline
Properties:
Name: !Sub '${AWS::StackName}-patch-baseline'
Description: Custom patch baseline for security updates
OperatingSystem: AMAZON_LINUX_2
PatchGroups:
- !Ref PatchGroupName
ApprovalRules:
PatchRules:
- PatchFilterGroup:
PatchFilters:
- Key: CLASSIFICATION
Values:
- Security
- Key: SEVERITY
Values:
- Critical
- Important
ApproveAfterDays: 7
ComplianceLevel: CRITICAL
EnableNonSecurity: false
- PatchFilterGroup:
PatchFilters:
- Key: CLASSIFICATION
Values:
- Bugfix
ApproveAfterDays: 14
ComplianceLevel: HIGH
EnableNonSecurity: false

MaintenanceWindow:
Type: AWS::SSM::MaintenanceWindow
Properties:
Name: !Ref MaintenanceWindowName
Description: Maintenance window for automated patching
Schedule: !Ref MaintenanceWindowSchedule
Duration: 3
Cutoff: 1
AllowUnassociatedTargets: false
ScheduleTimezone: UTC

MaintenanceWindowTarget:
Type: AWS::SSM::MaintenanceWindowTarget
Properties:
WindowId: !Ref MaintenanceWindow
ResourceType: INSTANCE
Targets:
- Key: tag:PatchGroup
Values:
- !Ref PatchGroupName
Name: !Sub '${AWS::StackName}-patch-target'
Description: Target instances in the patch group

MaintenanceWindowTask:
Type: AWS::SSM::MaintenanceWindowTask
Properties:
WindowId: !Ref MaintenanceWindow
Targets:
- Key: WindowTargetIds
Values:
- !Ref MaintenanceWindowTarget
TaskArn: AWS-RunPatchBaseline
TaskType: RUN_COMMAND
Priority: 1
MaxConcurrency: '50%'
MaxErrors: '25%'
Name: !Sub '${AWS::StackName}-patch-task'
Description: Run patch baseline on target instances
TaskInvocationParameters:
MaintenanceWindowRunCommandParameters:
Parameters:
Operation:
- Install
RebootOption:
- RebootIfNeeded

Outputs:
PatchBaselineId:
Description: ID of the created patch baseline
Value: !Ref PatchBaseline

MaintenanceWindowId:
Description: ID of the maintenance window
Value: !Ref MaintenanceWindow

Deploy the stack:

aws cloudformation deploy \
--region us-east-1 \
--template-file patch-manager.yaml \
--stack-name ssm-patch-management \
--parameter-overrides PatchGroupName=production-servers

Important: Tag your EC2 instances with PatchGroup=production-servers (or your chosen group name) for them to be included in automated patching.

Terraform (optional)
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.0"
}
}
}

variable "patch_group_name" {
description = "Name of the patch group to target"
type = string
default = "production-servers"
}

variable "maintenance_window_schedule" {
description = "Cron expression for maintenance window"
type = string
default = "cron(0 2 ? * SUN *)"
}

variable "operating_system" {
description = "Operating system for the patch baseline"
type = string
default = "AMAZON_LINUX_2"
}

# Patch Baseline
resource "aws_ssm_patch_baseline" "this" {
name = "security-patch-baseline"
description = "Patch baseline for security updates"
operating_system = var.operating_system

approval_rule {
approve_after_days = 7
compliance_level = "CRITICAL"

patch_filter {
key = "CLASSIFICATION"
values = ["Security"]
}

patch_filter {
key = "SEVERITY"
values = ["Critical", "Important"]
}
}

approval_rule {
approve_after_days = 14
compliance_level = "HIGH"

patch_filter {
key = "CLASSIFICATION"
values = ["Bugfix"]
}
}
}

# Register patch group with baseline
resource "aws_ssm_patch_group" "this" {
baseline_id = aws_ssm_patch_baseline.this.id
patch_group = var.patch_group_name
}

# Maintenance Window
resource "aws_ssm_maintenance_window" "this" {
name = "patching-maintenance-window"
description = "Maintenance window for automated patching"
schedule = var.maintenance_window_schedule
duration = 3
cutoff = 1
schedule_timezone = "UTC"
}

# Maintenance Window Target
resource "aws_ssm_maintenance_window_target" "this" {
window_id = aws_ssm_maintenance_window.this.id
resource_type = "INSTANCE"
name = "patch-target"
description = "Target instances with PatchGroup tag"

targets {
key = "tag:PatchGroup"
values = [var.patch_group_name]
}
}

# Maintenance Window Task
resource "aws_ssm_maintenance_window_task" "this" {
window_id = aws_ssm_maintenance_window.this.id
task_type = "RUN_COMMAND"
task_arn = "AWS-RunPatchBaseline"
priority = 1
max_concurrency = "50%"
max_errors = "25%"
name = "run-patch-baseline"
description = "Run AWS-RunPatchBaseline on target instances"

targets {
key = "WindowTargetIds"
values = [aws_ssm_maintenance_window_target.this.id]
}

task_invocation_parameters {
run_command_parameters {
parameter {
name = "Operation"
values = ["Install"]
}

parameter {
name = "RebootOption"
values = ["RebootIfNeeded"]
}
}
}
}

output "patch_baseline_id" {
description = "ID of the patch baseline"
value = aws_ssm_patch_baseline.this.id
}

output "maintenance_window_id" {
description = "ID of the maintenance window"
value = aws_ssm_maintenance_window.this.id
}

Deploy:

terraform init
terraform apply -var="patch_group_name=production-servers"

Important: Tag your EC2 instances with PatchGroup=production-servers for them to be targeted by the maintenance window.

Verification

After running the patching operation:

  1. Go to Systems Manager > Node Management > Compliance
  2. Check that your instances show Compliant status for patch compliance
  3. Click on an instance to see detailed patch information
CLI verification commands

Check patch compliance for specific instances:

aws ssm describe-instance-patch-states \
--region us-east-1 \
--instance-ids "i-0123456789abcdef0" \
--query "InstancePatchStates[*].[InstanceId,PatchGroup,InstalledCount,MissingCount,FailedCount,OperationEndTime]" \
--output table

List non-compliant instances:

aws ssm list-compliance-items \
--region us-east-1 \
--filters "Key=ComplianceType,Values=Patch,Type=EQUAL" "Key=Status,Values=NON_COMPLIANT,Type=EQUAL" \
--query "ComplianceItems[*].[ResourceId,Status,Severity]" \
--output table

View detailed patch information for an instance:

aws ssm describe-instance-patches \
--region us-east-1 \
--instance-id "i-0123456789abcdef0" \
--filters "Key=State,Values=Missing" \
--query "Patches[*].[Title,Severity,State]" \
--output table

Additional Resources

Notes

  • Reboot consideration: Some patches require a reboot to take effect. Plan patching during maintenance windows to minimize disruption.
  • Test first: Consider using a staged rollout - patch development/test environments first, then production.
  • Multiple operating systems: You need separate patch baselines for different operating systems (Amazon Linux, Windows, Ubuntu, etc.).
  • Security Hub integration: Enable Patch Manager integration with AWS Security Hub to centralize security findings across your organization.
  • Compliance frameworks: This check maps to multiple compliance frameworks including PCI-DSS, HIPAA, SOC2, NIST 800-53, and FedRAMP. Maintaining patch compliance helps satisfy these regulatory requirements.