Implementation Guide: Patching Automation
This guide includes both RHEL and Windows machines. Discuss with your ODB before enabling the RHEL maintenance windows.
Sections:
- Existing Maps (add these entries inside maps that already exist in your old tfvars)
- New Maps (entire maps that did not exist previously)
1. Existing Maps (augment)
Policy Documents
################################################
############## Policy Documents #################
################################################
policy_documents = {
# ... existing entries ...
lambda_autoshutdown_role_assume_policy = {
statement = [{
principals = { identifiers = ["lambda.amazonaws.com", "events.amazonaws.com"] }
actions = ["sts:AssumeRole"]
}]
}
lambda_eventbridge_policy = {
statement = [{
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = ["*"]
condition = { StringLike = { "AWS:SourceArn" = "arn:aws:events:*:*:rule/*" } }
}]
}
lambda_ec2_instance_control_policy = {
statement = [{
effect = "Allow"
actions = ["ec2:StopInstances", "ec2:StartInstances"]
resources = ["arn:aws:ec2:*:*:instance/*"]
}]
}
eventbridge_ssm_automation_assume = {
statement = [{
principals = { identifiers = ["events.amazonaws.com"] }
actions = ["sts:AssumeRole"]
}]
}
eventbridge_ssm_automation_policy = {
statement = [{
effect = "Allow"
actions = [
"ssm:StartAutomationExecution",
"ssm:GetAutomationExecution",
"ssm:DescribeAutomationExecutions",
"ssm:DescribeAutomationStepExecutions"
]
resources = ["*"]
}]
}
ssm_lambda_invoke_policy = {
statement = [{
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = ["arn:aws:lambda:*:*:function:*"]
}]
}
maintenance_window_policy = {
statement = [{
effect = "Allow"
actions = [
"ec2:StartInstances","ec2:StopInstances","ec2:DescribeInstances","ec2:DescribeInstanceStatus",
"resource-groups:ListGroups","resource-groups:ListGroupResources","resource-groups:GetGroup",
"resource-groups:GetGroupQuery","ssm:SendCommand","ssm:CancelCommand","ssm:ListCommands",
"ssm:ListCommandInvocations","ssm:GetCommandInvocation","ssm:GetMaintenanceWindowTask",
"ssm:UpdateMaintenanceWindowTask","ssm:RegisterTargetWithMaintenanceWindow",
"ssm:DeregisterTargetWithMaintenanceWindow","ssm:RegisterTaskWithMaintenanceWindow",
"ssm:DeregisterTaskWithMaintenanceWindow","ssm:UpdateMaintenanceWindow","ssm:DescribeMaintenanceWindows",
"ssm:DescribeMaintenanceWindowTasks","ssm:DescribeMaintenanceWindowExecutions",
"ssm:DescribeMaintenanceTargets","ssm:GetParameter","ssm:GetParameters","ssm:GetParametersByPath",
"ssm:PutParameter","ssm:StartAutomationExecution","ssm:GetAutomationExecution",
"ssm:DescribeAutomationExecutions","ssm:DescribeAutomationStepExecutions","tag:GetResources","iam:PassRole"
]
resources = ["*"]
}]
}
}
IAM Policies
################################################
################# IAM Policies ##################
################################################
iampolicies = {
# ... existing entries ...
maintenance_window_policy = { policy = "maintenance_window_policy" }
Lambda_EventBridge_Invoke_Policy = { policy = "lambda_eventbridge_policy" }
Lambda_EC2_Instance_Control_Policy = { policy = "lambda_ec2_instance_control_policy" }
ssm_lambda_invoke = { policy = "ssm_lambda_invoke_policy" }
eventbridge_ssm_automation_policy = { policy = "eventbridge_ssm_automation_policy" }
}
IAM Roles
################################################
################## IAM Roles ####################
################################################
iamroles = {
# ... existing entries ...
Lambda_AutoShutdownRole = {
policies = [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
"arn:aws:iam::aws:policy/AmazonSSMDirectoryServiceAccess",
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
"arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy",
"arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess",
"Lambda_EventBridge_Invoke_Policy",
"Lambda_EC2_Instance_Control_Policy"
]
assume_role_policy = "lambda_autoshutdown_role_assume_policy"
}
EventBridge_SSM_AutomationRole = {
policies = ["eventbridge_ssm_automation_policy"]
assume_role_policy = "eventbridge_ssm_automation_assume"
}
SSMEC2RoleEpic = {
# merge into existing if present; ensure these policies exist
policies = [
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
"arn:aws:iam::aws:policy/AmazonSSMDirectoryServiceAccess",
"maintenance_window_policy",
"ssm_lambda_invoke"
]
assume_role_policy = "ssm_ec2"
}
}
### EC2 Instance Tagging
```hcl
################################################
################## EC2 - Instances ####################
################################################
instances = {
# ... existing entries ...
# ... existing EC@ Instance ...
tags = {
PatchGroup = "epicIREwss" # for Windows OR epicIREodb for RHEL
}
}
2. New Maps (add entire blocks)
Add these whole maps if missing. If a map already exists, manually merge the inner objects.
SSM Parameters
################################################
################# SSM Parameters ################
################################################
ssm_parameters = {
client_system_status = {
name = "/client_systems/ire/status"
description = "Current status of IRE Client Systems"
type = "String"
value = "Inactive"
tier = "Standard"
}
client_system_status_last_updated = {
name = "/client_systems/ire/status_last_updated"
description = "Last update timestamp of DR Client Systems"
type = "String"
value = "initial"
tier = "Standard"
}
}
Resource Groups
################################################
################ Resource Groups ################
################################################
resourcegroups_group = {
epic_servers = {
name = "epic-servers-group"
resource_query = {
ResourceTypeFilters = ["AWS::EC2::Instance"]
TagFilters = [{ Key = "PatchGroup", Values = ["epicIREwss"] }]
}
}
odb_servers = {
name = "epic-odb-servers-group"
resource_query = {
ResourceTypeFilters = ["AWS::EC2::Instance"]
TagFilters = [{ Key = "PatchGroup", Values = ["epicIREodb"] }]
}
}
}
SSM Maintenance Windows
################################################
############ SSM Maintenance Windows ############
################################################
ssm_maintenance_windows = {
monthly_patching = {
name = "3rd-sunday-patching"
schedule = "cron(0 12 ? * SUN#3 *)"
schedule_timezone = "UTC"
duration = 3
cutoff = 1
allow_unassociated_targets = false
}
monthly_rhel_patching = {
name = "3rd-sunday-rhel-patching"
schedule = "cron(0 12 ? * SUN#3 *)"
schedule_timezone = "UTC"
duration = 3
cutoff = 1
allow_unassociated_targets = false
}
}
SSM Maintenance Window Targets
################################################
######### SSM Maintenance Window Targets ########
################################################
ssm_maintenance_window_targets = {
windows_wss_servers = {
window_id = "monthly_patching"
name = "windows-wss-servers-target"
resource_type = "RESOURCE_GROUP"
targets = [{ key = "resource-groups:Name", values = ["epic-servers-group"] }]
}
rhel_odb_servers = {
window_id = "monthly_rhel_patching"
name = "rhel-odb-servers-target"
resource_type = "RESOURCE_GROUP"
targets = [{ key = "resource-groups:Name", values = ["epic-odb-servers-group"] }]
}
}
SSM Maintenance Window Tasks
################################################
######### SSM Maintenance Window Tasks ##########
################################################
ssm_maintenance_window_tasks = {
start_instances_task = {
window_id = "monthly_patching"
task_type = "AUTOMATION"
task_arn = "AWS-StartEC2Instance"
priority = 1
service_role_arn = "SSMEC2RoleEpic"
task_invocation_parameters = {
automation_parameters = { document_version = "$DEFAULT", parameter = { InstanceId = ["{{ RESOURCE_ID }}"] } }
}
targets = [{ key = "WindowTargetIds", values = ["windows_wss_servers"] }]
max_concurrency = "50%"
max_errors = "25%"
}
wait_task = {
window_id = "monthly_patching"
task_type = "RUN_COMMAND"
task_arn = "AWS-RunPowerShellScript"
priority = 2
service_role_arn = "SSMEC2RoleEpic"
task_invocation_parameters = {
run_command_parameters = {
timeout_seconds = 360
parameter = [{ name = "commands", values = ["Start-Sleep -Seconds 120"] }]
}
}
targets = [{ key = "WindowTargetIds", values = ["windows_wss_servers"] }]
max_concurrency = "100%"
max_errors = "25%"
}
patch_instances_task = {
window_id = "monthly_patching"
task_type = "RUN_COMMAND"
task_arn = "AWS-RunPatchBaseline"
priority = 3
service_role_arn = "SSMEC2RoleEpic"
task_invocation_parameters = {
run_command_parameters = {
timeout_seconds = 600
parameter = [
{ name = "Operation", values = ["Install"] },
{ name = "RebootOption", values = ["RebootIfNeeded"] }
]
}
}
targets = [{ key = "WindowTargetIds", values = ["windows_wss_servers"] }]
max_concurrency = "50%"
max_errors = "25%"
}
stop_instances_task = {
window_id = "monthly_patching"
task_type = "AUTOMATION"
task_arn = "Stop-Instances-Runbook"
priority = 4
service_role_arn = "SSMEC2RoleEpic"
task_invocation_parameters = { automation_parameters = { document_version = "$DEFAULT" } }
}
patch_rhel_instances_task = {
window_id = "monthly_rhel_patching"
task_type = "RUN_COMMAND"
task_arn = "AWS-RunPatchBaseline"
priority = 1
service_role_arn = "SSMEC2RoleEpic"
task_invocation_parameters = {
run_command_parameters = {
timeout_seconds = 7200
parameter = [
{ name = "Operation", values = ["Install"] },
{ name = "RebootOption", values = ["RebootIfNeeded"] }
]
}
}
targets = [{ key = "WindowTargetIds", values = ["rhel_odb_servers"] }]
max_concurrency = "50%"
max_errors = "25%"
}
}
SSM Patch Baselines & Groups
################################################
######## SSM Patch Baselines & Groups ##########
################################################
ssm_patch_baselines = {
windows_baseline = {
name = "windows-patch-baseline"
operating_system = "WINDOWS"
approval_rule = {
approve_after_days = 7
compliance_level = "CRITICAL"
patch_filter = {
product = ["WindowsServer2022"]
classification = ["CriticalUpdates","SecurityUpdates","Updates"]
}
}
}
rhel_baseline = {
name = "rhel-patch-baseline"
operating_system = "REDHAT_ENTERPRISE_LINUX"
approval_rule = {
approve_after_days = 7
compliance_level = "CRITICAL"
patch_filter = {
product = ["RedhatEnterpriseLinux9.3"]
classification = ["Security","Bugfix","Enhancement","Recommended"]
severity = ["Critical","Important","Medium"]
}
}
}
}
ssm_patch_groups = {
epicIREwss = { baseline_id = "windows_baseline" }
epicIREodb = { baseline_id = "rhel_baseline" }
}
Lambda Functions
################################################
############## Lambda Functions #################
################################################
lambdas = {
ec2_stop_function = {
role = "Lambda_AutoShutdownRole"
handler = "ec2_stop_function.lambda_handler"
runtime = "python3.12"
timeout = 300
}
}
Lambda Permissions
################################################
############ Lambda Permissions #################
################################################
lambda_permissions = {
eventbridge_permission = {
statement_id = "AllowEventBridgeInvoke"
action = "lambda:InvokeFunction"
function_name = "ec2_stop_function"
principal = "events.amazonaws.com"
source_arn = "ec2-shutdown-everynight"
}
ssm_permission = {
statement_id = "allow_ssm_invoke"
action = "lambda:InvokeFunction"
function_name = "ec2_stop_function"
principal = "ssm.amazonaws.com"
}
}
EventBridge Rules
################################################
############# EventBridge Rules #################
################################################
eb_rules = {
ec2-shutdown-everynight = {
description = "MyEC2StateChangeEvent"
schedule_expression = "cron(20 0 ? * * *)"
}
}
EventBridge Targets
################################################
############ EventBridge Targets ################
################################################
eb_targets = {
shutdown-target = {
ssm_runbook = "Stop-Instances-Runbook-TF"
rule = "ec2-shutdown-everynight"
role_arn = "EventBridge_SSM_AutomationRole"
}
}
SSM Runbooks
################################################
################# SSM Runbooks ##################
################################################
ssm_runbooks = {
Stop-Instances-Runbook-TF = {
name = "Stop-Instances-Runbook"
document_type = "Automation"
document_format = "YAML"
content = {
schema_version = "0.3"
description = "Stop pilot light instances using Lambda function"
mainSteps = [{
name = "invokeLambda"
action = "aws:invokeLambdaFunction"
isEnd = true
inputs = {
FunctionName = "ec2_stop_function"
Payload = <<-EOT
{"patch_groups":["epicIREwss"],"targets":{"use2-ephsw-i":1,"use2-epicf-i":"50%"}}
EOT
}
}]
}
}
Update-Client-Systems-Status-Runbook-TF = {
name = "Update-Client-Systems-Status-Runbook"
document_type = "Automation"
document_format = "YAML"
content = {
schema_version = "0.3"
description = "Update DR Client Systems status in SSM Parameter Store"
parameters = {
DRClientSystemsStatus = {
type = "String"
description = "Status to set (active/inactive)"
allowedValues = ["active","inactive"]
}
}
mainSteps = [
{
name = "updateStatus"
action = "aws:executeAwsApi"
isEnd = false
inputs = {
Service = "ssm"
Api = "PutParameter"
Name = "/client_systems/dr/status"
Value = "{{DRClientSystemsStatus}}"
Type = "String"
Overwrite = true
Tier = "Standard"
}
},
{
name = "updateTimeStamp"
action = "aws:executeAwsApi"
isEnd = true
inputs = {
Service = "ssm"
Api = "PutParameter"
Name = "/client_systems/dr/status_last_updated"
Value = "{{ automation:EXECUTION_ID }}"
Type = "String"
Overwrite = true
Tier = "Standard"
}
}
]
}
}
}