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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir