-
タグ
タグ
- アーキテクト
- アジャイル開発
- アプリ開発
- インシデントレスポンス
- イベントレポート
- カスタマーストーリー
- カルチャー
- 官民学・業界連携
- 企業市民活動
- クラウド
- クラウドインテグレーション
- クラブ活動
- コーポレート
- 広報・マーケティング
- 攻撃者グループ
- 子育て、生活
- サイバー救急センター
- サイバー救急センターレポート
- サイバー攻撃
- サイバー犯罪
- サイバー・グリッド・ジャパン
- サプライチェーンリスク
- システム開発
- 趣味
- 障がい者採用
- 初心者向け
- 白浜シンポジウム
- 情シス向け
- 情報モラル
- 情報漏えい対策
- 人材開発・教育
- 診断30周年
- スレットインテリジェンス
- すごうで
- セキュリティ
- セキュリティ診断
- セキュリティ診断レポート
- 脆弱性
- 脆弱性管理
- ゼロトラスト
- 対談
- テレワーク
- データベース
- デジタルアイデンティティ
- 働き方改革
- 標的型攻撃
- プラス・セキュリティ人材
- モバイルアプリ
- ライター紹介
- ラックセキュリティアカデミー
- ランサムウェア
- リモートデスクトップ
- AI
- ASM
- CIS Controls
- CODE BLUE
- CTF
- CYBER GRID JOURNAL
- CYBER GRID VIEW
- DevSecOps
- DX
- EC
- EDR
- FalconNest
- IoT
- IR
- JSOC
- JSOC INSIGHT
- LAC Security Insight
- OWASP
- SASE
- Tech Crawling
- XDR
クラウドサービス部の石田です。
OCI Functionsサービス(以下、Functions)で、イベント通知のメール成型をご紹介する連載の第3回です。前回の記事では、OCI Functionsサービスをイベント・サービスから呼び出すところまで紹介しました。今回は、イベント・サービスから呼び出されたFunctions内でイベント内容を成型してメールを作成し、OCIの通知サービスを使って送信する方法を説明します。
サービスの紹介と設定方法
OCI環境からメールを送信するために、追加の設定や他のOCIのサービスを利用する必要があります。この章では、これらの設定とサービスについて説明します。まずは、メール送信に利用する通知サービスを説明し設定します。
通知サービスとは
通知サービスは、OCIの各種サービスからユーザへメッセージを送信する場合に使用します。サブスクリプションと呼ばれるメッセージの通知方法は、以下のものを1つ以上選べます。
- 電子メール
- Functions
- HTTPS(カスタムURL)
- PagerDuty
- Slack
- SMS
PagerDutyは、サードパーティーのインシデント管理サービスです※1。SMSは対応しているサービスに制限があり、例えばここで使用するFunctionsは対応していないためメッセージを送れません。他にも対応していないサービスがあるので、使用する際には注意してください。
※1 PagerDuty|インシデント管理プラットフォーム|PagerDuty株式会社
このサブスクリプションをトピックと呼ばれる通知の定義に1つ以上紐づけておくと、OCIサービスがトピックにメッセージを送ることによって、サブスクリプションに設定された通知方法でユーザにメッセージが届きます。トピックにメッセージを送れるサービスは以下のものがあります。
- アラーム
- お知らせサブスクリプション
- イベント・ルール
- コネクタ
- コンテキスト通知
- 直接公開
今回は、ファンクションのコードから直接公開して電子メールのサブスクリプションを使用します。その他のサービスについての説明は割愛しますので、必要に応じてOCIのドキュメントを参照ください※2。
※2 通知の概要|Oracle Cloud Infrastructureドキュメント
通知サービスの設定
それでは、通知サービスを設定していきます。通知サービスの画面へ遷移するには、OCIメニューの「開発者サービス」、「アプリケーション統合」、「通知」の順で選択します。切り替わった画面で「トピックの作成」ボタンを押すと、通知トピックの作成画面になります。

ここで設定が必須なのは名前だけです。今回は「test_topic」にしましょう。

