Ansible ile Firewall Kurallarının Merkezi Yönetimi: firewalld ve ufw Modülleri
Onlarca sunucuya tek tek bağlanıp firewall kuralı eklemek, sysadmin hayatının en sinir bozucu anlarından biridir. Bir kural eksik kalır, bir sunucuya farklı yazılmış olur, ya da kim ne zaman ne ekledi bilinmez. Ansible’ın firewall modülleri tam olarak bu kaosa son vermek için tasarlanmış. Bugün firewalld ve ufw modüllerini gerçek dünya senaryolarıyla ele alacağız.
Neden Merkezi Firewall Yönetimi?
Klasik yöntemde şöyle bir tablo ortaya çıkar: 20 sunucun var, her birine SSH açmışsın, bazılarına HTTP/HTTPS eklemişsin, bir kısmına uygulama portları var. Birisi bir sunucuya bağlanıp elle kural eklemiş ama dokümante etmemiş. Altı ay sonra o kurala neden ihtiyaç duyulduğunu kimse bilmiyor.
Ansible ile firewall yönetiminde her kural kod olarak yaşar, Git’te versiyonlanır, kim ne zaman hangi değişikliği yaptı görülür. Yeni bir sunucu kurduğunda aynı playbook’u çalıştırırsın, tüm kurallar otomatik uygulanır. Bu yaklaşımın getirdiği en büyük fayda idempotency: aynı playbook’u kaç kez çalıştırırsan çalıştır, sonuç değişmez.
Temel Kavramlar: firewalld ve ufw Farkı
İki modülü karıştırmadan önce hangisinin nerede kullanıldığını netleştirelim.
firewalld: RHEL, CentOS, Rocky Linux, AlmaLinux ve Fedora tabanlı sistemlerde varsayılan olarak gelir. Zone (bölge) kavramı üzerine kurulmuş, dinamik bir firewall yönetim daemon’ıdır. Servisleri yeniden başlatmadan kural ekleyip çıkarabilirsin.
ufw (Uncomplicated Firewall): Ubuntu ve Debian tabanlı sistemlerde yaygındır. iptables’ın üzerinde daha sade bir arayüz sunar. İsmi “karmaşık olmayan” demek ama yeterince kapsamlıdır.
Kurulum ve Ön Hazırlık
Önce inventory dosyamızı düzenleyelim. Gerçekçi bir senaryo için karma bir ortam düşünelim: bir kısmı RHEL/Rocky, bir kısmı Ubuntu sunucular.
# inventory/hosts.ini
[rhel_servers]
web01 ansible_host=192.168.1.10
web02 ansible_host=192.168.1.11
db01 ansible_host=192.168.1.20
[ubuntu_servers]
app01 ansible_host=192.168.1.30
app02 ansible_host=192.168.1.31
cache01 ansible_host=192.168.1.40
[all:vars]
ansible_user=sysadmin
ansible_become=true
ansible_become_method=sudo
RHEL sunucularında firewalld’nin kurulu ve çalışır olması gerekiyor. Ubuntu’da ufw paketinin kurulu olması gerekiyor. Bunları da Ansible ile halledebiliriz:
# playbooks/firewall_prerequisites.yml
---
- name: Firewall paketlerini hazirla
hosts: all
become: true
tasks:
- name: RHEL - firewalld kur ve baslat
ansible.builtin.package:
name: firewalld
state: present
when: ansible_os_family == "RedHat"
- name: RHEL - firewalld servisini aktif et
ansible.builtin.service:
name: firewalld
state: started
enabled: true
when: ansible_os_family == "RedHat"
- name: Ubuntu - ufw kur
ansible.builtin.package:
name: ufw
state: present
when: ansible_os_family == "Debian"
firewalld Modülü ile Kural Yönetimi
Temel firewalld Kullanımı
ansible.posix.firewalld modülü, firewalld’nin tüm özelliklerine erişim sağlar. En sık kullandığım parametreler:
state: enabled veya disabled – kuralın aktif olup olmadığı permanent: true veya false – sistem yeniden başlatıldığında da geçerli olsun mu immediate: true veya false – hemen uygulansın mı zone: Hangi zone’a uygulanacak (public, internal, dmz vb.) service: firewalld’nin tanıdığı servis isimleri (http, https, ssh vb.) port: Port numarası ve protokol (8080/tcp gibi)
Dikkat: permanent: true ile immediate: true birlikte kullanmazsanız, kurallar ya sadece şu an geçerli olur ya da sadece yeniden başlatmadan sonra. İkisini birlikte kullanmak en güvenli yaklaşım.
# playbooks/firewalld_web_servers.yml
---
- name: Web sunuculari icin firewall kurallari
hosts: rhel_servers
become: true
tasks:
- name: SSH portunu ac
ansible.posix.firewalld:
service: ssh
permanent: true
immediate: true
state: enabled
zone: public
- name: HTTP portunu ac
ansible.posix.firewalld:
service: http
permanent: true
immediate: true
state: enabled
zone: public
- name: HTTPS portunu ac
ansible.posix.firewalld:
service: https
permanent: true
immediate: true
state: enabled
zone: public
- name: Ozel uygulama portu ac (8443/tcp)
ansible.posix.firewalld:
port: 8443/tcp
permanent: true
immediate: true
state: enabled
zone: public
Zone Tabanlı Gelişmiş Konfigürasyon
Gerçek dünya senaryosunda genellikle sunucuların birden fazla ağ arayüzü olur. Örneğin bir veritabanı sunucusu hem dış ağa hem iç ağa bağlı olabilir. Bunu zone’larla yönetmek çok daha temiz:
# playbooks/firewalld_db_servers.yml
---
- name: Veritabani sunuculari icin zone yapilandirmasi
hosts: db01
become: true
tasks:
- name: Internal zone'a network arayzunu ata
ansible.posix.firewalld:
zone: internal
interface: eth1
permanent: true
immediate: true
state: enabled
- name: MySQL portunu sadece internal zone'da ac
ansible.posix.firewalld:
port: 3306/tcp
zone: internal
permanent: true
immediate: true
state: enabled
- name: Public zone'da sadece SSH izin ver
ansible.posix.firewalld:
service: ssh
zone: public
permanent: true
immediate: true
state: enabled
- name: Disaridan MySQL erisimini engelle (public zone'da kapali tut)
ansible.posix.firewalld:
port: 3306/tcp
zone: public
permanent: true
immediate: true
state: disabled
Rich Rule Kullanımı
Bazen basit port açma/kapamadan daha ince ayar gerekir. Belirli bir IP’den gelen trafiği kabul edip diğerlerini reddetmek, ya da rate limiting uygulamak isteyebilirsin. Bunun için rich rule’lar kullanılır:
# playbooks/firewalld_rich_rules.yml
---
- name: Rich rule ornekleri
hosts: rhel_servers
become: true
tasks:
- name: Sadece yonetim agından SSH izin ver
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept'
permanent: true
immediate: true
state: enabled
zone: public
- name: Belirli IP'den MySQL erisimi
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="192.168.1.30" port port="3306" protocol="tcp" accept'
permanent: true
immediate: true
state: enabled
zone: internal
- name: SSH brute force koruması icin rate limiting
ansible.posix.firewalld:
rich_rule: 'rule service name="ssh" limit value="10/m" accept'
permanent: true
immediate: true
state: enabled
zone: public
ufw Modülü ile Kural Yönetimi
Temel ufw Kullanımı
community.general.ufw modülü Ubuntu/Debian sistemler için kullanılır. firewalld’den biraz farklı bir syntax var ama mantık aynı.
Temel parametreler:
state: enabled, disabled, reloaded, reset rule: allow, deny, reject, limit port: Port numarası veya servis adı proto: tcp, udp, any from_ip: Kaynak IP adresi to_ip: Hedef IP adresi direction: in, out, routed delete: true olursa kuralı siler
# playbooks/ufw_app_servers.yml
---
- name: Ubuntu uygulama sunuculari icin ufw kurallari
hosts: ubuntu_servers
become: true
tasks:
- name: ufw'u aktif et (varsayilan deny ile)
community.general.ufw:
state: enabled
policy: deny
direction: incoming
- name: Giden trafiğe varsayilan izin ver
community.general.ufw:
policy: allow
direction: outgoing
- name: SSH'e izin ver
community.general.ufw:
rule: allow
port: "22"
proto: tcp
- name: HTTP portunu ac
community.general.ufw:
rule: allow
port: "80"
proto: tcp
- name: HTTPS portunu ac
community.general.ufw:
rule: allow
port: "443"
proto: tcp
- name: Node.js uygulama portunu ac
community.general.ufw:
rule: allow
port: "3000"
proto: tcp
comment: "Node.js uygulama portu"
IP Bazlı Kısıtlamalar
# playbooks/ufw_ip_restrictions.yml
---
- name: IP bazli erisim kuralları
hosts: ubuntu_servers
become: true
tasks:
- name: Sadece belirli IP'den Redis erisimi
community.general.ufw:
rule: allow
port: "6379"
proto: tcp
from_ip: "192.168.1.10"
comment: "Web sunucudan Redis erisimi"
- name: Yonetim agından tam erisim
community.general.ufw:
rule: allow
from_ip: "10.0.0.0/8"
comment: "Yonetim agi tam erisim"
- name: Bilinen kotu IP'yi engelle
community.general.ufw:
rule: deny
from_ip: "203.0.113.100"
comment: "Kotu IP engeli"
- name: SSH icin rate limiting uygula
community.general.ufw:
rule: limit
port: "22"
proto: tcp
comment: "SSH brute force koruması"
Rol Tabanlı Yaklaşım: Gerçek Dünya Senaryosu
Küçük playbook’lar güzel ama büyük ortamlarda rol (role) kullanmak çok daha yönetilebilir. Bir web sunucusu rolü için firewall konfigürasyonunu nasıl organize ederiz?
# roles/webserver/defaults/main.yml
---
firewall_allowed_ports:
- port: "80"
proto: tcp
- port: "443"
proto: tcp
firewall_allowed_services: # firewalld icin
- http
- https
- ssh
firewall_management_network: "10.0.0.0/8"
firewall_app_port: "8080"
# roles/webserver/tasks/firewall_rhel.yml
---
- name: RHEL - Temel servisleri ac
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
immediate: true
state: enabled
zone: public
loop: "{{ firewall_allowed_services }}"
- name: RHEL - Uygulama portunu ac
ansible.posix.firewalld:
port: "{{ firewall_app_port }}/tcp"
permanent: true
immediate: true
state: enabled
zone: public
- name: RHEL - Yonetim agından tam erisim
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="{{ firewall_management_network }}" accept'
permanent: true
immediate: true
state: enabled
zone: public
# roles/webserver/tasks/firewall_ubuntu.yml
---
- name: Ubuntu - ufw'u baslat
community.general.ufw:
state: enabled
policy: deny
direction: incoming
- name: Ubuntu - Temel portları ac
community.general.ufw:
rule: allow
port: "{{ item.port }}"
proto: "{{ item.proto }}"
loop: "{{ firewall_allowed_ports }}"
- name: Ubuntu - Uygulama portunu ac
community.general.ufw:
rule: allow
port: "{{ firewall_app_port }}"
proto: tcp
- name: Ubuntu - Yonetim agına tam erisim
community.general.ufw:
rule: allow
from_ip: "{{ firewall_management_network }}"
# roles/webserver/tasks/main.yml
---
- name: RHEL firewall yapilandirması
ansible.builtin.include_tasks: firewall_rhel.yml
when: ansible_os_family == "RedHat"
- name: Ubuntu firewall yapilandirması
ansible.builtin.include_tasks: firewall_ubuntu.yml
when: ansible_os_family == "Debian"
Bu yapı sayesinde tek bir rol hem RHEL hem Ubuntu’da çalışıyor. ansible_os_family fact’i sayesinde doğru görev dosyası seçiliyor.
Mevcut Kuralları Kontrol Etme ve Doğrulama
Playbook çalıştırdıktan sonra kuralların gerçekten uygulandığını doğrulamak için check mode ve doğrulama task’ları eklemek iyi bir pratik:
# playbooks/verify_firewall.yml
---
- name: Firewall kurallarini dogrula
hosts: all
become: true
tasks:
- name: RHEL - Aktif kurallari listele
ansible.builtin.command: firewall-cmd --list-all
register: firewalld_rules
changed_when: false
when: ansible_os_family == "RedHat"
- name: RHEL - Kural ciktisini goster
ansible.builtin.debug:
var: firewalld_rules.stdout_lines
when: ansible_os_family == "RedHat"
- name: Ubuntu - Aktif kurallari listele
ansible.builtin.command: ufw status verbose
register: ufw_rules
changed_when: false
when: ansible_os_family == "Debian"
- name: Ubuntu - Kural ciktisini goster
ansible.builtin.debug:
var: ufw_rules.stdout_lines
when: ansible_os_family == "Debian"
Playbook’u her zaman önce --check modunda çalıştırmayı alışkanlık haline getir:
ansible-playbook -i inventory/hosts.ini playbooks/firewall_web_servers.yml --check --diff
--diff flag’i neyin değişeceğini gösterir. Prod ortamına uygulamadan önce bu çıktıyı incelemek ciddi kazaları önler.
Sık Yapılan Hatalar ve Çözümleri
SSH’i kapatmak: En klasik hata. Eğer default policy’yi deny yapıp SSH kuralını eklemeyi unutursan sunucuya erişimi kaybedersin. Bunun için SSH kuralını her zaman listenin başına koy ve ayrı bir task olarak yaz.
permanent ve immediate karışıklığı: firewalld’de permanent: true tek başına yetmez, kuralı hemen uygulamaz. immediate: true eklemezsen sunucuyu yeniden başlatana kadar kural aktif olmaz. İkisini birlikte kullanmayı kural haline getir.
ufw reset sorunu: ufw reset komutu tüm kuralları siler. Playbook’ta state: reset kullanırken dikkatli ol, ardından tüm kuralları yeniden tanımlamalısın.
Zone kavramını atlamak: firewalld’de zone belirtmezsen varsayılan zone kullanılır. Bu bazen istediğin olmayabilir. Her task’ta zone parametresini açıkça belirt.
Ekip Ortamında İş Akışı
Bu playbook’ları bir ekiple yönetirken şu yapıyı öneririm:
- Her ortam için ayrı inventory dosyası (dev, staging, prod)
- Firewall kuralları için ayrı bir Git branch’i, merge request zorunluluğu
- Prod’a uygulamadan önce staging’de test
- Playbook çıktılarını bir log sunucusuna veya Slack’e gönderen notification task’ları
- Her değişikliği bir ticket numarasıyla etiketle (tag veya yorum olarak)
Bu yaklaşımla “kim bu portu açtı” sorusu Git log’una bakılarak saniyeler içinde cevaplanır.
Sonuç
Ansible’ın firewalld ve ufw modülleri, firewall yönetimini elle uğraşmaktan çıkarıp kod tabanına taşımanın en temiz yolu. Başlangıçta playbook yazmak biraz zaman alıyor ama karşılığını fazlasıyla veriyor: tekrarlanabilir konfigürasyonlar, izlenebilir değişiklikler ve insani hatalara karşı koruma.
Özellikle mixed ortamlar (hem RHEL hem Ubuntu) için ansible_os_family fact’i ile koşullu görevler yazmak, tek bir otomasyon katmanından her iki sistemi yönetmeni sağlıyor. Rol bazlı yaklaşımla da bu kuralları farklı sunucu tipleri arasında kolayca paylaşabilirsin.
En önemli öneri: SSH kuralını asla unutma, her zaman --check ile başla ve değişiklikleri küçük tutup adım adım uygula. Firewall bir kez yanlış yapılandırıldığında sunucuya erişimi geri kazanmak saatler alabilir.
