マルチゾーンでFirewalldをらくらく設定

#はじめに
先日、[firewalldで特定IPアドレスのみsshを許可する](https://qiita.com/kimisyo/items/553d8879808834077fb1) という記事を書いては見たものの、-add-rich-rule というオプションを使ってゴリゴリの特別ルールでの対応がどうも引っかかった。Firewalldといえばiptablesの後継として比較的新しいライブラリであり、もっと洗練されたうまいやり方があるはずだと。

そこで色々調べた結果、あったあった。その名も**マルチゾーン**機能。この機能を使うことにより、かなり柔軟かつ効率的に設定できることが分かったので今回調べたことを共有したい。

#環境
- CentOS 8
- firewalld 0.8.0-4

#Firewalldの利点
まずはおさらいとして、Firewalldって何がいいのか見てみよう。
参考文献[1]によると、iptablesに対するFirewallの利点は以下とされている。

- 送信元IPやネットワークインターフェイスで定義されたゾーンごとに、拒否、許可等のルールを定義することができる。
- ポートやプロトコルではなく、サービスの名前を指定することができるため、構文が簡略化される
- iptablesのように、ステートメントの順序を気にする必要がない。
- リロード機能などにより、一時的に変更するなどのインタラクティブな設定変更が可能である。

なるほど、**ゾーン**をうまく活用することが重要そうだ。
そういえば前回の私の記事は、publicゾーンに無理やりルールを追加していた。

#ゾーンとは
次にゾーンについて見てみよう。
これまた参考文献[1]によると、ゾーンを理解するにあたって、**Intarface**と**Source**が重要な概念であることが分かる。

###Interface
- Intafaceはハードウェアおよび仮想ネットワークアダプタのシステム名である。
- 全てのアクティブなIntafaceは、デフォルトゾーンまたはユーザー指定のゾーンのいずれかにゾーンに割り当てられる。
- Intafaceを複数のゾーンに割り当てることは**できない**。
- デフォルトの設定では、firewalldは全Interfaceをpublicゾーンに紐づけており、かつどのゾーンにもSourceを設定していない。その結果、publicゾーンが**唯一の**アクティブゾーンになっている。

### Source 

- Sourceは送信元のIPアドレス範囲のことであり、ゾーンに割り当てることもできる。
- Sourceを複数のゾーンに割り当てることは**できない**。これができてしまうと、そのSourceにどのルールを適用する必要があるか分からなくなるためである。
- あるSourceに一致するSourceを持つゾーンが必ずしも存在するわけではない。その場合何等かの優先順位によって処理されるが詳しくは後述する。

ゾーンそのものの説明はしていないが、何となく分かっただろうか？

#で、マルチゾーンとは
マルチゾーン(Multi-Zone)とは、参考文献[1]で使われている用語であるが、要するに1つのゾーンだけではなく、**複数のアクティブゾーン**を使うことによって、**うまいこと**ルールを設定する方法と考えてもらえばよい。

たびたびお世話になるが、参考文献[1]によると、マルチゾーンの場合のFirewalldの適用ルールはざっくりと以下の通りとなる
（注) かなり端折って書いているため、厳密に理解されたい場合、参考文献[1]、またはFirewalldのマニュアルを見てください.

- アクティブゾーンはIntafaceが紐づけられたゾーンか、Sourceが紐づけられたゾーンかによって役割が異なる。ただし両方の役割を果たすことも可能である。
- Firewalldは、次の順序でパケットを処理する。

1. パケットに対応するSourceをもつゾーンがあった場合(ない場合もある)、パケットがRich Ruleを満たしている、サービスがホワイトリストに登録されているなどの理由により、このゾーンで処理されれば終了する。 そうでない場合次に進む。
2. パケットに対応するInterfaceをもつゾーンにより処理されれば終了する。そうでない場合次に進む。
3. Firewalldのデフォルトアクションが適用される。すなわちicmpパケットは受け入れ、それ以外はすべて拒否される。

重要なことは、SourceゾーンがInterfaceゾーンよりも優先される、ということである。したがって、マルチゾーン構成の一般的な設計パターンとしては、Sourceゾーンにより特定のIPに対し特定のサービスへのアクセスを許可するようにし、Interfaceゾーンでは他の全てのユーザに対してアクセスを制限することになる。

#疑似構成でやってみよう
では、次から実例を元にやってみよう。

### ネットワーク構成
今回はこんな構成で進めることとしたい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/a9f55d74-1cb7-9917-4102-5de8b1e7e419.png)

概要を説明しよう。

- 色付けしたWEBサーバが今回Firwalldを設定するサーバである。
- WEBサーバはhttp/httpsにより、インターネット上にサービスを提供する。
- バックアップサーバは、WEBサーバのシステムをバックアップするサーバであり、WEBサーバ上のMySQLサーバとNFSサーバにアクセスする。
- メンテナンス端末は、WEBサーバのメンテナンスを行う端末であり、WEBサーバにsshで接続する。
- DMZにつなげるべきではないとか、グローバルIPアドレスがおかしいというツッコミはあると思うが、今回は話を単純にするためにこれでいかせてほしい。

