systemd Credential ile Servis Şifrelerini Güvenli Şekilde Yönetme

Servis şifrelerini yönetmek, sistem yöneticilerinin en çok baş ağrısı yaşadığı konulardan biri. Environment variable olarak düz metin şifre geçmek mi? Hayır, güvenli değil. Şifreyi servis dosyasına gömmek mi? Kesinlikle hayır. Peki ya systemd-creds ve LoadCredential mekanizması? İşte bu bambaşka bir hikaye.

systemd 247 sürümüyle hayatımıza giren Credential sistemi, servis şifrelerini ve hassas verileri yönetmek için geliştirilmiş, kernel keyring ile TPM2 entegrasyonu olan güçlü bir araç. Bu yazıda gerçek dünya senaryolarıyla bu sistemi derinlemesine inceleyeceğiz.

Neden Geleneksel Yöntemler Yetersiz Kalıyor

Klasik sysadmin hayatında şifre yönetimi genellikle şu patikalardan birinde ilerliyor:

  • Environment file’a düz metin şifre yazıp EnvironmentFile=/etc/myapp/secrets kullanmak
  • Servis unit dosyasına doğrudan Environment="DB_PASS=supersecret123" yazmak
  • Şifreyi bir config dosyasına koyup chmod ile 600 yapmak
  • HashiCorp Vault gibi harici araçlara bağımlı kalmak

Her birinin sorunu farklı ama özü aynı: ya şifreler düz metin olarak diskte duruyor ya da yönetimi karmaşık. systemctl show myservice yaptığınızda environment değişkenleri görünebiliyor. ps aux çıktısında şifreler gözükebiliyor. Log sistemleri yanlışlıkla bu değerleri kaydedebiliyor.

systemd Credential sistemi bu sorunlara farklı bir bakış açısı getiriyor: şifreleri servise özel, geçici, şifreli bir biçimde sunmak.

systemd Credential Sisteminin Temelleri

Credential mekanizması temelde şu şekilde çalışıyor: Servis başlatılmadan önce systemd, belirlediğiniz credential’ları hazırlıyor ve servise özel bir dizin oluşturuyor. Bu dizin $CREDENTIALS_DIRECTORY ortam değişkeni ile servise bildiriliyor. Servis bu dizindeki dosyaları okuyarak şifrelerine ulaşıyor.

Bu dizin birkaç kritik özelliğe sahip:

  • tmpfs üzerinde oluşturuluyor, disk’e yazılmıyor
  • Yalnızca o servisin process’i erişebiliyor
  • Servis durduğunda otomatik olarak siliniyor
  • systemd-creds ile şifrelenmiş saklama imkanı var

Temel Yapılandırma Direktifleri

LoadCredential: Diskteki bir dosyadan credential yükler.

LoadCredentialEncrypted: Şifrelenmiş credential dosyasından okur.

SetCredential: Direkt olarak unit dosyasına credential gömmenizi sağlar (şifresiz, dikkatli kullanın).

SetCredentialEncrypted: Unit dosyasına şifrelenmiş değer gömer.

İlk Pratik Örnek: PostgreSQL Şifresi

Diyelim ki bir uygulama servisi var ve PostgreSQL’e bağlanıyor. Geleneksel yöntemle environment file kullanıyordunuz, şimdi bunu credential’a taşıyalım.

Önce şifreyi bir dosyaya yazalım ve güvenli şekilde saklayalım:

# Şifreyi credential dosyası olarak oluştur
echo -n "MySuper$ecretDBPass2024" | sudo tee /etc/systemd/credentials/myapp-db-password
sudo chmod 600 /etc/systemd/credentials/myapp-db-password
sudo chown root:root /etc/systemd/credentials/myapp-db-password

Şimdi servis unit dosyamızı düzenleyelim:

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
After=network.target postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server

# Credential yükleme
LoadCredential=db-password:/etc/systemd/credentials/myapp-db-password

# Uygulama credential dizinini okusun
ExecStartPre=/bin/bash -c 'export DB_PASSWORD=$(cat $CREDENTIALS_DIRECTORY/db-password)'

[Install]
WantedBy=multi-user.target

Ancak bu yaklaşımda ExecStartPre ile environment’a almak tam doğru değil. Uygulamanın credential’ı doğrudan dosyadan okuması daha güvenli:

