Scriptlerinizde şifreleri düz metin olarak saklıyorsanız, bir gün bu kodu başkasının eline geçtiğini ya da yanlışlıkla bir Git reposuna push’ladığınızı hayal edin. O his gerçekten korkunç. Yıllar içinde pek çok sysadmin meslektaşımın “şimdilik böyle yapalım” diyerek hardcode ettiği şifrelerin nasıl felakete yol açtığını gördüm. Bu yazıda Python scriptlerinizde şifre ve anahtar yönetimini nasıl düzgün yapacağınızı, hangi araçları kullanmanız gerektiğini ve gerçek dünya senaryolarıyla pratik çözümler üretmeyi ele alacağız.
Neden Hardcode Şifre Tehlikelidir?
Klasik sysadmin scripti şöyle görünür:
# YANLIŞ ÖRNEK - Bunu ASLA yapmayin!
db_password = "SuperSecretP@ss123"
api_key = "sk-1234567890abcdef"
ssh_password = "root123"
Bu kodun birkaç dakika sonra GitHub’a gitme ihtimali, ekip arkadaşınız tarafından görülme ihtimali ya da log dosyalarına düşme ihtimali her zaman vardır. Şifreleri kod içinde saklamamak sysadmin dünyasının altın kuralıdır.
Peki ne yapacağız? Birkaç farklı yaklaşım var ve her birinin kullanım senaryosu farklı.
Environment Variable ile Şifre Yönetimi
En basit ve en yaygın yöntem environment variable kullanmaktır. Özellikle CI/CD pipeline’larında ve container ortamlarında bu yaklaşım standarttır.
import os
import sys
def get_db_credentials():
"""Environment variable'dan veritabani bilgilerini al."""
db_host = os.environ.get("DB_HOST", "localhost")
db_user = os.environ.get("DB_USER")
db_password = os.environ.get("DB_PASSWORD")
if not db_user or not db_password:
print("HATA: DB_USER ve DB_PASSWORD environment variable'lari ayarlanmamis!")
sys.exit(1)
return {
"host": db_host,
"user": db_user,
"password": db_password
}
if __name__ == "__main__":
creds = get_db_credentials()
print(f"Baglanti yapiliyor: {creds['user']}@{creds['host']}")
# Şifreyi asla print etmeyin!
Bu scripti çalıştırmadan önce shell’de şu şekilde export yaparsınız:
export DB_HOST="prod-db-01.sirket.local"
export DB_USER="appuser"
export DB_PASSWORD="gercek_sifreniz"
python3 db_backup.py
Ya da .env dosyası kullanıyorsanız python-dotenv kütüphanesi işinizi kolaylaştırır. Ama dikkat edin, .env dosyasını asla Git’e commit etmeyin. .gitignore dosyanıza .env eklemek zorundasınız.
# .env dosyasi ornegi
DB_HOST=prod-db-01.sirket.local
DB_USER=appuser
DB_PASSWORD=gercek_sifreniz
API_KEY=sk-abcdef1234567890
from dotenv import load_dotenv
import os
# .env dosyasini yukle
load_dotenv()
db_password = os.getenv("DB_PASSWORD")
api_key = os.getenv("API_KEY")
print(f"API Key yuklendi: {api_key[:8]}...") # Sadece ilk 8 karakteri goster
Python Keyring ile Sistem Anahtar Deposu
Linux’ta GNOME Keyring ya da KWallet, macOS’ta Keychain, Windows’ta Credential Manager kullanmak istiyorsanız keyring kütüphanesi mükemmel bir seçenek. Bu yöntem özellikle interaktif araçlar ve geliştirici workstation’ları için idealdir.
pip install keyring
import keyring
import getpass
def save_credentials(service_name, username):
"""Kimlik bilgilerini sistem anahtar deposuna kaydet."""
password = getpass.getpass(f"{service_name} icin sifre girin: ")
keyring.set_password(service_name, username, password)
print(f"Kimlik bilgileri '{service_name}' servisi icin kaydedildi.")
def get_credentials(service_name, username):
"""Sistem anahtar deposundan kimlik bilgilerini al."""
password = keyring.get_password(service_name, username)
if password is None:
print(f"Kayitli sifre bulunamadi. Ilk kez calistiriliyor...")
save_credentials(service_name, username)
password = keyring.get_password(service_name, username)
return username, password
# Kullanim ornegi
service = "prod-database"
user = "dbadmin"
username, password = get_credentials(service, user)
print(f"Kullanici: {username} icin sifre alindi.")
# password degiskenini direkt kullanin, yazdiırmayin
Bu yöntemin güzelliği şifrenin işletim sisteminin güvenli deposunda şifreli olarak tutulması. Script kodunda hiçbir şekilde şifre bulunmuyor.
Cryptography Kütüphanesi ile Şifreleme
Bazen şifreleri bir dosyada saklamak zorunda kalırsınız, örneğin headless sunucularda ya da batch işlemlerde. Bu durumda cryptography kütüphanesi ile Fernet simetrik şifreleme kullanabilirsiniz.
pip install cryptography
from cryptography.fernet import Fernet
import os
import json
import base64
class SecureCredentialStore:
"""Sifreli kimlik bilgisi deposu."""
def __init__(self, key_file="secret.key", creds_file="credentials.enc"):
self.key_file = key_file
self.creds_file = creds_file
self.key = self._load_or_create_key()
self.fernet = Fernet(self.key)
def _load_or_create_key(self):
"""Sifrelem anahtarini yukle veya olustur."""
if os.path.exists(self.key_file):
with open(self.key_file, "rb") as f:
return f.read()
else:
key = Fernet.generate_key()
with open(self.key_file, "wb") as f:
f.write(key)
# Anahtar dosyasini sadece sahibi okuyabilsin
os.chmod(self.key_file, 0o600)
print(f"Yeni sifreeleme anahtari olusturuldu: {self.key_file}")
return key
def save_credential(self, name, value):
"""Kimlik bilgisini sifreli olarak kaydet."""
creds = self._load_all_credentials()
creds[name] = value
encrypted = self.fernet.encrypt(json.dumps(creds).encode())
with open(self.creds_file, "wb") as f:
f.write(encrypted)
os.chmod(self.creds_file, 0o600)
print(f"'{name}' sifreli olarak kaydedildi.")
def get_credential(self, name):
"""Sifreli depodan kimlik bilgisini al."""
creds = self._load_all_credentials()
return creds.get(name)
def _load_all_credentials(self):
"""Tum sifreli kimlik bilgilerini yukle."""
if not os.path.exists(self.creds_file):
return {}
with open(self.creds_file, "rb") as f:
encrypted_data = f.read()
decrypted = self.fernet.decrypt(encrypted_data)
return json.loads(decrypted.decode())
# Kullanim
store = SecureCredentialStore()
store.save_credential("mysql_password", "ProdSifrem@2024")
store.save_credential("api_key", "sk-abcdef123456")
mysql_pass = store.get_credential("mysql_password")
print(f"MySQL sifresi alindi: {'*' * len(mysql_pass)}")
Önemli uyarı: Bu yöntemde anahtar dosyasını (secret.key) güvende tutmak kritik. Şifreli dosyayı Git’e alabilirsiniz ama anahtar dosyasını kesinlikle almayın.
HashiCorp Vault ile Kurumsal Şifre Yönetimi
Production ortamlarında gerçek bir secrets management çözümü kullanmak gerekir. HashiCorp Vault bu konuda en yaygın tercih. Küçük ve orta ölçekli ortamlar için bile Vault kurulumu karmaşık görünse de hvac Python kütüphanesi ile entegrasyon oldukça basit.
pip install hvac
import hvac
import os
class VaultClient:
"""HashiCorp Vault entegrasyonu."""
def __init__(self):
vault_addr = os.environ.get("VAULT_ADDR", "http://vault.sirket.local:8200")
vault_token = os.environ.get("VAULT_TOKEN")
if not vault_token:
raise ValueError("VAULT_TOKEN environment variable ayarlanmamis!")
self.client = hvac.Client(
url=vault_addr,
token=vault_token
)
if not self.client.is_authenticated():
raise ConnectionError("Vault kimlik dogrulama basarisiz!")
print("Vault baglantisi basarili.")
def get_secret(self, path, key=None):
"""Vault'tan secret al."""
try:
response = self.client.secrets.kv.v2.read_secret_version(
path=path,
mount_point="secret"
)
data = response["data"]["data"]
if key:
return data.get(key)
return data
except hvac.exceptions.InvalidPath:
print(f"HATA: '{path}' yolunda secret bulunamadi.")
return None
def write_secret(self, path, secret_data):
"""Vault'a secret yaz."""
self.client.secrets.kv.v2.create_or_update_secret(
path=path,
secret=secret_data,
mount_point="secret"
)
print(f"Secret '{path}' yoluna yazildi.")
# Kullanim ornegi
vault = VaultClient()
# Veritabani bilgilerini al
db_creds = vault.get_secret("database/production")
if db_creds:
print(f"DB kullanici: {db_creds['username']}")
# db_creds['password'] ile baglanti kur
# API anahtarini al
api_key = vault.get_secret("api/external-service", key="api_key")
Gerçek Dünya Senaryosu: Otomatik Yedekleme Scripti
Şimdi tüm bu bilgileri birleştirerek gerçekçi bir senaryo oluşturalım. Çok sayıda sunucunun MySQL veritabanlarını yedekleyen ve sonuçları S3’e yükleyen bir script yazalım.
#!/usr/bin/env python3
"""
Guvenli MySQL Yedekleme Scripti
Sifreler environment variable veya keyring'den alinir.
"""
import os
import sys
import subprocess
import logging
import keyring
import getpass
from datetime import datetime
from pathlib import Path
# Logging ayarlari - sifre log'a dusmemeli!
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/db_backup.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class DatabaseBackup:
def __init__(self):
self.db_host = os.environ.get("DB_HOST", "localhost")
self.db_user = os.environ.get("DB_USER", "backup_user")
self.db_password = self._get_db_password()
self.s3_bucket = os.environ.get("S3_BUCKET", "sirket-db-backups")
self.backup_dir = Path("/tmp/db_backups")
self.backup_dir.mkdir(exist_ok=True)
def _get_db_password(self):
"""
Sifre alma onceligi:
1. Environment variable
2. Keyring (sistem anahtar deposu)
3. Interaktif giris
"""
# Once environment variable'a bak
password = os.environ.get("DB_PASSWORD")
if password:
logger.info("Sifre environment variable'dan alindi.")
return password
# Keyring'e bak
password = keyring.get_password("mysql-backup", self.db_user)
if password:
logger.info("Sifre sistem anahtar deposundan alindi.")
return password
# Interaktif giris
logger.warning("Kayitli sifre bulunamadi, interaktif giris gerekli.")
password = getpass.getpass(f"MySQL {self.db_user} sifresi: ")
# Ileriki kullanimlar icin kaydet
save = input("Bu sifreyi sistem anahtar deposuna kaydetmek ister misiniz? (e/h): ")
if save.lower() == 'e':
keyring.set_password("mysql-backup", self.db_user, password)
logger.info("Sifre sistem anahtar deposuna kaydedildi.")
return password
def backup_database(self, db_name):
"""Belirtilen veritabanini yedekle."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_file = self.backup_dir / f"{db_name}_{timestamp}.sql.gz"
# MYSQL_PWD env variable kullanimi - komut satiri gecmisinde gorunmez
env = os.environ.copy()
env["MYSQL_PWD"] = self.db_password
cmd = [
"mysqldump",
f"--host={self.db_host}",
f"--user={self.db_user}",
"--single-transaction",
"--routines",
"--triggers",
db_name
]
try:
with open(backup_file, 'wb') as f:
dump_proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env # Sifre env uzerinden gidecek
)
gzip_proc = subprocess.Popen(
["gzip", "-9"],
stdin=dump_proc.stdout,
stdout=f,
stderr=subprocess.PIPE
)
dump_proc.stdout.close()
gzip_proc.communicate()
if dump_proc.returncode == 0:
logger.info(f"{db_name} yedegi basariyla olusturuldu: {backup_file}")
return backup_file
else:
logger.error(f"{db_name} yedegi basarisiz!")
return None
except Exception as e:
logger.error(f"Yedekleme hatasi: {e}")
return None
if __name__ == "__main__":
backup = DatabaseBackup()
databases = ["production_db", "analytics_db", "user_db"]
for db in databases:
result = backup.backup_database(db)
if result:
logger.info(f"Yedek hazir: {result}")
Bu script’in güzel yanı şifrenin hiçbir zaman log dosyasına ya da komut satırı geçmişine düşmemesi. MYSQL_PWD environment variable kullanımı, --password=sifre şeklinde komut satırına geçmekten çok daha güvenlidir.
SSH Anahtarı Yönetimi
Birden fazla sunucuya bağlanan scriptlerde SSH anahtar yönetimi de kritik. paramiko kütüphanesi ile şifreli SSH anahtarlarını nasıl kullanacağınızı görelim.
import paramiko
import os
import getpass
import keyring
def create_ssh_client(hostname, username, port=22):
"""
Guvenli SSH istemcisi olustur.
Anahtar dosyasi veya sifre kullanir.
"""
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.RejectPolicy()) # Bilinmeyen host'lari reddet!
# known_hosts dosyasini yukle
known_hosts = os.path.expanduser("~/.ssh/known_hosts")
if os.path.exists(known_hosts):
client.load_host_keys(known_hosts)
# SSH anahtari varsa kullan
ssh_key_path = os.path.expanduser("~/.ssh/id_rsa")
if os.path.exists(ssh_key_path):
# Anahtar passphrase'ini keyring'den al
passphrase = keyring.get_password("ssh-key", ssh_key_path)
try:
client.connect(
hostname=hostname,
port=port,
username=username,
key_filename=ssh_key_path,
passphrase=passphrase,
look_for_keys=False,
allow_agent=False
)
print(f"SSH baglantisi kuruldu: {username}@{hostname}")
return client
except paramiko.ssh_exception.PasswordRequiredException:
# Passphrase gerekiyor, interaktif sor
passphrase = getpass.getpass(f"SSH anahtar passphrase'i ({ssh_key_path}): ")
keyring.set_password("ssh-key", ssh_key_path, passphrase)
client.connect(
hostname=hostname,
port=port,
username=username,
key_filename=ssh_key_path,
passphrase=passphrase
)
return client
raise ConnectionError(f"SSH anahtari bulunamadi: {ssh_key_path}")
# Kullanim
try:
ssh = create_ssh_client("prod-web-01.sirket.local", "deploy")
stdin, stdout, stderr = ssh.exec_command("df -h")
print(stdout.read().decode())
ssh.close()
except Exception as e:
print(f"SSH hatasi: {e}")
Şifre Doğruluğunu Test Etme ve Maskeleme
Scriptlerinizin çıktılarında şifrelerin yanlışlıkla görünmesini engellemek için bir yardımcı sınıf oluşturalım:
import re
import logging
class SensitiveDataFilter(logging.Filter):
"""Log kayitlarindan hassas verileri temizler."""
SENSITIVE_PATTERNS = [
(r'password["s]*[:=]["s]*S+', 'password=***'),
(r'passwd["s]*[:=]["s]*S+', 'passwd=***'),
(r'secret["s]*[:=]["s]*S+', 'secret=***'),
(r'token["s]*[:=]["s]*S+', 'token=***'),
(r'api[_-]?key["s]*[:=]["s]*S+', 'api_key=***'),
(r'sk-[a-zA-Z0-9]{20,}', 'sk-***'), # OpenAI tarzı key'ler
]
def filter(self, record):
record.msg = self._mask_sensitive_data(str(record.msg))
if record.args:
record.args = tuple(
self._mask_sensitive_data(str(arg)) if isinstance(arg, str) else arg
for arg in record.args
)
return True
def _mask_sensitive_data(self, text):
for pattern, replacement in self.SENSITIVE_PATTERNS:
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
return text
# Logger'a filtre ekle
logger = logging.getLogger()
logger.addFilter(SensitiveDataFilter())
# Test
logger.info("Baglanti: password=SuperSecret123 ile kuruldu")
# Cikti: Baglanti: password=*** ile kuruldu
logger.info("API key=sk-abcdef1234567890 kullaniliyor")
# Cikti: API key=*** kullaniliyor
En İyi Pratikler ve Hatırlatmalar
Tüm bu yöntemlerin yanında günlük pratik hayatta dikkat etmeniz gereken bazı temel kurallar var:
.gitignorezorunlu:.env,*.key,credentials.enc,secrets.yamlgibi dosyalar mutlaka.gitignore‘da olmalı- Dosya izinleri: Şifre içeren dosyalar
chmod 600ile sadece sahibine okunabilir olmalı - Şifreyi asla print etmeyin: Log ve print ifadelerinde şifreyi doğrudan yazmayın, maskeleme kullanın
- subprocess’te komut satırından şifre geçirmeyin:
--password=sifreyerine environment variable ya da stdin kullanın - Düzenli rotasyon: Scriptlerin kullandığı şifreleri ve API anahtarlarını belirli aralıklarla değiştirin
- Minimum yetki prensibi: Backup user’ın sadece SELECT yetkisi olsun, admin yetkisi olmasın
- Secrets scanning: Git hook’larına
git-secretsya datruffleHogekleyin, yanlışlıkla commit’i engelleyin - Production ve test ayrımı: Farklı ortamlar için farklı credential’lar kullanın, production şifresi test scriptinde bulunmasın
Sonuç
Python scriptlerinde şifre yönetimi “sonra düzeltirim” diyerek ertelenen ama gerçekten kritik bir konu. Basit bir cron job bile production veritabanına bağlanıyorsa doğru yönetilmelidir.
Küçük ve tek kullanıcılı ortamlar için environment variable ve keyring kombinasyonu çoğunlukla yeterli. Takım ortamlarında .env dosyalarını şifreli bir password manager’da saklayın ve paylaşın. Kurumsal ve büyük ölçekli ortamlarda HashiCorp Vault ya da AWS Secrets Manager, Azure Key Vault gibi managed servislere yatırım yapın.
Unutmayın, bir şifrenin sızdığı an onu ele geçiren kişinin sisteminizde ne kadar süre gezindiğini bilemezsiniz. Güvenlik her zaman “sonra” yapılacak bir şey değil, scriptinizi yazarken düşünmeniz gereken bir tasarım kararıdır. Bugün 15 dakika harcayarak doğru yöntemi uygularsanız, ileride saatler süren incident müdahalesinden kurtulmuş olursunuz.