Azure Web Apps + Azure Let's Encrypt の環境で送信元IPアドレスを制限する


はじめに

自身の学習用にAzure Web Appsを使ってWebアプリを立てています。
Webアプリへの通信をHTTPS化するため、Let's Encryptによる無料証明書を発行し、Webアプリに食わせています。
Let's Encryptは3ヶ月に1回の頻度で証明書の更新を行う必要がありますが、
Azure Web Appsには便利な拡張機能として「Azure Let's encrypt」があり、本機能を利用することで煩わしい更新作業を自動化することができます。

※参考記事※

Azure Web Apps + Azure Let's Encryptの実装方法については別記事を引用しておきます。
●  Let’s Encrypt を使って Azure Web Apps でのhttps通信を設定する
●  Securing an Azure App Service Website under SSL in minutes with Let's Encrypt

本題

私のAzure上Webアプリはプライベート用途なので限られた環境からのみアクセスできるよう、送信元IPアドレスの制限をかけようと試みました。
まず最初にAzure App Service のアクセス制限を利用したのですが、3ヶ月後にLet's Encryptの証明書の更新が失敗する問題が発生しました。
その際に別の方法を使って解決することができましたので備忘録として残しておきます。

環境

  • Azure Web Apps
  • Webアプリ(ASP.NET MVC 5)

証明書更新ができなくなった原因

Azure App Service のアクセス制限により、Let's Encryptサーバ → Webアプリへの通信が遮断されていたことが原因でした。
Azure Let's encryptを使って証明書を発行/更新する場合、
Let's Encryptサーバが対象Webアプリの特定のディレクトリに対してACMEチャレンジ通信(http接続)を行うのですが、
IPアドレス制限によりアクセスできず、更新に失敗していました。(盲点でした、、)

●  Let's Encryptのチャレンジの仕様

どうするか?

改めてやりたいことを整理します。
 ①公開Webアプリの画面には許可された送信元IPのみ接続を許可する
 ②Let's Encryptとの証明書更新通信は許可する
 ③それ以外の通信は遮断する

上記を実現するため、最初にAzure App Service のアクセス制限にLet's EncryptサーバのIPアドレス/FQDNを登録できれば解決できると考えましたが、
Let's EncryptのIPアドレスは公開されておらず(そもそもIPが動的に変わるためIPによる制限は非推奨とのこと)、
また、Azure App Service のアクセス制限はFQDNの登録が不可のため、
①と②を実現するには別の手段を使って対応する必要がありました。

解決策

結論、web.configファイルを使って目的を達成することができました。
本ファイルを利用すれば、Web Appsのディレクトリ単位で柔軟に接続制限を行うことができるため、
今回のようにWebアプリの公開ディレクトリはIP制限をかけるが、
Let's Encryptのチャレンジ通信用のディレクトリへは不特定の送信元からでも接続できるようにする といったことが可能です。

※参考記事※

● Azure WebサイトでソースIPアドレスによるアクセス許可/拒否(制限)を設定する
● Azure App ServiceでIP制限

設定手順

【1】 IPアクセス制限(ホワイトリスト用)のweb.configを作成する

接続を許可するIPアドレスを以下のような構成で記述し、web.configを作成します。

web.config(Webアプリ公開用)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <security>
            <ipSecurity allowUnlisted="false" denyAction="NotFound">
                <add allowed="true" ipAddress="x.x.x.x" subnetMask="x.x.x.x" />  <!-- ここに許可するIPを記述-->
                <add allowed="true" ipAddress="x.x.x.x" subnetMask="x.x.x.x" />  <!-- 複数記述することも可能-->
            </ipSecurity>
        </security>
   </system.webServer>
</configuration>

上記のdenyActionにて、許可されていないIPからアクセスされた際の応答ステータスコードを指定することができます。
私の環境ではNotFound(404)としましたが、他にも以下のように指定できるようです。

ステータスコード denyAction属性の設定
401 Unauthorized denyAction="Unauthorized"
403 Forbidden denyAction="Forbidden"
502 Bad Gatewayなど(応答中止) denyAction="AbortRequest"

【2】 Webアプリ公開ディレクトリ(wwwroot)に手順1で作成したweb.configを配置する

Kuduを開き、 D:\home\site\wwwroot ディレクトリ内にweb.configを配置します。

home\site\wwwroot
wwwroot
└── App_Data
└── Views
└── web.config # ←配置

この状態にすることで、wwwrootおよびその配下へのアクセスは指定した送信元IP以外拒否されます。

【3】 手順1のIPアクセス制限を「無効化」させるweb.configを作成する

Let's Encryptのチャレンジ接続をlistenできるよう、手順1で作成したweb.configを無効化させる用のweb.configを別途作成します。

web.config(Let'sEncrypt接続許可用)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <security>
            <ipSecurity allowUnlisted="true">
              <clear/>  <!-- 親ディレクトリに設定されているIP制限を無効化させる-->
            </ipSecurity>
        </security>
   </system.webServer>
</configuration>

【4】 Let's Encryptのチャレンジ通信が行われるディレクトリを作成する

※1回でも証明書の作成・更新を行っていて、本ディレクトリがすでに作られている場合は読み飛ばして下さい。

Let's Encryptがチャレンジ通信の際に接続を試みるディレクトリを手動で作成します。
Kuduを開き、 D:\home\site\wwwroot ディレクトリに移動し、以下2つのディレクトリを新規作成します。

home\site\wwwroot
wwwroot
└── App_Data
└── Views
└── web.config
└── .well-known  # ←作成
   └── acme-challenge  # ←作成

【5】 手順3で作成したweb.configを手順4のディレクトリ内に配置する

Kuduを開き、 手順3で作成したweb.config
手順4で作成したD:\home\site\wwwroot\.well-known\acme-challenge ディレクトリ内に配置します。

home\site\wwwroot
wwwroot
└── App_Data
└── Views
└── web.config
└── .well-known
   └── acme-challenge
      └── .web.config  # ←手順3のweb.configを配置  

こうすることで、.well-known\acme-challengeへのアクセス時は親ディレクト(wwwroot直下においたweb.config)に設定したIP制限が無効化される状態となるため、送信元IPが特定できないLet's Encryptサーバからの通信を受け付けることが可能です。

※私の環境では無効化用のweb.configacme-challenge配下に格納しましたが、仕様上、.well-known配下に置いてもよいかもしれません。

以上で設定作業は完了です。

【6】 接続確認

今回の要件を満たしているかを以下のパターンで接続を行い確認します。

①Webアプリへのアクセス(接続が許可されたIP)
 結果: アクセス可能(Web画面が表示される)

②Webアプリへのアクセス(接続が許可されていないIP)
 結果: アクセス不可(手順1で設定したdenyActionに応じたエラーコードが返る)

http://[url]/.well-known/acme-challengeへのアクセス(接続が許可されていないIP)
 結果: アクセス可能

【7】 Let's Encryptの証明書更新

この状態であれば、Azure Web Appsの拡張機能 [Azure Let's encrypt]を利用して証明書の更新ができるはずです。
※更新手順は割愛します。

更新できた場合、Let's Encryptサーバが.well-Known\acme-challengeディレクトリ内にチャレンジ検証用のファイルを作成することが確認できます。

home\site\wwwroot
wwwroot
└── App_Data
└── Views
└── web.config
└── .well-known  
   └── acme-challenge
      └── web.config
      └── 3bN#87AtTUrQME9F1CCTknf9Oafpm8r43XJGRxdejml  # ←チャレンジ通信時に生成されるファイル

以上です。