Amazon SNS に設定されている iOS アプリの APNs 証明書の有効期限についてアラートを通知する

# ０．はじめに

![unnamed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161939/9dd2b630-35ce-de02-5137-6e644442f44a.png)

iOS アプリの通知について、[Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) を利用しており、以下の記事を参考にして、先日 iOS アプリの APNs 証明書の更新したんですが…、

- [iOS アプリの Certificate \(証明書\) を更新後、Bitrise の Code Signing の設定を更新する \- Qiita](https://qiita.com/kusokamayarou/items/1f418e54b610e6459112)

[Bitrise](https://www.bitrise.io/) の設定は更新したものの、[Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) を忘れてしまって、通知が届かなくなるというトラブルが発生してしいました…。

😱😱😱

iOS アプリの APNs 証明書の有効期限は、[Apple](https://www.apple.com/jp/) から警告メールが届くので良いんですが、[Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) の設定状況についても、検知する仕組みが必要だな、と思い、作ってみました。

# １． 全体の構成

構成としては、こんな感じです。

![Qiita_03_960x320.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161939/88807751-cfd6-997f-0e01-6fa61dd7f86d.jpeg)

1. [Amazon CloudWatch Events](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html) から、日時で [AWS Lambda](https://aws.amazon.com/jp/lambda/) を実行。
2. [AWS Lambda](https://aws.amazon.com/jp/lambda/) で、[Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) に登録されている APNs プラットフォームアプリケーションの有効期限をチェック。
3. 有効期限に近いものが確認された場合、[Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) のトピックにパブリッシュして、メールを通知。

# ２． [Python](https://www.python.jp/) @ [AWS Lambda](https://aws.amazon.com/jp/lambda/) のコード

```python
#!/usr/bin/env python
# -*- coding: utf-8-unix; -*-
"""AWS Lambda Function - Maintenance SNS Check to Expire
"""
from __future__ import print_function

import os
import math
import boto3

# 「Python 3 で少しだけ便利になった datetime の新機能 - Qiita」
# <https://qiita.com/methane/items/d7ac3c9af5a2c659bc51>
from datetime import datetime, timezone, timedelta
TimeZone = timezone(timedelta(hours=+9), 'JST')

ClientSNS = boto3.client('sns')

# ------------------------------------------------------------------------------
# Function
# ------------------------------------------------------------------------------

# 「【Python】Lambdaからメールを送信 | ハックノート」
# https://hacknote.jp/archives/35679/
def MailSend(prmSubject, prmMessage):
    result = -1
    try:
        response = ClientSNS.publish(
            TopicArn = '[送信先トピックのARN]',
            Message = prmMessage,
            Subject = prmSubject
        )
    except Exception as e:
        print(e)
        result = -1
        raise
    else:
        pass
    finally:
        pass
    return result

def lambda_handler(event, context):
    try:
        print(event)
        print(context.__dict__)
        now_datetime = datetime.now(TimeZone)
        print(now_datetime)
        logdata = ""
        list_platform_applications_paginator = ClientSNS.get_paginator('list_platform_applications')
        #print(list_platform_applications_paginator)
        list_platform_applications_pageiterator = list_platform_applications_paginator.paginate()
        #print(list_platform_applications_pageiterator)
        for list_platform_applications_page in list_platform_applications_pageiterator:
            #print(list_platform_applications_page)
            platform_applications = list_platform_applications_page.get('PlatformApplications', [])
            for platform_application in platform_applications:
                print(platform_application)
                platform_application_arn = platform_application.get('PlatformApplicationArn', '')
                platform_application_arn_split = platform_application_arn.split('/')
                #print(platform_application_arn_split)
                if len(platform_application_arn_split) < 3:
                    continue
                platform_application_type = platform_application_arn_split[1]
                #print(platform_application_type)
                if platform_application_type != 'APNS':
                    continue
                platform_application_attributes = platform_application.get('Attributes', {})
                platform_application_enabled = platform_application_attributes.get('Enabled', '')
                #print(platform_application_enabled)
                if platform_application_enabled != 'true':
                    continue
                platform_application_expiration_date = platform_application_attributes.get('AppleCertificateExpirationDate', '').replace('Z', '+00:00')
                print(platform_application_expiration_date)
                platform_application_expiration_date_timestamp = 0
                try:
                    platform_application_expiration_date_timestamp = datetime.fromisoformat(platform_application_expiration_date).timestamp()
                except Exception as e:
                    print(e)
                #print(platform_application_expiration_date_timestamp)
                now_datetime_timestamp = now_datetime.timestamp()
                #print(now_datetime_timestamp)
                diff_timestamp = platform_application_expiration_date_timestamp - now_datetime_timestamp
                diff_day = math.floor(diff_timestamp/(60*60*24))
                print(diff_day)
                if diff_day < 1:
                    pass
                elif diff_day in [30,60,90]:
                    pass
                else:
                    continue
                logdata += """\
- Last %d Days !!! - %s
""" % (diff_day, platform_application_arn)
        #
        #print(logdata)
        if len(logdata) > 0:
            subject = """[%s] SNS Check to Expire !!!""" % (now_datetime.isoformat(' ', 'seconds'))
            msg = """\
----

【有効期限注意】

%s
""" % (logdata)
            print(subject)
            print(msg)
            MailSend(subject, msg)
    except Exception as e:
        print(e)
    else:
        pass
    finally:
        pass
    #
    return "normal end"

```

このコードでは、警告の判断は、以下としていますが、状況に合わせて変更して頂ければよろしいかと。

- 有効期限が 14 日以内
- 有効期限が、90 or 60 or 30 日前

[AWS Lambda](https://aws.amazon.com/jp/lambda/) に割り当てる [IAM Role](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html) については、こんな感じです。

- [Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) からのプラットフォームアプリケーションの情報の取得
    - sns:ListPlatformApplications
- [Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) のトピックへのパブリッシュ
    - sns:Publish
- [AWS Lambda](https://aws.amazon.com/jp/lambda/) の [CloudWatch Logs](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) へのログの出力
    - logs:CreateLogGroup
    - logs:CreateLogStream
    - logs:PutLogEvents

# ９９．ハマりポイント

- 今回は、特にハマることはありませんでしたが…

- ただ、よくよく考えてみると、[Bitrise](https://www.bitrise.io/) にしても、[Amazon SNS](https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) にしても、[Apple Developer](https://developer.apple.com/account/) で証明書を更新したら、自動でそちらも更新されると非常に便利だなぁ、と思ったりもしますので、先々その辺の自動化の仕組みも検討しても良いかもしれません。

- それによっては、使うサービスを変えても良いかなとも思ったりします。
    - [Certificates \| Apple Developer Documentation](https://developer.apple.com/documentation/appstoreconnectapi/certificates) とか？
    - [GitHub Actions](https://github.co.jp/features/actions) とか？
    - この辺を使えば、良い感じに出来るかな？？？
    - [Bitrise](https://www.bitrise.io/) 側から、全部出来る機能があったりすると良いんだけどなぁ…

![unnamed.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161939/38279370-ff50-563e-7d71-93c892079ab9.gif)

# ＸＸ．まとめ

ご参考になれば ♪♪♪

👋👋👋

## 【2021.05.07 追記】

因みに、証明書の更新手順は、以下。

- [AWS SNSのiOS向けPush通知\(APNs\)証明書の更新手順 \- Qiita](https://qiita.com/b_a_a_d_o/items/e3bf9cd52b6cd9252088)
- [\[2019年度版\]AWS SNSでiOSのプッシュ通知 設定手順まとめ \| カフーブログ](https://kahoo.blog/howto-aws-sns-ios-push-notification/)
- [iOS アプリをブルブルさせてみる \- Qiita](https://qiita.com/kusokamayarou/items/84523034ef16bf32a33c)