エムオーテックス株式会社が運営するテックブログです。

AWS Certificate Manager で複数の証明書を発行する作業を自動化する

はじめに

こんにちは、サービス戦略課の森田です。
サービス戦略課では LANSCOPE エンドポイントマネージャー クラウド版の技術的負債解消やフローの自動化など開発者の生産性向上のためのサービス改善に日々取り組んでいます。

今回は、AWS Certificate Manager (以下 ACM という) から複数の証明書を発行する作業をスクリプトで自動化した件についてご紹介したいと思います。

自動化の経緯

弊社では、AWS 上でサービスを構築しており、本番環境と検証環境を AWS アカウントレベルで分離しています。
検証環境の AWS アカウントは複数存在し、複数の開発チーム間で利用期間を調整し、利用する検証環境を決定しています。

チームの拡大に伴い、現状の検証環境の数ではチーム間の調整が煩雑になることが増えてきました。
そのため、新規に検証用の AWS アカウントを 3 つ追加することとしました。

課題

弊社のサービスでは、API Gateway や CloudFront を多数利用しており、それぞれに ACM から発行された証明書を設定しています。

1つの AWS 環境あたり約 40 個の ACM 証明書が必要であり、今回新たに 3 つの検証環境を追加するため、合計で 120 個の ACM 証明書を発行する必要があります。

マネジメントコンソール上での手動操作が煩雑であるため、AWS CLI を用いてスクリプト化することにしました。

作業内容

作業の流れは以下の通りです。

  1. Route 53 コンソールでドメインを購入します。
  2. 購入したドメインを承認します。
  3. 既存の検証環境から ACM 証明書の一覧を出力します。
  4. 既存の検証環境から ACM 証明書のタグを出力します。
  5. 入力用の CSV を整形します。
  6. 新しい検証環境に ACM 証明書を作成します。
  7. 作成結果を確認します。

1. Route 53 コンソールでドメインを購入します。

まずは、新しいドメインの登録 - Amazon Route 53 を参考に、Route53 コンソール上でドメインを購入します。

※ドメインを購入すると、AWS によって自動的に Hosted Zone が作成されます。
※ドメイン購入前に同名の Hosted Zone がすでに存在する場合、Hosted Zone が作成されないのに加えてドメインの NS レコードが仮のものになるため、Hosted Zone の NS レコードに変更する必要があります。

特に必要がなければ、同名の Hosted Zone が存在する場合は削除しておきましょう。

2. 購入したドメインを承認します。

Route53 コンソールでドメインを購入すると、ドメインの登録者宛に承認用のメールが送信されます。
ドメインの登録者はメール本文のリンクをクリックして承認します。

承認が完了すると、Route53 コンソールで登録済みドメインの「連絡先情報」にて、登録者欄に「検証済み」と表示されます。

検証済みを示す表示

3. 既存の検証環境から ACM 証明書の一覧を出力します。

既存の検証環境から ACM 証明書のドメイン名などを取得し、それらを新しい検証環境で ACM 証明書を作成する際の入力として使用します。

# 東京リージョンの ACM 証明書
aws acm list-certificates --profile xxx --region ap-northeast-1 \
 | jq -r '.CertificateSummaryList | .[] \
 | [.CertificateArn, .DomainName, "ap-northeast-1"] \
 | @csv' | sort -V \
  > certificates_ap-northeast-1.csv

# バージニアリージョンの ACM 証明書
aws acm list-certificates --profile xxx --region us-east-1 \
 | jq -r '.CertificateSummaryList | .[] \
 | [.CertificateArn, .DomainName, "us-east-1"] \
 | @csv' | sort -V \
  > certificates_us-east-1.csv

4. 既存の検証環境から ACM 証明書のタグを出力します。

弊社では、ACM 証明書にタグを設定しているため、新しい検証環境にも同じタグを付ける目的で、既存環境のタグ情報を取得します。
新しい検証環境に ACM 証明書を作成する際の入力として使用します。

# 東京リージョン
for certarn in $(aws acm list-certificates --profile xxx --region ap-northeast-1 --query 'CertificateSummaryList[*].CertificateArn' --output text | sort -V);
do
  tags=$(aws acm list-tags-for-certificate --profile xxx --region ap-northeast-1 --certificate-arn ${certarn} | jq -r '.Tags[] | ["Key=" + .Key, "Value=" + .Value] | @csv')
  echo \"${certarn}\",${tags}
done

# バージニアリージョン
for certarn in $(aws acm list-certificates --profile xxx --region us-east-1 --query 'CertificateSummaryList[*].CertificateArn' --output text | sort -V);
do
  tags=$(aws acm list-tags-for-certificate --profile xxx --region us-east-1 --certificate-arn ${certarn} | jq -r '.Tags[] | ["Key=" + .Key, "Value=" + .Value] | @csv')
  echo \"${certarn}\",${tags}
done

5. 入力用の CSV を整形します。

手順 3 および手順 4 で出力された情報をもとに、ドメイン名とリージョン名、タグ情報を 1 行に収めた CSV ファイルを作成します。

※弊社では、ドメイン名のサフィックスに検証環境を示す固有文字列(環境名)を付与しています。
 手順 6 のスクリプト内で置き換えるため、「envname」というプレースホルダーで仮置きしています。

*service1.hoge-envname.com, ap-northeast-1,"Key=Name,Value=hoge"
*service2.hoge-envname.com, us-east-1,"Key=Name,Value=fuga"

