AWSパッチマネージャーとCloudFormation＿後編

## はじめに

[前編記事](https://qiita.com/t13801206/items/506d9a6ac93ae442a539)では、[先人のテンプレート](https://dev.classmethod.jp/articles/patchmanager-cloudformation/#toc-2)を使ってパッチマネージャーに関するリソースを作成し、
たしかにテンプレート通りのリソースが配置されていることをGUIで確認した

今回はそのテンプレートを自分なりに改変してみる

具体的には以下の仕様でパッチ適用環境を構築する
もちろんリソース配置は CloudFormation によって行う

- Windowsサーバ向けにする
  - 2月にリリースされたWinSrv2019のAMIに対して、4月度パッチを適用してみる
- EC2のタグに`PG_WINDOWS`が付いているものを対象とする

成果物であるCloudFormationのYAMLファイルは巻末にまとめて記載する。

## 記事の構成

1. パッチマネージャーのYAMLをWindows仕様にしてデプロイ。
2. 検証用にEC2を2台たてる。1台はパッチ適用、もう1台はパッチ適用せず。
3. パッチが適用された／されていないことを確認する。

2台のEC2を同時にたてることで、パッチを適用した場合と、適用しなかった場合との
挙動の違いを観察する

## 手順1. Windowsサーバ用のパッチマネージャーをたてる

パッチマネージャーをCloudFormationで配置する。

- パッチベースライン
- メンテナンスウィンドウ
- メンテナンスウィンドウターゲット
- メンテナンスウィンドウタスク

の4つで構成される
これらの要素の関係性については[こちら](https://zenn.dev/t13801206/scraps/d7ac10d8e44420)を参照

### AWS::SSM::PatchBaseline 

[AWS::SSM::PatchBaseline](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-patchbaseline.html)

[公式の例](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-patchbaseline.html#aws-resource-ssm-patchbaseline--examples)に合わせて記述順序を直した
先に名前や説明があったほうがわかりやすい
※以下、ほかのリソースについても同じ考え

また、今回対象はWindowsサーバなので、パッチグループ名を`PG_WINDOWS`とした
大事なのは`PatchRules`以下であり、なんのパッチを当てるかを定めている
承認日は、そのパッチがリリースされてから何日経過してからあてるかを定めている

```yaml
  # ------------------------------------------------------------#
  # PatchBaseline
  # ------------------------------------------------------------#
  PatchBaseline:
    Type: AWS::SSM::PatchBaseline
    Properties:
      Name: WindowsPatchBaseline
      Description: "PatchBaseline for Windows created by CloudFormation"
      OperatingSystem: WINDOWS
      PatchGroups:
        - "PG_WINDOWS" # ★★★
      ApprovalRules:
        PatchRules: # この下が重要
          - PatchFilterGroup:
              PatchFilters:
                - Values:
                    - SecurityUpdates
                    - CriticalUpdates
                  Key: CLASSIFICATION
                - Values:
                    - Critical
                    - Important
                  Key: MSRC_SEVERITY
            ApproveAfterDays: 7
```
### AWS::SSM::MaintenanceWindow

[AWS::SSM::MaintenanceWindow](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-maintenancewindow.html)

`AllowUnassociatedTargets`は安全の方向で`false`
無効化した場合 (`false`)は、事前にメンテナンスウィンドウにターゲットとして登録されたインスタンスでのみ実行される

スケジュールの`cron`文は理解しにくい文法なので、AIに頼むとよし

```yaml
  # ------------------------------------------------------------#
  # MaintenanceWindow
  # ------------------------------------------------------------#
  MaintenanceWindow:
    Type: AWS::SSM::MaintenanceWindow
    Properties:
      Name: "MaintenanceWindows-PG_WINDOWS"
      Description: Maintenance Window to update Windows OS
      AllowUnassociatedTargets: false
      Cutoff: 1
      Duration: 2
      Schedule: "cron(00 13 ? * SUN *)" # AIにまかせる
      ScheduleTimezone: Asia/Tokyo
```

### AWS::SSM::MaintenanceWindowTarget

[AWS::SSM::MaintenanceWindowTarget](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-maintenancewindowtarget.html)

タグ`Patch Group`の値が`PG_WINDOWS`であるEC2が対象

```yaml
  # ------------------------------------------------------------#
  # MaintenanceWindowTarget
  # ------------------------------------------------------------#
  MaintenanceWindowTarget:
    Type: AWS::SSM::MaintenanceWindowTarget
    Properties:
      Name: WindowsPatchTarget
      Description:  Target is PG_WINDOWS
      ResourceType: INSTANCE
      Targets:
        - Key: "tag:Patch Group" # このタグをEC2に付与する
          Values:
            - "PG_WINDOWS"
      WindowId: !Ref MaintenanceWindow
    DependsOn: MaintenanceWindow

```

### AWS::SSM::MaintenanceWindowTask

[AWS::SSM::MaintenanceWindowTask](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-maintenancewindowtask.html)

最後にタスクを定義する
やることは`RUN_COMMAND`で`AWS-RunPatchBaseline`をしているだけ
その制御パラメータとして、先に定義したメンテナンスウィンドウとメンテナンスウィンドウターゲットを用いる

```yaml
  # ------------------------------------------------------------#
  # MaintenanceWindowTask
  # ------------------------------------------------------------#
  MaintenanceWindowTask:
    Type: AWS::SSM::MaintenanceWindowTask
    Properties:
      Name: WinOS-PatchTask
      MaxConcurrency: 50
      MaxErrors: 0
      Priority: 1
      Targets: #-----------------------------ここから
        - Key: WindowTargetIds
          Values:
            - !Ref MaintenanceWindowTarget
      TaskArn: AWS-RunPatchBaseline
      TaskType: RUN_COMMAND
      WindowId: !Ref MaintenanceWindow #---ここまでが重要
      TaskInvocationParameters:
        MaintenanceWindowRunCommandParameters:
          TimeoutSeconds: 600
          Parameters: { "Operation": ["Install"] }
    DependsOn: MaintenanceWindowTarget
```

`RUN_COMMAND`実行履歴の例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/f473357b-4b53-4db2-a8d9-79313e5495f1.png)


## 手順2.検証用EC2をたてる

パッチを適用する側の環境ができたので、次は適用対象のサーバを用意する

AWSのAMIには公式が日本語版のWinサーバOSを用意してくれている
しかも最新のものだけではなく、2019OSであったり、2月度のパッチまでしか適用されていないものなど
ある程度古いものまでそろっている。ありがたい。

パッチあて検証にもってこいなので、2月度パッチまで適用されたWinSrv2019を使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/9719a886-d34f-4c23-8599-c96628945682.png)

### CloudFormationでEC2を配置する（2台）

EC2をデプロイしたYAMLは巻末にまとめて記載する

EC2は全部で2台配置した
- タグに`Patch Group`を付与したEC2を1台配置
- もう1台は`Patch Group`タグを付与せず、パッチ適用の対象から意図して外す

```yaml
      Tags:
        - Key: Name
          Value: WinSrv-201902-PatchOn
        - Key: Patch Group # ★このタグの有無でパッチ適用する／しないを制御
          Value: PG_WINDOWS
```

## 手順3.パッチ適用検証

パッチマネージャーとEC2それぞれのリソースを配置し終えた
ここから実際にパッチ適用された様子を観察する

### 起動したWindowsサーバにログイン

CloudformationでEC2を配置したら、RDP接続する
たしかに**2月分のパッチまでしか適用されていない**ことがわかる
（もし最新版のAMIしかなかったら、手動でパッチをアンインストールする手間があった）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/a8a8a727-910b-4b3f-afbb-b45552071862.png)