すると、最初のトピックのリスト画面に戻ります。ここで追加設定するので、今設定した「test_topic」をクリックします。以下の画面が表示されたらサブスクリプションを設定するので、「サブスクリプションの作成」ボタンを押してください。

今回は電子メールで通知するので、「プロトコル」は「電子メール」にし、「電子メール」に自分のメールアドレスを入れて、「作成」ボタンを押してください。

ここで、サブスクリプションに最初に登録されたメールアドレスには、以下のようなアドレスの到達性を確認するメールが送信されます。

このメールの、「Confirm subscription」のリンクをクリックして確認ページにアクセスすることによってサブスクリプションは有効になります。それまでは「状態」が「Pending」のままで利用はできません。

ちなみに、サブスクリプションに追加される2人目以降は登録されたという内容だけのメールで、「Confirm subscription」のリンクが無い点にご注意ください。
これで通知サービスの設定ができましたが、最後にこの通知トピックのOCIDを記録しておきます(トピックの方です。サブスクリプションの方ではないので気をつけてください)。あとでファンクションから通知を呼び出す際に使用します。

OCI SDKとは
Functionsを含むプログラムから、通知をはじめとするOCIリソース・サービスの各種操作をするためには、OCI SDKと呼ばれる専用のモジュール・ライブラリの使用が一般的です。インフラ構築や運用の際によく使用されるOCI CLIも、このOCI SDKを用いてPythonで実装されています。
JAVA、Python、TypeScript and JavaScript、.NET、Go、Ruby用のものがありFunctionsで使える言語は一通り対応しているので、FunctionsからOCIリソース操作をする場合はこのOCI SDKを使うのが一般的です。今回もこのOCI SDKを使用していきます。
Pythonでの外部モジュールの利用
OCI SDKはPython向けのものはありますが、Pythonの標準ライブラリには含まれていません。また、Functionsで使用するPython環境は標準的なライブラリしか用意されていないので、そのままではOCI SDKは利用できません。このような場合に備えて、Dockerには非標準の外部モジュールをイメージに組み込む機能が用意されています。
この機能を利用するには、「requirements.txt」というファイルを作成し、使いたいPythonの外部モジュールの名前を書いておきます。すると、ファンクションデプロイの中で必要なモジュールが外部※3から自動でダウンロードされてDockerイメージに同梱されます。最初に用意されるサンプルコードでもこの仕組みは使われており、実際サンプルコードのファイル構成を見ると「requirements.txt」ファイルが存在しているのがわかります。そのため、この既存のファイルに手を加えていけば、必要なモジュールを簡単に追加できます。実際の修正内容は、後ほどコードの修正の際にあわせて説明します。
※3 PyPI · The Python Package Index
リソースプリンシパル(権限の設定)
ファンクションからOCIリソースを操作するには、許可する権限の付与が必要です。その際に利用するのが、リソースプリンシパルという仕組みと動的グループです。
リソースプリンシパルとは、OCI CLIなどでよく使われるインスタンスプリンシパルのOCIサービス版になります。インスタンスを利用しないOCIサービス(FunctionsやAutonomous DBなど)からOCIに各種リクエストを送信する際の権限設定に使われる、IAMサービスの機能の1つです。ユーザに権限を与えるのではなくOCI上のリソースに権限を与えるという仕組みになり、別途権限設定用のユーザを用意する必要がないので推奨されています。
設定手順はインスタンスプリンシパルと同様で、以下の2ステップです。
- 1.動的グループの作成
- 2.作成した動的グループに、必要な権限を与えるポリシーを作成
動的グループは、リソースタイプ「fcfunc」に該当するものを含めるルールにします。これでFunctionsが動的グループに含まれます。必要に応じてコンパートメントやリソースID等で制限を追加してください。今回はコンパートメントの指定だけを行うことにして、以下のようなルールにします。「<ocid_of_compartment_where_functions_run>」の部分はファンクションが実行されるコンパートメントのOCIDに置き換えてください。
ALL {resource.type = 'fnfunc', resource.compartment.id = '<ocid_of_compartment_where_functions_run>'}
後は動的グループに通知(ons-topic)の利用を許可するポリシーを設定します。通知からメッセージを発行するには「use」以上が必要なので、以下のように設定します。「<dynamic_group_name>」は先に作成した動的グループの名前に、「<name_of_compartment_where_functions_run>」は通知が存在するコンパートメントの名前に、それぞれ置き換えてください。こちらも必要に応じて対象の制限を適宜加えてください。
Allow dynamic-group <dynamic_group_name> to use ons-topics in compartment <name_of_compartment_where_functions_run>
OCIコード・エディタの紹介
以上の環境準備が終わったら実際にコードを用意しますが、そのためにはクラウドシェル環境のファイルを編集できるテキストエディタが必要です。クラウドシェルから都度ファイルをダウンロードして修正し、再アップロードしても実現可能ですがちょっと手間です。そのような用途のため、OCIにはクラウドシェル環境のファイルを修正できる「コード・エディタ」という機能があるので、今回はそれを使用します。
まずはクラウドシェルを立ち上げたメニューにコード・エディタがあるので、それをクリックします。

