NSGで「明示的に許可した通信以外は全て拒否」を設定する際の考慮点

# はじめに

セキュリティ要件として、「明示的に許可した通信以外は全て拒否したい」というのはよくあると思いますが、Azureでそれをやろうとした際にちょっとハマったポイントがあり、調べて「そうなんだ」と思ったので内容をまとめます。

今は仕事で（ほぼ初めて）Azureを触っているのですが、Azureのコンソールは（AWSに比べて）あまり優しくないな、というのが現時点の正直な感想です。

# システム構成

（構成イメージ）
<img width="668" alt="system.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319027/38a6cb85-cc11-eebf-9309-a4c1c8cc0fc6.png">


※やりたいこと

- クライアントサーバ（VNet-A）からWebサーバ（VNet-B）にHTTP接続する
- Webサーバの前段にALBを配置し、内部ロードバランシングを実施（ALBはWebサーバと同じサブネット上に配置）

# 環境情報

【クライアントサーバ】

- Windows Server 2016 Datacenter
- 自PCからの接続用にパブリックIP割り当てとRDP許可以外は全てデフォルトで構築

【Webサーバ】

- Ubuntu Server 18.04 LTS
- 構築後、NGINXをデフォルトでインストール
- 自PCからの接続用にパブリックIP割り当てを実施
- NSG設定は下記の通り（インバウンドルールのみ記載。アウトバウンドルールはデフォルト）

|ソース|ポート|宛先|許可/拒否|
|:--|:--|:--|:--|
|10.0.1.0/24|80|any|許可|
|10.0.1.0/24|22|any|許可|
|(自PCのIP)|22|any|許可|


# 事象
Azureでセキュリティを厳密にするため、Webサーバ側のNSGに対し、個別にCIDR指定で許可したプロトコル（HTTPなど）以外のVirtualNetworkからの通信を全て拒否（Deny）するインバウンドルールを追加しました。

※追加設定

|ソース|ポート|宛先|許可/拒否|
|:--|:--|:--|:--|
|any|*|VirtualNetwork|拒否|

NSG（AWSのSGも同様ですが）は優先度が高い（若い番号）のルールで許可されている方が優先されるはずでしたが、この設定を入れた瞬間にALB経由でのHTTP接続ができなくなりました。

## 事象切り分け

- NSG追加前はALB経由で接続OK
- NSG追加後もALBを経由しない場合（クライアントサーバからWebサーバに直接続する場合）は接続OK

## エラーメッセージ

    PS C:\Users\azureuser> curl http://10.1.1.4
    curl : Unable to connect to the remote server
    At line:1 char:1
    + curl http://10.1.1.4
    + ~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
        + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
    

※参考までに成功時

