[Linux] firewalldでfirewallの設定をする

firewallは必要なアクセスを許可したり、外部から攻撃を防ぐために拒否したりするルールを決める仕組みです。Linuxのfirewallと言えば、かつては
 iptables が使われていましたが、最近のLinuxでは firewalld が主流となっています。新しく覚えなおす目的で基本的な操作方法をまとめました

# Firewalld の操作

Firewalld の操作は `firewall-cmd` コマンドを使います

firewalld のルールはデフォルト拒否となっていて、必要なサービスを許可していくホワイトリスト方式となってます

サービスは従来のようにポート番号で指定することもできますが、httpやsshと言ったサービス名で指定することもできます。ポート番号はネットワークの知識を持った人でないと敷居が高く感じられますが、サービス名で直感的に設定ができるようになったことが、firewalldのアドバンテージの1つと言えます

## 許可

### サービス名で指定する

書式) --add-service=%サービス名%

```shell-session:root_shell
# firewall-cmd --add-service=dns --zone=public
```

※zoneについては後に説明します

ルールを再起動しても(恒久的に)適用させたい場合には --permanent オプションを付けます

```shell-session:root_shell
# firewall-cmd --add-service=dns --zone=public --permanent
```

#### 用意されているサービスの一覧を見る

--get-services オプションで firewalld に定義されているサービス名を確認できます

```shell-session:root_shell
# firewall-cmd --get-services
RH-Satellite-6 amanda-client amanda-k5-client bacula bacula-client bitcoin bitcoin-rpc bitcoin-testnet bitcoin-testnet-rpc ceph ceph-mon cfengine cockpit condor-collector ctdb dhcp dhcpv6 dhcpv6-client dns docker-registry dropbox-lansync elasticsearch freeipa-ldap freeipa-ldaps freeipa-replication freeipa-trust ftp ganglia-client ganglia-master high-availability http https imap imaps ipp ipp-client ipsec iscsi-target kadmin kerberos kibana klogin kpasswd kshell ldap ldaps libvirt libvirt-tls managesieve mdns mosh mountd ms-wbt mssql mysql nfs nrpe ntp openvpn ovirt-imageio ovirt-storageconsole ovirt-vmconsole pmcd pmproxy pmwebapi pmwebapis pop3 pop3s postgresql privoxy proxy-dhcp ptp pulseaudio puppetmaster quassel radius rpc-bind rsh rsyncd samba samba-client sane sip sips smtp smtp-submission smtps snmp snmptrap spideroak-lansync squid ssh synergy syslog syslog-tls telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server
```

#### サービスの詳細を確認する

/usr/lib/firewalld/services にサービスの定義ファイルがあります。このファイルを見ることで開放されるポートを確認することができます

やってしまいそうですが、定義ファイルは直接編集してはダメだそうです。編集したい場合はこのファイルを `/etc/firewalld/services` にコピーした上で、コピーしたファイルを編集します。

#### 新しいサービスを定義する

必要なサービスが用意されていない場合は、新しいサービスを作ることができます。 `/etc/firewalld/services` に定義ファイルを配置します。書き方は `man firewalld.service` を参照ください。

例) IPsec用の定義ファイル

```xml:/etc/firewalld/services/ipsec.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>IPsec</short>
  <description>IPsec is a virtual private network (VPN) solution. It is used to create encrypted point-to-point tunnels between computers. If you plan to provide a VPN service, enable this option.</description>
  <port protocol="udp" port="500"/>
  <port protocol="udp" port="1701"/>
  <port protocol="udp" port="4500"/>
</service>
```

変更後に `firewall-cmd --reload` を実行します。reloadした後に `--add-service=%サービス名%` で追加できるようになります

例) 作成したipsecルールを追加する

```shell-session:root_shell
定義ファイルを作成後
# firewall-cmd --reload
# firewall-cmd --add-service=ipsec --zone=public --permanent
```

### ポート番号で指定する

書式) --add-port=%ポート番号%/%プロトコル%

例) DNSサーバー用のポートのアクセスを許可する

```shell-session:root_shell
# firewall-cmd --add-port=53/udp --zone=public --permanent
# firewall-cmd --add-port=53/tcp --zone=public --permanent
```

ポート番号を複数指定する場合は開始番号と終了番号を "-" で区切って範囲を指定します

例) 138番 から 139 番のポートのアクセスを許可する

```shell-session:root_shell
# firewall-cmd --add-port=138-139/udp --zone=public --permanent
```

## 削除

`--remove-service` または `--remove-port` を使います。書式は `--add-service`、`--add-port` と同じです

## リロード

追加、削除したルールを適用するには --reload を実行します

