IBM Cloud Code Engineを利用してCISの送信元IPアドレス変更をメール通知する

# はじめに

IBM Cloud CodeEngineを利用してCISの送信元IPアドレス変更を定期的にメール通知する方法を紹介します。

コンテナ実行環境サービスであるサーバーレスCode Engineのジョブを用いて定期実行する例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/8dd55b6b-a146-8983-5b1c-e11e7b435eca.png)


スクリプト(bash)で実行した例を下記記事で紹介しておりますのでご参照ください。

IBM Cloud CISの送信元IPアドレス変更を検知する
https://qiita.com/aktech/items/f6cf0a9771c26c117825

IBM Cloud Functionsを利用した例を下記記事で紹介していますが、2023/10/26 Cloud Functionsは非推奨の発表がされています。

IBM Cloud Functionsを利用してCISの送信元IPアドレス変更をSlackへ通知する
https://qiita.com/aktech/items/85334f23b4e071249ad8

IBM Cloud Functions 非推奨の概要
https://cloud.ibm.com/docs/openwhisk?topic=openwhisk-dep-overview

# 主な手順

[1. Code Engineにデプロイするコンテナのアクションの作成](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#1-code-engine%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E4%BD%9C%E6%88%90)
[2. GitHubへPush](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#2-github%E3%81%B8push)
[3. Code Engine用のレジストリの準備](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#3-code-engine%E7%94%A8%E3%81%AE%E3%83%AC%E3%82%B8%E3%82%B9%E3%83%88%E3%83%AA%E3%81%AE%E6%BA%96%E5%82%99)
[4. Code Engine プロジェクトの作成](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#4-code-engine-%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
[5. Code Engine シークレットの作成](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#5-code-engine-%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
[6. Code Engine ジョブの作成](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#6-code-engine-%E3%82%B8%E3%83%A7%E3%83%96%E3%81%AE%E4%BD%9C%E6%88%90)
[7. Code Engine ジョブの実行](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#7-code-engine-%E3%82%B8%E3%83%A7%E3%83%96%E3%81%AE%E5%AE%9F%E8%A1%8C)
[8. Code Engine ログ監視](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#8-code-engine-%E3%83%AD%E3%82%B0%E7%9B%A3%E8%A6%96)
[9. Code Engine イベント設定（定期実行）](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#9-code-engine-%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E8%A8%AD%E5%AE%9A%E5%AE%9A%E6%9C%9F%E5%AE%9F%E8%A1%8C)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/a5670fe0-5746-55ae-58c0-5bead5a50f49.png)



# 1. Code Engineにデプロイするコンテナのアクションの作成

今回は実行環境にPythonを利用

## 用意する物
・Dockerfile
・cis-ip-check.py

```console:Dockerfile
FROM python:slim
ADD ./cis-ip-check.py /root/cis-ip-check.py
WORKDIR /root
RUN apt update
RUN pip install sdcclient
RUN pip install sendgrid
CMD ["python","/root/cis-ip-check.py"]
```


```console:cis-ip-check.py
import os
import json
import requests
from requests.exceptions import Timeout
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail

# Secect the value (0 = False, 1 = True)
MAIL_CUSTOM = os.getenv('MAIL_CUSTOM')
MAIL_SEND_EVERY_TIME = os.getenv('MAIL_SEND_EVERY_TIME')
MAIL_TO_ADDRESS_HIDE = os.getenv('MAIL_TO_ADDRESS_HIDE')

if MAIL_CUSTOM == "1":
   MAIL_SUBJECT = os.getenv('MAIL_SUBJECT')
   MAIL_BODY_HTML = os.getenv('MAIL_BODY_HTML')
   MAIL_BODY_TEXT = os.getenv('MAIL_BODY_TEXT')
   MAIL_SUBJECT_NOT_CHANGED = os.getenv('MAIL_SUBJECT_NOT_CHANGED')
   MAIL_BODY_HTML_NOT_CHANGED = os.getenv('MAIL_BODY_HTML_NOT_CHANGED')
   MAIL_BODY_TEXT_NOT_CHANGED = os.getenv('MAIL_BODY_TEXT_NOT_CHANGED')
else:
   MAIL_SUBJECT = "CIS IPS Changed."
   MAIL_BODY_HTML = "<strong>CIS IPS has been changed.</strong><br>Please check your access-lists."
   MAIL_BODY_TEXT = "CIS IPS has been changed.\nPlease check your access-lists.\nThis is a text mail."
   MAIL_SUBJECT_NOT_CHANGED = "CIS IPS not Changed."
   MAIL_BODY_HTML_NOT_CHANGED = "This is a user-edited mail."
   MAIL_BODY_TEXT_NOT_CHANGED = "This is a user-edited mail.\nThis is a text mail."

if MAIL_TO_ADDRESS_HIDE == "1":
   address_hide = True
else:
   address_hide = False

MAIL_ADDRESS_TO = [m.strip() for m in os.getenv('MAIL_ADDRESS_TO').split(',')]
print(MAIL_ADDRESS_TO)

print("MAIL_ADDRESS_FROM : ",os.getenv('MAIL_ADDRESS_FROM'))
print("MAIL_ADDRESS_TO : ",os.getenv('MAIL_ADDRESS_TO'))

print("MAIL_CUSTOM : ",MAIL_CUSTOM)
print("MAIL_SEND_EVERY_TIME : ",MAIL_SEND_EVERY_TIME)
print("MAIL_TO_ADDRESS_HIDE : ",MAIL_TO_ADDRESS_HIDE,address_hide)
   
message = Mail(
    from_email=os.getenv('MAIL_ADDRESS_FROM'),
    to_emails=MAIL_ADDRESS_TO,
    subject=MAIL_SUBJECT,
    html_content=MAIL_BODY_HTML,
    plain_text_content=MAIL_BODY_TEXT,
    is_multiple=address_hide)
    
message_not_changed = Mail(
    from_email=os.getenv('MAIL_ADDRESS_FROM'),
    to_emails=MAIL_ADDRESS_TO,
    subject=MAIL_SUBJECT_NOT_CHANGED,
    html_content=MAIL_BODY_HTML_NOT_CHANGED,
    plain_text_content=MAIL_BODY_TEXT_NOT_CHANGED,
    is_multiple=address_hide)

try:
  url = "https://api.cis.cloud.ibm.com/v1/ips"
  r = requests.get(url, timeout=3.0)
  
  if r.status_code != 200:
     print("status_code from cis-ips was not 200.")
  
  elif r.json()["result"]["etag"] == os.getenv('ETAG'):
     print("There is no difference.")
     if MAIL_SEND_EVERY_TIME == "1":
         sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY'))
         response = sg.send(message_not_changed)
         print(response.status_code)
         print(response.body)
         print(response.headers)
  
  else:
     print("There is difference. CIS IPS has been changed.")
     sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY'))
     response = sg.send(message)
     print(response.status_code)
     print(response.body)
     print(response.headers)
  
except Timeout:
  print("timeout.")
  pass
```

環境変数はCode Engineの[シークレット](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88 "シークレット")として登録

## ジョブ動作概要
・CIS (Cloudflare) の送信元アドレス情報を公開しているURL(API)へアクセス
・応答のETAGが異なる場合は変更ありと判断
・変更がある場合にメール送付
・オプション指定(シークレット)により変更がない場合でもメール送付
・変更がある場合のメール内容はカスタマイズ可能(Subject/Bodyはシークレットに事前登録)
・変更がない場合のメール内容はカスタマイズ可能(Subject/Bodyはシークレットに事前登録)
・宛先(TO)は複数指定可能
・オプション指定(シークレット)により複数指定した場合に他アドレスを隠す
・コードの編集は行わず、Code Engine シークレット入力によりオプション変更
・URL(API)からの応答がTimeout(3秒)した場合は"timeout"という文字列をログ出力
 （※Timeout時に再送したり、メール送付を希望の場合はコード修正をご検討ください）


# 2. GitHubへPush

上記ファイルをGitHubへpushする（手順は省略）

GitHub URLに公開(public)しています
https://github.com/akd8/ibmcloud-cis-ip-check.git


# 3. Code Engine用のレジストリの準備

Code Engine用にContainer Registoryの名前空間を作成する

| 項目 | 入力内容 |
|--|----------|
| リソースグループ | rg-xxx |
| 名前 | cr-code-engine |


# 4. Code Engine プロジェクトの作成

| 項目 | 入力内容 |
|--|----------|
| ロケーション | 東京(jp-tok)<br>大阪(jp-osa) |
| リソースグループ | rg-xxx |
| 名前 | CodeEngine |


# 5. Code Engine シークレットの作成

プロジェクト > シークレットおよびConfigmap > 作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/5ea6e6f1-f3ba-3e57-e68e-c4a43af5956e.png)

| 項目 | 入力内容 |
|--|----------|
| タイプ | シークレット |
| 名前 | cisipcheck |


## シークレット

| キー | 値(入力例) | 説明 |
|-----------|----------|----------|
| ETAG | 38f79d050aa027e3be3865e495dcc9bc | 最新のETAGを入力(変更があれば更新する) |
| SENDGRID_API_KEY |  | Sendgridを利用するユーザのAPI KEY<br>API KEYの入手方法は[参考URL](https://qiita.com/aktech/private/fc034d84b5fd631fb9cf#%E5%8F%82%E8%80%83url)へ |
| MAIL_ADDRESS_FROM | xxx＠test.com | 送信元アドレスを指定(Sendgridを利用するユーザメールアドレス) |
| MAIL_ADDRESS_TO | aaa＠test.com,bbb＠test.com,ccc＠test.com | 複数宛先を指定する場合は「,」区切りで指定 |
| MAIL_CUSTOM | 0 or 1 | 1の場合はシークレットに設定したSubject/Bodyを応答する |
| MAIL_SEND_EVERY_TIME | 0 or 1 | 1の場合はETAGに変更がなくともメールする |
| MAIL_TO_ADDRESS_HIDE | 0 or 1 | 1の場合はTOに複数指定しても送信時に他のアドレスを隠す |
| MAIL_SUBJECT | CIS IPS Changed. | メールタイトル文面 |
| MAIL_BODY_HTML | This is a user-edited mail.&lt;br&gt;You can edit this mail. | メール文面(HTML形式) 
| MAIL_BODY_TEXT | This is a user-edited mail.\nYou can edit this mail.\nThis is a text mail.| メール文章(TEXT形式) |
| MAIL_SUBJECT_NOT_CHANGED | CIS IPS not Changed. | ETAGに変更がない場合のメールタイトル文面 |
| MAIL_BODY_HTML_NOT_CHANGED |  | ETAGに変更がない場合のメール文面(HTML形式) |
| MAIL_BODY_TEXT_NOT_CHANGED |  | ETAGに変更がない場合のメール文章(TEXT形式) |


# 6. Code Engine ジョブの作成

## 一般

| 項目 | 入力内容 |
|--|----------|
| 名前 | ce-job-cis-ip-check |


## 実行するコードの選択

| 項目 | 入力内容 |
|--|----------|
| 実行するコードの選択 | ソース・コード |
| コード・リポジトリー URL | https://github.com/akd8/ibmcloud-cis-ip-check.git |
| コード・リポジトリー・アクセス | なし |
| ブランチ名 | main |
| コンテキスト・ディレクトリー | 指定なし(ブランク) | 
| Dockerfile | Dockerfile |
| タイムアウト | 10m | 
| ブランチ名 | main |
| ビルド用のリソース | 小 (0.5 vCPU / 2GB) | 
| レジストリー・サーバー | private.jp1.icr.io(IBM レジストリー東京) <BR> もしくは、private.jp2.icr.io(IBM レジストリー大阪)| 
| レジストリー・アクセス・シークレット | ce-cisip-ras |
| 名前空間 | cr-code-engine | 
| リポジトリー (イメージ名) | codeengine-ce1.git-xx(名前は任意) |
| ビルド用のリソース | 小 (0.5 vCPU / 2GB) | 

## リソースおよびスケーリング

| 項目 | 入力内容 |
|--|----------|
| インスタンス数 | 1 |
| インスタインス・リソース | 0.125個のvCPU / 0.25 GB |
| 一時ストレージ(GB) | 0.4 |
| ジョブの再試行回数 | 3 |
| ジョブ・タイムアウト (秒) | 7200 |

## 環境変数（オプション）

| 項目 | 入力内容 |
|--|----------|
| シークレット | cisipcheck |

上記値を入力して「作成」を実施すると、githubのコンテナ・アクション情報を元にしてコンテナイメージがレジストリに作成され、ビルドが完了する。

# 7. Code Engine ジョブの実行

作成したジョブ画面右上の「ジョブの実行依頼」より単発でのジョブ実行が可能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/f4402321-3bf2-b5fb-426f-8ef3a2f13ee8.png)

## ジョブの実行依頼

| 項目 | 入力内容 |
|--|----------|
| インスタンスの数 | 1 |

# 8. Code Engine ログ監視

ジョブ画面の「ロギングの起動」より、IBM Log Analysis with LogDNA (事前に要作成) を起動する

自動的に「_platform:'Code Engine' label.Project:'CodeEngine' app:ce-job-cis-ip-check」(例)のようなフィルタリングされた画面が表示される。

```
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 ['aaa＠test.com', 'bbb＠test.com', 'ccc＠test.com']
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 MAIL_ADDRESS_FROM :  xxx＠test.com
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 MAIL_ADDRESS_TO :  aaa＠test.com, bbb＠test.com, ccc＠test.com
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 MAIL_CUSTOM :  0
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 MAIL_SEND_EVERY_TIME :  0
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 MAIL_TO_ADDRESS_HIDE :  0 False
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 There is no difference.
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 202
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 b''
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 Server: nginx
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 Date: Thu, 16 Nov 2023 04:31:15 GMT
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 Content-Length: 0
Nov 16 13:31:25 Code Engine ce-job-cis-ip-check-jobrun-12xyz-0-0 Connection: close
```



# 9. Code Engine イベント設定（定期実行）

## イベント・サブスクリプションの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/546262d9-26d0-81ef-9fc4-0ab3375e363c.png)

定期実行する間隔を指定する（下記は毎分の実行の例）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/a6ba7050-dca9-8869-46ca-4c746a6aeb7a.png)

作成したジョブを選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329464/5b396cf7-c941-69c5-6495-b8206c239a8c.png)

実行されたジョブの時刻と状況(成功/失敗)は、ジョブの「ジョブ実行」タブから確認可能


# 参考URL

IBM Cloudのメール配信サービス（SendGrid）を使ってNode.js、Pythonからメール配信してみた！
https://qiita.com/strada/items/f4e6536b6e973b5ca2e9

Twilio SendGridとPythonでメールを一斉送信する方法
https://sendgrid.kke.co.jp/blog/?p=12510