### 実現したいFirewalldルール
で、文章で書くと曖昧になるため、WEBサーバでパケットを許可するルールを表にしてみる。

| 送信元 | プロトコル | ゾーン名 |ゾーン役割 | 
|:-----------:|:------------:|:------------:|:------------:|
| ANY       | https, http        | public         | Interfaceゾーン |
| 192.168.11.24       | mysql, nfs  | share         |　Sourceゾーン  |
| 192.168.11.29      |ssh        | mainte        |Sourceゾーン  |

上以外は全て拒否するルールとなる。
なお、すでに表に「ゾーン名」「ゾーン役割」欄があり、public、share、mainteといった名前のゾーンの記載があるが、まさにこれから、**これらのマルチゾーン**によって、実際に上記ルールを設定していこう。

### publicゾーンの設定
publicゾーンはデフォルトのものを修正する感じになる。

```sh
firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
firewall-cmd --permanent --zone=public --remove-service=cockpit
firewall-cmd --permanent --zone=public --remove-service=ssh
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --set-target=DROP
firewall-cmd --reload
```

- まず上の3行は、最初からpublicゾーンにサービスとして入っていたため、削除している（入っていない場合は無視してほしい)。
- 4, 5行目が全てのユーザ向けにhttps, httpを許可する設定である。
- 6行目はssh対策である。内部ゾーン外のIPからサーバーにSSHで接続しようとした場合に, DENYではなく、DROPをした方が安全だからのようである。(これによってpingも通らなくなるが、回避策は参考文献[1]を参照のこと)

### mainteゾーンの設定
mainteゾーンは、元々存在しないため新規に作成した上で設定する。

```sh
firewall-cmd --permanent --new-zone=mainte
firewall-cmd --permanent --zone=mainte --add-service=ssh
firewall-cmd --permanent --zone=mainte --add-source=192.168.11.29
firewall-cmd --reload
```

2行目で許可するサービスとしてssh、3行目で許可する送信元IPとしてメンテナンス端末のIPを指定している。

### shareゾーンの設定
shareゾーンも、元々存在しないため新規に作成した上で設定する。

```sh
firewall-cmd --permanent --new-zone=share		
firewall-cmd --permanent --zone=share --add-port=2049/tcp		
firewall-cmd --permanent  --zone=share --add-port=3306/tcp		
firewall-cmd --permanent --zone=share  --add-source=192.168.11.24		
firewall-cmd --reload		
```

2, 3行目で許可するポートとしてnfs, mysqlのポートを指定し、4行目で、許可する送信元IPとしてバックアップサーバのIPを指定している

### かくにん！
まずは、メンテナンス端末からnmapを実行してみよう。

```sh
[kimisyo@localhost ~]$ nmap 192.168.11.26
Starting Nmap 7.70 ( https://nmap.org ) at 2020-10-25 20:59 JST
Nmap scan report for 192.168.11.26
Host is up (0.00063s latency).
Not shown: 997 filtered ports
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https
```
確かにhttp/httpsポートに加え、sshが見えている。(逆にnfs, mysqlは見えていない)

次に、バックアップサーバからnmapを実行してみよう。

```sh
(base) kimisyo@ubuntu20:~$ nmap 192.168.11.26
Starting Nmap 7.80 ( https://nmap.org ) at 2020-10-25 11:59 UTC
Nmap scan report for 192.168.11.26
Host is up (0.0011s latency).
Not shown: 996 filtered ports
PORT     STATE SERVICE
80/tcp   open  http
443/tcp  open  https
2049/tcp open  nfs
3306/tcp open  mysql
```
http/httpsポートに加え、2049/tcpと3306/tcpが見えている。(逆にsshは見えていない)


#おわりに
最後に、実際にやってみて感じたマルチゾーンのメリットをまとめてみる。

- 1ゾーンだとプロトコル(サービス)と送信元IPアドレスの組み合わせが多くなった場合、サービスや送信元IPの変更による影響が大きいが、マルチゾーンだとゾーンにadd-serviceや、add-sourceだけで済む。例えば、今回の例でメンテナンス端末に別の端末を追加する場合 ```firewall-cmd --permanent --zone=mainte  --add-source=192.168.11.19``` のようなルールを追加するだけで済む。
- 参考文献[1]にもあるが、不正アクセスしてくる特定のIPを除外するといったことが簡単に行える。具体的には、全ての接続をDROPするDROPゾーンを作っておき、そのゾーンにadd-sourceで除外したいIPを加えるだけで済む。
- あるゾーンに含まれる送信元IP群に対して、許可するサービスを追加したり除去したりすることをまとめて行うことができる。
- まさに柔軟さは無限大である。詳しく知りたい方は是非、参考文献や、Firewalldのドキュメントを見てほしい。


#参考文献
- [1][Understanding Firewalld in Multi-Zone Configurations](https://www.linuxjournal.com/content/understanding-firewalld-multi-zone-configurations)