AWS LambdaにSSL証明書の有効期限を確認して通知するSlackボットを作成した


## はじめに

現在、私がプライベートで持っているサーバのSSL証明書は `Let's Encrypt` で設定します。
`Let's Encrypt` は証明書の期限についてはメールでお知らせしてくれますが、メールだと他のメールと埋もれて確認漏れが起こっていたりしていました。

**自動で更新したらええやん！** というようなご指摘はあるかと思いますが、今回はそれは無視で

## 環境

### ローカル

- Python 3.6
- macOS Sierra 10.12.4

### AWS

- Lambda
  - Python 3.6
- CloudWatch

## リポジトリ

https://github.com/nnsnodnb/slackbot_ssl_expiration

## 準備

`Apps & integrations` で `Bots` を設定しておいてください

![Bots___ひやかしプロジェクト_Slack.png](https://qiita-image-store.s3.amazonaws.com/0/97280/a2353d18-dced-17f7-8986-f24326d68f7e.png "Bots___ひやかしプロジェクト_Slack.png")


## ライブラリ

```:requirements.txt
appdirs==1.4.3
packaging==16.8
pyparsing==2.2.0
requests==2.13.0
six==1.10.0
slacker==0.9.42
```

## サンプルソース

```python:bot.py
from slacker import Slacker
import datetime
import socket
import ssl
import slack_settings # 同ディレクトリに slack_settings.py を配置


slack = Slacker(slack_settings.SLACK_API_TOKEN)


def ssl_valid_time_remaining(hostname):
    expires = ssl_expiry_datetime(hostname)
    return expires - datetime.datetime.utcnow()


def ssl_expires_in(hostname, buffer_days=7):  # 7日前で期限分岐
    remaining = ssl_valid_time_remaining(hostname)
    if remaining < datetime.timedelta(days=0):
        raise AlreadyExpired("Cert expired %s days ago" % remaining.days)
    elif remaining < datetime.timedelta(days=buffer_days):
        return True
    else:
        return False


def ssl_expiry_datetime(hostname):
    ssl_date_fmt = r'%b %d %H:%M:%S %Y %Z'
    context = ssl.create_default_context()

    conn = context.wrap_socket(
            socket.socket(socket.AF_INET),
            server_hostname=hostname,
    )

    conn.settimeout(3.0)
    conn.connect((hostname, 443))
    ssl_info = conn.getpeercert()
    return datetime.datetime.strptime(ssl_info['notAfter'], ssl_date_fmt)  # ssl_info['notAfter'] が証明書の期限


def post_slack(hostname):
    message = '@channel https://' + hostname + ' '
    if ssl_expires_in(hostname):
        message += 'そろそろヤバイ'
    else:
        message += 'はまだ期限内' 

    # ここら辺のメソッドはslackerパッケージを使用
    slack.chat.post_message(
            '#expiration',
            message,
            as_user=True,
            link_names=True
    )


def execute(event, context):
    post_slack('<YOUR DOMAIN>')
```

```python:slack_setting.py
SLACK_API_TOKEN = ''
```

### ローカルで実行

```console
$ python bot.py
```

![スクリーンショット_2017-06-07_17_21_48.png](https://qiita-image-store.s3.amazonaws.com/0/97280/1ccde198-a9e2-33eb-cbbd-564640f52ce0.png "スクリーンショット_2017-06-07_17_21_48.png")

今回もまた `渡辺曜` さんにお知らせ係を担当していただきました。

## Lambdaの設定

![Lambda_Management_Console.png](https://qiita-image-store.s3.amazonaws.com/0/97280/9695f198-32f9-085e-b100-79e1d8f97c92.png "Lambda_Management_Console.png")

1. トリガーの設定
![Lambda_Management_Console.png](https://qiita-image-store.s3.amazonaws.com/0/97280/32d58ea8-4dff-d0fa-54c1-dadce0e72bf4.png "Lambda_Management_Console.png")
各自好きなように設定。自分は `cron` で月〜金のAM2時に通知される設定を選択しています。
2. 関数の名前は適当に入力
3. `ランタイム` は `Python 3.6` を選択
4. ソースコードアップロードは下で説明
5. `ハンドラ` は `bot.execute` を入力
6. ロール等は各自設定

### 外部ライブラリの反映

ローカル環境では以下コマンド等でなんとかなりますが、

```console:ローカル環境
$ pip install -r requirements.txt
```

AWS Lambda上ではライブラリが認識されないので外部ライブラリごとアップロードする必要があります。

```console:外部ライブラリをプロジェクトディレクトリに保存
$ pip install <LIBRARY_NAME> -t .
```

という感じにやればプロジェクトディレクトリに保存されます。
しかし、1つ1つやるのが面倒なので私は以下でしました。

```console
$ pip freeze > requirements.txt  # requirements.txt がなければ
$ pip install -r requirements.txt -t .
```

あとはzipに圧縮してアップロードする

```console
$ zip -r bot.zip *  # bot.zip は好きな名前
```

プロジェクトディレクトリに `bot.zip` ができているので `.ZIPファイルをアップロード` を選択して `bot.zip` をアップロードする

![スクリーンショット 2017-06-07 17.41.57.png](https://qiita-image-store.s3.amazonaws.com/0/97280/2b44491f-d850-1fe0-307b-9db8210cee43.png "スクリーンショット 2017-06-07 17.41.57.png")

うまく設定できれいれば設定した時間にSlackに通知がくるはず！
私の環境では先述通り `月〜金 AM2時` に渡辺曜さんがお知らせしてくれます！！