すると、このようにクラウドシェルの画面を拡張する形でエディタ画面が表示されます。

マウスで操作できるので、「開く」メニューをクリックして目的のファイルを開けば編集できます。一般的なIDE環境の基本的な機能、Git連携や文法ハイライトや補完機能、ツールチップでの簡易ヘルプやタブによる複数ファイル編集画面切り替えなどが揃っているので、何らかのIDEの利用経験があればさほど迷うことなく使用できるのではないでしょうか。

クラウドシェルとコード・エディタの表示は、「アクション」メニューから縦分割の表示にしたりタブ切り替え表示にしたりできるので、お好みでご利用ください。ちなみにこのコード・エディタもTheiaというOpen-Sourceプロダクトを使用しているようです※4。
※4 Theia IDE - Open-Source Cloud and Desktop IDE
ファンクションから成形済みメールを送信する
それでは、実際にイベントを受けてその内容を成型し、通知経由でメールを送信するファンクションのコードを作っていきましょう。検証用として連載第2回で作成した、コンピュートインスタンスの起動・停止のイベントと、そこから呼び出されるファンクション「event-test」に手を加える形で進めます。
サンプルコード
ファンクションの機能としては、イベントの対象サーバ、対象サーバの所属するコンパートメント、発生時刻、操作内容(起動か停止か)をイベント内容から抽出して、メールを作成します。
まず、前述のように外部ライブラリであるOCI SDKを利用可能にするために、requirements.txtを設定します。OCI SDKのモジュール名は「oci」なので、コード・エディタを使ってファイルに「oci」と追記します。
requirements.txt
fdk>=0.1.86 oci
既存の記載内容のように必要バージョンを指定することもできますが、ここではモジュールの指定だけにしておきます。
次は、ファンクションのロジック本体のコードを記述する、func.pyを編集していきます。コード本体の説明は長くなるので要点だけにしますが、前述のようにリソースプリンシパルを使う場合は、get_resource_principals_signerを使ってsigner(認証済みの署名)を取得します。後はそのsignerを、使用するOCIリソースに対応したOCI SDKのクライアントに渡すだけで利用可能になります。通常は認証関係も含む各種設定を記載するconfigも、リソースプリンシパルを使う場合は特に指定する必要はないので、空のdictを渡します。その他OCI SDKの詳細は公式ドキュメント※5を参照ください。
※5 API Reference -- oci 2.149.2 documentation
イベントから受け取るJSONをPythonの辞書に変換するロジックは、既にサンプルコードに実装されています。後は、その辞書の内容を元にメールを作成し、OCI SDK経由で通知サービスに渡せば完了です。メールには、イベントが発生した「コンパートメント」「対象サーバ」「時刻」「操作内容」の4つの情報を成型して出力されるようにします。さらに、イベントの時刻はUTC表記固定なので、JST表記にする処理も追加しました。
また、連載第2回でファンクションのログを有効にしましたが、そこにメッセージを出力したい場合はloggingモジュールを使うのが一般的です。前述の通りファンクションでは実行状況が見えないので、こまめに出力した方が良いでしょう。今回は例外発生時とメール送信成功時にメッセージが出力されるようにしています。最終的にできたコードは以下になります。既存のfunc.pyの内容をこれに差し替えます。その際「<topic_to_send_message_ocid>」の部分は先ほど作成した通知トピックのOCIDに置き換えてください。
func.py
import io import json import logging from datetime import timedelta, datetime, timezone from fdk import response import oci topic_ocid = '<topic_to_send_message_ocid>' def handler(ctx, data: io.BytesIO = None): try: body = json.loads(data.getvalue()) body['eventTime'] = datetime.fromisoformat(body['eventTime']).astimezone( timezone(timedelta(hours=9))).isoformat(' ') mailbody = """インスタンスの操作を検知しました。 コンパートメント: {body[data][compartmentName]} 対象サーバ: {body[data][resourceName]} 時刻: {body[eventTime]} 操作内容:{body[data][additionalDetails][instanceActionType]} """.format(body=body) subject = "{body[data][resourceName]}の操作検知".format(body=body) except (Exception, ValueError, KeyError) as ex: logging.getLogger().error('error parsing json payload: ' + str(ex)) # 最小限のresponseを返して終了 return response.Response(ctx) try: # リソースプリンシパルによる署名情報を取得 signer = oci.auth.signers.get_resource_principals_signer() # 通知クライアントを生成 ons_client = oci.ons.NotificationDataPlaneClient( config={}, signer=signer) # メッセージ発行 publish_message_response = ons_client.publish_message( topic_id=topic_ocid, message_details=oci.ons.models.MessageDetails( body=mailbody, title=subject ) ) logging.getLogger().info('publishing result: ' + str(publish_message_response.data)) except (Exception) as ex: logging.getLogger().error('error publishing notification: ' + str(ex)) # 最小限のresponseを返して終了 return response.Response(ctx) # 特に出力はないので最小限のresponseを返す return response.Response(ctx)
各ファイルの修正が終わったら、コード・エディタから保存して、OCIコンソールのアプリケーションの作成手順の項番10、「fn -v deploy~」を実行します。これで、イベントからファンクションが呼び出され、そこから通知が送信される準備が整いました。
ファンクションのテスト
ファンクションの動作を確認するにあたり、実際のイベントを発生させるテストをする前に、クラウドシェル内でファンクションの単体テストを行うことをおすすめします。単体テストには、イベント・ルールを設定する際に取得しておいた、サンプルのイベントJSONを使います。
今回、サンプルJSONは「test.json」というファイルとしてクラウドシェル上に配置しています。それを「fn invoke~」コマンドの標準入力に入力することで、イベントから呼び出された状態を疑似的に発生させます。
cat test.json | fn invoke event-test event-test
これを実行すると、設定が間違っていなければサンプルJSONの内容に沿って「コンパートメント」「対象サーバ」「時刻」「操作内容」の4つの情報が成型されたメールが、設定したアドレスに届いているはずです。連載第2回の「属性指定の追加」の節で例示したサンプルJSONと見比べてみてください。その内容が反映され、さらに時刻もちゃんと日本時間になっているのがわかると思います。