```shell-session:root_shell
# firewall-cmd --reload
```

## 許可したサービスを確認する

* --list-all  (許可したサービスとポートの両方を表示)
* --list-services  (許可したサービスを表示)
* --list-port  (許可したポートを表示)

例) public zoneで許可したサービスとポートを確認する

```shell-session:root_shell
# firewall-cmd --list-all --zone=public
public
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: ssh mdns dhcpv6-client
  ports:
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules:
```

# 補足：zoneについて

firewalldには zone という iptables には無かった概念があります。これはどのインタフェースがどの場所(zone)に繋がっているかを示す物で、これを適切に設定することで、より直感的にfirewallを設定することができます

iptablesでは eth0 が内部に繋がって、eth1 が外部に繋がって。。。ということを考えながら設定してました。設定する人によってどのインタフェースに繋ぐかもバラバラだったので、設定ファイルを使いまわすにも注意が必要でした。zoneで決められれば流用も簡単になります

zoneは以下の物が用意されているようです。

| zone名 | 説明 |
|----------|------|
| public | デフォルトで使用されるルール |
| work | 会社のネットワークで想定されるルール (信頼できるネットワーク用) |
| home | 家庭内での利用を想定したルール (workより緩い) |
| internal | ファイアウォールを構築するのに使うルール。内側のインタフェース用 |
| external | ファイアウォールを構築するのに使うルール。外側のインタフェース用 |
| dmz　| ファイアウォールを構築するのに使うルール。DMZのインタフェース用 |
| block	| 送信の戻りパケットだけを許可して、受信パケットを全て拒否するルール |
| drop | 送信の戻りパケットだけを許可して、受信パケットを全て破棄するルール |
| trusted | 全ての通信を許可するルール |

大まかには上の通りですが、 `/usr/lib/firewalld/zones` にあるxmlファイルを見ればルールの詳細が確認できます

なお、fedoraの場合(恐らくFedora-Server)は FedoraServer というzoneがあり、デフォルトでこのzoneに設定されています。
public ではなく、FedoraServer に対して設定してください。

手元の環境では /etc/sysconfig/network-scripts/ifcfg-%インタフェース% の"ZONE="の項目で適用されるzoneが指定されているようでした

Fedora release 26 (Twenty Six) の場合は FedoraServer でした

```shell-session:root_shell
# grep ZONE /etc/sysconfig/network-scripts/ifcfg-eth0
ZONE=FedoraServer
```

CentOS Linux release 7.4.1708 (Core) の場合は public でした

```shell-session:root_shell
# grep ZONE /etc/sysconfig/network-scripts/ifcfg-ens35
ZONE=public
```

後で気づいたのですが --get-zone-of-interface オプションでも確認できました

```shell-session:shell
$ firewall-cmd --get-zone-of-interface=eth0
FedoraServer
```

# 補足：SELinuxについて

ファイアウォールは正しく設定しているはずなのに、それでも疎通ができない場合は SELinux によってガードされている可能性もあります。
`setenforce 0` を実行して SELinux を無効にする例もよく見かけますが、無効にする前に audit ログを見てみましょう

例) sshd が 2200 番ポートの開放を拒否された時のログ

```shell-session
# tail /var/log/audit/audit.log

    :   (略)   :

type=SERVICE_STOP msg=audit(1513372812.700:6209): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=user@0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
type=AVC msg=audit(1513372876.398:6210): avc:  denied  { name_bind } for  pid=652 comm="sshd" src=2200 scontext=system_u:system_r:sshd_t:s0-s0:c0.c1023 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
type=AVC msg=audit(1513372876.399:6211): avc:  denied  { name_bind } for  pid=652 comm="sshd" src=2200 scontext=system_u:system_r:sshd_t:s0-s0:c0.c1023 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
```

このようにポート番号を変えた時によくハマります。以下のようにデーモンに対して新しいポート開放を許可を与えれば良いです

例) sshのポートを新しく追加する

```shell-session:root_shell
# semanage port -a -t ssh_port_t -p tcp %ポート番号%
```

`-t` オプションに指定するのはSELinuxのタイプです

※ semanageコマンドが無い場合は `policycoreutils-python` のパッケージをインストールしてください

```shell-session:root_shell
# yum install policycoreutils-python
```

許可されているポートは次のようにして確認できます

例) httpに関係するタイプのポートを確認する

```shell-session:root_shell
# semanage port -l |grep http
http_cache_port_t              tcp      8080, 8118, 8123, 10001-10010
http_cache_port_t              udp      3130
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t            tcp      5988
pegasus_https_port_t           tcp      5989
```