【小ネタ】 IPアクセス制限されたAzure Cosmos DBにAzure Pipelines(Microsoft-hostedランナー)から通信を行う
はじめに
コンサル部の神野です。
Azure Pipelines(Microsoft-hostedランナー)からAzure Cosmos DB(以下Cosmos DB)への接続でハマっていたポイントが、
「[Accept connections from within Azure datacenters]」オプションを有効にすることで解決できたので共有したいと思います。
前提条件と問題
ある環境で、セキュリティ要件からCosmos DBに対して特定のIPアドレスからのみ接続できるようにファイアウォール設定を行っていました。
これにより、Cosmos DBには許可リストに追加されたIPアドレスからしかアクセスできない状態にしていました。
しかし、Azure PipelinesからCosmos DBにアクセスする必要が出てきたとき、次のような問題に直面しました。
- Azure PipelinesのMicrosoft-hostedランナーは固定IPアドレスを持たない
- 毎回のビルドで異なるエージェントが使用される可能性があるためIPが変動する
- IPを固定可能であるセルフホステッドランナーがコストの兼ね合いで採用は検討中であった。
そのため、どうやってAzure Pipelines(Microsoft-hostedランナー)→ Cosmos DBへの通信を実現しようか考えていました。
接続イメージ
具体的にはCosmos DBからデータを取得する処理をAzure Pipelinesから実行するために通信したい状況です。
ですが、セルフホステッドランナーが使用できないので、IP固定化ができずCosmos DBの許可IPアドレスのリストにも追加できない状態です。
そこで「[Accept connections from within Azure datacenters]」オプションを有効化することで接続を実現します。
解決策:Azure内部からの接続を許可する
Cosmos DBのネットワーク設定で「[Accept connections from within Azure datacenters]」オプション(Azureデータセンター内からの接続を許可する)を有効にするだけで接続できるようになりました。
このオプションを有効にすると、以下が有効になります。
- IPアドレス「0.0.0.0」が許可リストに自動的に追加
- これにより、Azureデータセンター内からの接続をすべて許可するようにファイアウォールが構成
- Azure PipelinesもAzureリソースとして認識され、通信が許可
Microsoft公式ドキュメントにも記載があるので、ご参照ください。
Cosmos DBにおいて、「0.0.0.0」を許可することでAzureリソース内からのインバウンド通信はすべて許可されます。
ただあくまでAzureデータセンター内のリソースからの接続のみが許可され、インターネットからの一般的なアクセスは引き続きブロックされます
この設定により、Azure PipelinesのビルドエージェントはAzureリソース内のアクセスとみなされアクセスが可能になります。
接続イメージ
設定方法
Cosmos DBのネットワーク設定で「Accept connections from within Azure datacenters」オプションを有効にする方法は以下の通りです。
- Azure Portal検索バーからCosmos DBを検索し、
Azure Cosmos DB
を押下
- 該当のCosmos DBを選択
- 左側のメニューから
「ネットワーク」
を選択し、「Azure データセンター内からの接続を許可する」オプション
を有効にして、保存
ボタンを押下
0.0.0.0
が追加されて保存されていればOKです!
以上でAzure Pipelines → Cosmos DBへ接続が可能となります!
検証してみましょう。
検証
実際に Azure Pipelines から Cosmos DB への接続が「Accept connections from within Azure datacenters」オプションの有効/無効によってどのように変わるかを確認します。
準備
検証のため、以下の Azure Pipelines と Python スクリプトを用意しました。
azure-pipelines.yml
)
Azure Pipelines 定義 (Microsoft-hosted ランナー (ubuntu-latest
) を使用し、Python 環境をセットアップして cosmos-test.py
を実行するシンプルなパイプラインです。Cosmos DB への接続情報は Azure DevOps の Variables 機能などを使用して環境変数経由でスクリプトに渡します。
具体的には、パイプラインの Variables で定義した cosmosEndpoint
, cosmosKey
, cosmosDatabase
, cosmosContainer
という変数を env
ブロックで環境変数としてマッピングしています。それぞれの値はAzure PortalのCosmos DBの画面から確認して設定します。
Variable | 説明 |
---|---|
cosmosEndpoint |
Cosmos DBアカウントのエンドポイントURI |
cosmosKey |
Cosmos DBアカウントのプライマリキー(またはセカンダリキー) |
cosmosDatabase |
接続対象のデータベース名 |
cosmosContainer |
接続対象のコンテナ名 |
azure-pipelines.yml全体
trigger:
- main
pool:
vmImage: 'ubuntu-latest' # Microsoft-hostedランナー
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
displayName: 'Pythonのインストール'
- script: |
pip install -r requirements.txt
displayName: '依存関係のインストール'
- script: |
python cosmos-test.py
displayName: 'CosmosDB接続テスト実行'
env:
COSMOS_ENDPOINT: $(cosmosEndpoint)
COSMOS_KEY: $(cosmosKey)
COSMOS_DATABASE: $(cosmosDatabase)
COSMOS_CONTAINER: $(cosmosContainer)
cosmos-test.py
)
Cosmos DB 接続テストスクリプト (環境変数から受け取った情報をもとに Cosmos DB へ接続し、指定されたデータベースとコンテナが存在するか、またコンテナ内のアイテム数を取得できるかを確認する Python スクリプトです。このスクリプトによる実行ログを確認して接続できているかどうかを確認します。
cosmos-test.py全体
import os
import sys
from azure.cosmos import CosmosClient, exceptions
def main():
# 環境変数から接続情報を取得
endpoint = os.environ.get('COSMOS_ENDPOINT')
key = os.environ.get('COSMOS_KEY')
database_name = os.environ.get('COSMOS_DATABASE')
container_name = os.environ.get('COSMOS_CONTAINER')
# ... (環境変数チェック等の処理) ...
print(f'Azure Cosmos DB接続テスト開始...')
print(f'エンドポイント: {endpoint}')
print(f'データベース: {database_name}')
print(f'コンテナ: {container_name}')
try:
# Cosmos DBクライアントの初期化
client = CosmosClient(endpoint, credential=key)
# データベースとコンテナの取得
database = client.get_database_client(database_name)
container = database.get_container_client(container_name)
# データベースとコンテナが存在するか確認
print(f'データベース [{database_name}] の検証中...')
try:
database_properties = database.read()
print(f'データベース [{database_name}] は存在します。')
except exceptions.CosmosResourceNotFoundError:
print(f'エラー: データベース [{database_name}] が見つかりません。')
sys.exit(1)
print(f'コンテナ [{container_name}] の検証中...')
try:
container_properties = container.read()
print(f'コンテナ [{container_name}] は存在します。')
except exceptions.CosmosResourceNotFoundError:
print(f'エラー: コンテナ [{container_name}] が見つかりません。')
sys.exit(1)
# 接続テスト - アイテム数の確認(実際のデータにアクセス)
print(f'コンテナ内のアイテム数を確認中...')
items = list(container.query_items(
query="SELECT * FROM c",
enable_cross_partition_query=True
))
count = len(items)
print(f'コンテナ内のアイテム数: {count}')
# 成功メッセージ
print('Azure Cosmos DB接続テスト成功!')
print('環境設定は正しく構成されており、接続が確立されました。')
except exceptions.CosmosHttpResponseError as e:
print(f'エラー: Cosmos DB接続中にエラーが発生しました。')
print(f'HTTPエラーコード: {e.status_code}')
print(f'エラーメッセージ: {e.message}')
sys.exit(1)
except Exception as e:
print(f'エラー: 予期しないエラーが発生しました。')
print(f'エラーメッセージ: {str(e)}')
sys.exit(1)
if __name__ == '__main__':
main()
オプション有効時
まず、Cosmos DB のネットワーク設定で「Accept connections from within Azure datacenters」オプションを有効にした状態で、上記の Azure Pipelines を実行します。
実行結果
パイプラインは成功し、Python スクリプトは Cosmos DB への接続、データベース・コンテナの検証、アイテム数の取得を正常に完了しました。これにより、オプション有効時には Microsoft-hosted ランナーからのアクセスが許可されることが確認できました。
Azure Cosmos DB接続テスト開始...
エンドポイント: ***
データベース: cosmicworks
コンテナ: products
データベース [cosmicworks] の検証中...
データベース [cosmicworks] は存在します。
コンテナ [products] の検証中...
コンテナ [products] は存在します。
コンテナ内のアイテム数を確認中...
コンテナ内のアイテム数: 0
Azure Cosmos DB接続テスト成功!
環境設定は正しく構成されており、接続が確立されました。
参考:ローカルからパブリックインターネット経由でアクセスした場合
念のため、ファイアウォール設定が意図通り機能しているかを確認するため、許可リストにないローカル開発環境(パブリックIP)から同じスクリプトを実行しました。想定通り、ファイアウォールによってブロックされ、接続は失敗しました。
Azure Cosmos DB接続テスト開始...
エンドポイント: ***
データベース: cosmicworks
コンテナ: products
エラー: Cosmos DB接続中にエラーが発生しました。
HTTPエラーコード: 403
エラーメッセージ: (Forbidden) Request originated from IP xxx.xxx.xxx.xxx through public internet. This is blocked by your Cosmos DB account firewall settings. More info: https://aka.ms/cosmosdb-tsg-forbidden
ActivityId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, Microsoft.Azure.Documents.Common/2.14.0
Code: Forbidden
Message: Request originated from IP xxx.xxx.xxx.xxx through public internet. This is blocked by your Cosmos DB account firewall settings. More info: https://aka.ms/cosmosdb-tsg-forbidden
ActivityId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, Microsoft.Azure.Documents.Common/2.14.0
オプション無効時
次に、「Accept connections from within Azure datacenters」オプションを無効(チェックを外して保存)にし、再度 Azure Pipeline を実行します。
実行結果
パイプラインは失敗しました。Python スクリプトのログを確認すると、Cosmos DB への接続試行時に HTTP 403 (Forbidden) エラーが発生しています。エラーメッセージには、リクエスト元の IP アドレス (Azure Pipeline のランナーの IP) がファイアウォール設定によってブロックされたことが示されています。これにより、オプションが無効な状態では Microsoft-hosted ランナーからのアクセスが拒否されることが確認できました。
Azure Cosmos DB接続テスト開始...
エンドポイント: ***
データベース: cosmicworks
コンテナ: products
エラー: Cosmos DB接続中にエラーが発生しました。
HTTPエラーコード: 403
エラーメッセージ: (Forbidden) Request originated from IP yyy.yyy.yyy.yyy through public internet. This is blocked by your Cosmos DB account firewall settings. More info: https://aka.ms/cosmosdb-tsg-forbidden
ActivityId: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy, Microsoft.Azure.Documents.Common/2.14.0
Code: Forbidden
Message: Request originated from IP yyy.yyy.yyy.yyy through public internet. This is blocked by your Cosmos DB account firewall settings. More info: https://aka.ms/cosmosdb-tsg-forbidden
ActivityId: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy, Microsoft.Azure.Documents.Common/2.14.0
意図通りの疎通結果となっていることを確認できましたね!!
おわりに
今回の問題は「Accept connections from within Azure datacenters」オプションを有効にすることで解決しました。
これにより、固定IPアドレスを持たないAzure Pipelinesからでも、Cosmos DBにアクセスできるようになりました!
Azure PipelinesはAzureという名前がついていますが、Azureポータルとは別物のサービスだと思われがちで、今回の設定を有効化してもAzureデータセンター内からのアクセスとみなされるか心配でしたが問題なく疎通できました。
セルフホステッドランナーでIP固定化する方法もありますが、それができない場合の回避策としてはいいかと思いました!
最後までご覧いただきありがとうございました!
その他案:セルフホステッドランナーの利用
「Accept connections from within Azure datacenters」オプションを有効にしたくない、またはより厳密なアクセス制御を行いたい場合、セルフホステッドランナーを利用する方法が考えられます。
セルフホステッドランナーとは
セルフホステッドランナーは、ユーザー自身が管理するインフラストラクチャ(Azure VM、オンプレミスのサーバーなど)上で Azure Pipelines エージェントを実行する方式です。Microsoft-hosted ランナーとは異なり、実行環境を自身で制御できます。
IPアドレスの固定化
セルフホステッドランナーを Azure Virtual Network (VNet) 内の仮想マシン (VM) などで実行する場合、NAT Gateway 経由などで固定の送信元 IP アドレスを持たせることが可能です。
これにより、Cosmos DB のファイアウォール設定で、特定の IP アドレス (セルフホステッドランナーの固定 IP) からのアクセスのみを明示的に許可 することができます。これは、「Accept connections from within Azure datacenters」オプションよりも、より限定的なアクセス制御を実現可能とします。
構築ステップの例
あくまで一例となります。
- Azure VM の準備
- VNet 内に Linux または Windows の VM を作成します。
- 静的 IP の設定
- サブネットに NAT Gateway を関連付けて固定の送信元 IP を確保します。
- Azure Pipelines エージェントのインストール
- VM上にAzure Pipelinesエージェントをインストール・設定し、事前に生成したPAT(Personal Access Token)を使用してAzure DevOps組織に登録後、サービスとして起動します。
- Cosmos DB ファイアウォールの設定
- Cosmos DB のネットワーク設定で、手順 2 で確保した固定 IP アドレスからのアクセスを許可します。
- パイプラインの構成
azure-pipelines.yml
で、pool
をセルフホステッドエージェントプール名に変更します。
コストと管理に関する考慮事項
- インフラコスト
- セルフホステッドランナーを実行するための VM や NAT Gateway などの Azure リソースに対する継続的なコストが発生します。
- 管理オーバーヘッド
- VM の OS パッチ適用、セキュリティ対策、エージェントソフトウェアの更新など、ランナー環境の維持管理はユーザーの責任となります。
- Azure Bastion
- VM へのセキュアな接続のために Azure Bastion を利用する場合、そのコストも考慮に入れる必要があります。
トレードオフ
セルフホステッドランナーは IP アドレスを固定化でき、より厳密なセキュリティ制御が可能になるというメリットがありますが、その反面、インフラの構築・維持管理コストと手間がかかります。
どちらの方法を選択するかは、セキュリティ要件、予算、運用体制などを総合的に評価して判断する必要があるかと思います。