また、ログを見るとファンクションからのメッセージがいくつか出ていることがわかります。

以上でファンクションが想定通り動作することが確認できました。
イベントからの起動テスト
ファンクションの正常動作が確認できたら、後は実際に対象イベントを発生させて確認します。対象のサーバを起動または停止させ、今発生したイベントの内容で成型済みのメールが届いたら成功です。これで、イベントの内容を成型してメール通知を行うファンクションが作成できました。
OCIクラウドシェル上のファイルバックアップ
今回コードを修正したので、最後にクラウドシェル環境をバックアップしましょう。ファイルが少なければ、クラウドシェルにあるファイルアップロード・ダウンロード機能を使えば良いのですが、数が多い場合はファイルをまとめてOCI上のObject Storageにバックアップします。クラウドシェルではOCI CLIが使えるので、それを使ってバックアップしたいファイルを圧縮ファイルにまとめ、オブジェクトストレージにアップロードします。リストアしたい場合はその逆で、ファイルをOCI CLIでダウンロードして展開します。
Oracleが紹介しているコマンドライン※6を多少アレンジしたものが以下になります。「OCI_CS_USER_BACKUPS_*」変数の値は適宜自分の環境に合わせ修正ください。また、リストア時は更新があったファイルは上書きしないようになっています。
※6 クラウド・シェルの使用|Oracle Cloud Infrastructureドキュメント
- バックアップ
OCI_CS_USER_BACKUPS_BUCKET_NAME="MyCloudShellBackupsBucket" OCI_CS_USER_BACKUPS_NAMESPACE="NamespaceForBackupsBucket" OCI_CS_USER_BACKUPS_OBJECT_NAME="CloudShellHomeDirectoryBackup.tar.gz" cd ~ tar -zcvf - ./ | oci os object put --namespace-name $OCI_CS_USER_BACKUPS_NAMESPACE --bucket-name $OCI_CS_USER_BACKUPS_BUCKET_NAME --name $OCI_CS_USER_BACKUPS_OBJECT_NAME --file -
- リストア
OCI_CS_USER_BACKUPS_BUCKET_NAME="MyCloudShellBackupsBucket" OCI_CS_USER_BACKUPS_NAMESPACE="NamespaceForBackupsBucket" OCI_CS_USER_BACKUPS_OBJECT_NAME="CloudShellHomeDirectoryBackup.tar.gz" cd ~ oci os object get --namespace-name $OCI_CS_USER_BACKUPS_NAMESPACE --bucket-name $OCI_CS_USER_BACKUPS_BUCKET_NAME --name $OCI_CS_USER_BACKUPS_OBJECT_NAME --file - |tar --skip-old-files -xzvf -
おわりに
これで、イベント・サービスからFunctionsを使用して成形済みメールを送れるようになりました。ただ、このコードを改めて見てみると、環境によって変動しそうな項目がそのまま埋め込まれているので、再利用の際に少し手間がかかりそうです。
最終回となる次回は、再利用に便利なFunctionsの機能について説明しますので、お楽しみに。
プロフィール

