App Service からの通信を Azure Firewall で TLS インスペクションする

azure
Published: 2024-05-16

はじめに

VNet 統合した App Service は、アウトバウンド通信を Azure Firewall 経由にできます。この構成にすることで、App Service からインターネットへの送信元 IP アドレスを固定したり、App Service からのインターネットへの通信をホワイトリストで厳密に制御できるようになります。

VNet 統合と Azure Firewall の構成イメージ

Azure Firewall を使用して送信トラフィックを制御する

「この構成で App Service からの通信を TLS インスペクションできるのだろうか?」という素朴な疑問がわいたので調べた&試しました

TLS インスペクションの前提

TLS インスペクションを成立させるためには、Azure Firewall がクライアントとの HTTPS 通信で利用するサーバ証明書を発行する際に使う CA 証明書を、クライアントの証明書リストに追加する必要があります。

証明書を追加しないと、HTTPS 通信はエラーになります。TLS インスペクションな環境では、クライアントに届くサーバ証明書が Azure Firewall の CA 証明書で署名されています。そのため、Azure Firewall の CA 証明書がクライアントに追加されていない場合、クライアントはサーバ証明書を発行した認証局が正規なものではないと判断して SSL をエラーで終了させます。

Azure Firewall Premium の証明書

つまり、PaaS の通信を TLS インスペクションできるかは、通信を行う PaaS のインスタンスに Azure Firewall の CA 証明書を追加できるかどうかが焦点になります。

App Service の場合

App Service の場合、通信を行うインスタンスがユーザ専用になる App Service Environment でのみ CA 証明書を追加できます。マルチテナント型の App Service には CA 証明書を追加できません。

Web Apps for Container の場合

Web App for Container の場合、App Service を構成するインスタンスではなく、実際に通信を行うコンテナに CA 証明書を追加する必要があります。コンテナの中身はユーザが自由にいじれますので、CA 証明書を配置できます。実際に試してみました。

検証

アクセスされたら ifcofig.me に HTTPS でアクセスして自分の送信元グローバル IP アドレスを返す Web アプリをコンテナで動作させます。HTTPS でアクセスしているので、TLS インスペクションの対象になります。

import http.server
import socketserver
import requests

LISTEN_PORT = 8080

class ServerHandler(http.server.SimpleHTTPRequestHandler):

    def do_GET(self):
        r = requests.get("https://ifconfig.me")
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(r.text.encode('utf8'))

if __name__ == "__main__":
    HOST, PORT = '', LISTEN_PORT

    with socketserver.TCPServer((HOST, PORT), ServerHandler) as server:
        server.serve_forever()

参考:Pythonで簡易Webサーバーを作る

FROM python:3.6.9

WORKDIR /app

COPY ./opt /app/opt

EXPOSE 8080

RUN pip install requests
CMD ["python", "opt/web.py"]

Azure Firewall で TLS インスペクションを無効にしている場合は、正常に動作します。VNet 統合によって経由している Azure Firewall の Public IP アドレスが返ってきていることが分かります。

> $azfw = Get-AzFirewall -ResourceGroupName labnet -Name azfw
> $pip = Get-AzResource -Id $azfw.IpConfigurations[0].PublicIpAddress.Id
> $pip.Properties.ipAddress
20.18.96.149

> curl https://ymacr.azurewebsites.net/
20.18.96.149

一方で、Azure Firewall で TLS インスペクションを有効化すると、ifconfig.me にアクセスできなくなるためアプリが正常に動作しません。502 が返ってきます。

> curl https://ymacr.azurewebsites.net/ --verbose
*   Trying 13.73.26.73:443...
* Connected to ymacr.azurewebsites.net (13.73.26.73) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.1
> GET / HTTP/1.1
> Host: ymacr.azurewebsites.net
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 502 Bad Gateway
< Content-Length: 0
< Date: Wed, 15 May 2024 14:41:01 GMT
<
* Connection #0 to host ymacr.azurewebsites.net left intact

アプリのログには証明書の検証に失敗したことを示すエラーが記録されます。

requests.exceptions.SSLError: HTTPSConnectionPool(host='ifconfig.me', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:852)'),))

TLS インスペクションを成立させるために、コンテナに Azure Firewall の証明書を組み込みます。これが正しい方法なのか不明ではあるのですが動きはしました…

Azure Firewall の CA 証明書を KeyVault から CER 形式でダウンロードした上で、OpenSSL を利用して CRT に変換します。さらに Dockerfile に CA 証明書を配置する処理を追加した上でイメージをビルド、Azure Container Registory にプッシュします。

FROM python:3.6.9

WORKDIR /app

COPY ./opt /app/opt
RUN mkdir /usr/share/ca-certificates/mylocal
COPY ./opt/azfw.crt /usr/share/ca-certificates/mylocal/azfw.crt
RUN echo "mylocal/azfw.crt" >> /etc/ca-certificates.conf
RUN update-ca-certificates

EXPOSE 8080

ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt

RUN pip install requests
CMD ["python", "opt/web.py"]

最後に Web Apps for Container が利用するイメージのバージョンを最新のものに変更します。そうすると、エラーなく動作するようになりました。Azure Firewall の CA 証明書を python の requests が利用することでサーバ証明書の検証が成功するようになったので、アプリが正常に動作して自分の送信元 IP アドレスを返せるようになりました。

> curl https://ymacr.azurewebsites.net/ --verbose
*   Trying 13.73.26.73:443...
* Connected to ymacr.azurewebsites.net (13.73.26.73) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.1
> GET / HTTP/1.1
> Host: ymacr.azurewebsites.net
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Wed, 15 May 2024 16:47:07 GMT
< Server: SimpleHTTP/0.6 Python/3.6.9
< Transfer-Encoding: chunked
<
20.18.96.149* Connection #0 to host ymacr.azurewebsites.net left intact

まとめ

「App Service からの通信を Azure Firewall で TLS インスペクションできるのか?」という素朴な疑問を試してみました。CA 証明書をインポートしなければならないという制約があるので、App Service Environment か Web Apps for Container を使えばよさそうです。

Note

  • 当サイトは個人のブログです。このブログに示されている見解や意見は個人的なものであり、所属組織の見解や意見を表明するものではありません。
  • 公開情報を踏まえて正確な情報を掲載するよう努めますが、その内容の完全性や正確性、有用性、安全性、最新性について一切保証しません。
  • 添付文章やリンク先などを含む本サイトの内容は作成時点でのものであり、予告なく変更される場合があります。