6. 新しい検証環境に ACM 証明書を作成します。

以下のスクリプトを実行します。
入力となる CSV の行数分、ACM 証明書を発行します。

スクリプトの解説は、インラインで記載しています。

#!/bin/bash

set -e

if [ $# != 2 ] || [ $1 = "" ] || [ $2 = "" ]; then
  echo -e "two parameters are required

  * 1st - string: EnvName (e.g. dev)
  * 2nd - string: Input File Name (e.g. certificates.csv)

  example command - sh ./issue-certificates.sh dev certificates.csv"
  exit
fi

ENV_NAME=$1
CSV_FILE=$2
HOSTED_DOMAIN=hoge-$ENV_NAME.com
NONE="None"

while IFS=, read -r one two three  || [[ -n "${one}" ]]; do
  TARGET_DOMAIN="$one"
  REGION="$two"
  TAGS="$three"

  # CSV のドメイン名に含めているプレースホルダー (envname) を実際の環境名に置き換えます
  TARGET_DOMAIN=${TARGET_DOMAIN//envname/$ENV_NAME}

  # CSV のダブルクォーテーションを削除します
  TAGS=${TAGS//\"/}

  echo "${HOSTED_DOMAIN},${TARGET_DOMAIN},${REGION},${TAGS}"

  # ACM 証明書をリクエストします
  echo "Request a certificate for '${TARGET_DOMAIN}' from ACM."
  if [ "$TAGS" == "$three" ]; then
    # タグを設定しない場合
    CERT_ARN=$( \
      aws acm request-certificate \
      --profile xxx \
      --domain-name ${TARGET_DOMAIN} \
      --validation-method DNS \
      --region ${REGION} \
      --output text) \
    && sleep 10 \
    && echo -e "\t CERT_ARN = ${CERT_ARN}"
  else
    # タグを設定する場合
    CERT_ARN=$( \
      aws acm request-certificate \
      --profile xxx \
      --domain-name ${TARGET_DOMAIN} \
      --validation-method DNS \
      --tags ${TAGS} \
      --region ${REGION} \
      --output text) \
    && sleep 10 \
    && echo -e "\t CERT_ARN = ${CERT_ARN}"
  fi

  # ドメイン検証用のレコードセットを作成します
  echo "Create a record set to validate the domain in Route 53."
  VALIDATION_RECORD_NAME=$( \
    aws acm describe-certificate \
    --profile xxx \
    --certificate-arn ${CERT_ARN} \
    --query "Certificate.DomainValidationOptions[0].ResourceRecord.Name" \
    --region ${REGION} \
    --output text) \
  && echo -e "\t VALIDATION_RECORD_NAME = ${VALIDATION_RECORD_NAME}"

  VALIDATION_RECORD_VALUE=$( \
    aws acm describe-certificate \
    --profile xxx \
    --certificate-arn ${CERT_ARN} \
    --query "Certificate.DomainValidationOptions[0].ResourceRecord.Value" \
    --region ${REGION} \
    --output text) \
  && echo -e "\t VALIDATION_RECORD_VALUE = ${VALIDATION_RECORD_VALUE}"

  HOSTED_ZONE_ID=$( \
    aws route53 list-hosted-zones \
    --profile xxx \
    --query "HostedZones[?Name=='${HOSTED_DOMAIN}.'].Id" \
    --output text) \
  && echo -e "\t HOSTED_ZONE_ID = ${HOSTED_ZONE_ID}"

  if [ $VALIDATION_RECORD_NAME == $NONE ] || [ $VALIDATION_RECORD_VALUE == $NONE ] || [ $HOSTED_DOMAIN == $NONE ]; then
    echo "Failed to obtain the required parameters for domain validation."
    exit
  fi

  CHANGE_ID=$( \
    aws route53 change-resource-record-sets \
    --profile xxx \
    --hosted-zone-id ${HOSTED_ZONE_ID} \
    --change-batch \
    "{
      \"Changes\": [
        {
          \"Action\": \"CREATE\",
          \"ResourceRecordSet\": {
            \"Name\": \"${VALIDATION_RECORD_NAME}\",
            \"Type\": \"CNAME\",
            \"TTL\": 300,
            \"ResourceRecords\": [{\"Value\": \"${VALIDATION_RECORD_VALUE}\"}]
          }
        }
      ]
    }" \
    --query "ChangeInfo.Id" \
    --output text) \
  && echo -e "\t CHANGE_ID : ${CHANGE_ID}\n"

  echo;
  echo;

done < $CSV_FILE

7. 作成結果を確認します。

マネジメントコンソールの ACM 証明書一覧において、各証明書のステータスが「保留中の変更」から「発行済み」へ更新されていることを確認します。

発行済みを示す表示

※今回は数分以内に更新されました。

おわりに

今回は、新規に検証環境を追加する際に必要となる ACM 証明書の作成を自動化しました。

ACM 証明書を手動で作成すると 1 個あたり数分かかりますが、約 140 個の作成作業を自動化することで大幅に時間を削減できました。
また、自動化することで作成ミスも防ぐことができます。

ACM 証明書は繰り返し発行することは稀であるため、IaC 化することは珍しいかもしれません。

ニッチな内容になってしまったかもしれませんが、本内容がお役に立てれば幸いです。

ここまでお読みいただき、誠にありがとうございます。