CentOS
Ansible
firewalld

Ansibleでfirewalldを設定(ポート開閉)

More than 3 years have passed since last update.

CentOS7のfirewalldに対して、特定portを開閉する Ansible Playbook を書いた。

若干、素直な動作でなかったので、注意点をメモしておく。

★10/17更新:with_itemsを利用した別解を、Playbook2として掲載


お題

こんな感じのPlaybookを書く。


  • Firewalldが起動中のマシンだけ、設定する

  • portのON/OFFを即座に反映、マシンの再起動は不要

  • 永続的に設定、マシン再起動後も設定を維持

  • 既に設定済みの状況なら、余計なことをしない(changed=0)


利用イメージ

普通に実行

ansible-playbook -i hosts firewalld.yml

ポート状態を引数で指定: port 443をオープンする場合

ansible-playbook -i hosts firewalld.yml -e "port=443/tcp state=enabled"


Playbook1|notifyを利用


firewalld.yml

--- # file: firewalld.yml

- hosts: all
remote_user: root
gather_facts: False

vars:
port: 80/tcp
state: enabled # enabled: open, disabled: close

tasks:
- name: check if firewalld is running
command: systemctl is-active firewalld
register: firewalld_result
changed_when: False
ignore_errors: True # rc is 3 when firewalld is stopped

- name: set the port state
firewalld: permanent=True port={{ port }} state={{ state }}
notify: reload firewalld
when: firewalld_result.stdout == "active"

handlers:
- name: reload firewalld
service: name=firewalld state=restarted



注意点

上記Playbookでの注意点は3つ。


  1. permanent=True で書き換わるのは、設定ファイルのみ

  2. 設定ファイルが変化した時だけ、リロードするため、notifyを利用

  3. リロードでは restarted を使う: reloadedではダメだった

[補足] reloadedでは、portクローズについては即反映されるが、portオープンは反映されない様子。portクローズ&オープン両方とも即時反映するために、restarted を使っている。

また、少し脇道にそれるけれども。。。


  • 上記PlaybookをRole化して、Tag付けして利用する時は、tasks側とhandlers側の両方のファイルに、同じTagを付ける必要あり

というバグ?に引っかかってしまった。roleとtagの組み合わせは色々と動きがおかしいような。。。相性悪いのかな?


実行結果


firewalld が停止している時

$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
failed: [localhost] => {"changed": false, "cmd": ["systemctl", "is-active", "firewalld"], "delta": "0:00:00.003834", "end": "2014-10-16 17:10:22.305641", "rc": 3, "start": "2014-10-16 17:10:22.301807", "stdout_lines": ["inactive"]}
stdout: inactive
...ignoring

TASK: [set the port state] ****************************************************
skipping: [localhost]

PLAY RECAP ********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0


firewalld が起動中: disabled -> enabled

$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
changed: [localhost]

NOTIFIED: [reload firewalld] **************************************************
changed: [localhost]

PLAY RECAP ********************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0


firewalld が起動中: enabled -> enabled

$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
ok: [localhost]

PLAY RECAP ********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0


Playbook2|with_itemsを利用

Playbook1だと、より大きなPlaybookに組み込む場合には、望ましくない性質がある。

Handlers部分は、Playbook実行の最後に行われるので、Tasks部分でfirewalldを設定した後、何らかの後続の処理が失敗、実行が中断すると、Handlers部分のリロードが行われない。以降は、Playbookを再実行しても(既に設定は変更済みなわけだから)notifyもされず、設定ファイルは一生リロードされない。

場合によっては、Notifyを使うより、現在のサービス状態だけを見て、その場でサービス状態を変更するほうが良いかもしれない。

そのように書いたPlaybookが以下。


firewalld.yml

--- # file: firewalld.yml

- hosts: all
remote_user: root
gather_facts: False

vars:
port: 80/tcp
state: enabled # enabled: open, disabled: close

tasks:
- name: check if firewalld is running
command: systemctl is-active firewalld
register: firewalld_result
changed_when: False
ignore_errors: True # rc is 3 when firewalld is stopped

- name: set the port state
firewalld: permanent={{ item }} port={{ port }} state={{ state }}
with_items: [ True, False ]
when: firewalld_result.stdout == "active"


with_itemsでループを回している。

ループの1回めは item==True になり、設定ファイルのみ変更。

ループの2回めは、item==False になり、サービス状態のみ変更。

設定ファイルのリロードは無し。

小さくなったし、こっちの方がベターかな。

ただこの場合は、設定ファイルを複数回変更する場合は、まとめて1度だけリロードする、といった芸当はできなくなる。

リロード回数が重要な時は、Playbook1も良いかも。


実行結果


firewalld が停止している時

$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
failed: [localhost] => {"changed": false, "cmd": ["systemctl", "is-active", "firewalld"], "delta": "0:00:00.006989", "end": "2014-10-17 07:51:41.657187", "rc": 3, "start": "2014-10-17 07:51:41.650198", "stdout_lines": ["inactive"]}
stdout: inactive
...ignoring

TASK: [set the port state] ****************************************************
skipping: [localhost] => (item=True)
skipping: [localhost]

PLAY RECAP ********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0


firewalld が起動中: disabled -> enabled

with_itemsを使っているので、2つ変更しても changed=1 ですね。

$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
changed: [localhost] => (item=True)
changed: [localhost]

PLAY RECAP ********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0


firewalld が起動中: enabled -> enabled

$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
ok: [localhost] => (item=True)
ok: [localhost]

PLAY RECAP ********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0


参考:firewalldの状態確認

ポートの状態は、以下のコマンドで確認した。

# firewall-cmd --list-all

public (default)
interfaces:
sources:
services: dhcpv6-client ssh
ports: 80/tcp
masquerade: no
forward-ports:
icmp-blocks:
rich rules: