朝から昼寝

整理しておきたいIT関連の小ネタ

Hyper-Vゲストの通信をホスト側で制限(拡張ACL: Add-VMNetworkAdapterExtendedAcl)

概要

仮想環境において、ゲストの通信を必要最低限の範囲に制限しておきたい場合があります。特に、ネットワーク側のファイアウォール設定だけでは都合が悪い場合、ホスト側で制限をかけられると便利です。
本記事は、Hyper-V環境でAdd-VMNetworkAdapterExtendedAclコマンドレット(拡張ACL)によりゲストの通信を制限する方法についてまとめたものです。Windows 10上のHyper-Vで確認したものですが、Windows Serverでも同様です。
この拡張ACLは便利ですが、Microsoftのページを含む公開情報では欲しい説明を探しづらく、仕様把握に手間取りました。事前に知っておきたかったと思うポイントを整理しておきます。

本記事の目的

  • Hyper-V環境で拡張ACLの使用方法を把握する。

基本

拡張ACLの概要とユースケース

  • 概要

    • Hyper-Vホスト上のWindowsファイアウォールは、ゲストの通信に対しては適用されません。そのため、ゲストの通信をホスト側で制限したい場合にはAdd-VMNetworkAdapterExtendedAclのような別の設定が必要です。
    • Add-VMNetworkAdapterExtendedAclは、Hyper-V上の各仮想ネットワークアダプタに対し、アクセス制御ルール(ACL)を作成するコマンドレットです。Add-VMNetworkAdapterAclACLと対比する場合等、"拡張ACL"と呼ぶこともあります。
    • IPアドレスやポート番号によるACLを作成可能です。
    • 簡易的な実装ではあるものの、各ゲストOS内の設定でなくホスト側で集中的にアクセス制御できる利点があります。
    • 拡張ACLは、初期状態では未定義であり、ゲストの通信は全て許可されます。
    • 仮想スイッチの種類に関わらず拡張ACLを使用可能です。補足事項は後述します。
    • NAT(WinNAT)の有無に関わらず、拡張ACLを使用可能です。
    • 仮想マシンごと(仮想ネットワークアダプタごと)に定義が必要です。便利な一括設定の方法については後述します。

  • ユースケース
    以下のように、トラブル防止等の理由により、Add-VMNetworkAdapterExtendedAclを使用できます。

    • そのHyper-V環境をテストや検証のために使用する際、Hyper-Vの外にある本番環境に影響を与えないようにしたい
      • ゲストから特定のネットワーク以外に対する通信を遮断したい
      • あるいは、逆に特定のネットワークに対する通信を遮断したい
    • ゲストOSのローカルファイアウォール設定だけでなく、追加の対策としてホスト側でも通信を制限しておきたい
    • ネットワーク側のファイアウォール設定では都合がよくない
    • セキュリティポリシー上、ゲストに許可する通信を必要最低限の範囲に制限しておきたい

設定例

例として、以下の図に示すアクセス制御を実現するACLを設定してみます。

例として設定する拡張ACL
設定する拡張ACLの例

