LoginSignup
0
0

More than 1 year has passed since last update.

ELBで利用中の証明書の期限が切れる前に通知

Posted at

監視の必要性

皆さん、SSL/TLS証明書をELBで利用していますか。
期限が切れてしまうと、即サービスに影響しますので、監視しておくことが重要ですよね。

え?証明書なんて自動更新されるでしょ。監視なんて要らないよ。

はい。確かにACMで発行された証明書の場合は、失効前に自動更新されるようになっていますが、どうしてもサードパーティーで発行した証明書を使わなければならない ケースもあります。
その場合に、ACMでは自動更新しません。
忘れないようにと、毎日・毎週手動でチェックしにいくわけにもいかないので、適切なタイミングで通知を受け取りたいです。

CloudWatchの方法

クラスメソッドさんで公開されている情報にありますが、2021年3月より CloudWatchの DaysToExpiry メトリクスで取得できるようになっています。

このメトリクスでCloudWatchアラームを設定すれば、自分のベストタイミングの日数に通知を受け取ることができます。

CloudWatchの方法で困ること

CloudWatchで設定する場合に、いくつか困ることがあります。

  • 新しい証明書をACMにインポートするごとにCloudWatchアラームの設定が必要
  • 期限切れ前に証明書を入れ替えたタイミングで古いCloudWatchアラームは削除が必要
  • ELBの廃止で使わなくなった証明書のCloudWatchアラームも削除が必要
  • ACMの情報だけだと、どのELBが影響するのかがパッと分からない(対象ELBが複数あると特に)

これを解決するためには、Lambda関数によるカスタム監視ですよね。
そっかー、Lambda関数を作るしかないなあー、仕方ないなあー(単に作りたいだけ)

Lambda関数の方法

概要

デフォルトリージョンの全てのELBに対して、証明書を利用しているか、および、そのACMの期限をチェックします。
last_days 変数で指定したしきい値を切っていたら、通知します。
自動更新の証明書なら(RenewalEligibility が ELIGIBLE)、通知の対象外にします。
これにより、メンテナンスフリーで、かつ、必要十分な通知を行うことができます。
さらに通知内容には、ELBのARN、リスナーのARN、ドメイン名を含めることでパッと分かるようにします。

コード

Pythonで記述します。

import boto3
import json
from datetime import datetime, timedelta

elbv2 = boto3.client('elbv2')

# 期限切れまでの残り日数のしきい値
last_days = 30

# ACMリスト
acms = boto3.client('acm').list_certificates()['CertificateSummaryList']

# ELB(elbv2)リスト
elbs = elbv2.describe_load_balancers()['LoadBalancers']

# 期限切れが近いかをチェック
# 自動更新の対象ではなく かつ 期限切れ近いなら引数をそのまま返す
def select_expiration_warning(filtered):
    if not filtered:
        return None
    if filtered[0]['RenewalEligibility'] == 'ELIGIBLE':
        return None
    if filtered[0]['NotAfter'].replace(tzinfo=None) - datetime.now() > timedelta(days=last_days):
        return None
    return filtered

def lambda_handler(event, context):

    expiration_warnings = []

    # ELB, Listener, Certification の組み合わせリスト生成
    elbs_listeners_certs = []
    for elb_arn in map(lambda x: x['LoadBalancerArn'], elbs):
        for listener in elbv2.describe_listeners(LoadBalancerArn=elb_arn)['Listeners']:
            for cert in listener.get('Certificates', []):
                elbs_listeners_certs.append( (elb_arn, listener, cert ) )

    # 組み合わせリストの単位で期限切れが近いかをチェック
    for e_l_c in elbs_listeners_certs:
        cert_filtered = list(filter(lambda x: x['CertificateArn'] == e_l_c[2]['CertificateArn'], acms))
        if select_expiration_warning(cert_filtered):
            # warning対象をリストに格納
            expiration_warnings.append({
                'LoadBalancer': e_l_c[0],
                'ListenerArn': e_l_c[1]['ListenerArn'],
                'CertificateArn': cert_filtered[0]['CertificateArn'],
                'NotAfter': cert_filtered[0]['NotAfter'].isoformat(),
                'DomainName': cert_filtered[0]['DomainName']
            })

    if len(expiration_warnings) > 0:
        boto3.client('sns').publish(
            TopicArn = 'arn:aws:sns:REGION_NAME:ACCOUNT_ID:TOPIC_NAME',
            Message  = json.dumps(expiration_warnings, indent=4)
        )

    return {}

このコードで書き換えが必須の箇所は、
arn:aws:sns:REGION_NAME:ACCOUNT_ID:TOPIC_NAME の箇所です。
ここは各自のSNSトピックを作成しておき、そのARNを指定します。

Lambda関数の設定

  • ランタイム
    • Python 3.10
  • 実行ロールのIAMポリシー
    • AWSLambdaBasicExecutionRole
    • AWSCertificateManagerReadOnly
    • ElasticLoadBalancingReadOnly
    • AmazonSNSFullAccess (※sns.publishが利用できるポリシーに絞ってもOK)
  • タイムアウト
    • 3秒 → 3 + <リスナー数 × 0.5>秒 程度 (少し余裕を持たせておく)

last_days をチューニングする必要があれば、

import os
last_days = int(os.getenv('last_days'))

としてもいいでしょう。

EventBridgeの設定

EventBridgeスケジューラで、Lambdaを1日1回実行するように設定します。

CloudWatchアラームを使うたびに思う

CloudWatchアラームは、1つ1つのリソースに設定するので、運用する中でリソースが増加・減少するたびに、CloudWatchアラームも(ある程度は自動化できるとしても)メンテナンスが必要だなぁと感じています。
Lambda関数で動的にリソースのリストを取得することで、リソースが増加・減少してもメンテフリーにできる方が楽!というケースがところどころあるんですよね。
適材適所で使っていきましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0