パッチの最終確認日は2018年だった
Windows Updateの自動更新が無効化されているが、**これは`Patch Group`タグの有無によらず、2台とも自動更新が無効化されていた**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/6e74c775-1da8-4e97-b6b0-1173ba4dbefd.png)

ちなみにグルポリの設定でこうなるらしい
参考）[Windows Update 自動更新を無効化・停止する方法](https://rin-ka.net/windows-update-disenable/#toc4)

#### スケジュールでパッチ適用後

先にデプロイしたパッチマネージャーのスケジュールに則り
自動的にパッチが適用されるのを待った
※日曜まで待てないので、スケジュールは一時的に変更している

結果、`PG_WINDOWS`タグを付けているマシンだけがアップデートされた
図左がタグあり、図右がタグなし
[Windows Server 2019 の累積更新プログラム (KB5055519)](https://support.microsoft.com/ja-jp/topic/2025-%E5%B9%B4-4-%E6%9C%88-8-%E6%97%A5-kb5055519-os-%E3%83%93%E3%83%AB%E3%83%89-17763-7136-417d1340-ce40-4d0b-98ac-637c0f6dca35)が左のマシンにだけインストールされていることがわかる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/1c412df1-e866-4b67-9510-4f57d998c053.png)

#### パッチ適用後はマシン再起動

今回、再起動オプションの有無について検証を忘れていた

パッチが適用されたマシンは再起動がかかっていた
なお、GUI上の「更新プログラムのチェック」は最終チェック日が2018年のままであった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/999a2010-fd1e-4551-9f93-6054b37232c1.png)

##### 再起動オプション

再起動オプションは以下のように書く
[パラメータ名: RebootOption](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/patch-manager-aws-runpatchbaseline.html#patch-manager-aws-runpatchbaseline-parameters-norebootoption)
デフォルトでは再起動することになっているから、可用性を重視するEC2を対象とする場合は
再起動なしにするよう注意が必要

```yaml
      TaskInvocationParameters:
        MaintenanceWindowRunCommandParameters:
          TimeoutSeconds: 600
          Parameters: 
            "Operation": ["Install"]
            "RebootOption": ["RebootIfNeeded"]
```

### パッチマネージャーを確認

EC2台あるうち、1台は準拠（緑）、もう1台は非準拠（黄色）になっている
コンプライアンスレポートとしては2台ともスキャンされていることがわかる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/dcc0e023-d8c3-4402-94d3-691105286aba.png)

#### すぐにパッチのインストールを走らせたいとき

奥の手として以下のコマンドで該当するタグのEC2にパッチを走らせることができる

```powershell
aws ssm send-command \
    --targets "Key=tag:Patch Group,Values=PG_WINDOWS" \
    --document-name "AWS-RunPatchBaseline" \
    --parameters "Operation=Install" \
    --region ap-northeast-1
```


## リファレンス

- [AWS Systems Manager resource type reference](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/AWS_SSM.html)

## 参考

- [検証・本番環境を考慮した EC2インスタンスへのパッチ適用環境を作ってみる【SSMメンテナンスウィンドウ編】](https://dev.classmethod.jp/articles/patching-env-using-maintenance-windows/)
- [【AWS Systems Manager】パッチマネージャー + メンテナンスウインドウの構成をCloudFormationテンプレート化してみた](https://dev.classmethod.jp/articles/patchmanager-cloudformation/)
- [【AWS Patch Manager】OSパッチ適用を自動化しよう](https://devlog.arksystems.co.jp/2023/04/13/18013/)
- [日本語パッケージ版EC2をデプロイする](https://qiita.com/u-bayashi/items/4c96265ef81ba65c7e69)

## 成果物

CloudformationにアップロードしたYAMLファイルを記載する

### パッチマネージャー 

EC2を再起動したくない場合は、[前述のオプション](#再起動オプション)を有効にすべし
また、4つあるリソースの関係性は[こちら](https://zenn.dev/t13801206/scraps/d7ac10d8e44420)（再掲）を参照

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Create PatchBaseline for Windows"

Resources:
  # ------------------------------------------------------------#
  # PatchBaseline
  # ------------------------------------------------------------#
  PatchBaseline:
    Type: AWS::SSM::PatchBaseline
    Properties:
      Name: WindowsPatchBaseline
      Description: "PatchBaseline for Windows created by CloudFormation"
      OperatingSystem: WINDOWS
      PatchGroups:
        - "PG_WINDOWS"
      ApprovalRules:
        PatchRules:
          - PatchFilterGroup:
              PatchFilters:
                - Values:
                    - SecurityUpdates
                    - CriticalUpdates
                  Key: CLASSIFICATION
                - Values:
                    - Critical
                    - Important
                  Key: MSRC_SEVERITY
            ApproveAfterDays: 7
  # ------------------------------------------------------------#
  # MaintenanceWindow
  # ------------------------------------------------------------#
  MaintenanceWindow:
    Type: AWS::SSM::MaintenanceWindow
    Properties:
      Name: "MaintenanceWindows-PG_WINDOWS"
      Description: Maintenance Window to update Windows OS
      AllowUnassociatedTargets: false
      Cutoff: 1
      Duration: 2
      Schedule: "cron(00 03 ? * MON *)"
      ScheduleTimezone: Asia/Tokyo
  # ------------------------------------------------------------#
  # MaintenanceWindowTarget
  # ------------------------------------------------------------#
  MaintenanceWindowTarget:
    Type: AWS::SSM::MaintenanceWindowTarget
    Properties:
      Name: WindowsPatchTarget
      Description:  Target is PG_WINDOWS
      ResourceType: INSTANCE
      Targets:
        - Key: "tag:Patch Group"
          Values:
            - "PG_WINDOWS"
      WindowId: !Ref MaintenanceWindow
    DependsOn: MaintenanceWindow
  # ------------------------------------------------------------#
  # MaintenanceWindowTask
  # ------------------------------------------------------------#
  MaintenanceWindowTask:
    Type: AWS::SSM::MaintenanceWindowTask
    Properties:
      Name: WinOS-PatchTask
      MaxConcurrency: 50
      MaxErrors: 0
      Priority: 1
      Targets:
        - Key: WindowTargetIds
          Values:
            - !Ref MaintenanceWindowTarget
      TaskArn: AWS-RunPatchBaseline
      TaskType: RUN_COMMAND
      WindowId: !Ref MaintenanceWindow
      TaskInvocationParameters:
        MaintenanceWindowRunCommandParameters:
          TimeoutSeconds: 600
          Parameters: { "Operation": ["Install"] }
    DependsOn: MaintenanceWindowTarget

```

### 検証用EC2

`Patch Group`タグの有無でEC2を2つに分けた
この記事を投稿したのは2025年5月であるが、パッチ適用検証のため
あえてWin2019の2月版AMIを使っている
**いずれ2月版AMIはなくなると思うので、AMI-IDは適宜変更のこと**。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Create TWO EC2s for Windows Server 2019 Mo-02

Resources:
  EC2Instance201902n1:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-033b26e504cfde89c
      KeyName: mykey
      SecurityGroupIds:
        - sg-010831940c566d1c6
      SubnetId: subnet-0273f8bda45f19219
      Tags:
        - Key: Name
          Value: WinSrv-201902-PatchOn
        - Key: Patch Group
          Value: PG_WINDOWS
        - Key: OS
          Value: Windows_Server-2019-Japanese-Full-Base-2025.02.13
      IamInstanceProfile: EC2SSMManagementRole
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 15
            VolumeType: gp2
    DeletionPolicy: Delete
  
  EC2Instance201902n2:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-033b26e504cfde89c
      KeyName: mykey
      SecurityGroupIds:
        - sg-010831940c566d1c6
      SubnetId: subnet-0273f8bda45f19219
      Tags:
        - Key: Name
          Value: WinSrv-201902-PatchOff
        - Key: OS
          Value: Windows_Server-2019-Japanese-Full-Base-2025.02.13
      IamInstanceProfile: EC2SSMManagementRole
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 15
            VolumeType: gp2
    DeletionPolicy: Delete
```

## 糸冬了！！