はじめに
こんにちは、CISO部の兵藤です。日々ZOZOの安全のためにSOC対応を行なっています。
本記事ではサイバー脅威インテリジェンス(CTI)のプラットフォームの1つであるOpenCTIをAzure上に構築した事例を紹介します。また、CISO部ではその他にもZOZOを守るための取り組みを行っています。詳細については以下の「フィッシングハントの始め方」をご覧いただければと思います。
目次
背景と概要
そもそもサイバー脅威インテリジェンスとは何かですが、「脅威情報を意思決定のプロセスに必要な状態へ分析、解釈、集約、補強されたもの」で、インテリジェンスのレベル感によって大きく3段階に分かれます。
- Strategic level
- Operational level
- Tactical level
SOCでは悪意のあるドメイン名やマルウェアのハッシュ値など、Tactical levelでのインテリジェンス(IOCなど)を日々扱うことが多いです。詳しくはサイバー脅威インテリジェンスについて調べていただければと思いますが、これらのインテリジェンスは活用スパンが短く、自動運用を進めることが望ましいです。ですが、ZOZO内では上記Tactical levelでのインテリジェンスを各個人が扱い、SOC対応に活かしている状況でした。
SOC対応を実施するにあたって各個人で調査収集していた情報を1つのプラットフォームで自動収集、管理し、ゆくゆくはSIEMと連携したいと考えていました。そこで、CTIのプラットフォームのOSSであるOpenCTIを検討しました。
OpenCTIとは
OpenCTIについては先ほど少し紹介した、CTIのプラットフォームのOSSであり、Filigranが提供しているものです。公式サイトはこちらをご参照ください。
ReactやGraphQL、Elasticsearchなどを使用しており、モダンでカッコいい見た目がたまらなくセキュリティエンジニアをしている感が出ているプラットフォームです。
※以下画像は公式1から引用。
日本語での詳しい解説は「にのせき」さんの記事2が参考になります。
このOSSはDockerを用いて構築が可能で、初期構築が素早くできます。また、OpenCTIの機能を拡張するための「Connectors」についてもDockerイメージが提供されており、容易に機能拡張が可能です。アプリケーションの構築の経験が少ない方でも、Dockerを用いて簡単に構築できるのでおすすめのOSSです。
構築
概要
ZOZOではAzure上にOpenCTIを構築しました。以下が概要図です。
- Azure VM上にDockerを用いてOpenCTIを構築
- 環境変数などはKeyコンテナで管理
- Azure Application Gatewayを用いてHTTPS化
- Azure ADと連携しSAML認証を実装
- Azure Network Security Group(NSG)でアクセス制限
- Azure File Storageを用いてデータのバックアップを実施
Azure VM でのOpenCTI実装
OpenCTIは先ほども述べたようにDockerでの構築が可能です。要するにコンテナサービスを提供しているクラウドサービスであれば構築可能です。例えばAzureでのコンテナサービスは以下のようなものがあります。
- Azure Container Instances
- Azure App Service
- Azure Container Apps
- Azure Kubernetes Service (AKS)
上記のうちコンテナを複数起動する場合は後半の2つ、Container AppsまたはAKSを検討することになると思います。筆者も最初はこの2つを検討し、Kubernetesで構築するほどでもなく簡易的に複数コンテナを起動できれば良かったのでContainer Appsでの構築を目指しました。では何故Azure VMでの構築になったのでしょうか?
答えは簡単です。Container Appsでの構築を進めていると、インスタンスの起動の段階で壁にぶち当たります。Elasticsearchなどのコンテナがスペック不足で落ちてしまうのです。Microsoft公式のドキュメントによると従量課金プランだと記事執筆の時点(2023年6月15日)ではメモリ4Gがリミットとなってます。ですので、これ以上スペックを上げることがContainer Appsではできそうもありませんでした。ここら辺のお話や知見はSlackのコミュニティでも議論されていて、今回の私の知見も共有しています。このようにSlackではOpenCTIに関する情報が多く共有されているので、構築する際には参考になると思います。
これらの背景により、今回はAzure VM上にDockerを用いてOpenCTIを構築しました。
ここまでAzureにこだわっている理由は後ほど説明します。
Keyコンテナでの管理
OpenCTIを構築する際には、環境変数を設定しなければなりません。Connectorsの種類によっては、APIキーなどの秘匿情報を設定する必要があります。このような秘匿情報をハードコーディングすることはよろしくないので、Keyコンテナを用意し、そこで環境変数を管理するようにしました。
この変数についてですが、コンテナを起動する際に必要なので、Keyコンテナから値を読み込むOSSツールのvaultenv
を利用しています。
以下のフォーマットのように記述すれば環境変数を置き換えてくれるので便利です。
APIKEY={{ kv "https://keyvault-name.vault.azure.net/secrets/example-key" }}
Azure Application Gatewayを用いたHTTPS化
Application GatewayでSSL/TLSオフロードをしています。OpenCTIはデフォルトでは8080のportで起動するのでこのportへ443からの通信をマッピングすることでHTTPS化を実現しています。特段変わったことはしていません。
Azure ADと連携しSAML認証を実装
OpenCTIはデフォルトでID、Password認証です。これをAzure ADと連携しSAML認証に変更しました。この実装については文章化されているものが少ないので詳細を記載しようと思います。公式のドキュメントはこちらのリンクを参照してください。
OpenCTIのコンフィグ変更
OpenCTIのデフォルトのdocker-compose.ymlは以下のようになっています。
opencti: image: opencti/platform:5.7.4 environment: - NODE_OPTIONS=--max-old-space-size=8096 - APP__PORT=8080 - APP__BASE_URL=${OPENCTI_BASE_URL} - APP__ADMIN__EMAIL=${OPENCTI_ADMIN_EMAIL} - APP__ADMIN__PASSWORD=${OPENCTI_ADMIN_PASSWORD} - APP__ADMIN__TOKEN=${OPENCTI_ADMIN_TOKEN} - APP__APP_LOGS__LOGS_LEVEL=error - REDIS__HOSTNAME=redis - REDIS__PORT=6379 - ELASTICSEARCH__URL=http://elasticsearch:9200 - MINIO__ENDPOINT=minio - MINIO__PORT=9000 - MINIO__USE_SSL=false - MINIO__ACCESS_KEY=${MINIO_ROOT_USER} - MINIO__SECRET_KEY=${MINIO_ROOT_PASSWORD} - RABBITMQ__HOSTNAME=rabbitmq - RABBITMQ__PORT=5672 - RABBITMQ__PORT_MANAGEMENT=15672 - RABBITMQ__MANAGEMENT_SSL=false - RABBITMQ__USERNAME=${RABBITMQ_DEFAULT_USER} - RABBITMQ__PASSWORD=${RABBITMQ_DEFAULT_PASS} - SMTP__HOSTNAME=${SMTP_HOSTNAME} - SMTP__PORT=25 - PROVIDERS__LOCAL__STRATEGY=LocalStrategy ports: - "8080:8080" depends_on: - redis - elasticsearch - minio - rabbitmq restart: always
ローカル認証に関する環境変数はPROVIDERS__LOCAL__STRATEGY=LocalStrategy
です。ここに追加で環境変数を設定していき、SAML認証のみを行うようにコンフィグを変更できます。以下のように追加します。
...省略 - SMTP__PORT=25 - PROVIDERS__LOCAL__STRATEGY=LocalStrategy - PROVIDERS__LOCAL__CONFIG__DISABLED=true # ここから追加した環境変数 - PROVIDERS__SAML__STRATEGY=SamlStrategy - "PROVIDERS__SAML__CONFIG__LABEL=Login with SAML" # ログイン画面のボタン名 - PROVIDERS__SAML__CONFIG__ISSUER=${PROVIDERS_SAML_CONFIG_ISSUER} # Issuer名 - PROVIDERS__SAML__CONFIG__ENTRY_POINT=${PROVIDERS_SAML_CONFIG_ENTRY_POINT} #IdPのエンドポイント - PROVIDERS__SAML__CONFIG__SAML_CALLBACK_URL=${PROVIDERS_SAML_CONFIG_SAML_CALLBACK_URL} # SAML認証後のリダイレクト先 - PROVIDERS__SAML__CONFIG__CERT=${PROVIDERS_SAML_CONFIG_SAML_CERT} # IdPの証明書 ports: - "8080:8080" ...省略
これらの設定でSAML認証のみを行うようになります。実際に認証画面を覗きにいくと以下のようにSAML認証のみ可能になっています。各種環境変数の詳細な設定値については後ほどADの設定の際に確認します。
Azure ADの設定
OpenCTIのSAML認証を有効化するためにAzure ADでアプリを登録し、SAML認証の各種設定をしなければなりません。まずはAzure ADでアプリを登録します。アプリの登録は公式ドキュメントの「独自のアプリケーションの作成」を参考にしてください。
続いて、SAML認証を設定します。Azure ADのアプリの設定画面から「シングル サインオン」を選択し、「SAML」を選択します。
ここから具体的なSAMLの設定に移っていくのですが、その前にSAML認証でAzure AD、OpenCTIで設定が必要な各種項目について整理しておきます。
項目 | 設定箇所 | 説明 |
---|---|---|
識別子(Entity ID) | Azure AD | SPを一意に識別するためのID。URL形式をとり、IdPとSPで一致させる必要がある。別名「Issuer」。 |
応答URL(Assertion Consumer Service URL) | Azure AD | SAML Responseの宛先URL。 |
Issuer | OpenCTI | IdPを一意に識別するためのID。URL形式をとり、IdPとSPで一致させる必要がある。別名「Entity ID」。 |
Entry Point | OpenCTI | IdPのエンドポイント。SAML Requestの宛先URL。 |
SAML Callback URL | OpenCTI | SAML Responseの受信URL。 |
CERT | OpenCTI | SAML Responseに対して署名を検証するための証明書。 |
上記「識別子(Entity ID)」と「Issuer」はIdP(Azure AD)とSP(OpenCTI)で一致させないといけない点に注意して設定します。「応答URL(Assertion Consumer Service URL)」については「SAML Callback URL」で設定したURL(ここでは「https[:]//opencti-domain/autth/saml/callback」とします)を記載します。このURLの階層はそのまま変えずにドメインを変更すればいいでしょう。「CERT」については以下の赤枠の「ダウンロード」をクリックすると証明書がダウンロードできます。
最後に「Entry Point」については以下の「ログインURL」の項目をOpenCTIに設定すれば良いです。
以上でAzure AD(とOpenCTI)の設定は完了です。
SAML認証フローの確認
OpenCTIのSAML認証がどのように動作しているか確認をしていこうと思います。SAML認証のフローには大きく「SP-initiated」と「IdP-initiated」の2つがあります。OpenCTIの認証前の緑色のボタンは「SP-initiated」のフローを開始するボタンなので、今回はこちらの方を確認していきます。
通信のフローを確認するためにChromeの拡張機能「SAML-tracer」をインストールしておきます。この拡張機能を用いるとSAML認証の通信のフロー(SAML Request, SAML Response)を簡単に確認できます。以下が「SP-initiated」のSAML Requestです。
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_80a2e36b3cc742a5b5b7" Version="2.0" IssueInstant="2023-06-06T04:29:02.704Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Destination="https://login.microsoftonline.com/XXXXXXX-XXXXXX-XXXXXX-XXXXXX/saml2" AssertionConsumerServiceURL="https://opencti-domain/auth/saml/callback" > <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://opencti/entity-id</saml:Issuer> <samlp:NameIDPolicy xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate="true" /> <samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="exact" > <saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext> </samlp:AuthnRequest>
Requestの中身を確認すると、NameIDPolicy
のFormat
項目でemailAddress
を要求しているのでメールアドレスの単位でOpenCTIのユーザを一意に認識していると考えられます。また、AllowCreate
の項目がtrue
となっているのでOpenCTI側でユーザを作成していなくてもIdP(Azure AD)がOpenCTIにユーザを作成し初期化してくれます。ProtocolとしてはHTTP-POSTバインディングで返信するように要求しています。認証が成功すると<NameID>
の項目でemailAddress
がSAML Responseで返ってきます。
HTTPベースでこれらの情報をやり取りするのでHTTPSでの実装をしていることが前提になります。
Azure Network Security Group(NSG)でアクセス制限
SAML認証でもアクセス制御を行っていますが、それ以上にNSGで接続元を制限していたりと多層防御を実施しています。このように設計していた場合、Azure ADを用いたSAML認証の認証フローを許可するのが面倒かと思われるかもしれません。
ですが、Azureで実装していればその点の面倒さは解消されます。Azureにこだわっていたのはこのためです。以下のようにソースを「Service Tag」、ソースサービスタグを「AzureActiveDirectory」に設定しておけば煩わしい設定が一気に簡単になります。
Azure File Storageを用いたデータのバックアップ
コンテナの難点にデータの永続化が挙げられると思います。OpenCTIではDocker volumeを利用して永続化を行なっていますが、本設計だとVMを丸ごとスナップショットとして保存しておかないといけなくなります(それでもいいのですけど)。ですので、VM内のコンテナのファイルをAzure File Storageにマウントし、OpenCTIのMinIOのデータを外部記憶しています。
docker-compose.ymlのvolumesの部分を以下のように変更することで実現しています。
...省略 volumes: esdata: s3data: # ここから変更 driver: local driver_opts: type: cifs o: "mfsymlinks,vers=3.0,username=${AFS_NAME},password=${AFS_KEY},addr=${AFS_NAME}.file.core.windows.net" device: "//${AFS_NAME}.file.core.windows.net/${AFS_CONTAINER}"
正常にマウントされていれば以下のようにAzure File Storageにデータが保存されていると思います。
基本的にこのFile StorageはOpenCTIからしかアクセスしないのでNW制御でVnetからしかアクセスできないようにしておくのがおすすめです。また、アクセスキーについては定期的に更新しておきましょう。
構築結果
上記画像のようにAzure上にOpenCTIを構築できました。これにより自動でインテリジェンスを取り込み、管理できるようになりました。Search欄から気になるIOCなど検索できて便利です。
また、日本語対応もしていて設定がとてもしやすいと感じました。開発してくださったNFLabs3の皆様ありがとうございます。
まとめ
サイバー脅威インテリジェンスのプラットフォームの1つであるOpenCTIをAzure上に構築する取り組みについて紹介しました。意外とAzureでのOpenCTI構築やSAML認証の実装事例が少ないので、OpenCTIをこれから構築する方達のご参考になれば幸いです。
ZOZOではこれからも脅威情報を逐次収集し、意思決定プロセスに必要なインテリジェンスの活用に努めていき、ZOZOの安全性の向上を図っていきたいと考えています。続いてのフェーズではIOCのトリアージを詳細に詰めていこうと目論んでいます。
おわりに
ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクから是非ご応募ください!