How To Patch A Windows Instance Using AWS SSM Patch Manager
Hello Everyone
Welcome to CloudAffaire and this is Debjeet.
In the last blog post, we have discussed how to patch a Linux instance using AWS SSM Patch Manager.
https://cloudaffaire.com/how-to-patch-a-linux-instance-using-aws-ssm-patch-manager/
In this blog post, we will discuss how to patch a Windows instance using AWS SSM patch manager.
What Is AWS SSM Patch Manager:
AWS Systems Manager Patch Manager automates the process of patching managed instances with both security-related and other types of updates. You can use the Patch Manager to apply patches for both operating systems and applications. (On Windows Server, application support is limited to updates for Microsoft applications.) You can use the Patch Manager to install Service Packs on Windows instances and perform minor version upgrades on Linux instances. You can patch fleets of EC2 instances or your on-premises servers and virtual machines (VMs) by operating system type. This includes supported versions of Windows Server, Amazon Linux, Amazon Linux 2, CentOS, Debian Server, Oracle Linux, Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu Server.
Patch Manager uses patch baselines, which include rules for auto-approving patches within days of their release, as well as a list of approved and rejected patches. You can install patches on a regular basis by scheduling patching to run as a Systems Manager maintenance window task. You can also install patches individually or to large groups of instances by using Amazon EC2 tags. (Tags are keys that help identify and sort your resources within your organization.) You can add tags to your patch baselines themselves when you create or update them.
Patch Manager provides options to scan your instances and report compliance on a schedule, install available patches on a schedule, and patch or scan instances on demand whenever you need to. Patch Manager integrates with AWS Identity and Access Management (IAM), AWS CloudTrail, and Amazon EventBridge to provide a secure patching experience that includes event notifications and the ability to audit usage.
What Is A Patch Baseline In AWS SSM Patch Manager:
A patch baseline defines which patches are approved for installation on your instances. You can specify approved or rejected patches one by one. You can also create auto-approval rules to specify that certain types of updates (for example, critical updates) should be automatically approved. The rejected list overrides both the rules and the approved list.
Patch Manager provides predefined patch baselines for each of the operating systems supported by Patch Manager. You can use these baselines as they are currently configured (you can’t customize them) or you can create your own custom patch baselines. Custom patch baselines allows you greater control over which patches are approved or rejected for your environment.
What Is A Patch Group In AWS SSM Patch Manager:
You can use a patch group to associate instances with a specific patch baseline. Patch groups help ensure that you are deploying the appropriate patches, based on the associated patch baseline rules, to the correct set of instances. Patch groups can also help you avoid deploying patches before they have been adequately tested. For example, you can create patch groups for different environments (such as Development, Test, and Production) and register each patch group to an appropriate patch baseline.
How Security Patches Are Selected In Case Of Windows OS:
On Microsoft Windows operating systems, Patch Manager retrieves a list of available updates that Microsoft publishes to Microsoft Update and are automatically available to Windows Server Update Services (WSUS). Patch Manager continuously monitors for new updates in every AWS Region. The list of available updates is refreshed in each Region at least once per day. When the patch information from Microsoft is processed, Patch Manager removes updates that have been replaced by later updates from its patch list. Therefore, only the most recent update is displayed and made available for installation. For example, if KB4012214 replaces KB3135456, only KB4012214 is made available as an update in Patch Manager.
How To Patch A Windows Instance Using AWS SSM Patch Manager:
Requirements:
AWS CLI installed and configured. You can follow the below blog post to install and configure AWS CLI.
https://cloudaffaire.com/how-to-install-aws-cli/
https://cloudaffaire.com/how-to-configure-aws-cli/
Step 1: Create an EC2 SSM Managed Instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
################################################################# ## How To Patch A Windows Instance Using AWS SSM Patch Manager ## ################################################################# ## -------------------------------------- ## Create An AWS EC2 SSM Managed Instance ## -------------------------------------- ## Create a directory for this demo mkdir ssmdemo && cd ssmdemo ## Create a VPC AWS_VPC_ID=$(aws ec2 create-vpc \ --cidr-block 10.0.0.0/16 \ --query 'Vpc.{VpcId:VpcId}' \ --output text) ## Enable DNS hostname for your VPC aws ec2 modify-vpc-attribute \ --vpc-id $AWS_VPC_ID \ --enable-dns-hostnames "{\"Value\":true}" ## Create a public subnet AWS_SUBNET_PUBLIC_ID=$(aws ec2 create-subnet \ --vpc-id $AWS_VPC_ID --cidr-block 10.0.1.0/24 \ --availability-zone ap-south-1a --query 'Subnet.{SubnetId:SubnetId}' \ --output text) ## Enable Auto-assign Public IP on Public Subnet aws ec2 modify-subnet-attribute \ --subnet-id $AWS_SUBNET_PUBLIC_ID \ --map-public-ip-on-launch ## Create an Internet Gateway AWS_INTERNET_GATEWAY_ID=$(aws ec2 create-internet-gateway \ --query 'InternetGateway.{InternetGatewayId:InternetGatewayId}' \ --output text) ## Attach Internet gateway to your VPC aws ec2 attach-internet-gateway \ --vpc-id $AWS_VPC_ID \ --internet-gateway-id $AWS_INTERNET_GATEWAY_ID ## Create a route table AWS_CUSTOM_ROUTE_TABLE_ID=$(aws ec2 create-route-table \ --vpc-id $AWS_VPC_ID \ --query 'RouteTable.{RouteTableId:RouteTableId}' \ --output text ) ## Create route to Internet Gateway aws ec2 create-route \ --route-table-id $AWS_CUSTOM_ROUTE_TABLE_ID \ --destination-cidr-block 0.0.0.0/0 \ --gateway-id $AWS_INTERNET_GATEWAY_ID ## Associate the public subnet with route table AWS_ROUTE_TABLE_ASSOID=$(aws ec2 associate-route-table \ --subnet-id $AWS_SUBNET_PUBLIC_ID \ --route-table-id $AWS_CUSTOM_ROUTE_TABLE_ID \ --output text) ## Create a security group aws ec2 create-security-group \ --vpc-id $AWS_VPC_ID \ --group-name myvpc-security-group \ --description 'My VPC non default security group' ## Get security group ID's AWS_DEFAULT_SECURITY_GROUP_ID=$(aws ec2 describe-security-groups \ --filters "Name=vpc-id,Values=$AWS_VPC_ID" \ --query 'SecurityGroups[?GroupName == `default`].GroupId' \ --output text) && AWS_CUSTOM_SECURITY_GROUP_ID=$(aws ec2 describe-security-groups \ --filters "Name=vpc-id,Values=$AWS_VPC_ID" \ --query 'SecurityGroups[?GroupName == `myvpc-security-group`].GroupId' \ --output text) ## Create security group ingress rules aws ec2 authorize-security-group-ingress \ --group-id $AWS_CUSTOM_SECURITY_GROUP_ID \ --ip-permissions '[{"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "Allow SSH"}]}]' && aws ec2 authorize-security-group-ingress \ --group-id $AWS_CUSTOM_SECURITY_GROUP_ID \ --ip-permissions '[{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "Allow HTTP"}]}]' ## Get Microsoft Windows 2019 latest AMI ID AWS_AMI_ID=$(aws ec2 describe-images \ --owners 'amazon' \ --filters 'Name=name,Values=Windows_Server-2019-English-Full-Base-????.??.??' 'Name=state,Values=available' \ --query 'sort_by(Images, &CreationDate)[-1].[ImageId]' \ --output 'text') ## Create a key-pair aws ec2 create-key-pair \ --key-name myvpc-keypair \ --query 'KeyMaterial' \ --output text > myvpc-keypair.pem ## Create user data to restart SSM Agent cat < Restart-Service AmazonSSMAgent EOF ## Create an EC2 instance AWS_EC2_INSTANCE_ID=$(aws ec2 run-instances \ --image-id $AWS_AMI_ID \ --instance-type t2.micro \ --key-name myvpc-keypair \ --monitoring "Enabled=false" \ --security-group-ids $AWS_CUSTOM_SECURITY_GROUP_ID \ --subnet-id $AWS_SUBNET_PUBLIC_ID \ --user-data file://myuserdata.txt \ --private-ip-address 10.0.1.10 \ --query 'Instances[0].InstanceId' \ --output text) ## Create an Instance profile for SSM aws iam create-instance-profile \ --instance-profile-name "AmazonSSMInstanceProfileForInstances" ## Create a trust relation json file cat < { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF ## Create an IAM role for SSM aws iam create-role \ --role-name "AmazonSSMRoleForInstances" \ --assume-role-policy-document file://trust_policy.json ## Attach the required policy for SSM aws iam attach-role-policy \ --role-name "AmazonSSMRoleForInstances" \ --policy-arn "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" ## Add the role to the instance profile aws iam add-role-to-instance-profile \ --instance-profile-name "AmazonSSMInstanceProfileForInstances" \ --role-name "AmazonSSMRoleForInstances" ## Attach the Instance Profile to the EC2 instance aws ec2 associate-iam-instance-profile \ --instance-id "$AWS_EC2_INSTANCE_ID" \ --iam-instance-profile "Name=AmazonSSMInstanceProfileForInstances" ## Add a tag to the ec2 instance aws ec2 create-tags \ --resources $AWS_EC2_INSTANCE_ID \ --tags 'Key=Name,Value=MYAPP' 'Key="Patch Group",Value=MYAPPPatchGroup' ## Check if instance is added to SSM Managed Instance aws ssm describe-instance-information \ --filters "Key=InstanceIds,Values=$AWS_EC2_INSTANCE_ID" ## It may take some time for the instance to appear |
Step 2: Create a new custom patch baseline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
## ------------------------------ ## Create A Custom Patch Baseline ## ------------------------------ ## Create a patch baseline definition file cat < { "Name": "MYAPP_Patch_Baseline", "Description": "Custom patch baseline for Windows OS", "OperatingSystem": "WINDOWS", "Tags": [ { "Key": "Name", "Value": "MYAPP" } ], "ApprovalRules": { "PatchRules": [ { "ApproveAfterDays": 7, "PatchFilterGroup": { "PatchFilters": [ { "Key": "MSRC_SEVERITY", "Values": [ "Important", "Critical" ] }, { "Key": "CLASSIFICATION", "Values": [ "SecurityUpdates", "Updates", "CriticalUpdates", "UpdateRollups" ] } ] } } ] } } EOF ## Create the patch baseline AWS_SSM_PATCH_BASELINE_ID=$(aws ssm create-patch-baseline \ --cli-input-json file://myPatchDef.json \ --query 'BaselineId' \ --output text) ## Get effective patches for your custom patch baseline (Only for Windows Patch Baseline) aws ssm describe-effective-patches-for-patch-baseline \ --baseline-id "$AWS_SSM_PATCH_BASELINE_ID" ## List all patch baselines aws ssm describe-patch-baselines ## List all patch baselines owned by you aws ssm describe-patch-baselines \ --filters "Key=OWNER,Values=[Self]" ## Get default patch baseline of a OS aws ssm get-default-patch-baseline \ --operating-system "WINDOWS" ## Make your custom patch baseline as default patch baseline aws ssm register-default-patch-baseline \ --baseline-id "$AWS_SSM_PATCH_BASELINE_ID" && aws ssm get-default-patch-baseline \ --operating-system "WINDOWS" ## Get details for your patch baseline aws ssm get-patch-baseline \ --baseline-id "$AWS_SSM_PATCH_BASELINE_ID" |
Step 3: Register a patch group to the patch baseline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
## --------------------------- ## Create A Custom Patch Group ## --------------------------- ## Register a patch group to your patch baseline aws ssm register-patch-baseline-for-patch-group \ --baseline-id "$AWS_SSM_PATCH_BASELINE_ID" \ --patch-group "MYAPPPatchGroup" ## Get patch group details aws ssm describe-patch-groups ## Get patch baseline for a patch group aws ssm get-patch-baseline-for-patch-group \ --patch-group "MYAPPPatchGroup" \ --operating-system "WINDOWS" |
Step 4: Create a new SSM Maintenance Window.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
## ---------------------------------- ## Create AWS SSM Maintenance Windows ## ---------------------------------- ## create a new maintenence window AWS_SSM_MW_ID=$(aws ssm create-maintenance-window \ --name "MyMaintenanceWindow" \ --description "Maintenance Windows Created Through AWS CLI" \ --schedule "rate(8 hours)" \ --duration 5 --cutoff 1 \ --allow-unassociated-targets \ --tags "Key=Name,Value=MYAPP" \ --query 'WindowId' \ --output text) ## register a target by tag AWS_SSM_MW_TARGET_ID=$(aws ssm register-target-with-maintenance-window \ --window-id "$AWS_SSM_MW_ID" \ --resource-type INSTANCE \ --targets "Key=tag:Patch Group,Values=MYAPPPatchGroup" \ --query 'WindowTargetId' \ --output text) ## register a task for the maintenance window AWS_SSM_MW_TASK_ID=$(aws ssm register-task-with-maintenance-window \ --name "Patching" \ --window-id "$AWS_SSM_MW_ID" \ --targets "Key=WindowTargetIds,Values=$AWS_SSM_MW_TARGET_ID" \ --task-arn "AWS-RunPatchBaseline" \ --task-type "RUN_COMMAND" \ --task-invocation-parameters "RunCommand={Comment=,TimeoutSeconds=600,Parameters={SnapshotId=[''],Operation=[Install]}}" \ --max-concurrency "500" --max-errors "20%" \ --query 'WindowTaskId' \ --output text) ## update the maintenance window (every 10 minutes) aws ssm update-maintenance-window \ --window-id "$AWS_SSM_MW_ID" \ --schedule "rate(10 minutes)" ## Wait for 10 mins and then change back the schedule to weekly aws ssm update-maintenance-window \ --window-id "$AWS_SSM_MW_ID" \ --schedule "cron(0 0 0 ? * MON *)" |
Step 5: Get the Maintenance Window execution data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
## ------------------------------------- ## Get maintenance window execution data ## ------------------------------------- ## Get execution data aws ssm describe-maintenance-window-executions \ --window-id "$AWS_SSM_MW_ID" ## Get execution details AWS_SSM_MW_EXE_ID=$(aws ssm describe-maintenance-window-executions \ --window-id "$AWS_SSM_MW_ID" \ --query 'WindowExecutions[0].{WindowExecutionId:WindowExecutionId}' \ --output text) && aws ssm describe-maintenance-window-execution-tasks \ --window-execution-id "$AWS_SSM_MW_EXE_ID" ## Get details of maintenance window execution AWS_SSM_MW_EXE_TASK_ID=$(aws ssm get-maintenance-window-execution \ --window-execution-id "$AWS_SSM_MW_EXE_ID" \ --query 'TaskIds[0]' \ --output text) && aws ssm describe-maintenance-window-execution-task-invocations \ --window-execution-id "$AWS_SSM_MW_EXE_ID" \ --task-id "$AWS_SSM_MW_EXE_TASK_ID" ## Get details about a specific task run as part of a maintenance window execution. aws ssm get-maintenance-window-execution-task \ --window-execution-id "$AWS_SSM_MW_EXE_ID" \ --task-id "$AWS_SSM_MW_EXE_TASK_ID" |
Step 6: Get patching details.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
## ----------------- ## Get Patching Data ## ----------------- ## Get the instance patch states for a patch group aws ssm describe-instance-patch-states-for-patch-group \ --patch-group "MYAPPPatchGroup" ## Get the instance patch states aws ssm describe-instance-patch-states \ --instance-ids "$AWS_EC2_INSTANCE_ID" ## Get patch compliance state for a patch group aws ssm describe-patch-group-state \ --patch-group "MYAPPPatchGroup" ## List all the patches applied to the instance aws ssm describe-instance-patches \ --instance-id "$AWS_EC2_INSTANCE_ID" ## Get patch compliance summary aws ssm list-compliance-summaries |
Step 7: Cleanup.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
## ------- ## Cleanup ## ------- ## deregister the maintenance window task aws ssm deregister-task-from-maintenance-window \ --window-id "$AWS_SSM_MW_ID" \ --window-task-id "$AWS_SSM_MW_TASK_ID" ## deregister the maintenance window target aws ssm deregister-target-from-maintenance-window \ --window-id "$AWS_SSM_MW_ID" \ --window-target-id "$AWS_SSM_MW_TARGET_ID" ## delete the maintenance window aws ssm delete-maintenance-window \ --window-id "$AWS_SSM_MW_ID" ## Deregister a patch group to your patch baseline aws ssm deregister-patch-baseline-for-patch-group \ --baseline-id "$AWS_SSM_PATCH_BASELINE_ID" \ --patch-group "MYAPPPatchGroup" && aws ssm describe-patch-baselines \ --filters "Key=OWNER,Values=[AWS]" ## Make your custom patch baseline as non-default patch baseline AWS_SSM_DEFAULT_PATCH_BASELINE_ID=$(aws ssm describe-patch-baselines \ --filters "Key=OWNER,Values=[AWS]" \ --query 'BaselineIdentities[?BaselineName == `AWS-DefaultPatchBaseline`].BaselineId' \ --output text) && aws ssm register-default-patch-baseline \ --baseline-id "$AWS_SSM_DEFAULT_PATCH_BASELINE_ID" && aws ssm get-default-patch-baseline \ --operating-system "WINDOWS" ## Delete the patch baseline aws ssm delete-patch-baseline \ --baseline-id "$AWS_SSM_PATCH_BASELINE_ID" ## Terminate the ec2 instance aws ec2 terminate-instances \ --instance-ids $AWS_EC2_INSTANCE_ID && rm -f myuserdata.txt ## wait for some time sleep 30 ## Delete key pair aws ec2 delete-key-pair \ --key-name myvpc-keypair && rm -f myvpc-keypair.pem ## Delete custom security group aws ec2 delete-security-group \ --group-id $AWS_CUSTOM_SECURITY_GROUP_ID ## Delete internet gateway aws ec2 detach-internet-gateway \ --internet-gateway-id $AWS_INTERNET_GATEWAY_ID \ --vpc-id $AWS_VPC_ID && aws ec2 delete-internet-gateway \ --internet-gateway-id $AWS_INTERNET_GATEWAY_ID ## Delete the custom route table aws ec2 disassociate-route-table \ --association-id $AWS_ROUTE_TABLE_ASSOID && aws ec2 delete-route-table \ --route-table-id $AWS_CUSTOM_ROUTE_TABLE_ID ## Delete the public subnet aws ec2 delete-subnet \ --subnet-id $AWS_SUBNET_PUBLIC_ID ## Delete the vpc aws ec2 delete-vpc \ --vpc-id $AWS_VPC_ID ## Remove the IAM role from Instance Profile aws iam remove-role-from-instance-profile \ --instance-profile-name "AmazonSSMInstanceProfileForInstances" \ --role-name "AmazonSSMRoleForInstances" ## Dettach policy from the role aws iam detach-role-policy \ --role-name "AmazonSSMRoleForInstances" \ --policy-arn "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" ## Delete the IAM role aws iam delete-role \ --role-name "AmazonSSMRoleForInstances" ## Delete the IAM Instance Profile aws iam delete-instance-profile \ --instance-profile-name "AmazonSSMInstanceProfileForInstances" ## Delete the directory created for this demo cd .. && rm -rf ssmdemo |
Hope you have enjoyed this blog post, to get more details on AWS SSM, please refer below AWS documentation
https://docs.aws.amazon.com/systems-manager/index.html