石田 翼
ハードウェア側からインフラ業務に入ったのに今や1mmもハードウェアを使わないクラウドのお仕事が中心になって困惑しているインフラエンジニア。LinuxやOracle DBの設計・構築などに従事しつつスキルをクラウドシフト中。
タグ
- アーキテクト
- アジャイル開発
- アプリ開発
- インシデントレスポンス
- イベントレポート
- カスタマーストーリー
- カルチャー
- 官民学・業界連携
- 企業市民活動
- クラウド
- クラウドインテグレーション
- クラブ活動
- コーポレート
- 広報・マーケティング
- 攻撃者グループ
- もっと見る +
- 子育て、生活
- サイバー救急センター
- サイバー救急センターレポート
- サイバー攻撃
- サイバー犯罪
- サイバー・グリッド・ジャパン
- サプライチェーンリスク
- システム開発
- 趣味
- 障がい者採用
- 初心者向け
- 白浜シンポジウム
- 情シス向け
- 情報モラル
- 情報漏えい対策
- 人材開発・教育
- 診断30周年
- スレットインテリジェンス
- すごうで
- セキュリティ
- セキュリティ診断
- セキュリティ診断レポート
- 脆弱性
- 脆弱性管理
- ゼロトラスト
- 対談
- テレワーク
- データベース
- デジタルアイデンティティ
- 働き方改革
- 標的型攻撃
- プラス・セキュリティ人材
- モバイルアプリ
- ライター紹介
- ラックセキュリティアカデミー
- ランサムウェア
- リモートデスクトップ
- AI
- ASM
- CIS Controls
- CODE BLUE
- CTF
- CYBER GRID JOURNAL
- CYBER GRID VIEW
- DevSecOps
- DX
- EC
- EDR
- FalconNest
- IoT
- IR
- JSOC
- JSOC INSIGHT
- LAC Security Insight
- OWASP
- SASE
- Tech Crawling
- XDR