AWS Certificate Manager (ACM) を利用したSSLサーバ証明書の運用・管理

##1. 概要
* SSL証明書のインストール作業や更新作業は地味だけど、失敗や更新を忘れると大障害になる。私も過去に２、３回くらいSSL証明書作業で失敗して大反省した経験がある。
* 昔のSSL証明書作業のことを思い出して書いてもいまさらなので結論から言うと、Amazon で発行して AWS Certificate Manager (ACM) で管理が理想。とはいえ、ACM にはいろいろ制約があり、条件を満たしていないと自動更新できない。すべてのSSL証明書でACMの恩恵を受けることはできないけど、AWSを利用しているのであれば、可能な限りACMを使ったほうが良い。
* ACMは以前から利用はしているが、ACMによる自動更新についてまだほとんど理解や経験がないので、ACM についてや、事前の準備・確認設定や、その日（自動更新）を迎えるまでのことを書いておく。


##2. AWS Certificate Manager (ACM) について
参考：[AWS ドキュメント » AWS Certificate Manager (ACM)](https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/acm-overview.html) 
参考：[AWS Certificate Manager よくある質問](https://aws.amazon.com/jp/certificate-manager/faqs/)
参考：[ACM 証明書自動更新時の注意点](http://aws.typepad.com/sajp/2016/12/aws-certificate-manager-managed-renewal.html)

### 2.1. ACM 概要
* 2016年 1月、AWS は AWS Certificate Manager (ACM) の提供を開始した。このサービスは、AWS サービスで使用できる SSL/TLS 証明書を簡単に作成、管理できるようにする。
* <font color="Red">証明書にはアマゾンの自社認証局 Amazon Trust Services から発行されたものを追加料金無しで利用できます。</font>
* ブラウザやその他のアプリケーションが証明書を信頼するには、証明書の発行者がブラウザなどが信頼している認証局一覧である 信頼ストア に含まれている必要があります。もし信頼ストアに発行した認証局が含まれていない場合は、ブラウザはエラーメッセージ（参考例）を表示したり、アプリケーションは独自のエラーを表示します。AWS は Amazon Trust Services 認証局 があらゆるところで確実に使えるようにするために、<font color="Red">2005年以降のほとんどのブラウザで信頼されているルート認証局である Starfield Services の認証局の一つ</font>を購入しました。これは Amazon Trust Services で発行された証明書を使うために何もする必要が無いことを意味しています。
* <font color="Red">ACM は、ACM の更新プロセスと、更新後の証明書のプロビジョニングを管理します。</font>自動更新は、証明書の設定ミス、失効、期限切れによるダウンタイムを防止するために役立ちます。通常、ACM では自動で証明書更新が完了いたします。ご利用者側で証明書をインストールしたりソフトウェアを更新いただく必要はありません。<font color="Red">自動更新による通信の瞬断もありません。</font>
* <font color="Red">ACMはワイルドカード証明書も無料で利用可能です。</font>
* SSLサーバ証明書新規発行時のドメイン所有者確認は、[メール検証](https://aws.amazon.com/jp/certificate-manager/faqs/#email_validation) か [DNS検証](https://aws.amazon.com/jp/certificate-manager/faqs/#dns_validation) を利用できます。
* ACM 証明書の更新ステータスの確認には、AWS Certificate Manager コンソール、ACM API、AWS CLI、または Personal Health Dashboard を使用できます。

### 2.2. ACM 注意点
* ACMで提供される証明書はドメイン証明書までです。それ以上のOVやEVといった企業実在証明型の証明書の発行はできません。
* <font color="Red">ACM での証明書の有効期限は発行日から13か月。</font>
* CloudFront で ACM を利用する場合は、米国東部 (バージニア北部) リージョン を利用する。
* <font color="Red">ACM 証明書のプライベートキーをダウンロードすることはできない。</font>
 * ということは、EC2 で稼働している Apache や Nginx などの Webサーバに ACM で発行した Amazon の SSLサーバ証明書を直接インストールすることはできない。なので、EC2で稼働しているWebサーバを HTTPS化 にするためには、ELB構成にする必要がある。
 * ALB の Host-based routing 設定と、ワイルドカードSSL証明書を使用すれば、１つのALBでドメインに応じて適切な EC2 にリクエストをルーティングできるので、ALBの節約になる。ただし、ワイルドカード名の制約に注意する([ACM 証明書の特徴](https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/acm-certificate.html)⇒ワイルドカード名)。
 * ちなみに、私が担当しているインフラ環境では、ACM は CloudFront と ELB(ALB/CLB)で利用している。
* ACM にインポートしたSSL証明書（ACMでAmazonから発行したものではなく、例えば GlobalSign などから発行したものをACMにインポートしたもの）に対しては、ACM によるマネージド型更新（更新プロセスと、更新後の証明書のプロビジョニング）は行われない。
* ドメイン検証
 * <font color="Red">証明書を更新する前に、ACM は証明書の各ドメイン名を自動的に検証することを試行します。</font>ACM によって自動的にドメイン名が検証できない場合には、手動による検証手続きが必要であることが通知されます。証明書が使用されている場合 (ACM に統合される AWS サービスに関連付けられている場合) に証明書のすべてのドメイン名が検証されると、ACM によって証明書が更新されます。
 * <font color="Red">ドメインを検証するには、ACM はドメインに自動で定期的な HTTPS リクエストを送信します。 </font>ACM が HTTPS 接続の確立に成功したら、ACM は返された証明書と ACM が更新する証明書が一致することを調べます。証明書が一致すれば、ACM はドメイン名が検証されたと見なします。
 * ACM によって自動的に証明書の 1 つまたは複数のドメイン名が検証されない場合、手動でドメインの検証を行う必要があることが ACM から通知されます。ドメインの手動検証が必要になるのは、次のような理由によります。<br />・ACM がドメインと HTTPS 接続を確立できない。<br />・HTTPS リクエストに対する応答で返された証明書が ACM が更新する証明書と一致しない。
* 自動更新プロセス適用条件
 * 証明書に記載されている全ての完全修飾ドメイン名 (FQDN) が DNS で名前解決できること。なお、ワイルドカードドメイン (例：*.example.com) に関しては ACM は名前解決の確認を行いません。
 * CloudFront や ELB (Classic Load Balancer や Application Load Balancer) など、証明書と関連づいている AWS リソースが、インターネット経由で SSL/TLS 接続できるようになっていること。なお、ワイルドカードドメイン (例：*.example.com) に関しては、ACM側が接続先を正しく特定できない可能性があるので、「[自動更新が失敗した場合](https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/troubleshooting-renewal.html)」に備えてください。
* <font color="Red">ACM は、失効日の 60 日前に ACM 証明書を自動的に更新しようとします。ACM で証明書を自動的に更新できない場合は、失効日の 45 日前、30 日前、15 日前、7 日前、3 日前、1 日前の間隔でACM は次の方法で通知を行います。</font>
 * ドメイン所有者に E メールで通知する (E メール検証)
 * AWS アカウントに E メールで通知する (DNS 検証)
 * AWS Personal Health Dashboard

## 3. ドメインの所有者の検証
参考：[DNS を使用したドメインの所有権の検証](https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/gs-acm-validate-dns.html)

### 3.1. ドメインの所有者の検証の概要
* Amazon 認証局 (CA) がサイトの証明書を発行する前に、AWS Certificate Manager (ACM) は、ユーザーがリクエストで指定したすべてのドメイン名の所有者または管理者であることを確認する必要があります。証明書をリクエストするときに、E メールによる検証と DNS による検証のいずれかを選択できます。
* 2018年5月25日に施行された「EU一般データ保護規則（GDPR）」により、<font color="Red">AWS では DNS による検証を推奨している</font>。

>ACMは、WHOISのデータに基づいて電子メール検証を使用する際にドメイン所有者を識別します。しかし、最近では、多くのレジストラがWHOISデータの連絡先情報の入力を停止しています。したがって、ドメインレジストラにWHOISデータの連絡先メールアドレスが含まれていない場合は、DNS検証に切り替えるか、次の5つのメールアドレスのうち少なくとも1つがドメインで有効であることを確認することをおすすめします。

### 3.2. DNS を使用したドメインの所有権の検証
* 当初はEメールによる検証方法しかなかったけど、2017年11月にDNSによる検証方法が追加された。今後のことを考えて、AWSのアナウンスにある通り、Eメール検証で発行したものを DNS検証に切り替えておく。
* DNS検証の設定は AWS のドキュメントに書いておるとおり実施すれば容易にできるので、ここでは割愛。

## 4. ACM からの通知受信設定

### 4.1. ACM からの通知受信について
* SSL証明書の新規発行とプロビジョニングが無事に終わったら、それ以降のサービスランニング中の更新・プロビジョニング作業はすべてACMが行うので、SSLサーバ証明書の運用管理作業はまったく何も考えなくても良くなるのが理想。なので、ACMからの失効日カウントダウン通知なども受け取る必要はなくなる。
* 心配性の私は、初回の自動更新が無事に終わるかどうかを確認できるまでは、ACMからの通知を受ける設定を入れておく。万が一自動更新ができない場合は、有効期限前までにその旨の通知が来るはずなので、条件の再確認や、手動更新の対応が取れるように。

### 4.2. AWS Health イベントのモニタリング
参考：[Amazon CloudWatch Events での AWS Health イベントのモニタリング](https://docs.aws.amazon.com/ja_jp/health/latest/ug/cloudwatch-events-health.html)

* Amazon CloudWatch Events を使用して、AWS Personal Health Dashboard (AWS Health) イベントのステータスの変化を検出し、対応することができます。次に、作成したルールで指定した値とイベントが一致すると、 CloudWatch イベント で 1 つ以上のターゲットアクションが呼び出されます。イベントのタイプに応じて、通知の送信、イベント情報の取得、是正措置の実施、またはその他の対策を行うことができます。
* 次にユースケースをいくつか示します。
 * イベントの発生時に、Lambda 関数を使用して通知を Slack チャネルに渡します。
 * AWS Health イベントの発生時に、Lambda と CloudWatch イベント を使用して Amazon SNS でカスタムテキストまたは SMS 通知を送信します。
* 設定例
 1. https://console.aws.amazon.com/cloudwatch/ にある CloudWatch コンソールを開きます。 
 2. ナビゲーションペインの [イベント] を選択します。
 3. [ルールの作成] を選択し、次に [イベントソース] の下の [サービス名] で [Health] を選択します。
 4. [イベントタイプ] で [特定のヘルスイベント] を選択して、その下の [Specific service(s)] で [ACM] を選択します。<br />
<イベントパターンのプレビュー><br />
![image.png](https://qiita-image-store.s3.amazonaws.com/0/148868/6332c2e8-0056-16cc-7c10-a77bfef186da.png)
 5. ターゲットで、通知先のターゲットを登録する。私は、すでに作成してある SNS トピックの適切な１つを登録した。

* 注意点
 * ALB は東京リージョンにあり、その ALB にプロビジョニングしている ACM 発行の Amazon SSLサーバ証明書は東京リージョンで発行しているので、東京リージョンでモニタリングの設定を行う。
 * CloudFront はグローバルリージョンにあり、その CloudFront にプロビジョニングしている ACM発行の Amazon SSLサーバ証明書はバージニア北部で発行しているので、バージニア北部でモニタリングの設定を行う。

## 5. SSLサーバ証明書の有効期限監視
### 5.1. SSLサーバ証明書の有効期限監視の概要
* 過去に痛い目にあっていて、人よりも心配性な私は、SSLサーバ証明書の有効期限監視も入れておく。
* 当初は Zabbix の外部チェックで監視しようと設定していたけど、簡単な監視内容にも関わらず様々な設定を施す必要があったり、GUI で設定画面をチラチラ切り替えたりするのにうんざりしてしまったので、CloudWatch の カスタムメトリクス (put-metric-data) を使うことにした。
* 指定した HTTPS サイトの SSLサーバ証明書の有効期日（あとn日で切れる）の n を取得して、その値を CloudWatch メトリクスへ Put する。Put したデータは、CloudWatch アラームでしきい値の設定や、ダッシュボードでグラフ化することが可能になる。
* Zabbix が稼働してる EC2 上で、その SSLサーバ証明書の有効期限監視のスクリプトを動かすことにする。ステージング環境用のHTTPSは限られたIPアドレスからの接続しか許可していないので、監視元のEC2のEIPからのHTTPSを許可を追加設定しておく。

### 5.2. SSLサーバ証明書有効期限チェックスクリプト
* Zabbixの外部チェック用のスクリプトをダウンロード
 * [Zabbix Template: External Website SSL Utilities](https://share.zabbix.com/cat-app/web-servers/ssl-certificates-check-new) ⇒ template_SSL_Detections.zip ⇒ zext_ssl_expiry.sh
* zext_ssl_expiry.sh を expiry.sh という名前に変更して、少し編集する。
* 編集した expiry.sh の内容
 * 第一引数：接続先ホスト名
 * 第二引数：接続先ポート番号
 * 第三引数：SSLサーバ証明書のドメイン名 

```
#!/bin/sh
HOST=$1
PORT=$2
SERVERNAME=$3
RETVAL=0
TIMESTAMP=`echo | date`
EXPIRE_DATE=`openssl s_client -connect $HOST:$PORT -servername $SERVERNAME </dev/null 2>/dev/null | openssl x509 -noout -dates 2>/dev/null | grep notAfter | cut -d'=' -f2`
EXPIRE_SECS=`date -d "${EXPIRE_DATE}" +%s`
EXPIRE_TIME=$(( ${EXPIRE_SECS} - `date +%s` ))
if test $EXPIRE_TIME -lt 0
then
RETVAL=0
else
RETVAL=$(( ${EXPIRE_TIME} / 24 / 3600 ))
fi
echo ${RETVAL}
```
* 動作確認１ ・・・ Amazonサイト

```
$ ./expiry.sh amazon.co.jp 443 amazon.co.jp
277
```
* 動作確認２ ・・・ CloudFront にプロビジョニングしたSSLサーバ証明書 ([SNIの仕組みをOpenSSLコマンドで理解する](https://qiita.com/greymd/items/5d2fc55430105620a550))
 * 第一引数：CloudFront の Domain Name ・・・ 例：**************.cloudfront.net
 * 第二引数：接続先ポート番号
 * 第三引数：Alternate Domain Names (CNAMEs) ・・・ ユーザーエンドのドメイン

### 5.3. cloudwatch put-metric-data
#### 5.3.1.  CloudWatch メトリクスへデータをPutする権限を持った IAM ユーザを作成する
* 必要な権限は「cloudwatch:PutMetricData」
* CloudFormationのテンプレート例

```
{
    "AWSTemplateFormatVersion" : "2010-09-09",
    "Description" : "IAM User - put-metrics",
    "Resources" : {
        "IAMUser" : {
            "Type" : "AWS::IAM::User",
            "Properties" : {
                "Path" : "/",
                "UserName" : "put-metrics",
                "Policies" : [
                    {
                        "PolicyName" : "put-metrics",
                        "PolicyDocument" : {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Sid": "cloudwatchputmetrics",
                                    "Effect": "Allow",
                                    "Action": [
                                        "cloudwatch:PutMetricData"
                                    ],
                                    "Resource": "*"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "AccessKey" : {
            "Type" : "AWS::IAM::AccessKey",
            "Properties" : {
                "UserName" : { "Ref" : "IAMUser" }
            }
        }
    },
    "Outputs" : {
        "IAMUser" : {
            "Description" : "IAM User put-metrics",
            "Value" : { "Ref" : "IAMUser" }
        },
        "AccessKey" : {
            "Description" : "IAM User put-metrics",
            "Value" : { "Ref" : "AccessKey" }
        },
        "SecretKey" : {
            "Description" : "IAM User put-metrics",
            "Value" : { "Fn::GetAtt" : [ "AccessKey", "SecretAccessKey" ] }
        }
    }
}
```

#### 5.3.2. 監視元EC2に上記IAMユーザのアクセスキー ID およびシークレットアクセスキーを登録する
```
# aws configure --profile put-metrics
```

#### 5.3.3. CloudWatch メトリクスへ Put するスクリプト
参考：[put-metric-data](https://docs.aws.amazon.com/cli/latest/reference/cloudwatch/put-metric-data.html)

* cloudwatch-put-metrics.sh

```
#!/bin/sh
/usr/bin/aws --profile put-metrics cloudwatch put-metric-data --namespace SSLCert --metric-name amazon.co.jp --value `./expiry.sh amazon.co.jp 443 amazon.co.jp`
/usr/bin/aws --profile put-metrics cloudwatch put-metric-data --namespace SSLCert --metric-name google.co.jp --value `./expiry.sh google.co.jp 443 google.co.jp`

```


### 5.4. スクリプトを配置して、実行権限をつける
例)

```
/root/scripts/ssl-certificate-check/expiry.sh
/root/scripts/ssl-certificate-check/cloudwatch-put-metrics.sh
```

### 5.5. cron設定
* cron で １日１回チェックスクリプトを実行する。<br />例：通知が飛ぶ場合は、昼間に飛ぶように毎日１０時に動かす

```
00 10 * * * cd /root/scripts/ssl-certificate-check && sh cloudwatch-put-metrics.sh 1> /dev/null
```

### 5.6. CloudWatch アラームで閾値を設定する。

## 6. ACM による自動更新

一応これで準備ができたので、その日が来たら更新する予定。。

## 7. 追記
* 2020/02/29
ACM の有効期限は、AWS Config の [acm-certificate-expiration-check](https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/acm-certificate-expiration-check.html) ルールでもチェックすることができる。<br />
(参考)[AWS Configの初期設定とマネージドルールを Terraform で設定して、非準拠項目を確認して対処してみる](https://qiita.com/tonishy/items/5f1f03057c1b2e56f13a)