Ansible ile Kullanıcı ve SSH Key Yönetimi
Onlarca, belki yüzlerce sunucuda kullanıcı oluşturmak, SSH key dağıtmak, eski kullanıcıları temizlemek… Bunları elle yapmaya çalışan her sysadmin’in er ya da geç saçını yolduğunu biliriz. Bir sunucuya eklemeyi unutursun, başka birinde yanlış bir key girersin, üçüncüsünde kullanıcı grubu eksik kalır. Ansible tam da bu kaos için var. Bu yazıda gerçek dünya senaryoları üzerinden Ansible ile kullanıcı ve SSH key yönetimini adım adım ele alacağız.
Neden Ansible ile SSH Key Yönetimi?
Geleneksel yöntemde her sunucuya SSH ile bağlanıp useradd, passwd, ssh-copy-id komutlarını çalıştırırsın. 5 sunucu için katlanılabilir, 50 sunucu için işkence, 500 sunucu için ise mümkün değil. Ansible’ın buradaki gücü şu: bir playbook yaz, hangi sunucularda çalışacağını tanımla, çalıştır ve uyu.
Bunun ötesinde idempotency özelliği kritik. Aynı playbook’u kaç kez çalıştırırsan çalıştır, sonuç değişmez. Kullanıcı zaten varsa tekrar oluşturmaz, key zaten eklenmişse tekrar eklemez. Bu hem güvenli hem de hata payını minimuma indirir.
Ortam Hazırlığı
Başlamadan önce temel bir inventory ve ansible.cfg dosyamızın olduğunu varsayalım.
# /etc/ansible/hosts veya proje dizinindeki inventory dosyan
[webservers]
web01.sirket.com
web02.sirket.com
web03.sirket.com
[dbservers]
db01.sirket.com
db02.sirket.com
[all:vars]
ansible_user=ansible_admin
ansible_ssh_private_key_file=~/.ssh/ansible_master_key
# ansible.cfg
[defaults]
inventory = ./inventory
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
deprecation_warnings = False
[privilege_escalation]
become = True
become_method = sudo
become_user = root
host_key_checking = False ayarını production’da dikkatli kullan. Yeni sunucular eklerken işe yarar ama tamamen kapalı tutmak güvenlik açısından tartışmalıdır. Bunu inventory’ye sunucu ilk eklendiğinde kullan, sonra açık bırak.
Kullanıcı Oluşturma: Temel Yapı
Ansible’da kullanıcı yönetiminin kalbi user modülüdür. Basit bir örnekle başlayalım.
# create_users.yml
---
- name: Kullanici ve SSH key yonetimi
hosts: all
become: yes
vars:
users:
- name: ahmet
comment: "Ahmet Yilmaz - DevOps"
groups: ["sudo", "docker"]
shell: /bin/bash
state: present
- name: zeynep
comment: "Zeynep Kaya - Backend Dev"
groups: ["developers"]
shell: /bin/bash
state: present
- name: eski_calisan
state: absent
tasks:
- name: Kullanicilari olustur veya sil
user:
name: "{{ item.name }}"
comment: "{{ item.comment | default('') }}"
groups: "{{ item.groups | default([]) }}"
shell: "{{ item.shell | default('/bin/bash') }}"
state: "{{ item.state | default('present') }}"
create_home: yes
append: yes
loop: "{{ users }}"
when: item.state | default('present') == 'present' or item.state == 'absent'
Burada append: yes önemli. Bu olmadan kullanıcıyı belirttiğin gruplara eklerken mevcut gruplardan çıkarır. Yani bir kullanıcı www-data grubundaysa ve sen onu docker grubuna ekliyorsan, append: no ile www-data‘dan çıkar. Çoğu zaman istemediğin bu davranış.
SSH Key Dağıtımı
Kullanıcıları oluşturduktan sonra SSH key’leri yerleştirme zamanı. authorized_key modülü bu iş için biçilmiş kaftan.
# ssh_keys.yml
---
- name: SSH key dagitimu
hosts: all
become: yes
vars:
ssh_keys:
- user: ahmet
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBkKk... ahmet@laptop"
state: present
- user: ahmet
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOther... ahmet@work"
state: present
- user: zeynep
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... zeynep@macbook"
state: present
tasks:
- name: SSH authorized key ekle
authorized_key:
user: "{{ item.user }}"
key: "{{ item.key }}"
state: "{{ item.state | default('present') }}"
exclusive: no
loop: "{{ ssh_keys }}"
exclusive: no ile mevcut keyler korunur, sadece yenileri eklenir. Eğer exclusive: yes kullanırsan o kullanıcının tüm keylerini silip yalnızca belirttiğini bırakır. Bu bazen işine yarayabilir, özellikle tam key rotasyonu yapacaksan.
Key’leri Dosyadan Okumak
Key’leri playbook içine gömmek iyi bir pratik değil. Bunun yerine dosyadan oku:
# Yerel makinedeki public key dosyasindan oku
- name: Lokal public key ile SSH erisimi ver
authorized_key:
user: "{{ item }}"
key: "{{ lookup('file', '~/.ssh/keys/' + item + '.pub') }}"
state: present
loop:
- ahmet
- zeynep
- mehmet
Bu yaklaşımda ~/.ssh/keys/ klasörüne her kullanıcı için ahmet.pub, zeynep.pub gibi dosyalar koyarsın. Düzenli, takip edilebilir ve git’e alabileceğin bir yapı.
Grup Yönetimi
Kullanıcı oluşturmadan önce grupların var olduğundan emin olmak gerekir, yoksa task hata verir.
# groups_and_users.yml
---
- name: Grup ve kullanici yonetimi
hosts: all
become: yes
vars:
groups_to_create:
- name: developers
gid: 2000
- name: devops
gid: 2001
- name: readonly
gid: 2002
tasks:
- name: Gruplari olustur
group:
name: "{{ item.name }}"
gid: "{{ item.gid | default(omit) }}"
state: present
loop: "{{ groups_to_create }}"
- name: Sudo yetkisi ver (sudoers.d)
copy:
content: "%devops ALL=(ALL) NOPASSWD: ALLn"
dest: /etc/sudoers.d/devops
owner: root
group: root
mode: '0440'
validate: 'visudo -cf %s'
validate: 'visudo -cf %s' kısmı kritik. Dosyayı yazmadan önce sözdizimini kontrol eder. Hatalı bir sudoers dosyası sistemden kilitlenmen anlamına gelebilir.
Gerçek Dünya Senaryosu: Yeni Çalışan Onboarding
Bir şirkette yeni geliştirici işe girdiğinde yapılacaklar listesi uzun olur. Bunu tam anlamıyla otomatize edelim.
# onboard_developer.yml
---
- name: Yeni gelistirici onboarding
hosts: webservers:dbservers
become: yes
vars_files:
- vars/new_developer.yml
tasks:
- name: Kullaniciyi olustur
user:
name: "{{ developer.username }}"
comment: "{{ developer.full_name }}"
shell: /bin/bash
groups: "{{ developer.groups }}"
append: yes
create_home: yes
state: present
- name: SSH home dizini olustur
file:
path: "/home/{{ developer.username }}/.ssh"
state: directory
owner: "{{ developer.username }}"
group: "{{ developer.username }}"
mode: '0700'
- name: SSH public key ekle
authorized_key:
user: "{{ developer.username }}"
key: "{{ developer.ssh_public_key }}"
state: present
exclusive: no
- name: Bashrc ozelleştirmesi
copy:
src: templates/bashrc_developer
dest: "/home/{{ developer.username }}/.bashrc"
owner: "{{ developer.username }}"
group: "{{ developer.username }}"
mode: '0644'
- name: Bilgilendirme mesaji
debug:
msg: "{{ developer.full_name }} icin hesap basariyla olusturuldu. Sunucular: {{ ansible_hostname }}"
# vars/new_developer.yml
developer:
username: can_demir
full_name: "Can Demir"
email: "[email protected]"
groups:
- developers
- docker
ssh_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... can@laptop"
Bu yaklaşımla yeni çalışan için sadece vars/new_developer.yml dosyasını dolduruyorsun ve playbook’u çalıştırıyorsun. İK’dan maile bile gerek yok, git’e pull request aç, review geçsin, pipeline çalıştırsın.
Gerçek Dünya Senaryosu: Çalışan Ayrılığı
Bu senaryo güvenlik açısından daha kritik. Birisi işten ayrıldığında erişiminin anında kesilmesi şart.
# offboard_user.yml
---
- name: Kullanici erisimini kes
hosts: all
become: yes
vars:
departing_user: "eski_calisan"
archive_home: yes
tasks:
- name: SSH keylerini temizle
authorized_key:
user: "{{ departing_user }}"
key: ""
state: absent
exclusive: yes
ignore_errors: yes
- name: Kullaniciyi kilitle (silme, arsivle)
user:
name: "{{ departing_user }}"
shell: /sbin/nologin
password_lock: yes
state: present
- name: Home dizinini arsivle
archive:
path: "/home/{{ departing_user }}"
dest: "/var/backups/users/{{ departing_user }}_{{ ansible_date_time.date }}.tar.gz"
format: gz
when: archive_home
- name: Aktif oturumu sonlandir
shell: "pkill -u {{ departing_user }} || true"
ignore_errors: yes
- name: Sudo yetkisini kaldir
file:
path: "/etc/sudoers.d/{{ departing_user }}"
state: absent
Kullanıcıyı direkt silmek yerine kilitleme ve arşivleme stratejisi daha sağlıklı. Hukuki süreçler veya teknik inceleme gerekirse veriler kaybolmaz.
Vault ile Güvenli Key Saklama
SSH public keyler zaten halka açık olduğundan vault gerekmez ama private bilgiler, parolalar veya hassas değişkenler için Ansible Vault kullanmak şart.
# Vault ile şifreli değişken dosyası oluştur
ansible-vault create vars/secrets.yml
# Mevcut dosyayı şifrele
ansible-vault encrypt vars/new_developer.yml
# Şifreli dosyayı düzenle
ansible-vault edit vars/secrets.yml
# Playbook'u vault şifresiyle çalıştır
ansible-playbook onboard_developer.yml --ask-vault-pass
# Ya da vault şifre dosyasıyla
ansible-playbook onboard_developer.yml --vault-password-file ~/.vault_pass
# vars/secrets.yml (vault ile şifrelenmiş hali düz gösterim)
admin_password_hash: "$6$rounds=656000$..."
deploy_key_private: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
SSH Yapılandırması: sshd_config Güçlendirme
Kullanıcı yönetimiyle birlikte SSH servisinin güvenliğini de playbook’a dahil etmek mantıklı.
# harden_ssh.yml
---
- name: SSH guclendir
hosts: all
become: yes
vars:
ssh_config:
PermitRootLogin: "no"
PasswordAuthentication: "no"
PubkeyAuthentication: "yes"
X11Forwarding: "no"
MaxAuthTries: "3"
LoginGraceTime: "30"
AllowGroups: "ssh_users devops"
Protocol: "2"
tasks:
- name: SSH grubunu olustur
group:
name: ssh_users
state: present
- name: sshd_config guncelle
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?{{ item.key }}"
line: "{{ item.key }} {{ item.value }}"
state: present
backup: yes
loop: "{{ ssh_config | dict2items }}"
notify: sshd yeniden baslat
- name: sshd_config syntax kontrolu
command: sshd -t
changed_when: false
handlers:
- name: sshd yeniden baslat
service:
name: sshd
state: restarted
AllowGroups ile sadece belirttiğin gruplardaki kullanıcıların SSH erişimine izin veriyorsun. Bu çok güçlü bir kısıtlama mekanizması. Kullanıcı sistemde var ama ssh_users grubunda değilse giremez.
Toplu Key Rotasyonu
Güvenlik olayı yaşandığında veya belirli periyotlarda tüm SSH keyleri rotasyona sokmak gerekebilir.
# rotate_ssh_keys.yml
---
- name: SSH key rotasyonu
hosts: all
become: yes
vars:
rotation_date: "{{ ansible_date_time.date }}"
users_to_rotate:
- username: ahmet
new_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAInew... ahmet@new-laptop"
- username: zeynep
new_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIother... zeynep@new-mac"
tasks:
- name: Eski keyleri yedekle
fetch:
src: "/home/{{ item.username }}/.ssh/authorized_keys"
dest: "./key_backups/{{ rotation_date }}/{{ item.username }}_{{ inventory_hostname }}_authorized_keys"
flat: no
loop: "{{ users_to_rotate }}"
ignore_errors: yes
- name: Yeni keyi exclusive olarak yaz
authorized_key:
user: "{{ item.username }}"
key: "{{ item.new_key }}"
state: present
exclusive: yes
loop: "{{ users_to_rotate }}"
fetch modülüyle eski keyleri Ansible çalıştıran makinene çekiyorsun. Bir şeyler ters giderse geri döndürebilirsin.
Role Yapısı ile Modüler Yaklaşım
Büyüyen projelerde playbook’ları tek dosyada tutmak yönetilemez hale gelir. Role yapısı kullan:
# Role yapısını oluştur
ansible-galaxy init roles/user_management
# Oluşan yapı:
roles/user_management/
tasks/
main.yml
create_users.yml
manage_keys.yml
cleanup.yml
vars/
main.yml
defaults/
main.yml
handlers/
main.yml
templates/
sudoers.j2
# roles/user_management/tasks/main.yml
---
- include_tasks: create_users.yml
tags: users
- include_tasks: manage_keys.yml
tags: keys
- include_tasks: cleanup.yml
tags: cleanup
when: cleanup_enabled | default(false)
# roles/user_management/defaults/main.yml
---
default_shell: /bin/bash
default_groups: []
cleanup_enabled: false
key_exclusive: false
archive_home_on_delete: true
# site.yml - Ana playbook
---
- name: Kullanici yonetimi
hosts: all
become: yes
roles:
- role: user_management
vars:
users: "{{ lookup('file', 'users.json') | from_json }}"
CI/CD Pipeline Entegrasyonu
GitLab CI veya GitHub Actions ile bu playbook’ları otomatize etmek kullanıcı yönetimini tamamen koda döker.
# .gitlab-ci.yml
stages:
- validate
- deploy
variables:
ANSIBLE_HOST_KEY_CHECKING: "False"
validate_playbook:
stage: validate
image: cytopia/ansible:latest
script:
- ansible-playbook --syntax-check onboard_developer.yml
- ansible-lint onboard_developer.yml
only:
- merge_requests
deploy_user_changes:
stage: deploy
image: cytopia/ansible:latest
before_script:
- eval $(ssh-agent -s)
- echo "$ANSIBLE_SSH_KEY" | ssh-add -
- mkdir -p ~/.ssh
- echo "$VAULT_PASSWORD" > ~/.vault_pass
script:
- ansible-playbook
--vault-password-file ~/.vault_pass
-i inventory/production
onboard_developer.yml
only:
- main
environment:
name: production
Bu yapıyla yeni çalışan için git’e PR açılır, review geçer, main’e merge edilir ve pipeline otomatik olarak tüm sunucularda hesabı oluşturur. Tamamen hands-off bir süreç.
Hata Ayıklama ve Doğrulama
Playbook çalıştırdıktan sonra gerçekten işe yarayıp yaramadığını doğrulamak için:
# Dry-run (check mode) ile ne olacağını gör
ansible-playbook create_users.yml --check --diff
# Sadece belirli tag'leri çalıştır
ansible-playbook site.yml --tags "users,keys"
# Belirli sunucularda test et
ansible-playbook create_users.yml --limit web01.sirket.com
# Kullanıcı var mı kontrol et
ansible all -m command -a "id ahmet" -i inventory
# Authorized key kontrol
ansible all -m command -a "cat /home/ahmet/.ssh/authorized_keys" -i inventory
# Verbose mod ile detaylı çıktı
ansible-playbook create_users.yml -vvv
--check --diff kombinasyonu production’a dokunmadan önce mutlaka kullanman gereken şey. Neyin değişeceğini önceden görürsün.
İzleme ve Denetim
Kimin ne zaman erişim aldığını veya kaybettiğini kayıt altına almak compliance açısından önemli.
# Degisiklikleri logla
- name: Kullanici islemini kayit altina al
lineinfile:
path: /var/log/ansible_user_changes.log
line: "{{ ansible_date_time.iso8601 }} | {{ inventory_hostname }} | user={{ item.name }} | action={{ item.state | default('present') }} | triggered_by={{ lookup('env', 'USER') }}"
create: yes
loop: "{{ users }}"
Bu log dosyasını merkezi bir log yönetim sistemine (Graylog, ELK stack) göndermek her şeyi kayıt altına alır.
Sonuç
Ansible ile kullanıcı ve SSH key yönetimi, sysadmin’in hayatını kökten değiştiren konulardan biri. Artık “hangi sunucuya ekledim, hangisine eklemedim” kaygısı yok. Playbook çalıştı mı, her yerde tutarlı.
Özetlemek gerekirse:
- Kullanıcı oluşturma:
usermodülü,append: yesile mevcut grupları koru - Key dağıtımı:
authorized_keymodülü, keyleri dosyadanlookupile oku - Güvenlik:
exclusive: yesile key rotasyonu, vault ile hassas veri koruma - Offboarding: Silme yerine kilitleme ve arşivleme
- SSH güçlendirme:
lineinfileilesshd_configyönetimi - Ölçeklendirme: Role yapısı ve CI/CD entegrasyonu
En kritik nokta şu: Bu playbook’ları git’te tut, değişiklikleri PR ile yönet, pipeline’a bağla. Kullanıcı yönetimi bir operasyon görevi olmaktan çıkıp bir kod inceleme sürecine dönüştüğünde hem güvenlik hem de denetlenebilirlik katlanarak artar. Altyapını kod olarak yönetmenin en somut ve hemen fayda gören alanlarından biri bu. Dene, göreceksin.
