【AWS】プロキシ設定が原因でSSM Automationが失敗した場合の対処法

# はじめに
<!-- 発端や概要を記載 -->
※かなり長い記事になるので、プロキシサーバはすでに作成済み、原因のとこだけ知りたいって方は１２項の[Automationの実行に失敗した原因](#automationの実行に失敗した原因)から先を読んでいただければと思います。

SSMオートメーションを実行すれば勝手にZIPをダウンロードしてきてEC2内のドライバが自動アップデートされるような仕組みって便利ですよね。
今回はそんな仕組みを作ろうとしたはいいが、プロキシの壁に阻まれ苦悩した結果を記事にまとめてみました。
セキュリティが厳しい会社様など、プロキシサーバが原因で処理が止まってしまった・詰まってしまった。。。ってときの解決法の１つになれば幸いです。
※本記事ではVPC内のEC2からS3への接続を行うので、VPCエンドポイントを使うのがベストプラクティスではありますが、あえてインターネット経由で取得するようにしてます。


# 目次
<!-- タイトルとアンカー名を編集 -->
1. [プロキシサーバとは](#プロキシサーバとは)
1. [概要図](#概要図)
1. [VPCを作成する](#vpcを作成する)
1. [EC2を起動する](#ec2を起動する)
1. [プロキシサーバを作成する](#プロキシサーバを作成する)
1. [セッションマネージャ接続がうまくいかない場合](#セッションマネージャ接続がうまくいかない場合)
1. [セキュリティグループを作成する](#セキュリティグループを作成する)
1. [SystemsManagerで疎通状況を確認する](#systemsmanagerで疎通状況を確認する)
1. [プロキシの設定を行う](#プロキシの設定を行う)
1. [SystemsManagerで疎通状況を再度確認する](#systemsmanagerで疎通状況を再度確認する)
1. [Automationを実行する](#automationを実行する)
1. [Automationの実行に失敗した原因](automationの実行に失敗した原因)
1. [LOCALSYSTEMのプロキシ設定を確認](#localsystemのプロキシ設定を確認)
1. [LOCALSYSTEMのプロキシ設定を変更](#LOCALSYSTEMのプロキシ設定を変更)
1. [Automatioを再実行する](#automationを再実行する)
1. [さいごに](#さいごに)

<!-- 各チャプター -->
# プロキシサーバとは
まずはじめにプロキシサーバってなに？
よく聞くけどどんなことするのかよくわかってない...という方向けに簡単に説明します。
プロキシサーバとはインターネットに接続されていないサブネット内(プライベートサブネット)のサーバが、インターネットに接続する際に中継するために使用するサーバです。もう少しかみ砕くとプライベートサブネット内サーバの代わりにインターネットに向けてリクエストを送信してくれるサーバのことです。
詳しい話は割愛させていただきますが、プロキシサーバを使用することで怪しいサイトや許可していないドメインへのアクセスをブロックできるなど、セキュリティ面でのメリットもあります。


# 概要図
本記事では概要図のようなイメージで構築を行います。
パブリックサブネットにプロキシサーバ（Linux）、プライベートサブネットにWindowsServerを1台ずつ構築し、WindowsServerに対してSSMAutomationを実行します。
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
踏み台サーバはプライベートサブネットのEC2にRDPで接続し、プロキシの設定を行うために使用してますが、フリートマネージャを使用してもOKです！!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/b8bdb0d8-9000-6e96-c1c6-46983611892c.png)
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

Automation用ドキュメントには「AWSSupport-UpgradeWindowsAWSDrivers」を使用して、インターネットからzipファイルを取得できるか検証します。
※プロキシの設定がうまくできていないと、インターネットからファイルをダウンロードできず失敗します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/194a46d6-32cc-6d0c-41eb-2ae6d61d8013.png)


# VPCを作成する
画像のような感じでサブネットを2つ(パブリックサブネットとプライベートサブネット)作ってください。
Cidrは自由に変更してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/d460f72e-e3f8-0e80-f23f-f49a9b406f4f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/86bdc5c6-8437-5497-71c1-7fff687e4c05.png)


# EC2を起動する
パブリックサブネットとプライベートサブネットにEC2を起動します。
プロキシサーバはパブリックサブネットに作成し、パブリックIPアドレスを設定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/6924ccc0-f114-2e6c-f76c-89058656ca80.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/17c02766-18fd-b2f9-2850-18936bc65122.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/b15fc0d9-0536-2795-d364-3097e7dcd74a.png)


# プロキシサーバを作成する
起動したLinuxサーバ(qiita-proxy)にセッションマネージャで接続します。
接続できたら以下のコマンドを実行し、squidをインストールします。
うまく接続できない場合はこちらを参考にしてください。
1. [セッションマネージャ接続がうまくいかない場合](#セッションマネージャ接続がうまくいかない場合)
``` 
#squidをインストールする
yum install squid
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/873ffc5b-2ea9-58bb-b079-e7f20b8d8148.png)
設定ファイルの格納場所：「/etc/squid」
設定ファイル名：squid.conf
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/1f805053-b1d0-9121-7fa5-d1e8fc54e760.png)

「squid.conf」の中身はデフォルトだとこんな感じになってます。
今回はこのまま変更しないで使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/a5321110-a19e-eced-8cf1-ed63444e98e5.png)

* ポートを確認する
以下のコマンドでsquidがport:3128を使用していることを確認します。(プロキシ設定時にポートの指定で必要です)
```
lsof -i:3128
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/61b14d7f-2346-be9d-c9d9-8a5649c24f6f.png)



# セッションマネージャ接続がうまくいかない場合
パブリックサブネットのルートテーブルにIGWへのルートが存在する場合、EC2のロールにセッションマネージャ接続で必要な権限が不足している可能性があります。
その場合はEC2のロールに以下のポリシーを付与されているか確認してください。
* 必要な権限：AmazonSSMManagedInstanceCore

過去にセッションマネージャを使用したCloudFormationに関する記事も書いているのでよかったら参考にしてみてください。
+ [【AWS】CloudFormationでセッションマネージャで接続可能なEC2を構築してみた(NATGateway編)](https://qiita.com/itkz1016/items/1c39f024a00b020c5bd7)
+ [【AWS】CloudFormationでセッションマネージャによる接続可能なEC2を構築してみた(VPCEndpoint編)](https://qiita.com/itkz1016/items/09af2ce3bf73943ab132)

<a id="#Chapter2"></a>
# セキュリティグループを作成する
セキュリティグループを3つ作成します。
１つ目はプロキシサーバ(Linux)用、2つ目はプライベートサブネットに作成するEC2(windows Server)用,３つ目は踏み台サーバ用です。
セキュリティ面を考慮するともっとガチガチのルールにしたほうがいいですが、
今回は検証なのでそれぞれのサーバのSGからの通信はすべて許可といった感じにしてます。
作成したらそれぞれのEC2にアタッチします。

* 踏み台サーバ用
踏み台サーバにRDPするIPをインバウンドルールに追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/00129281-cb75-f078-e758-132b6e1aa989.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/ac41fc54-c230-4146-1f70-03f6cb55ec5d.png)

* プロキシサーバ用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/cb05d14a-a264-355b-2388-645cc67059c8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/6f1809b1-bf24-839b-edb3-1b1da0d2e418.png)

* プライベートサブネットEC2(WindowsServer)用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/c56dd6bc-5bfa-65d5-5d37-5b3bde9906fe.png)

# SystemsManagerで疎通状況を確認する
SystemsManagerでAutomationを実行するためにはインスタンスにSSMAgentがインストールされており、マネージドインスタンスとして認識されている必要があります。
プロキシ設定前の状態を確認してみます。

SystemsManagerのフリートマネージャを開きます。
まだプロキシの設定を行っていないのでプライベートサブネット内のEC2(proxy-access-test)がマネージドノードとして認識されていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/53a7d49e-de9d-9c1a-b01c-a1e209230bfe.png)


# プロキシの設定を行う
踏み台サーバ経由でプライベートサブネットのWindowsServer(proxy-access-test)にRDP接続します。
※RDPがうまくいかない場合は接続先EC2のSGのインバウンドで許可されているか、接続元のEC2のSGのアウトバウンドが許可されているか確認してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/9a4887e9-55da-b8f1-cf57-a84764b5f781.png)

プライベートサブネット内のEC2に対してプロキシの設定を行います。
プロキシの設定を行うことでパブリックサブネットに作成したプロキシサーバを経由して
EC2内のSSMAgentがインターネットに接続できるようになり、SystemsManagerでもマネージドフリートとして認識されるようになります。

SSMAgentがインターネットに接続するために使用されるプロキシ設定にはいくつか種類があり、優先度があります。
今回は１番優先度の高いAmazonSSMAgentレジストリ設定を使用します。

プロキシ設定の優先順位について、詳しくはこちらのドキュメントをご覧ください。
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-install-ssm-proxy.html
```
【プロキシの優先度】
1.AmazonSSMAGent レジストリ設定 (HKLM:\SYSTEM\CurrentControlSet\Services\AmazonSSMAgent)
2.システム環境変数 (http_proxy、https_proxy、no_proxy)
3.LocalSystem ユーザーアカウントの環境変数 (http_proxy、https_proxy、no_proxy)
4.Internet Explorer の設定 (HTTP、セキュア、例外)
5.WinHTTP プロキシ設定 (http=、https=、bypass-list=)
```


* PowerShellを開き以下のコマンドを実行します
3行目の「hostname:port」は プロキシサーバのプライベートIPアドレス、[プロキシサーバを作成する](#プロキシサーバを作成する)で確認したport番号 に書き換えて実行してください。

```
$serviceKey = "HKLM:\SYSTEM\CurrentControlSet\Services\AmazonSSMAgent"
$keyInfo = (Get-Item -Path $serviceKey).GetValue("Environment")
$proxyVariables = @("http_proxy=hostname:port", "https_proxy=hostname:port", "no_proxy=169.254.169.254")

if ($keyInfo -eq $null) {
    New-ItemProperty -Path $serviceKey -Name Environment -Value $proxyVariables -PropertyType MultiString -Force
} 
else {
    Set-ItemProperty -Path $serviceKey -Name Environment -Value $proxyVariables
}
Restart-Service AmazonSSMAgent
```

* 以下のコマンドをPowerShellで実行し、SSMAgentのレジストリ設定を確認します。
Environmentの行が追加されていればOKです。
```
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\AmazonSSMAgent"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/cd7d49d7-dd3e-2d9c-747a-035e5ccb6505.png)


# SystemsManagerで疎通状況を再度確認する
SSMAgentレジストリ設定を行うことでプロキシを経由してSSMAgentがインターネットと通信できるようになりました。
先ほど同様にSystemsManagerのフリートマネージャで疎通状況を確認してみます。
今度はプライベートサブネットに作成したEC2(proxy-access-test)が認識されました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/3c650568-f718-4568-1e8b-84b1abaa258e.png)


# Automationを実行する
SSMドキュメント「AWSSupport-UpgradeWindowsAWSDrivers」を実行してみます。
SystemsManagerの「ドキュメント」を開き、「Amazonが所有」を選んだ状態で「AWSSupport-UpgradeWindowsAWSDrivers」と検索します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/e97127f3-1027-aa81-47dc-868a5272c3b8.png)
ドキュメント名を選択し、右上の「オートメーションを実行する」を押下します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/85242d59-c6a2-2f7b-b5fd-b9dd226458fa.png)
「シンプルな実行」を選び、プライベートサブネットに作成したインスタンス(proxy-access-test)を選択します。
そのままが右下の「実行」を押下します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/fb6f5375-4161-9e39-cc72-8d06c3e957bc.png)

オートメーションが開始されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/0a3fbf25-1a62-3d12-6ada-3fe6a6981173.png)
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/b14dcacc-680f-fe34-8855-ee26e21e9354.png)

プロキシの設定をし、マネージドフリートとして認識されているのに失敗してしまいました。


具体的にどんな処理を行っているのかドキュメント内を確認してみます。
※長いので最初に失敗したインストール処理(installAwsEnaNetworkDriverOnInstance)だけ載せます

```
      "name": "installAwsEnaNetworkDriverOnInstance",
      "action": "aws:runCommand",
      "onFailure": "Continue",
      "inputs": {
        "DocumentName": "AWS-RunPowerShellScript",
        "InstanceIds": [
          "{{ InstanceId }}"
        ],
        "Parameters": {
          "commands": [
            "try {",
            "    if (Test-Path -Path \"ena-state-{{ automation:EXECUTION_ID }}\") {",
            "        if (Select-String -Path \"ena-state-{{ automation:EXECUTION_ID }}\" -Pattern \"success\") {",
            "            Write-Host \"Installer has already run. Skipping installation\"",
            "            exit 0",
            "        }",
            "        else {",
            "            Write-Host \"Previous run of installer marked as failure. Skipping installation\"",
            "            exit 1",
            "        }",
            "    }",
            "    else {",
            "        # Download drivers",
            "        $url = \"https://s3.amazonaws.com/ec2-windows-drivers-downloads/ENA/Latest/AwsEnaNetworkDriver.zip\"",
            "        $tempPath = (Resolve-Path ${env:temp}).Path",
            "        Write-Host \"Downloading AWS ENA drivers\"",
            "        $tempFile = \"${tempPath}\\AwsEnaNetworkDriver.zip\"",
            "        $tempDir = \"${tempPath}\\AwsEnaNetworkDriver\"",
            "        $wc = New-Object \"System.Net.WebClient\"",
            "        $wc.Headers.Add(\"user-agent\", \"AWSSupport-UpgradeWindowsAWSDrivers\")",
            "        $wc.DownloadFile($url, $tempFile)",
            "        if (Test-Path $tempDir) {",
            "            Remove-Item -Path $tempDir -Recurse -Force",
            "        }",
            "",
            "        # Extract drivers",
            "        try {",
            "            # Unpacking the file this way is not supported in PowerShell 2",
            "            Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue",
            "            [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFile, $tempDir)",
            "        }",
            "        catch [System.InvalidOperationException] {",
            "            # Legacy unpacking method using COM Object",
            "            # Works with PowerShell 2",
            "            New-Item -Path $tempDir -ItemType \"directory\" -Force | Out-Null",
            "            ((New-Object -com shell.application).NameSpace($tempDir)).CopyHere(((New-Object -com shell.application).NameSpace($tempFile)).Items())",
            "        }",
            "",
            "        # Run install.ps1",
            "        Write-Host \"Running install script\"",
            "        $output = & \"${tempDir}\\install.ps1\" -NoReboot",
            "        Write-Host $output",
            "",
            "        # Evaluate output",
            "        New-Item \"ena-state-{{ automation:EXECUTION_ID }}\" -ItemType file | Out-Null",
            "        if ($output -match \"successful\") {",
            "            if ($output -match \"reboot\") {",
            "                Write-Host \"Rebooting to complete installation\"",
            "                Write-Output \"success\" | Set-Content -Path \"ena-state-{{ automation:EXECUTION_ID }}\"",
            "                exit 3010",
            "            }",
            "            Write-Output \"success\" | Set-Content -Path \"ena-state-{{ automation:EXECUTION_ID }}\"",
            "            exit 0",
            "        }",
            "        else {",
            "            Write-Output \"failure\" | Set-Content -Path \"ena-state-{{ automation:EXECUTION_ID }}\"",
            "            exit 1",
            "        }",
            "    }",
            "}",
            "catch {",
            "    Write-Host $_.Exception.Message",
            "    exit 255",
            "}"
          ]
        }
      },
      "isCritical": "true",
      "nextStep": "installAWSNVMeOnInstance"
    },
```

処理内容を簡単に解説すると、PowerShellでコマンドを実行し、
「 $url = \"https://s3.amazonaws.com/ec2-windows-drivers-downloads/ENA/Latest/AwsEnaNetworkDriver.zip」
で指定したURLからzipファイルをダウンロードしてきています。

エラーメッセージを見てみしょう。
どうやらリモートサーバに接続できていないっぽいですね。
この場合は指定したURLにアクセスできなかったということを表しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/5008f109-746b-4b1f-c752-b8c8eb736e54.png)

```
【エラーメッセージを抜粋】
Downloading AWS ENA drivers
Exception calling "DownloadFile" with "2" argument(s): "Unable to connect to the remote server"
```

# Automationの実行に失敗した原因
SSMAgentレジストリの設定を行ってマネージドフリートとして認識されているにも拘わらず、なぜ失敗したのでしょうか。
原因はオートメーションを実行する際に使用されるユーザのプロキシ設定に問題があります。

SSMオートメーション(runCommand)実行時にはサービスアカウントの「LOCALSYSTEM」が使用されます。
また、プロキシ設定は「LOCALSYSTEM」のInternetExplorer(インターネットオプション)のプロキシ設定が使用されます。
インターネットオプションのプロキシ設定はユーザ単位で行う必要があるため、「LOCALSYSTEM」のプロキシ設定が施されていない状態でrunCommandを実行しても、プロキシサーバにたどり着けず処理に失敗してしまうというわけです。



# LOCALSYSTEMのプロキシ設定を確認
今回はコマンドベースで行います。
```
#PowerShellで実行
bitsadmin /util /getieproxy LOCALSYSTEM
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/73a81295-f986-3cac-d377-d6d5ac32366b.png)

確認の結果、「Proxy usage: AUTODETECT」となっていることが分かりました。
各種パラメータについてはMicrosoft公式ドキュメントをご確認ください。
https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/bitsadmin-util-and-setieproxy


* コマンド以外にもMicrosoftが提供するPsExecを使用する方法もあります。
簡単にですが、PsExexの使い方も載せておきます
PsExexダウンロード先：https://learn.microsoft.com/en-us/sysinternals/downloads/psexec
```
#コマンドプロンプトで以下のコマンドを実行
PsExec.exe -i -s powershell.exe

#PowerShellが開くのでコマンドを実行
inetcpl.cpl
```

# LOCALSYSTEMのプロキシ設定を変更
プロキシの設定変更を行います。
こちらも確認同様コマンドベースで実行します。
ポート番号については[プロキシサーバを作成する](#プロキシサーバを作成する)で確認したポートを指定してください。

```
#PowerShellで実行
bitsadmin /util /setieproxy LOCALSYSTEM MANUAL_PROXY プロキシサーバのプライベートIP:ポート "169.254.169.254;"
bitsadmin /util /getieproxy LOCALSYSTEM
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/a636f0d0-a4f1-16bb-eb29-828ece02e706.png)

Proxy listとProxy bypassが設定されたことが確認できました。


# Automationを再実行する
同じドキュメントを実行してみます。
LOCALSYSTEMのプロキシ設定を行っているので、今度は成功するはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/1a609fa9-2f8d-3133-28e7-310d4282afeb.png)

無事に成功しました！！



# さいごに
プロキシサーバの構築をやったことがなかったので、いまいち仕組みを理解していなかったところがあったのですが、今回のトラブル対応でかなり勉強になりました。
プロキシを使っている人で同様のトラブルに見舞われる方がいるかもしれないので、この記事がどこかの誰かの参考になれば幸いです。