この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
私が参加しているプロジェクトはDNS検証に対応する以前からACMの証明書を利用していて、一部Eメール検証のものが残っています。証明書の更新を自動化するため、証明書の期限切れ通知を受け取ったタイミングで逐次DNS検証に切り替えていたのですが、都度対応するのが面倒になってきました。
そこで、切り替えが必要な証明書がどれくらい残っているか確認するため、AWS SDK for Python (Boto3)を使って、Eメール検証の証明書を列挙してみます。
前提条件
- macOS: 10.13.6
- CPython: 3.7.0
- Pipenv: 2018.7.1
- boto3: 1.9.14
環境構築
Pipenv を使って環境構築します。
$ pipenv install --python 3.7.0 boto3 colorama
colorama
は表示を見やすくするために使います。
やってみる
AWSアカウントごと・リージョンごとにEメール検証の証明書を列挙してみます。 ACMのドキュメント を確認すると、 ListCertificates と DescribeCertificate で 実現できそうです。
以下の手順で処理するプログラムを作成します。
- 任意のプロファイル・任意のリージョン用のACMクライアントを作成
- ACMの証明書一覧を取得する
- 各証明書の詳細情報を取得する
- ドメイン検証オプションの
ValidationMethod
がEMAIL
の場合、切り替えが必要と伝わるように表示する
def main():
profiles = ["default", "staging"]
regions = ["ap-northeast-1", "us-east-1"]
results = [r for r in enumerate_certificates(profiles, regions)]
show_results(results)
enumerate_certificates
で手順1〜3の処理を、 show_results
で手順4の処理を行います。以降で enumerate_certificates
と show_results
を実装していきます。
完成版のソースは Gist に載せています。
各プロファイル・各リージョンの証明書を列挙する
各プロファイル・各リージョンのACMクライアントを作って、一覧取得・詳細取得のAPIを呼び出します。 Boto3のレスポンスはdict型なのでそのまま使っても良いのですが、(自分にとって)わかりやすいnamedtupleを使うようにしています。 enumerate_certificates
の戻り値も同様です。
import itertools
from collections import namedtuple
from boto3.session import Session
EnumerateResult = namedtuple("EnumerateResult", ["profile", "region", "certificates"])
def enumerate_certificates(profiles, regions):
for p, r in itertools.product(profiles, regions):
# ACMのクライアントを生成
session = Session(profile_name=p, region_name=r)
acm = session.client("acm")
# 証明書の一覧を取得
summaries = list(list_certificates(acm))
# 証明書の詳細を取得
certificates = [describe_certificate(acm, s.arn) for s in summaries]
# 結果を返却する
yield EnumerateResult(profile=p, region=r, certificates=certificates)
証明書の一覧を取得する (ListCertificates)
任意のプロファイル・リージョン用のACMクライアントを使って、証明書の一覧を取得します。
from collections import namedtuple
CertificateSummary = namedtuple("CertificateSummary", ["arn", "domain_name"])
def list_certificates(acm):
def create_summary(s):
arn = s["CertificateArn"]
domain_name = s["DomainName"]
return CertificateSummary(arn=arn, domain_name=domain_name)
complete = False
next_token = None
while not complete:
params = dict(NextToken=next_token) if next_token else {}
res = acm.list_certificates(**params)
next_token = res.get("NextToken", None)
complete = next_token is None
for x in res["CertificateSummaryList"]:
yield create_summary(x)
証明書の詳細情報を取得する (DescribeCertificate)
詳細情報の取得も同様です。ACMのクライアントに加えて、対象の証明書のARNを使います。
from collections import namedtuple
Certificate = namedtuple(
"Certificate", ["arn", "domain_name", "status", "validation_options"]
)
ValidationOption = namedtuple(
"DomainValidationOptions", ["validation_status", "validation_method"]
)
def describe_certificate(acm, cert_arn):
def create_validation_option(o):
status = o["ValidationStatus"]
method = o["ValidationMethod"]
return ValidationOption(validation_status=status, validation_method=method)
res = acm.describe_certificate(CertificateArn=cert_arn)
cert = res["Certificate"]
options = cert.get("DomainValidationOptions", [])
options = [create_validation_option(x) for x in options]
return Certificate(
arn=cert_arn,
domain_name=cert["DomainName"],
validation_options=options,
status=cert["Status"],
)
結果を表示する
最後に結果を表示します。証明書のステータスが ISSUED
のものを対象に、 ドメインの検証方法が DNS
の場合は ✓
を、 MAIL
の場合は ✗
を、それぞれ先頭に表示します。
import itertools
import colorama
from colorama import Fore
def prefix_for(cert):
def is_issued():
return cert.status == "ISSUED"
def has_mail_validation():
return any([o.validation_method == "EMAIL" for o in cert.validation_options])
if is_issued():
prefix = Fore.RED + "✗" if has_mail_validation() else Fore.GREEN + "✓"
prefix += Fore.RESET
return prefix
else:
return "-"
def show_results(results):
colorama.init(autoreset=True)
status_width = max([
len(c.status)
for c in itertools.chain.from_iterable([x.certificates for x in results])
])
for profile, profile_group in itertools.groupby(results, key=lambda x: x.profile):
if not profile_group:
continue
print("-" * 40)
print(f"[{profile}]")
for result in profile_group:
if not result.certificates:
continue
print(f"\n{result.region}")
for c in result.certificates:
prefix = prefix_for(c)
status = c.status.ljust(status_width)
methods = ",".join([o.validation_method for o in c.validation_options])
print(f"{prefix} {status} {c.domain_name} ({methods})")
動かすとこんな感じに表示します。
赤字で ✗
付きの証明書が対象のものです。
おわりに
今回はAWS SDK for Python (Boto3) を使って面倒な作業をスクリプト化してみました。画面から操作すると面倒なことでも簡単にプログラム化できることが多いので積極的にツール化していこうと思います。