例1 (基本的な定義)

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -Weight 1 …(1)
Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Inbound -VMName "VM1" -Weight 1 …(1')

Add-VMNetworkAdapterExtendedAcl -Action Deny -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.0/16 -Weight 10 …(2)
Add-VMNetworkAdapterExtendedAcl -Action Deny -Direction Inbound -VMName "VM1" -RemoteIPAddress 172.16.0.0/16 -Weight 10 …(2')

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 192.168.0.0/24 -Weight 11 …(3)
Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Inbound -VMName "VM1" -RemoteIPAddress 192.168.0.0/24 -Weight 11 …(3')

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.1/32 -RemotePort 53 -Protocol TCP -Weight 21 -Stateful $True …(4)
Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.1/32 -RemotePort 53 -Protocol UDP -Weight 22 -Stateful $True …(4')

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.2/32 -RemotePort 443 -Protocol TCP -Weight 23 -Stateful $True …(5)

図中のVM1という仮想マシンに対してACLを設定します。

  • (1)、(1') デフォルトアクション/社外ネットワーク(インターネット)との通信

    • 他のどのACLにもマッチしない場合に適用するACLです。IPアドレスやポート番号の指定はありません。
    • -Weight 1は最後に評価されるACLという意味です。
    • 今回の例ではインターネット向けの通信を許可するため、-Action Allowとしています。拒否したい場合は-Action Denyを指定します。
    • 例なので明示的に定義していますが、この許可ACLは定義しなくても動作に変わりはありません(デフォルトでマッチするACLが無ければ許可されるため)。
      • デフォルトで拒否したい場合には、-Action DenyACL定義が必要です。
    • なお今回の例では、仮想マシンは社外に公開するサービスが無く、社外から仮想マシンに対する通信は社外向けファイアウォールにより遮断されることを前提にしています。

  • (2)、(2') 社内ネットワーク(172.16.0.0/16)との通信

    • 社内ネットワーク(172.16.0.0/16)に対する通信を拒否するACLです。
    • -Weightは10なので、-Weightが1~9のACLより優先されます。逆に-Weightが11以上のACLはこのACLより優先されます。

  • (3)、(3') Hyper-V内ネットワーク(192.168.0.0/24)との通信

    • Hyper-V内ネットワーク(192.168.0.0/24)に対する通信を許可するACLです。
    • 例なので明示的に定義していますが、この許可ACLは定義しなくても動作に変わりはありません(マッチするACLが無ければ許可されるため)。
      • (1)のデフォルトアクションが拒否である場合、このような個別の許可ACLの定義が必要です。

  • (4)、(4') DNSサーバ(172.16.0.1)との通信

    • DNSサーバ(172.16.0.1)に対する通信を許可するACLです。
      • (2)の拒否ACLよりこのACLが先に評価されるよう、-Weightの値を10より大きな値にしてあります。
    • DNSの通信ポート(53)を指定してあります。
      • DNS通信なのでTCPUDPの両方を許可します。
      • 同じポート番号ですが、-Protocol TCP-Protocol UDP、それぞれ定義が必要です。理由は後述します。
    • -Stateful $Trueにて、ステートフルなアクセス制御となるよう指定します。
      • このACL 1つで、DNSサーバに対する行きパケットが許可されると、その戻りパケットも自動的に許可されます。
      • -Direction Inboundの方向の拒否ACLが無い場合は、戻りパケットを許可する必要性が無いため、-Stateful $Trueの指定は不要です。

  • (5) Webサーバ(172.16.0.2)との通信

    • Webサーバ(172.16.0.2)との通信を許可するACLです。
    • (4)と同様ですが、HTTPSなのでTCPのみの定義です。

例2 (省略した定義)

次に、例1からできるだけACLを省略した場合の例を記載します。

以下のいずれかに該当する場合、Hyper-Vの外のネットワークとの通信に関する-Direction Inboundの方向の拒否ACLは不要です。

  • 仮想マシンがNAT(WinNAT)の有効な仮想ネットワーク配下にある場合
  • NAT以外の仮想ネットワーク構成で、Hyper-Vの外から仮想マシンに対する通信を一律許可したい場合

省略しても動作に変わりのないACLを省略、さらに-Direction Inboundの方向の拒否ACLを省略すると、以下のようになります。

Add-VMNetworkAdapterExtendedAcl -Action Deny -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.0/16 -Weight 10 …(2)

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.1/32 -RemotePort 53 -Protocol TCP -Weight 21 -Stateful $True …(4)
Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.1/32 -RemotePort 53 -Protocol UDP -Weight 22 -Stateful $True …(4')

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "VM1" -RemoteIPAddress 172.16.0.2/32 -RemotePort 443 -Protocol TCP -Weight 23 -Stateful $True …(5)

かなりシンプルになります。仮想スイッチの構成にもよりますが、図に記載のアクセス制御を実現するだけであれば、この定義で十分です。
(2')のInbound方向のACLを定義していないので、(4)~(5)の-Stateful $Trueは省略可です。

詳細

仮想スイッチの種類ごとの拡張ACLの適用

どの仮想スイッチを使用していても拡張ACLは適用できますが、考慮するポイントが少しずつ違うので整理しておきます。

なお、New-VMSwitchにより個別に作成した仮想スイッチでも使用可能です。

コマンドレット説明

Add-VMNetworkAdapterExtendedAcl

以下のように実行できるコマンドです。

Add-VMNetworkAdapterExtendedAcl -Action Allow -Direction Outbound -VMName "仮想マシン名" -RemoteIPAddress x.x.x.x/x -RemotePort x -Protocol x -Weight x -Stateful $True

主要なオプションは以下です。

  • -Action(必須)

    • パケットが条件にマッチした際のアクションです。
    • 指定可能な値は、Allow(許可)、Deny(拒否)のいずれかです。

  • -Direction(必須)

    • そのACLを適用するパケットの方向です。
    • 指定可能な値は、Inbound、Outboundのいずれかです。
    • 省略時に両方向に適用されてくれたりすると定義をシンプルにできるのですが、残念ながら必須オプションです。

  • -Weight(必須)

    • ACLエントリに対する優先度指定です。大きな値のACLが先に適用されます。
      • いったんACLがパケットに適用されると、そのパケットには他のACLエントリは適用されません。
    • 指定可能な値は、1以上の整数です(型はINT32のようです)。
      • 大規模なACL設計でなければ、例えば1~100程度でも十分でしょう。
    • 一般的なファイアウォールのルールと同様に考えると良いでしょう。
      • ACLの数が多い場合には、狭いスコープの条件から広いスコープの条件の順に優先されるよう定義する等、設計上のポリシーを決めておくと良いです。

  • -LocalIPAddress-RemoteIPAddress(任意)

  • -LocalPort-RemotePort(任意)

    • そのACLを適用するポート番号です。TCPUDPに対して適用できます。
    • -LocalPort仮想マシン側のポート番号、-RemotePortがその通信先のポート番号です。
    • 指定可能な値は、以下のいずれかです。
      • "443"のように単一のポート番号
      • "49152-49182"のようにポート番号の範囲
    • 省略時は全てのポート番号が対象になります。

  • -Protocol(任意)

  • -Stateful(任意)

    • ステートフルなアクセス制御とするかどうかを指定します。
      • -Stateful $Trueを指定した許可ACLは、そのACL 1つで行きパケットが許可されると、その戻りパケットも自動的に許可されます。
    • 指定可能な値は、Bool値です(PowerShellの"$True"や"$False"を指定可能)。
    • -Statefulを指定する際には、-Protocolの指定が必須のようです。詳細は後述します。
    • 省略時はステートフルなアクセス制御を行いません。

  • -IdleSessionTimeout(任意)

    • 無通信タイムアウトを秒単位で指定します。
    • 指定可能な値は、1以上の整数です(型はINT32のようです)。
    • 省略時は255が設定されるようです(Get-VMNetworkAdapterExtendedAclの結果より)。

Get-VMNetworkAdapterExtendedAcl

以下のように実行できるコマンドです。

`Get-VMNetworkAdapterExtendedAcl -VMName "仮想マシン名"

定義済みのACLの内容を出力します。

以下、出力例です(一部省略してあります)。 デフォルトで仮想マシン(仮想ネットワークアダプタParentAdapter)ごとにソートはされるようです。

Direction          : Outbound
Action             : Allow
LocalIPAddress     : ANY
RemoteIPAddress    : 172.16.0.1/32
LocalPort          : ANY
RemotePort         : 53
Protocol           : TCP
Weight             : 21
Stateful           : True
IdleSessionTimeout : 255
IsolationID        : 0
ParentAdapter      : VMNetworkAdapter (Name = 'ネットワーク アダプター', VMName = 'VM1') [VMId = 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx']
IsTemplate         : False
CimSession         : CimSession: .
ComputerName       : DESKTOP-XXXXXX
IsDeleted          : False

…

Direction          : Outbound
Action             : Allow
LocalIPAddress     : ANY
RemoteIPAddress    : 192.168.0.0/24
LocalPort          : ANY
RemotePort         : ANY
Protocol           : ANY
Weight             : 11
Stateful           : False
IdleSessionTimeout : 0
IsolationID        : 0
ParentAdapter      : VMNetworkAdapter (Name = 'ネットワーク アダプター', VMName = 'VM1') [VMId = 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx']
IsTemplate         : False
CimSession         : CimSession: .
ComputerName       : DESKTOP-XXXXXX
IsDeleted          : False

…

Direction          : Inbound
Action             : Allow
LocalIPAddress     : ANY
RemoteIPAddress    : 192.168.0.0/24
LocalPort          : ANY
RemotePort         : ANY
Protocol           : ANY
Weight             : 11
Stateful           : False
IdleSessionTimeout : 0
IsolationID        : 0
ParentAdapter      : VMNetworkAdapter (Name = 'ネットワーク アダプター', VMName = 'VM1') [VMId = 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx']
IsTemplate         : False
CimSession         : CimSession: .
ComputerName       : DESKTOP-XXXXXX
IsDeleted          : False

Remove-VMNetworkAdapterExtendedAcl

以下のように実行できるコマンドです。

特定のACLを1つ削除する場合は、-Direction-Weightを指定して実行します。
Remove-VMNetworkAdapterExtendedAcl -VMName "VM1" -Direction InBound -Weight 23

その仮想マシンに紐づくACLを全て削除する場合は、ACLをオブジェクトで渡して実行します。
Get-VMNetworkAdapterExtendedAcl -VMName "VM1" | Remove-VMNetworkAdapterExtendedAcl

注意点として、ACLの削除が成功しても失敗しても何も出力されません。このコマンドを実行する前後でGet-VMNetworkAdapterExtendedAclの結果をテキストに保存しておき、差分チェックすると安心です。

-Stateful $True指定時に-Protocol指定が必須な理由と対処

Microsoftのページに記載は見当たりませんが、Add-VMNetworkAdapterExtendedAclでは、-Statefulを指定する際、-Protocolの指定が必須のようです。

その理由として、-Protocolの省略時は、全てのIPベースのプロトコルが対象となるようですが、このときTCPUDP以外の-Statefulに対応できないプロトコルも対象になってしまうためと思われます。例えば拡張ACLでは、ICMPに対して-Statefulを指定できないので、行きと戻り両方のパケットにACLを適用する場合には、InboundとOutboundの両方の定義が必要です。

この動作により、DNS等、TCPUDPで同じポート番号を使用するプロトコルにステートフルなACLを適用する場合に配慮が必要となります。
具体的には、-Protocol TCPACLと、-Protocol UDPACLをそれぞれ定義することになります。
これは、-Protocolを省略すると-Stateful指定はできないという点、また-ProtocolTCPUDPの2つを指定することができないという点から、結果的にACLを複数定義する必要性があるということです。

拡張ACLは、一般的なファイアウォールに比べると簡易的な実装ではあるので、このあたりは注意して使うしかないでしょう。

Add-VMNetworkAdapterAclとAdd-VMNetworkAdapterExtendedAclの違い

Add-VMNetworkAdapterAcl(ACLの追加)とAdd-VMNetworkAdapterExtendedAcl(拡張ACLの追加)は、異なるコマンドレットです。
異なるACLを扱います。併用すべきではないでしょう。混在させた場合の動作は不明です。

後者の拡張ACLが新しい機能です。Windows Server 2012 R2から使用できるようになったようです。

前者のACLは、ポート番号を指定したACLやステートフルなACLを作成できません。ACL自体に優先度指定は無く、最長マッチしたACLが優先的に適用されます。

後者の拡張ACLの方が多機能です。ポート番号を指定したACLやステートフルなACLを作成できます。ACLごとに優先度指定(-Weight)が可能です。ただしMACアドレスベースのアクセス制御に関しては前者のACLにのみ備わっているようです。IPベースのACLと言えるでしょう。

また参考までに、構成によってはSCVMM経由で拡張ACLの管理ができるようです(portACL)。

拡張ACLの一括設定

仮想マシンを対象に拡張ACLを一括設定

拡張ACLは全仮想マシンに適用されるグローバルな定義を設定できません。

代替手段として、全仮想マシンの仮想ネットワークアダプタを対象に、一括設定する方法があります。

Get-VMNetworkAdapter -vmname "*" | Get-VMNetworkAdapterExtendedAcl > "C:\any\path\before.txt"

Get-VMNetworkAdapter -vmname "*" | Add-VMNetworkAdapterExtendedAcl …
Get-VMNetworkAdapter -vmname "*" | Add-VMNetworkAdapterExtendedAcl …
…(定義したい分、繰り返す)…

Get-VMNetworkAdapter -vmname "*" | Get-VMNetworkAdapterExtendedAcl > "C:\any\path\after.txt"
  • 全ての仮想マシン("*")を対象にGet-VMNetworkAdapterコマンドレットを実行して得られる仮想ネットワークアダプタのオブジェクトを、Add-VMNetworkAdapterExtendedAcl等にパイプで渡すと、一括で処理してくれます。

  • 例えば、仮想マシンを追加した際に実行するバッチとして準備しておくことで、全ての仮想マシンに対して一律同じ設定を維持できます。

  • なお、繰り返し全ての仮想マシンを対象にAdd-VMNetworkAdapterExtendedAclを実行すると、追加しようとするACLと同じACL(同じ-Direction-Weight)が既に存在することになります。この場合、そのACLの追加に関してはAdd-VMNetworkAdapterExtendedAclがエラーとなりますので、重複した内容のACLが登録されることはありません。ACLが未設定の仮想ネットワークアダプタに対してのみ追加されます。
    言い換えると、Add-VMNetworkAdapterExtendedAclACLの上書きをしません。

仮想マシンを対象に拡張ACLを一括削除

同様に、全仮想マシンの仮想ネットワークアダプタを対象に、既に定義されている拡張ACLを一括削除する方法です。

Get-VMNetworkAdapter -vmname "*" | Get-VMNetworkAdapterExtendedAcl  | Remove-VMNetworkAdapterExtendedAcl
もしくは
Get-VMNetworkAdapterExtendedAcl -VMName "*" | Remove-VMNetworkAdapterExtendedAcl

(参考)ホストWindows側の仮想ネットワークアダプタに対するACL設定

あまりユースケースが無さそうですが、ホストWindows側の仮想ネットワークアダプタに対してACL設定することもできるようです。
-ManagementOSオプションを使用します。このサイトに例が載っています。拡張ACLでなくVMNetworkAdapterACLの方ですが、拡張ACLでも-ManagementOSオプションは使用可能です。

以下、Add-VMNetworkAdapterAclで試した際のメモです。

参考URL等