# Uygulama tarafında Python örneği
import os

credentials_dir = os.environ.get('CREDENTIALS_DIRECTORY', '')
if credentials_dir:
    db_password_file = os.path.join(credentials_dir, 'db-password')
    with open(db_password_file, 'r') as f:
        db_password = f.read().strip()
else:
    # Fallback veya hata
    raise RuntimeError("CREDENTIALS_DIRECTORY bulunamadı!")

systemd-creds ile Şifreleme

İşte asıl güç buradan geliyor. systemd-creds komutuyla credential’ları şifreleyebilirsiniz. Şifreleme iki modda çalışıyor:

host modu: Sistem’in makine kimliğiyle şifreleme yapar. Yalnızca o sunucuda çözülebilir.

tpm2 modu: TPM2 çipiyle şifreleme. Daha güçlü donanım tabanlı koruma.

# Host anahtarıyla şifreli credential oluşturma
echo -n "MySuper$ecretDBPass2024" | sudo systemd-creds encrypt 
  --name=db-password 
  - 
  /etc/systemd/credentials/myapp-db-password.cred

# Dosyanın içeriğine bakalım (şifreli olduğunu görmek için)
sudo cat /etc/systemd/credentials/myapp-db-password.cred

Çıktı okunaksız şifreli bir veri olacak. Şimdi unit dosyasını LoadCredentialEncrypted kullanacak şekilde güncelleyelim:

