Onlarca, belki yüzlerce sunucuda kullanıcı oluşturmak, SSH key dağıtmak veya eski bir çalışanın erişimini kaldırmak… Manuel yapıyorsan her sunucuya tek tek bağlanıp işlem yapman gerekiyor. Bu hem zaman kaybı hem de hata riski. Ansible tam da bu noktada hayat kurtarıcı oluyor. Bugün gerçek dünya senaryolarıyla toplu kullanıcı yönetimi ve SSH key dağıtımını nasıl otomatize edeceğini anlatacağım.
Temel Kavramlar ve Hazırlık
Ansible’ın bu iş için bize sunduğu iki temel modül var: user ve authorized_key. Bunlara ek olarak group modülünü de sık kullanacağız. Başlamadan önce inventory dosyanın düzgün yapılandırıldığını varsayıyorum.
Basit bir inventory örneğiyle başlayalım:
# /etc/ansible/hosts veya proje dizinindeki inventory.ini
[webservers]
web01 ansible_host=192.168.1.10
web02 ansible_host=192.168.1.11
web03 ansible_host=192.168.1.12
[dbservers]
db01 ansible_host=192.168.1.20
db02 ansible_host=192.168.1.21
[all:vars]
ansible_user=ansible_admin
ansible_ssh_private_key_file=~/.ssh/ansible_key
ansible_become=true
ansible_become_method=sudo
Bu yapıyla devam edeceğiz. Şimdi asıl konuya girelim.
Tekli Kullanıcı Oluşturma: Temelden Başlamak
Karmaşık playbook’lara geçmeden önce temel user modülünü iyi anlamak lazım. Aşağıdaki örnek yeni bir geliştirici kullanıcısı oluşturuyor:
# create_developer.yml
---
- name: Gelistirici kullanicisi olustur
hosts: webservers
become: true
tasks:
- name: developer grubunu olustur
group:
name: developers
state: present
gid: 2000
- name: ali kullanicisini olustur
user:
name: ali
comment: "Ali Yilmaz - Backend Developer"
uid: 2001
group: developers
groups:
- sudo
- docker
shell: /bin/bash
home: /home/ali
create_home: true
password: "{{ 'SecurePassword123!' | password_hash('sha512') }}"
state: present
- name: SSH public key ekle
authorized_key:
user: ali
state: present
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... ali@laptop"
Burada dikkat etmeni istediğim birkaç nokta var. uid ve gid sabit vermek, farklı sunucularda dosya sahipliği sorunlarını önlüyor. Özellikle NFS veya shared storage kullanıyorsan bu çok kritik. password alanında hash kullanmak zorundasın, düz metin kabul etmiyor.
Vault ile Şifre Yönetimi
Şifreleri playbook içine açık yazmak en büyük güvenlik hatalarından biri. Ansible Vault bu sorunu çözüyor:
# Vault ile şifrelenmiş değişken dosyası oluştur
ansible-vault create group_vars/all/vault.yml
# Mevcut dosyayı şifrele
ansible-vault encrypt group_vars/all/secrets.yml
# İçerik örneği (şifrelenmeden önce)
vault_user_password: "SuperSecretPass123!"
vault_db_password: "AnotherSecret456!"
Sonra playbook’unda şöyle kullanırsın:
# group_vars/all/vars.yml
users:
- name: ali
password: "{{ vault_user_password }}"
groups: [developers, docker]
shell: /bin/bash
Playbook’u çalıştırırken:
ansible-playbook -i inventory.ini create_users.yml --ask-vault-pass
# veya vault şifresini dosyadan oku
ansible-playbook -i inventory.ini create_users.yml --vault-password-file ~/.vault_pass
Vault şifre dosyasının izinlerini 600 yapmeyi unutma: chmod 600 ~/.vault_pass
Gerçek Dünya Senaryosu: Yeni Çalışan Onboarding
Şirkete yeni bir DevOps mühendisi katıldı. Bu kişiye 30 sunucuda erişim vermek gerekiyor. Elle yapsan saatler sürer, Ansible ile dakikalar içinde bitirirsin.
# onboard_engineer.yml
---
- name: Yeni muhendis onboarding
hosts: all
become: true
vars_files:
- group_vars/all/vault.yml
vars:
new_user:
name: mehmet
fullname: "Mehmet Kaya - DevOps Engineer"
uid: 2050
shell: /bin/bash
groups_webservers:
- developers
- docker
groups_dbservers:
- developers
ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... mehmet@workstation"
tasks:
- name: devops grubunu olustur
group:
name: devops
state: present
gid: 3000
- name: kullanici olustur
user:
name: "{{ new_user.name }}"
comment: "{{ new_user.fullname }}"
uid: "{{ new_user.uid }}"
group: devops
groups: "{{ new_user.groups_webservers if inventory_hostname in groups['webservers'] else new_user.groups_dbservers }}"
append: true
shell: "{{ new_user.shell }}"
create_home: true
state: present
- name: SSH authorized key ekle
authorized_key:
user: "{{ new_user.name }}"
state: present
key: "{{ new_user.ssh_key }}"
exclusive: false
- name: sudo ayarlarini yap (sadece webservers icin)
copy:
content: "{{ new_user.name }} ALL=(ALL) NOPASSWD: /usr/bin/docker, /bin/systemctl restart nginxn"
dest: "/etc/sudoers.d/{{ new_user.name }}"
mode: '0440'
validate: /usr/sbin/visudo -cf %s
when: inventory_hostname in groups['webservers']
append: true parametresi önemli. Bunu yazmazsan mevcut gruplar silinir, sadece belirttiğin gruplar kalır. Yanlışlıkla birinin sudo yetkisini kaldırmak istemezsin.
Toplu Kullanıcı Yönetimi: YAML ile Kullanıcı Listesi
Birden fazla kullanıcıyı aynı anda yönetmek için veri odaklı yaklaşım kullan. Kullanıcı bilgilerini ayrı bir YAML dosyasında tut:
# group_vars/all/users.yml
users:
- name: ali
fullname: "Ali Yilmaz"
uid: 2001
groups: [developers, docker]
shell: /bin/bash
ssh_keys:
- "ssh-rsa AAAAB3NzaC1yc2E... ali@laptop"
- "ssh-rsa AAAAB3NzaC1yc2E... ali@home"
state: present
- name: ayse
fullname: "Ayse Demir"
uid: 2002
groups: [developers]
shell: /bin/bash
ssh_keys:
- "ssh-ed25519 AAAAC3NzaC1lZ... ayse@macbook"
state: present
- name: can
fullname: "Can Ozturk"
uid: 2003
groups: [developers, docker]
shell: /bin/zsh
ssh_keys:
- "ssh-rsa AAAAB3NzaC1yc2E... can@workstation"
state: present
# Silinecek kullanicilari da burda yonetebilirsin
removed_users:
- name: eski_calisan
uid: 1999
Bu listeyi işleyecek playbook:
# manage_users.yml
---
- name: Toplu kullanici yonetimi
hosts: all
become: true
vars_files:
- group_vars/all/users.yml
- group_vars/all/vault.yml
tasks:
- name: Kullanicilari olustur veya guncelle
user:
name: "{{ item.name }}"
comment: "{{ item.fullname }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
append: true
shell: "{{ item.shell }}"
create_home: true
state: "{{ item.state }}"
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"
- name: SSH key'leri ekle
authorized_key:
user: "{{ item.0.name }}"
state: present
key: "{{ item.1 }}"
exclusive: false
loop: "{{ users | subelements('ssh_keys') }}"
loop_control:
label: "{{ item.0.name }} - key ekleniyor"
when: item.0.state == 'present'
- name: Ayrilmis kullanicilari kilitli ve archive et
user:
name: "{{ item.name }}"
state: present
password_lock: true
shell: /sbin/nologin
comment: "DISABLED - {{ ansible_date_time.date }}"
loop: "{{ removed_users }}"
loop_control:
label: "{{ item.name }} kilitlendi"
- name: Ayrilmis kullanicilarin SSH key'lerini temizle
authorized_key:
user: "{{ item.name }}"
state: absent
key: ""
exclusive: true
loop: "{{ removed_users }}"
loop_control:
label: "{{ item.name }} SSH key temizleniyor"
subelements filtresi ile her kullanıcının birden fazla SSH key’ini kolayca işleyebiliyoruz. exclusive: true ile de sadece belirtilen key’leri bırakıp gerisini siliyoruz.
Çalışan Ayrılığı Senaryosu: Hızlı Erişim Kaldırma
Bir çalışan şirketten ayrıldı ve erişiminin hemen kaldırılması gerekiyor. Bu durumda paniklemeden şu playbook’u çalıştırırsın:
# offboard_user.yml
---
- name: Kullanici erisimini kaldir
hosts: all
become: true
vars:
target_user: "{{ offboard_username }}"
tasks:
- name: Aktif SSH baglantilari varsa kes
shell: "pkill -u {{ target_user }} || true"
ignore_errors: true
- name: Shell erisimini engelle
user:
name: "{{ target_user }}"
shell: /sbin/nologin
password_lock: true
- name: Tum SSH authorized key'leri temizle
authorized_key:
user: "{{ target_user }}"
state: absent
key: ""
exclusive: true
- name: Sudo yetkisini kaldir
file:
path: "/etc/sudoers.d/{{ target_user }}"
state: absent
- name: Home dizinini yedekle (opsiyonel)
archive:
path: "/home/{{ target_user }}"
dest: "/backup/offboarded/{{ target_user }}_{{ ansible_date_time.date }}.tar.gz"
mode: '0600'
ignore_errors: true
- name: Log kaydi tut
lineinfile:
path: /var/log/user_offboard.log
line: "{{ ansible_date_time.iso8601 }} - {{ target_user }} erisimi kaldirildi - Islem: {{ ansible_user }}"
create: true
Kullanımı:
ansible-playbook -i inventory.ini offboard_user.yml -e "offboard_username=eski_calisan"
-e ile ekstra değişken geçmek, playbook’u esnek hale getiriyor. Kullanıcı adını her seferinde playbook içine yazman gerekmiyor.
SSH Key Rotasyonu
Güvenlik politikası gereği SSH key’leri periyodik olarak değiştirmek gerekebilir. Özellikle bir key’in ele geçirildiği şüphesi varsa:
# rotate_ssh_keys.yml
---
- name: SSH key rotasyonu
hosts: all
become: true
vars_files:
- group_vars/all/new_ssh_keys.yml
tasks:
- name: Mevcut authorized_keys dosyasini yedekle
copy:
src: "/home/{{ item.name }}/.ssh/authorized_keys"
dest: "/home/{{ item.name }}/.ssh/authorized_keys.bak.{{ ansible_date_time.date }}"
remote_src: true
owner: "{{ item.name }}"
mode: '0600'
loop: "{{ users_to_rotate }}"
ignore_errors: true
- name: Eski keyleri temizle ve yeni keyleri ekle
authorized_key:
user: "{{ item.name }}"
state: present
key: "{{ item.new_key }}"
exclusive: true
loop: "{{ users_to_rotate }}"
loop_control:
label: "{{ item.name }} key rotasyonu"
- name: Rotasyon logunu kaydet
local_action:
module: lineinfile
path: ./key_rotation_log.txt
line: "{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - {{ item.name }} key rotasyonu tamamlandi"
create: true
loop: "{{ users_to_rotate }}"
become: false
exclusive: true burada kritik. Eski key’leri silip sadece yeni key’i bırakıyor. local_action ile de log kaydını kendi makinene alıyorsun, merkezi loglama için kullanışlı.
Rol Tabanlı Yaklaşım: Ansible Roles
Eğer bu işlemleri farklı projelerde tekrar kullanmak istiyorsan, role yapısına geçmeni öneririm:
# Role yapısını oluştur
ansible-galaxy init roles/user_management
# Oluşan yapı:
# roles/user_management/
# ├── defaults/
# │ └── main.yml
# ├── tasks/
# │ ├── main.yml
# │ ├── create_users.yml
# │ └── manage_ssh_keys.yml
# ├── handlers/
# │ └── main.yml
# └── vars/
# └── main.yml
# roles/user_management/defaults/main.yml
users: []
removed_users: []
default_shell: /bin/bash
default_groups: []
ssh_key_exclusive: false
backup_home_on_remove: true
backup_dir: /backup/users
# roles/user_management/tasks/main.yml
---
- name: Kullanici gruplarini olustur
include_tasks: create_groups.yml
when: user_groups is defined
- name: Kullanicilari yonet
include_tasks: create_users.yml
- name: SSH key'leri yonet
include_tasks: manage_ssh_keys.yml
Role’ü çağırmak için ana playbook:
# site.yml
---
- name: Kullanici yonetimi
hosts: all
become: true
roles:
- role: user_management
vars:
users: "{{ developer_users }}"
Role yapısı, kodu modüler ve tekrar kullanılabilir kılıyor. Bir kez yazıyorsun, onlarca projede kullanıyorsun.
Idempotency: Ansible’ın Süper Gücü
Ansible’ın en güzel özelliklerinden biri idempotency, yani aynı playbook’u kaç kez çalıştırsan aynı sonucu vermesi. Kullanıcı zaten varsa tekrar oluşturmaya çalışmıyor, key zaten eklenmişse tekrar eklemiyor. Ama bu bazen beklenmedik durumlar yaratabilir.
Örneğin bir kullanıcının shell’ini değiştirdin ve playbook’ta eski shell yazıyor. Playbook çalışınca shell geri eski haline döner. Bu yüzden playbook’larını tek kaynak gerçek (single source of truth) olarak ele almalısın. Sunucuda manuel değişiklik yapma, her şeyi Ansible üzerinden yönet.
Bunu kontrol etmek için check mode kullanabilirsin:
# Değişiklikleri uygulamadan önce ne olacağını gör
ansible-playbook -i inventory.ini manage_users.yml --check --diff
# Sadece belirli bir task'ı çalıştır
ansible-playbook -i inventory.ini manage_users.yml --tags "ssh_keys"
# Belirli bir host için çalıştır
ansible-playbook -i inventory.ini manage_users.yml --limit web01
--diff parametresi özellikle faydalı. Hangi dosyalarda ne değişeceğini gösteriyor.
Audit ve Raporlama
Kim hangi sunuculara erişebiliyor? Bunu düzenli olarak kontrol etmek güvenlik açısından önemli:
# audit_ssh_access.yml
---
- name: SSH erisim denetimi
hosts: all
become: true
gather_facts: true
tasks:
- name: Mevcut kullanicilari listele
getent:
database: passwd
register: system_users
- name: Her kullanicinin SSH keylerini topla
slurp:
src: "/home/{{ item }}/.ssh/authorized_keys"
loop: "{{ system_users.ansible_facts.getent_passwd.keys() | list }}"
register: ssh_keys_raw
ignore_errors: true
- name: Raporu local'e kaydet
local_action:
module: copy
content: |
Sunucu: {{ inventory_hostname }}
Tarih: {{ ansible_date_time.iso8601 }}
IP: {{ ansible_default_ipv4.address }}
===================
{% for result in ssh_keys_raw.results %}
{% if result.content is defined %}
Kullanici: {{ result.item }}
Keys:
{{ result.content | b64decode }}
{% endif %}
{% endfor %}
dest: "./audit/{{ inventory_hostname }}_{{ ansible_date_time.date }}.txt"
become: false
Bu playbook her sunucudaki SSH key durumunu bir rapor dosyasına döküyor. Güvenlik denetimleri için biçilmiş kaftan.
Pratik İpuçları ve Yaygın Hatalar
Uzun yıllar boyunca bu işi yaparken birkaç önemli şey öğrendim:
- Test ortamında dene: Yeni playbook’ları önce test sunucularında çalıştır, production’da değil.
- Backup al: Özellikle
exclusive: truekullanıyorsan önce authorized_keys dosyasını yedekle. - UID sabit tut: Farklı sunucularda aynı kullanıcının farklı UID’si olursa NFS paylaşımlarında sorun çıkar.
- Vault kullan: Şifreleri asla düz metin olarak playbook’a yazma.
- Log tut: Her önemli değişikliği logla, kim ne zaman ne yaptı takip edilebilir olsun.
- İzinleri kontrol et: .ssh dizini 700, authorized_keys 600 olmalı. Ansible genellikle bunu otomatik yapıyor ama kontrol et.
- Shell değişkenlerinden kaçın: Kullanıcı adları için özel karakter kullanma, sadece harf, rakam ve alt çizgi.
Sık karşılaşılan bir hata da become kullanmadan user modülünü çalıştırmaya çalışmak. Root yetkisi olmadan başka kullanıcılar oluşturamazsın, become: true şart.
Sonuç
Ansible ile kullanıcı ve SSH key yönetimi, özellikle büyük altyapılarda manuel işlemlerin yarattığı kaosa gerçek bir çözüm sunuyor. Yeni çalışan onboarding’den güvenlik denetimlerine, key rotasyonundan acil erişim kapatmaya kadar tüm süreçleri otomatize edebilirsin.
Burada anlattığım yapıyı kendi ortamına uyarlayarak başla. Önce küçük bir kullanıcı listesiyle test et, role yapısına geç, sonra CI/CD pipeline’ına entegre et. HR sisteminden otomatik tetiklenen onboarding playbook’ları, güvenlik ihlalinde tek komutla tüm erişimi kapatan acil playbook’lar… Bunların hepsi mümkün ve fazlasıyla uygulanabilir.
En önemlisi: Playbook’larını Git’te tut. Kim ne zaman hangi kullanıcıya ne yaptı, tüm geçmişi görebilirsin. Bu hem hesap verebilirlik hem de olası hataları geri alma açısından çok değerli. Infrastructure as Code sadece sunucu yapılandırması için değil, kullanıcı yönetimi için de geçerli bir yaklaşım.