Skip to content

Implementation Guide: Patching Automation

This guide includes both RHEL and Windows machines. Discuss with your ODB before enabling the RHEL maintenance windows.

Sections:

  1. Existing Maps (add these entries inside maps that already exist in your old tfvars)
  2. 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"
          }
        }
      ]
    }
  }
}