PS C:\Users\azureuser> curl http://10.1.1.6
    
    
    StatusCode        : 200
    StatusDescription : OK
    Content           : <!DOCTYPE html>
                        <html>
                        <head>
                        <title>Welcome to nginx!</title>
                        <style>
                            body {
                                width: 35em;
                                margin: 0 auto;
                                font-family: Tahoma, Verdana, Arial, sans-serif;
                            }
                        </style>
                        <...
    RawContent        : HTTP/1.1 200 OK
                        Connection: keep-alive
                        Accept-Ranges: bytes
                        Content-Length: 612
                        Content-Type: text/html
                        Date: Tue, 18 Feb 2020 05:50:36 GMT
                        ETag: "5e4b58cf-264"
                        Last-Modified: Tue, 18 Feb 2020 ...
    Forms             : {}
    Headers           : {[Connection, keep-alive], [Accept-Ranges, bytes],[Content-Length, 612], [Content-Type,
                        text/html]...}
    Images            : {}
    InputFields       : {}
    Links             : {@{innerHTML=nginx.org; innerText=nginx.org; outerHTML=<A href="http://nginx.org/">nginx.org</A>;
                        outerText=nginx.org; tagName=A; href=http://nginx.org/}, @{innerHTML=nginx.com;
                        innerText=nginx.com; outerHTML=<A href="http://nginx.com/">nginx.com</A>; outerText=nginx.com;
                        tagName=A; href=http://nginx.com/}}
    ParsedHtml        : System.__ComObject
    RawContentLength  : 612
    


# 調査のためにやったこと

通信のどこで詰まっているかを調べるため、クライアントサーバ上でパケットキャプチャーを取得しました。

（Azureライクなやり方もあるのかもしれませんが）

## Windows Serverでのパケットキャプチャー取得手順

手順はこちらの記事を参考にしました。

https://qiita.com/legitwhiz/items/59794b2a97e4fe1aba21

ただ、netshコマンドで作成した.etlファイルを.pcap形式に変換する際に使う「Microsoft Message Analyzer」は既に配布終了となっているそうです。

代替ツールがMicrosoftから提供されているので、今回はそちらを使用しました。

[etl2pcapng(github)](https://github.com/microsoft/etl2pcapng)

※参考情報
https://troushoo.blog.fc2.com/blog-entry-416.html

# 解析結果

接続NG/OK時のキャプチャーはそれぞれ下記の通りです。

【接続NG時】
<img width="905" alt="pcap_NG.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319027/2a23c967-97bb-00d2-52c3-30cdb115cad2.png">

【接続OK時】
<img width="908" alt="pcap_OK.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319027/e4f86d07-cdb7-d306-a80b-48ee24cd09d6.png">

※要点

- 「10.0.1.5」がクライアントサーバ
- 「10.1.1.4」がALB
- 「10.1.1.6」がWebサーバ
- 成功時は「10.1.1.4」から「10.0.1.5」に200が返却されている（接続OK時のNo.44）
- 「[TCP Retransmission]」は通信エラーで再送しました的なメッセージ


これだけ見てもなかなか難しい間違い探しになりますが、共通点として、目的の宛先に繋ぐ前に「168.63.129.16」というアドレスに対して接続を行っているように見えました。
何かの手がかりになると思い、このアドレスが何者かについて調べました。

# 168.63.129.16とは？

調べてみたら、Microsoftのサイトに情報がありました。

https://docs.microsoft.com/ja-jp/azure/virtual-network/what-is-ip-address-168-63-129-16

> IP アドレス 168.63.129.16 は、Azure プラットフォーム リソースへの通信チャネルの使用を容易にするために使用される仮想パブリック IP アドレスです。 

> これは、既定のネットワーク セキュリティ グループの規則によって許可されています。 インバウンドとアウトバウンド両方向のすべてのローカル ファイアウォール ポリシーで、この IP アドレスを許可することをお勧めします。 


要するに、Azureが提供するサービスにアクセスするためのパブリックIP、ということです。
AWSでもS3などのリージョンサービスはパブリックネットワーク上にあるため、そのサービスを利用するためにはインターネット接続（もしくはVPCエンドポイントによるプライベート接続）が必要になりますが、おそらくそれと似たような話なのだと思います。

理屈はさておき、NSGで「VirtualNetwork」からの通信を拒否したことにより、ALBを含むAzureサービスを利用するために必要な「168.63.129.16」への通信も遮断されてしまったため、ALB経由で通信ができなくなったのが原因だと考えられます。


# 検証①

上記を踏まえ、先程追加した定義の上にパブリックIPの通信を許可する設定を追加しました。

|ソース|ポート|宛先|許可/拒否|
|:--|:--|:--|:--|
|168.63.129.16|*|any|許可|
|any|*|VirtualNetwork|拒否|

その結果、ALB経由でもHTTP接続が可能になりました。


# 検証②

一方で、ALBを前段にしてVMを構成するとNSGに「AzureLoadBalancer」の定義がデフォルト（優先度65001）で追加されますが、それを優先度を上げて定義したらどうなるか試してみました。

結論としてはこれでも接続OKでした。

|ソース|ポート|宛先|許可/拒否|
|:--|:--|:--|:--|
|AzureLoadBalancer|*|any|許可|
|any|*|VirtualNetwork|拒否|

下記のページによると、「AzureLoadBalancer」のサービスタグはパブリックIPに変換されるとあるので、これでも同じ意味になるようです。
※「VirtualNetwork」はもう少し範囲が広いものの、パブリックIPも含めた定義になっているようです。

https://docs.microsoft.com/ja-jp/azure/virtual-network/service-tags-overview


# まとめ

Azureはデフォルトだと同じVirtualNetworkからの通信は許可されてしまうため、「明示的に許可したもの以外は全て拒否」をしたい場合は、使用するサービスもサービスタグで明示的に許可を入れてあげる必要がある、とわかりました。

色々話を聞いていると、Azureも以前からするとかなり仕様が変わっていて、まだまだ発展途上ということらしいので、今後はアップデートに追いつけるようにしたいと思います。