[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server

# Şifreli credential yükleme
LoadCredentialEncrypted=db-password:/etc/systemd/credentials/myapp-db-password.cred

systemd servisi başlatırken bu dosyayı otomatik olarak çözecek ve $CREDENTIALS_DIRECTORY/db-password olarak servise sunacak. Harika, değil mi?

Gerçek Dünya Senaryosu: Nginx + PHP-FPM + MySQL Stack

Üç katmanlı bir uygulama için credential yönetimini ele alalım. MySQL root şifresi, uygulama şifresi ve bir API anahtarı yönetmemiz gerekiyor.

Önce tüm credential’ları şifreli biçimde oluşturalım:

# MySQL uygulama kullanıcısı şifresi
echo -n "AppUser@MySQL#2024!" | sudo systemd-creds encrypt 
  --name=mysql-app-password 
  - /etc/systemd/credentials/php-app-mysql.cred

# Harici API anahtarı
echo -n "sk-proj-abcdef123456789xyz" | sudo systemd-creds encrypt 
  --name=api-key 
  - /etc/systemd/credentials/php-app-apikey.cred

# Tüm credential dosyalarını listele
ls -la /etc/systemd/credentials/

Şimdi PHP-FPM servisini bu credential’larla yapılandıralım:

# /etc/systemd/system/php8.2-fpm.service.d/credentials.conf
# Override dosyası olarak oluşturuyoruz
[Service]
LoadCredentialEncrypted=mysql-app-password:/etc/systemd/credentials/php-app-mysql.cred
LoadCredentialEncrypted=api-key:/etc/systemd/credentials/php-app-apikey.cred

# PHP-FPM başlamadan önce credential'ları environment'a aktar
ExecStartPost=/bin/bash -c '
  echo "env[MYSQL_PASSWORD] = $(cat $CREDENTIALS_DIRECTORY/mysql-app-password)" 
  >> /etc/php/8.2/fpm/pool.d/www.conf'

Aslında bu yaklaşım da ideale değil. Daha temiz bir yol, PHP uygulamasının credential dosyasını doğrudan okuması. Bunun için PHP’de yardımcı bir fonksiyon yazabiliriz:

<?php
// credentials.php - Uygulama içinde kullanım
function loadCredential(string $name): string {
    $credDir = getenv('CREDENTIALS_DIRECTORY');
    if (!$credDir) {
        throw new RuntimeException('CREDENTIALS_DIRECTORY ortam değişkeni bulunamadı');
    }
    
    $credFile = $credDir . '/' . $name;
    if (!file_exists($credFile)) {
        throw new RuntimeException("Credential bulunamadı: {$name}");
    }
    
    return trim(file_get_contents($credFile));
}

// Kullanım
$dbPassword = loadCredential('mysql-app-password');
$apiKey = loadCredential('api-key');

$pdo = new PDO(
    'mysql:host=localhost;dbname=myapp',
    'appuser',
    $dbPassword
);

SetCredentialEncrypted ile Unit Dosyasına Gömme

Bazen credential dosyasını ayrı yönetmek yerine doğrudan unit dosyasına gömmek isteyebilirsiniz. Bu özellikle container ortamlarında veya immutable altyapılarda işe yarıyor.

# Şifreli değeri base64 formatında al
ENCRYPTED_VALUE=$(echo -n "MyApiSecret999" | sudo systemd-creds encrypt 
  --name=app-secret 
  --pretty 
  - -)

echo "$ENCRYPTED_VALUE"

Çıktı şuna benzer bir şey olacak:

SetCredentialEncrypted=app-secret: 
        k6iUCUh0RJCQyvL8k8q1UyAAAAABAAAADAAAABAAAAASfFsBo/XefFdnmL3H 
        /xGqAAAAAgAAAAAAAAAA...

Bu çıktıyı direkt unit dosyanıza kopyalayabilirsiniz:

[Service]
User=myapp
ExecStart=/opt/myapp/server
SetCredentialEncrypted=app-secret: 
        k6iUCUh0RJCQyvL8k8q1UyAAAAABAAAADAAAABAAAAASfFsBo/XefFdnmL3H 
        /xGqAAAAAgAAAAAAAAAA...

Bu sistemin güzelliği şu: Unit dosyası Git’te tutulabilir, CI/CD pipeline’ından dağıtılabilir. Şifreli değer o sunucunun host anahtarıyla şifrelendiği için başka sunucuda çalışmaz.

Credential Doğrulama ve Debug

Sistemin düzgün çalışıp çalışmadığını kontrol etmek için birkaç faydalı komut:

# Servis için yüklenen credential'ları listele
sudo systemctl show myapp.service -p LoadCredential

# Servis çalışırken credential dizinini kontrol et
sudo ls -la /proc/$(systemctl show -p MainPID --value myapp.service)/fd

# Credential dosyasını manuel test için çöz
sudo systemd-creds decrypt 
  /etc/systemd/credentials/myapp-db-password.cred 
  -

# Sistemin TPM2 desteği var mı kontrol et
sudo systemd-creds has-tpm2

# Credential listesi ve durumu
sudo systemd-creds list

Servis başlatıldıktan sonra $CREDENTIALS_DIRECTORY değişkenini görmek için:

# Çalışan servisin environment'ına bak
sudo cat /proc/$(systemctl show -p MainPID --value myapp.service)/environ | 
  tr '' 'n' | grep CREDENTIALS

TPM2 ile Donanım Tabanlı Güvenlik

Sunucunuzda TPM2 çipi varsa (modern sunucuların büyük çoğunluğunda var), şifrelemeyi donanım seviyesine taşıyabilirsiniz. Bu durumda şifrelenmiş credential başka bir fiziksel makineye kopyalanırsa çözülemiyor.

# TPM2 desteğini kontrol et
systemd-creds has-tpm2
# Çıktı: yes (tam destekli) veya partial veya no

# TPM2 ile şifrele
echo -n "UltraSensitiveKey#2024" | sudo systemd-creds encrypt 
  --with-key=tpm2 
  --name=ultra-secret 
  - /etc/systemd/credentials/ultra-secret.cred

# Hem host hem TPM2 ile şifrele (en güvenli)
echo -n "UltraSensitiveKey#2024" | sudo systemd-creds encrypt 
  --with-key=host+tpm2 
  --name=ultra-secret 
  - /etc/systemd/credentials/ultra-secret.cred

host+tpm2 kombinasyonu şunu sağlıyor: hem makine kimliği hem TPM2 çipi eşleşmeli. Disk başka makineye takılsa bile, TPM2 çipi olmayan başka sisteme taşınsa bile şifre çözülemiyor.

Uygulama Credential’larını Rotation Etme

Şifre rotasyonu credential sistemiyle de yapılabiliyor. Servis yeniden başlatılmadan önce yeni credential hazırlanıyor:

#!/bin/bash
# credential-rotate.sh - Şifre rotasyon script'i

SERVICE_NAME="myapp"
CRED_NAME="db-password"
CRED_FILE="/etc/systemd/credentials/${SERVICE_NAME}-${CRED_NAME}.cred"

# Yeni şifreyi al (vault'tan, pwgen'dan, neredense)
NEW_PASSWORD=$(pwgen -s 32 1)

# Yeni credential'ı şifrele
echo -n "$NEW_PASSWORD" | systemd-creds encrypt 
  --name="$CRED_NAME" 
  - "$CRED_FILE"

# Veritabanında şifreyi güncelle
mysql -u root -p"$MYSQL_ROOT_PASS" -e 
  "ALTER USER 'appuser'@'localhost' IDENTIFIED BY '$NEW_PASSWORD';"

# Servisi yeniden başlat
systemctl restart "$SERVICE_NAME"

# Yeni şifreyi güvenli sil (memory'den)
unset NEW_PASSWORD

echo "Şifre rotasyonu tamamlandı: $SERVICE_NAME"

Bu script’i bir systemd timer ile periyodik olarak çalıştırabilirsiniz. Otomatik şifre rotasyonu için mükemmel bir temel.

Güvenlik Sertleştirme Önerileri

Credential sistemi tek başına yeterli değil. Birkaç ek güvenlik önlemi ile güçlendirilmeli:

PrivateTmp ve diğer izolasyon direktifleri ile birlikte kullanın:

[Service]
User=myapp
Group=myapp
LoadCredentialEncrypted=db-password:/etc/systemd/credentials/myapp-db.cred

# Güvenlik sertleştirme
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
CapabilityBoundingSet=
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service
ReadWritePaths=/var/lib/myapp /var/log/myapp

Bu yapılandırmayla servis:

  • /tmp dizinine izole erişiyor
  • Sistem dosyalarını yalnızca okuyabiliyor
  • Yeni privilege kazanamıyor
  • Sadece gerekli sistem çağrılarını yapabiliyor

Credential dosyalarının izinleri konusunda dikkatli olun:

# Credential dizinini sertleştir
sudo chmod 700 /etc/systemd/credentials
sudo chown root:root /etc/systemd/credentials

# Her credential dosyası 600 olmalı
find /etc/systemd/credentials -type f -exec chmod 600 {} ;

# Audit log için izleme ekle
sudo auditctl -w /etc/systemd/credentials -p rwxa 
  -k systemd-credentials

Journald ve Credential Gizliliği

systemd credential sistemi journal log’larında şifrelerinizin görünmemesini sağlar, ama uygulamanızın log’larına dikkat etmeniz gerekiyor. Bir servisin credential değerini yanlışlıkla log’laması ciddi sorun yaratır.

# Journal'da credential değeri arama (test amaçlı)
sudo journalctl -u myapp.service | grep -i "password|secret|key" | head -20

# Eğer çıktı varsa, uygulama kodunuzu gözden geçirin
# Credential'ların log'a düşmediğini doğrulayın

Sonuç

systemd Credential sistemi, Linux servis güvenliğinde ciddi bir olgunluk seviyesini temsil ediyor. HashiCorp Vault gibi bağımlılıklar olmadan, sadece systemd’nin kendi araçlarıyla güçlü bir şifre yönetimi kurabiliyorsunuz.

Özetlemek gerekirse:

  • LoadCredential ile mevcut dosyaları credential olarak yükleyebilirsiniz
  • LoadCredentialEncrypted ve SetCredentialEncrypted ile şifreli saklama yapabilirsiniz
  • TPM2 entegrasyonu ile donanım seviyesinde güvenlik elde edebilirsiniz
  • $CREDENTIALS_DIRECTORY sayesinde uygulamalar şifreleri sadece ihtiyaç duydukları an okuyabiliyor
  • Environment variable olarak düz metin şifre geçme dönemine son verebilirsiniz

Sisteminiz en azından systemd 247 (Ubuntu 22.04, RHEL 9, Debian 12 üzerinde standart) çalıştırıyorsa bu sistemi hemen kullanmaya başlayabilirsiniz. Küçük bir servis için bile olsa LoadCredential ile başlayın, zamanla LoadCredentialEncrypted ve TPM2 entegrasyonuna geçin.

Düz metin şifrelerin dosyalarda gezindiği günler geride kalsın.

Bir yanıt yazın

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