Ansible ile SSL Sertifika Yönetimi: Certbot Entegrasyonu
SSL sertifika yönetimi, her sistem yöneticisinin kabusu olabilir. Özellikle onlarca, yüzlerce sunucu yönetiyorsanız, her birinin sertifikasını ayrı ayrı yenilemek hem zaman alıcı hem de hata yapmaya açık bir süreç. Certbot ile Let’s Encrypt sertifikalarını Ansible üzerinden otomatik hale getirdiğinizde ise bu iş tamamen elinizden çıkıyor. Bu yazıda, gerçek dünya senaryolarıyla birlikte Ansible ve Certbot entegrasyonunu adım adım ele alacağız.
Neden Ansible ile SSL Yönetimi?
Manuel SSL yönetimi şu sorunları beraberinde getirir: sertifika sona erme tarihlerini takip edememe, farklı sunucularda farklı yapılandırmalar ve insan hatası kaynaklı kesintiler. Ansible bu sorunları çözmek için mükemmel bir araç çünkü idempotent yapısı sayesinde aynı playbook’u defalarca çalıştırabilirsiniz, her seferinde tutarlı bir sonuç elde edersiniz.
Gerçek bir senaryo düşünelim: 50 web sunucusu olan bir altyapıda her ay birkaç sertifikanın süresi dolmak üzere. Bunu takip etmek için ya bir monitoring çözümü kurmanız ya da takvime not almanız gerekiyor. Ya da Ansible ile her şeyi otomatize edip arkasına yaslanırsınız.
Ön Gereksinimler
Başlamadan önce ortamınızın hazır olduğundan emin olun:
- Ansible 2.9 veya üzeri kurulu olmalı
- Yönetilen sunucularda Python 3.6+ bulunmalı
- DNS kayıtları doğru şekilde yapılandırılmış olmalı (certbot doğrulama için şart)
- Sunucularda 80 ve 443 portları erişilebilir olmalı
Ansible kontrol makinenizde gerekli koleksiyonu kuralım:
ansible-galaxy collection install community.crypto
ansible-galaxy collection install community.general
pip3 install cryptography
Envanter ve Değişken Yapısı
Önce envanter dosyanızı düzenleyin. Gerçek projelerimde genellikle grup bazlı bir yapı kullanıyorum:
# inventory/hosts.ini
[web_servers]
web01.sirketim.com ansible_user=ubuntu
web02.sirketim.com ansible_user=ubuntu
web03.sirketim.com ansible_user=ubuntu
[staging]
staging01.sirketim.com ansible_user=ubuntu
[web_servers:vars]
ansible_ssh_private_key_file=~/.ssh/production_key
[staging:vars]
ansible_ssh_private_key_file=~/.ssh/staging_key
Değişkenlerinizi group_vars altında tutmak, bakımı kolaylaştırır. Her ortam için ayrı değişken dosyası oluşturun:
# group_vars/web_servers.yml
certbot_email: "[email protected]"
certbot_staging: false
certbot_auto_renew: true
certbot_renew_hook: "systemctl reload nginx"
certbot_domains:
- domain: "sirketim.com"
extra_domains:
- "www.sirketim.com"
- "api.sirketim.com"
- domain: "blog.sirketim.com"
extra_domains: []
# group_vars/staging.yml
certbot_email: "[email protected]"
certbot_staging: true
certbot_auto_renew: true
certbot_renew_hook: "systemctl reload nginx"
certbot_staging: true değeri, staging ortamında Let’s Encrypt’in test sunucusunu kullanmanızı sağlar. Böylece rate limit’e takılmazsınız.
Certbot Kurulum Role’ü
Role tabanlı bir yapı kullanmak, playbook’larınızı yeniden kullanılabilir hale getirir. Önce role iskeletini oluşturalım:
ansible-galaxy init roles/certbot
Role yapımız şöyle görünmeli:
roles/certbot/
├── defaults/
│ └── main.yml
├── handlers/
│ └── main.yml
├── tasks/
│ ├── main.yml
│ ├── install.yml
│ ├── obtain_cert.yml
│ └── renew.yml
└── templates/
└── certbot_renew.cron.j2
Defaults dosyasını hazırlayalım:
# roles/certbot/defaults/main.yml
certbot_package: "certbot"
certbot_nginx_package: "python3-certbot-nginx"
certbot_email: ""
certbot_staging: false
certbot_auto_renew: true
certbot_renew_cron_hour: "3"
certbot_renew_cron_minute: "30"
certbot_renew_hook: "systemctl reload nginx"
certbot_webroot_path: "/var/www/html"
certbot_domains: []
certbot_agree_tos: true
Kurulum Task’ları
# roles/certbot/tasks/install.yml
---
- name: Certbot ve Nginx plugin'ini kur (Debian/Ubuntu)
apt:
name:
- "{{ certbot_package }}"
- "{{ certbot_nginx_package }}"
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Certbot ve Nginx plugin'ini kur (RHEL/CentOS)
dnf:
name:
- certbot
- python3-certbot-nginx
state: present
enablerepo: epel
when: ansible_os_family == "RedHat"
- name: Certbot'un snap ile kurulup kurulmadığını kontrol et
command: which certbot
register: certbot_path
changed_when: false
failed_when: false
- name: Snap ile certbot kur (alternatif)
community.general.snap:
name: certbot
classic: yes
state: present
when: certbot_path.rc != 0
- name: Certbot versiyonunu kontrol et
command: certbot --version
register: certbot_version
changed_when: false
- name: Certbot versiyonunu göster
debug:
msg: "Certbot versiyonu: {{ certbot_version.stdout }}"
Sertifika Alma Task’ı
Bu kısım biraz dikkat istiyor. Certbot’un idempotent çalışması için mevcut sertifikaları kontrol etmeniz gerekiyor:
# roles/certbot/tasks/obtain_cert.yml
---
- name: Mevcut sertifikaları listele
command: certbot certificates
register: existing_certs
changed_when: false
failed_when: false
- name: Her domain için sertifika al ya da yenile
block:
- name: Sertifika durumunu kontrol et
stat:
path: "/etc/letsencrypt/live/{{ item.domain }}/fullchain.pem"
register: cert_stat
loop: "{{ certbot_domains }}"
loop_control:
label: "{{ item.domain }}"
- name: Yeni sertifika al (Nginx plugin ile)
command: >
certbot certonly
--nginx
--non-interactive
--agree-tos
--email {{ certbot_email }}
{% if certbot_staging %}--staging{% endif %}
-d {{ item.item.domain }}
{% for extra in item.item.extra_domains %}-d {{ extra }} {% endfor %}
loop: "{{ cert_stat.results }}"
loop_control:
label: "{{ item.item.domain }}"
when: not item.stat.exists
notify: reload nginx
- name: Sertifika son kullanma tarihini kontrol et
command: >
openssl x509 -enddate -noout
-in /etc/letsencrypt/live/{{ item.domain }}/fullchain.pem
register: cert_expiry
loop: "{{ certbot_domains }}"
loop_control:
label: "{{ item.domain }}"
changed_when: false
failed_when: false
- name: Sertifika bilgilerini göster
debug:
msg: "{{ item.item.domain }}: {{ item.stdout }}"
loop: "{{ cert_expiry.results }}"
loop_control:
label: "{{ item.item.domain }}"
when: item.rc == 0
Otomatik Yenileme Yapılandırması
Sertifika alma kadar önemli olan şey, otomatik yenileme. Let’s Encrypt sertifikaları 90 günlük olduğu için bu kritik:
# roles/certbot/tasks/renew.yml
---
- name: Certbot renew hook dizinini oluştur
file:
path: /etc/letsencrypt/renewal-hooks/deploy
state: directory
mode: '0755'
- name: Yenileme sonrası web sunucusunu yeniden başlatacak hook oluştur
template:
src: certbot_renew_hook.sh.j2
dest: /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh
mode: '0755'
- name: Certbot timer durumunu kontrol et (systemd)
systemd:
name: certbot.timer
state: started
enabled: yes
when: ansible_service_mgr == "systemd"
failed_when: false
- name: Cron ile otomatik yenileme ayarla (systemd yoksa)
cron:
name: "Certbot SSL sertifika yenileme"
hour: "{{ certbot_renew_cron_hour }}"
minute: "{{ certbot_renew_cron_minute }}"
job: "certbot renew --quiet --post-hook '{{ certbot_renew_hook }}'"
state: "{{ 'present' if certbot_auto_renew else 'absent' }}"
when: ansible_service_mgr != "systemd" or certbot_timer_status is failed
Template dosyası:
# roles/certbot/templates/certbot_renew_hook.sh.j2
#!/bin/bash
# Ansible tarafından yönetilmektedir - manuel değişiklik yapmayın
# Oluşturulma tarihi: {{ ansible_date_time.iso8601 }}
{{ certbot_renew_hook }}
# Yenileme logunu kaydet
echo "$(date): SSL sertifikaları yenilendi - {{ inventory_hostname }}" >> /var/log/certbot-renew.log
Handler’lar
# roles/certbot/handlers/main.yml
---
- name: reload nginx
systemd:
name: nginx
state: reloaded
- name: restart nginx
systemd:
name: nginx
state: restarted
- name: reload apache
systemd:
name: apache2
state: reloaded
when: ansible_os_family == "Debian"
Ana Playbook
Tüm bu role’leri bir araya getirelim:
# ssl_management.yml
---
- name: SSL Sertifika Yönetimi
hosts: web_servers
become: yes
gather_facts: yes
pre_tasks:
- name: Nginx'in çalıştığını doğrula
systemd:
name: nginx
state: started
register: nginx_status
failed_when: false
- name: Nginx durumunu göster
debug:
msg: "Nginx durumu: {{ 'çalışıyor' if nginx_status.status is defined else 'kontrol edilemedi' }}"
- name: 80 portunu dinleyen servisi kontrol et
wait_for:
port: 80
timeout: 10
state: started
register: port_check
failed_when: false
- name: Port durumu uyarısı
debug:
msg: "UYARI: 80 portu açık değil, certbot doğrulama başarısız olabilir"
when: port_check.failed is defined and port_check.failed
roles:
- role: certbot
post_tasks:
- name: SSL sertifika özetini al
command: certbot certificates
register: cert_summary
changed_when: false
- name: Sertifika özetini göster
debug:
msg: "{{ cert_summary.stdout_lines }}"
- name: Sertifika son kullanma tarihlerini kaydet
copy:
content: "{{ cert_summary.stdout }}"
dest: /var/log/ssl_cert_summary.txt
mode: '0644'
Wildcard Sertifika Yönetimi
Wildcard sertifikalar için DNS challenge kullanmanız gerekiyor. Bu biraz daha karmaşık ama çok daha güçlü:
# Cloudflare DNS challenge örneği
# group_vars/web_servers.yml dosyasına ekle:
# certbot_dns_provider: cloudflare
# certbot_cloudflare_credentials: /etc/letsencrypt/cloudflare.ini
- name: Cloudflare kimlik bilgilerini yapılandır
template:
src: cloudflare.ini.j2
dest: /etc/letsencrypt/cloudflare.ini
mode: '0600'
owner: root
group: root
- name: Certbot Cloudflare plugin'ini kur
pip:
name: certbot-dns-cloudflare
state: present
- name: Wildcard sertifika al (DNS challenge)
command: >
certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
--dns-cloudflare-propagation-seconds 30
--non-interactive
--agree-tos
--email {{ certbot_email }}
{% if certbot_staging %}--staging{% endif %}
-d "*.{{ item.domain }}"
-d "{{ item.domain }}"
loop: "{{ certbot_wildcard_domains }}"
loop_control:
label: "{{ item.domain }}"
when: certbot_dns_provider == "cloudflare"
Sertifika İzleme ve Uyarı
Sadece sertifika almak yetmez, izlemeniz de gerekiyor. İşte basit ama etkili bir izleme playbook’u:
# ssl_monitor.yml
---
- name: SSL Sertifika İzleme
hosts: web_servers
become: yes
gather_facts: yes
vars:
warning_days: 30
critical_days: 14
tasks:
- name: Tüm sertifika dosyalarını bul
find:
paths: /etc/letsencrypt/live
patterns: "fullchain.pem"
recurse: yes
register: cert_files
- name: Her sertifikanın son kullanma tarihini kontrol et
command: >
openssl x509 -checkend {{ item | int * 86400 }}
-noout -in {{ cert_item.path }}
register: cert_check
loop: "{{ cert_files.files }}"
loop_control:
loop_var: cert_item
label: "{{ cert_item.path }}"
with_items:
- "{{ critical_days }}"
failed_when: false
changed_when: false
- name: Kritik sertifikaları listele
debug:
msg: "KRİTİK: {{ item.cert_item.path }} {{ critical_days }} gün içinde sona eriyor!"
loop: "{{ cert_check.results }}"
loop_control:
label: "{{ item.cert_item.path }}"
when: item.rc == 1
- name: Slack uyarısı gönder (opsiyonel)
community.general.slack:
token: "{{ slack_token }}"
msg: "UYARI: {{ inventory_hostname }} üzerinde {{ item.cert_item.path }} sertifikası {{ critical_days }} gün içinde sona eriyor!"
channel: "#ops-alerts"
color: danger
loop: "{{ cert_check.results }}"
loop_control:
label: "{{ item.cert_item.path }}"
when:
- item.rc == 1
- slack_token is defined
Gerçek Dünya Senaryosu: Çoklu Ortam Yönetimi
Bir e-ticaret şirketinde çalıştığınızı düşünün. Production, staging ve development ortamlarınız var. Her ortamda farklı domainler ve farklı sertifika politikaları geçerli:
# site.yml - Ana orchestration playbook
---
- name: Development SSL Kurulumu
hosts: development
become: yes
vars:
certbot_staging: true
certbot_domains:
- domain: "dev.sirketim.com"
extra_domains: []
roles:
- certbot
- name: Staging SSL Kurulumu
hosts: staging
become: yes
vars:
certbot_staging: true
certbot_domains:
- domain: "staging.sirketim.com"
extra_domains:
- "api.staging.sirketim.com"
roles:
- certbot
- name: Production SSL Kurulumu
hosts: production
become: yes
serial: 1
vars:
certbot_staging: false
certbot_domains:
- domain: "sirketim.com"
extra_domains:
- "www.sirketim.com"
- "api.sirketim.com"
- "shop.sirketim.com"
pre_tasks:
- name: Production değişikliği onayı iste
pause:
prompt: "Production SSL değişikliği yapılacak. Devam etmek için ENTER'a basın, iptal için CTRL+C"
when: not auto_approve | default(false)
roles:
- certbot
serial: 1 parametresi production’da çok önemli. Bir sunucu patlarsa diğerleri etkilenmiyor.
CI/CD Pipeline Entegrasyonu
GitLab CI ile entegrasyon örneği:
# .gitlab-ci.yml
stages:
- validate
- deploy-staging
- deploy-production
variables:
ANSIBLE_HOST_KEY_CHECKING: "False"
ANSIBLE_FORCE_COLOR: "true"
ssl-lint:
stage: validate
image: cytopia/ansible:latest
script:
- ansible-playbook ssl_management.yml --syntax-check
- ansible-lint ssl_management.yml
only:
- merge_requests
- main
ssl-staging:
stage: deploy-staging
image: cytopia/ansible:latest
before_script:
- eval $(ssh-agent -s)
- echo "$STAGING_SSH_KEY" | ssh-add -
script:
- ansible-playbook -i inventory/staging ssl_management.yml
--extra-vars "certbot_staging=true"
environment:
name: staging
only:
- main
ssl-production:
stage: deploy-production
image: cytopia/ansible:latest
before_script:
- eval $(ssh-agent -s)
- echo "$PRODUCTION_SSH_KEY" | ssh-add -
script:
- ansible-playbook -i inventory/production ssl_management.yml
--extra-vars "certbot_staging=false auto_approve=true"
environment:
name: production
when: manual
only:
- main
Sık Karşılaşılan Sorunlar ve Çözümleri
Rate Limit Hatası: Let’s Encrypt bir domain için haftalık 5 sertifika limiti koyuyor. Çözüm staging ortamını kullanmak ve testlerinizi orada yapmak.
DNS Propagation Gecikmesi: DNS challenge kullanıyorsanız propagation süresi beklenenden uzun sürebilir. --dns-cloudflare-propagation-seconds 60 gibi bir değer genellikle yeterli oluyor.
Nginx Konfigürasyon Hatası: Certbot nginx eklentisi bazen mevcut nginx konfigürasyonunu bozabilir. Pre-task olarak nginx konfigürasyonunu backup alın:
- name: Nginx konfigürasyonunu yedekle
archive:
path: /etc/nginx
dest: "/tmp/nginx_backup_{{ ansible_date_time.date }}.tar.gz"
format: gz
before: certbot kurulumu
Sertifika Dizini İzinleri: Bazı uygulamalar sertifika dosyalarını doğrudan okumak istiyor. /etc/letsencrypt/live dizini root’a ait, bu yüzden uygulamanızın kullanıcısına okuma izni vermeniz gerekebilir:
- name: SSL sertifika grubu oluştur
group:
name: ssl-cert
state: present
- name: Web sunucusu kullanıcısını ssl-cert grubuna ekle
user:
name: www-data
groups: ssl-cert
append: yes
- name: Letsencrypt dizin izinlerini ayarla
file:
path: /etc/letsencrypt/live
group: ssl-cert
mode: '0750'
recurse: no
Sonuç
Ansible ile Certbot entegrasyonu başta karmaşık görünebilir, ama bir kez kurduğunuzda SSL yönetimi gerçekten “set and forget” haline geliyor. Yazdığımız bu yapıyla 50 sunucuya da 500 sunucuya da aynı playbook’la bakabilirsiniz.
Öne çıkan kazanımlar şunlar: sertifika yenileme unutulmuyor çünkü otomatik, farklı sunucularda tutarsız konfigürasyon olmuyor çünkü her şey kod, ve bir şeyler ters gittiğinde git history’ye bakıp ne değişti görüyorsunuz.
Bir sonraki adım olarak Vault ile hassas verileri (Cloudflare API token gibi) şifrelemenizi, ve sertifika izlemeyi Prometheus + Alertmanager ile entegre etmenizi öneririm. SSL sona erme uyarıları için ayrıca prometheus/ssl-exporter oldukça iyi çalışıyor.
Sorularınız olursa yorumlarda buluşalım.
