Ansibleでufwによるファイアウォール構築を試す
要約
- Ubuntu Server 22.04 LTSに標準でインストールされているファイアウォール管理ツールであるufwを試した。
- 検証にはAnsibleを用い、community.general.ufwを通して動作を確認した。
- ポートや送信元/宛先に基づくパケットフィルタくらいのシンプルな用途であれば十分に使えそうに感じられた。
- community.general.ufwモジュールの各種パラメータにおいて、いくつかのパラメータ間に依存関係があるのでその点は注意が必要。
はじめに
勉強のためにufwを使ってLinuxベースのファイアウォール構築を試しました。
ufwのuは"Uncomplicated"の略であり、その名の通り素のiptablesなどと比べて直感的なインターフェースを提供しています。
★参考:ufw in Launchpad
ufwは、iptablesでは可能な細やかな制御(パケットの特定フィールドの内容に基づくフィルタルール設定、カスタムチェーンの作成など)ができない反面、使いやすさに重きを置いているようです。
調べる前はホスト型ファイアウォール専用という先入観がありましたが、実際はそんなことはなく、後述の通り転送パケットに対してもフィルタの設定は可能です。
ポートや送信元/宛先を指定して許可/拒否するくらいの、アプライアンスを導入するまでもないファイアウォール構築であれば丁度良いツールかもしれません。
検証
Ansibleのcommunity.general.ufwを使い検証しました。
★参考:community.general.ufw module – Manage firewall with UFW — Ansible Documentation
具体的には以下のようなシナリオを想定しています。
- ネットワーク型ファイアウォールとして構築
- 当該ファイアウォールは4つのインターフェースを持ち、それぞれglobal、dmz、internal、managementの4つのセグメントに接続されている
- 各セグメントとネットワークアドレスの対応関係は下記の通り
- global: 192.168.201.0/24
- dmz: 192.168.202.0/24
- internal: 192.168.203.0/24
- management: 10.0.2.0/24
- 設定するルールは下記の通り
上記に基づき、下記のようなPlaybookとvarsファイルを作成しました。
Playbook
--- - name: Set Default Deny Policy become: true community.general.ufw: direction: "{{ item }}" policy: deny loop: - incoming - routed - name: Set Allow Rule To Incoming become: true community.general.ufw: rule: allow comment: "{{ item.comment }}" from_ip: "{{ item.from_ip }}" interface_in: "{{ item.if_in }}" port: "{{ item.port }}" loop: "{{ fw_allow_incoming_list }}" - name: Set Allow Rule To Routing become: true community.general.ufw: rule: allow comment: "{{ item.comment }}" route: true from_ip: "{{ item.from_ip }}" interface_in: "{{ item.if_in }}" to_ip: "{{ item.to_ip }}" interface_out: "{{ item.if_out }}" port: "{{ item.port }}" loop: "{{ fw_allow_routing_list }}" - name: Set UFW ON become: true community.general.ufw: state: enabled
上記のPlaybookについて簡単に説明します。
まず"Set Default Deny Policy"で、ファイアウォール自身への接続および転送パケットのフィルタリングのポリシーをDeny(原則許可しない)にしています。
続けて"Set Allow Rule To Incoming"で、ファイアウォール自身への接続を許可するためのルールを設定しています。
ルールの設定には送信元IPアドレス、入力インタフェース、宛先ポートの3つを指定しています。
また"comment"のパラメータにより、そのルールの意図を記録できるようにしています。
"Set Allow Rule To Routing"で、ファイアウォールのパケットの転送を許可するためのルールを設定しています。
ルールの設定には送信元IPアドレス、入力インタフェース、宛先IPアドレス、出力インタフェース、宛先ポートの5つを指定しています。
最後に"Set UFW ON"により、ufwを有効化しています。
このufwの有効化は、ポリシーをDenyにしてから許可するルールを設定する前に実行してしまうと、対象のファイアウォールへの通信が一切できなくなってしまうため注意が必要です。
このPlaybookに対して、下記のように変数を設定することで、想定シナリオに合ったルールを設定します。
varsファイル
--- fw_allow_incoming_list: - comment: "management -> self: SSH" from_ip: 10.0.2.0/24 if_in: enp0s3 port: 22 fw_allow_routing_list: - comment: "global -> dmz: HTTP" from_ip: 192.168.201.0/24 if_in: enp0s8 to_ip: 192.168.202.0/24 if_out: enp0s9 port: 80 - comment: "global -> dmz: HTTPS" from_ip: 192.168.201.0/24 if_in: enp0s8 to_ip: 192.168.202.0/24 if_out: enp0s9 port: 443 - comment: "internal -> global: SSH" from_ip: 192.168.203.0/24 if_in: enp0s8 to_ip: 192.168.201.0/24 if_out: enp0s9 port: 22 - comment: "internal -> global: HTTP" from_ip: 192.168.203.0/24 if_in: enp0s8 to_ip: 192.168.201.0/24 if_out: enp0s9 port: 80 - comment: "internal -> global: HTTPS" from_ip: 192.168.203.0/24 if_in: enp0s8 to_ip: 192.168.201.0/24 if_out: enp0s9 port: 443 - comment: "internal -> dmz: SSH" from_ip: 192.168.203.0/24 if_in: enp0s8 to_ip: 192.168.202.0/24 if_out: enp0s9 port: 22 - comment: "internal -> dmz: HTTP" from_ip: 192.168.203.0/24 if_in: enp0s8 to_ip: 192.168.202.0/24 if_out: enp0s9 port: 80 - comment: "internal -> dmz: HTTPS" from_ip: 192.168.203.0/24 if_in: enp0s8 to_ip: 192.168.202.0/24 if_out: enp0s9 port: 443
Ansibleの実行結果は下記の通りです。
実行ログ
TASK [fw : Install Require Packages] ******************************************* ok: [extfw0101] TASK [fw : Set Kernel Parameter] *********************************************** ok: [extfw0101] TASK [fw : Set Default Deny Policy] ******************************************** ok: [extfw0101] => (item=incoming) ok: [extfw0101] => (item=routed) TASK [fw : Set Allow Rule To Incoming] ***************************************** changed: [extfw0101] => (item={'comment': 'management -> self: SSH', 'from_ip': '10.0.2.0/24', 'if_in': 'enp0s3', 'port': 22}) TASK [fw : Set Allow Rule To Routing] ****************************************** changed: [extfw0101] => (item={'comment': 'global -> dmz: HTTP', 'from_ip': '192.168.201.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.202.0/24', 'if_out': 'enp0s9', 'port': 80}) changed: [extfw0101] => (item={'comment': 'global -> dmz: HTTPS', 'from_ip': '192.168.201.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.202.0/24', 'if_out': 'enp0s9', 'port': 443}) changed: [extfw0101] => (item={'comment': 'internal -> global: SSH', 'from_ip': '192.168.203.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.201.0/24', 'if_out': 'enp0s9', 'port': 22}) changed: [extfw0101] => (item={'comment': 'internal -> global: HTTP', 'from_ip': '192.168.203.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.201.0/24', 'if_out': 'enp0s9', 'port': 80}) changed: [extfw0101] => (item={'comment': 'internal -> global: HTTPS', 'from_ip': '192.168.203.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.201.0/24', 'if_out': 'enp0s9', 'port': 443}) changed: [extfw0101] => (item={'comment': 'internal -> dmz: SSH', 'from_ip': '192.168.203.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.202.0/24', 'if_out': 'enp0s9', 'port': 22}) changed: [extfw0101] => (item={'comment': 'internal -> dmz: HTTP', 'from_ip': '192.168.203.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.202.0/24', 'if_out': 'enp0s9', 'port': 80}) changed: [extfw0101] => (item={'comment': 'internal -> dmz: HTTPS', 'from_ip': '192.168.203.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.202.0/24', 'if_out': 'enp0s9', 'port': 443}) TASK [fw : Set UFW ON] ********************************************************* changed: [extfw0101] PLAY RECAP ********************************************************************* extfw0101 : ok=7 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
対象のファイアウォールのマシンにログインし、ufwのルールのリストを表示させた結果は下記の通りです。
vagrant@extfw0101:~$ sudo ufw status verbose Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), deny (routed) New profiles: skip To Action From -- ------ ---- 22 on enp0s3 ALLOW IN 10.0.2.0/24 # management -> self: SSH 192.168.202.0/24 80 on enp0s9 ALLOW FWD 192.168.201.0/24 on enp0s8 # global -> dmz: HTTP 192.168.202.0/24 443 on enp0s9 ALLOW FWD 192.168.201.0/24 on enp0s8 # global -> dmz: HTTPS 192.168.201.0/24 22 on enp0s9 ALLOW FWD 192.168.203.0/24 on enp0s8 # internal -> global: SSH 192.168.201.0/24 80 on enp0s9 ALLOW FWD 192.168.203.0/24 on enp0s8 # internal -> global: HTTP 192.168.201.0/24 443 on enp0s9 ALLOW FWD 192.168.203.0/24 on enp0s8 # internal -> global: HTTPS 192.168.202.0/24 22 on enp0s9 ALLOW FWD 192.168.203.0/24 on enp0s8 # internal -> dmz: SSH 192.168.202.0/24 80 on enp0s9 ALLOW FWD 192.168.203.0/24 on enp0s8 # internal -> dmz: HTTP 192.168.202.0/24 443 on enp0s9 ALLOW FWD 192.168.203.0/24 on enp0s8 # internal -> dmz: HTTPS
意図した通りにルールがセットされていることが分かります。
所感
上記の通りcommunity.general.ufwでシンプルにファイアウォールの設定ができますが、試した際にいくつか注意するべきと感じたポイントがありました。
パラメータ間の依存関係
公式ドキュメントに記載の通り、複数同時に設定できないパラメータがあります。
"interface"が"interface_in"および"interface_out"と同時に使えない、というのは分かりやすいのですが、"direction"が"interface_in"および"interface_out"と同時に使えない、というのはやや直感的ではないかもしれません。
また"interface_in"と"interface_out"が設定されており、かつ"route"が設定されていない場合には下記のようなエラーが出力されます。
TASK [fw : Set Allow Rule To Routing] ****************************************** failed: [extfw0101] (item={'comment': 'global -> dmz: HTTP', 'from_ip': '192.168.201.0/24', 'if_in': 'enp0s8', 'to_ip': '192.168.202.0/24', 'if_out': 'enp0s9', 'port': 80}) => {"ansible_loop_var": "item", "changed": false, "item": {"comment": "global -> dmz: HTTP", "from_ip": "192.168.201.0/24", "if_in": "enp0s8", "if_out": "enp0s9", "port": 80, "to_ip": "192.168.202.0/24"}, "msg": "Only route rules can combine interface_in and interface_out"}
"Only route rules can combine interface_in and interface_out"というメッセージを見て「routeパラメータは使ってないはずなのにどういうことだ?」と一瞬混乱しましたが、該当箇所のコードを見て理解しました。
★参考:community.general/plugins/modules/ufw.py at main · ansible-collections/community.general · GitHub
要するに「"route"のパラメータ指定が無い、かつ、"interface_in"のパラメータ指定がある、かつ、"interface_out"のパラメータ指定がある」の場合にエラーになるということなので、後者2つの条件のみがtrueになった場合でも"Only route rules~" のエラーが出るということですね。