Teknoloji

VPS’te Secrets Yönetimi Nasıl Tatlı Tatlı Çözülür? sops + age, GitOps Akışı, systemd ve Rotasyon

Ofiste Başlayan Küçük Panik: Bir .env Dosyası, Bir PR ve Soğuk Ter

Hiç başınıza geldi mi? Akşam üstü bir PR açıyorsunuz, her şey tertemiz. Kod güzel, testler yeşil. Son kontrole bakarken bir şey göze çarpıyor: .env dosyasındaki API anahtarı, yanlışlıkla düz metin halde commitlenmiş. O an insanın içinden geçen tek şey, bir kahveyle beraber birkaç küfür. Birkaç dakika içinde PR kapatılıyor, anahtar iptal ediliyor, yeni anahtar üretiliyor. Ama o kısacık panik bize bir şey söylüyor: Gizli anahtar işi şansa bırakılmaz.

Ben de yıllar içinde bu hikayeyi defalarca yaşadım; bazen staging’de şifreli dosya unutuluyor, bazen prod sunucuda manuel bir değişiklik kalıcı sanılıyor. İşin sonu genelde gecenin bir vakti SSH ile sunucuya girip temizlemeye, ve ertesi gün “bu işi nasıl kalıcı çözeriz?” sorusunu masaya koymaya varıyor. Bugün tam da bunu konuşalım istiyorum: VPS’te secrets yönetimini sops + age ile nasıl düzgün bir GitOps akışına bağlarız, systemd ile nasıl güvenle yükleriz, ve rotasyonu nasıl korkulacak bir iş olmaktan çıkarırız?

Yazının devamında tattığınız o minik paniklerin nedenlerini birlikte çözeceğiz. Mesela şöyle düşünün: Her şey kodla yönetiliyor, PR’la değişiyor, deploy’la hayata geçiyor. Gizli anahtarlar da aynı akışa katıldığı an işler bir anda berraklaşıyor. sops + age bize bunun pratik ve güvenli yöntemini sunuyor. Üstüne bir de systemd ile servislerimize sızdırmadan enjekte edince, işler tadından yenmiyor.

VPS’te Secrets Neden Dert Olur? GitOps’ta Taş Gibi Bir Akış

VPS tarafında işleri yürütürken en kolay tuzak, ‘zaten küçük bir sunucu, bir scp atarız biter’ diye düşünmek. Bir süre idare ediyor, kabul. Fakat takım büyüdüğünde, servis sayısı arttığında, geliştirme ortamı, staging ve production ayrıştığında aynı dosyayı nereye, kiminle, nasıl güncellediğinizi takip etmek zorlaşıyor. Bu karmaşa genelde düz metinle saklanan .env dosyaları ve dağınık notlarla başlıyor.

GitOps dediğimiz yaklaşım işte tam burada ilaç gibi geliyor. Fikir basit: Altyapı ve yapılandırma dahil her değişiklik Git üzerinden gözlemlenebilir, denetlenebilir, geri alınabilir olmalı. Secrets da bu hikayenin kahramanı. Peki düz metin commitlenmeden nasıl yapacağız? Şifreli commit. Yani repo içinde secrets dosyası var, ama şifreli. Decrypt sadece yetkili VPS’lerde, sadece servis başlarken yapılıyor. Böylece PR’ınızda gizli anahtarı görmüyorsunuz, ama yine de değişiklikleri yönetiyor, versiyonluyor, iz bırakıyorsunuz.

Mesela dün staging’de kullanılan bir üçüncü parti API anahtarını değiştirmeniz gerekti. PR açtınız, dosya şifreli, değişiklik review’dan geçti, merge edildi. Dağıtım pipeline’ı yeni dosyayı sunucuya getirdi, servis başlamadan önce şifreyi çözdü ve güvenli bir şekilde belleğe aldı. Ne panik, ne sürpriz. İşte aradığımız düzen bu.

sops + age ile Şifreli Depo: Hızlı ve Temiz Başlangıç

sops ve age ne yapar, nasıl çalışır?

sops, dosyalarınızı alan bazlı şifrelemenize yarayan minik ama güçlü bir araç. YAML, JSON, dotenv gibi formatları sever; sadece belirli alanları şifrelemeye de izin verir. age ise modern, sade ve hızlı bir dosya şifreleme aracı. İkisi birleşince, repoda duran secrets dosyası şifreli, açması ise sadece doğru kimliklere sahip sunucularda mümkün hale geliyor. Kısacası, commit ediyorsunuz ama kimse içeriği çıplak gözle göremiyor.

Başlangıç için önce age kimliği üretelim:

age-keygen -o agekey.txt
# İçeriği görün, 'public key' kısmını sops'a vereceğiz
cat agekey.txt

age anahtar dosyasını VPS’inizde güvenli bir yerde saklayın. Gerekirse sadece root’un okuyabildiği bir dizin, izinleri sıkı, hatta disk şifrelemesi düşünülmüş bir yaklaşım kullanın. Lokal makinenizde tutacaksanız şifreli bir kasa da iyi fikir. Sonra sops’a bu alıcıyı tanıtalım. Proje köküne bir .sops.yaml koyarak kural yazıyoruz:

cat > .sops.yaml <<'YAML'
creation_rules:
  - path_regex: 'secrets/.*.yaml'
    encrypted_regex: '^(data|secret|password|token)'
    age: ['age1q3...alici_kamu_anahtari...']
  - path_regex: 'secrets/.*.env'
    age: ['age1q3...alici_kamu_anahtari...']
YAML

Artık repo içinde secrets klasöründeki dosyalar commit edilirken sops tarafından age’le şifrelenmiş olacak. Yeni bir secrets dosyası üretmeyi deneyelim:

mkdir -p secrets
EDITOR=vim sops secrets/app.env

sops komutu dosyayı açtığınızda, kaydedip çıktığınız anda dosya şifreli halde repoda duracak. İçeride düz metinle yazdığınız satırlar, diske şifreli yazılıyor. Kontrol etmek isterseniz:

cat secrets/app.env
# Şifreli blob görmelisiniz

Bu arada resmi kaynaklara göz atmak isterseniz, sops için sops projesinin GitHub sayfası ve age için age dokümantasyonu gayet anlaşılır bir başlangıç sunuyor.

Hangi dosyaları şifrelemeli?

İçinde token, parolalar, API anahtarları, bağlantı stringleri, sertifika özel anahtarları gibi değerler olan her dosya. Bazı ekipler komple dosyayı şifreliyor, bazıları sadece belirli alanları. Ben küçük ekiplerde komple şifrelemeyi, büyüdükçe alan bazlı şifrelemeyi daha pratik buluyorum. Önemli olan, repoda hiçbir gizli şeyin düz metin kalmaması.

GitOps Akışı: PR, CI ve Sunucu Tarafında Çözücü

PR tabanlı değişim, audit izleri ve huzur

Şimdi taşı netleştirelim: Secrets dosyası repoda şifreli. Bir değişiklik gerektiğinde PR açıyorsunuz. İnceleme yapan kişi içeriği düz göremez, ama değişiklik satırlarını şifreli halden anlar; esas kontrol, değişikliğin motivasyonunda ve süreçte. Merge edildi mi? CI pipeline’ı devreye girer, hedef VPS’e şifreli dosyayı yollar.

Sunucu tarafında age kimliği duruyor, sops ile dosyayı yalnızca orada çözüyoruz. Böylece ‘çözüldü mü, kim gördü, kim kopyaladı’ derdi yok. Kayıtlar Git’te, gözler PR’da, üretim ise yalnızca üretimde.

Dağıtım otomasyonu ve ilk hazırlık

İlk kurulumda yaşamı kolaylaştıran iki dost var: cloud-init ve Ansible. VPS ilk nefesini aldığında, age anahtarlarınızı ve sops yapılandırmanızı güvenle yerleştirmek işin yarısı. Bu yaklaşımın ruhu için şu rehbere göz atmak iyi gelir: cloud-init ve Ansible ile tekrar üretilebilir VPS. Üstüne altyapı nesnelerini Git’le yönetmek isterseniz Terraform katmanı ekleyebilirsiniz; DNS ve VPS tarafını da aynı akışa bağlamak, orkestrasyonu sadeleştiriyor. Detaylı bir yol haritası için şu rehber gayet iyi: Terraform ile VPS ve DNS otomasyonu.

CI tarafında ise basit bir kural yeter: Şifreli dosyalar olduğu gibi paketlenip sunucuya kopyalansın; çözme işlemi sunucuda ve servis başlarken gerçekleşsin. Böylece CI sisteminizin secrets görmesine gerek kalmaz.

systemd ile Entegrasyon: Güvenli Çevre Değişkenleri ve Credentials

Güvenli bir yol: Decrypt et, /run içine yaz, EnvironmentFile ile yükle

Pratik bir desen: Servis başlamadan hemen önce sops ile şifreli dosyayı çözüp, çıktı dosyasını tmpfs üstünde bir dizine yazmak. /run dizini bu iş için ideal; reboot ile temizlenir, dosyalar RAM’de durur, iz bırakma riski azalır. Sonra systemd’nin EnvironmentFile direktifiyle o dosyayı servis ortamına yüklersiniz.

# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myapp
Group=myapp
EnvironmentFile=/run/myapp/app.env
ExecStartPre=/usr/bin/mkdir -p /run/myapp
ExecStartPre=/usr/bin/chmod 0700 /run/myapp
ExecStartPre=/usr/bin/sops -d /etc/myapp/secrets/app.env > /run/myapp/app.env
ExecStart=/usr/local/bin/myapp
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

Burada dikkat: /etc/myapp/secrets/app.env repodan gelen şifreli dosya; /run/myapp/app.env ise şifresi çözülmüş, sadece servis tarafından okunacak dosya. İzinleri sıkı tutmak önemli. Ayrıca günlüklerde gizli değerlerin görünmemesi için uygulamanın log formatına dikkat edin; bazen farkında olmadan env’i döken logger ayarları oluyor.

systemd credentials ve modern dokunuş

systemd’nin yeni sürümlerinde credentials mekanizması var; servis başlatılırken dosya benzeri kimlikleri güvenli bir şekilde içeri almak için iyi bir seçenek. Detayları systemd credentials dokümanında bulabilirsiniz. Basit bir kullanımda, ExecStartPre ile sops -d çalıştırıp output’u $CREDENTIALS_DIRECTORY içine düşürür, serviste o dosyayı okursunuz. Bu sayede Environment değişkenlerinden kaçınır, dosya tabanlı tüketim yaparsınız. Bazı diller ve framework’ler dosyadan okuma konusunda daha disiplinli olduğu için hoş bir desen.

Güvenlik küçük notları

Servis user’ı ayrı olsun; root’la çalıştırmaktan kaçının. Decrypt edilen dosyayı sadece o kullanıcı okuyabilsin. Dosyayı iş bittikten sonra gereksiz yere bırakmayın; servis dururken ExecStopPost ile temizlemek iyi bir alışkanlık. Ve mümkünse secrets hiçbir şekilde journal, stdout, core dump gibi yerlere uğramasın. Küçük ayarlar büyük kazalar önler.

Rotasyon: Anahtarları İnşaat Tozu Kaldırmadan Döndürmek

age anahtarı nasıl döndürülür?

Bir gün gelecek, age kimliklerini yenilemek isteyeceksiniz. Belki ekipten biri ayrıldı, belki de güvenlik politikası böyle istiyor. Korkulacak bir şey yok. Yeni bir age kimliği üretirsiniz, sops’a yeni alıcıyı eklersiniz, sonra tüm dosyaları bu alıcıyla yeniden şifreletirsiniz. Eski alıcıyı hemen silmek zorunda değilsiniz; kademeli geçiş rahatlatır.

# Yeni kimlik
age-keygen -o agekey_2024q4.txt

# .sops.yaml içine yeni alıcıyı ekleyin
# ardından projedeki şifreli dosyaları güncelleyin
sops updatekeys -y

İlk deploy’da servisler hem eski hem yeni alıcıyla çözülebilir halde olur. Birkaç deploy sonra, güvenle eskisini çıkartırsınız. Bu yaklaşım bana DNSSEC key rollover’da sıfır kesintili anahtar döndürme planlarını anımsatır. Aynı nezaketle, kullanıcıyı rahatsız etmeden işler yürür.

Otomasyonla küçük bir dans

İşleri el yordamıyla yapmak yerine, küçük bir script ve bir systemd timer ile hatırlatmalar ekleyebilirsiniz. Mesela her ay bir rapor üretip hangi secrets hangi alıcılarla şifreli, hangileri eski anahtarı hâlâ taşıyor görelim. Script sadece rapor üretir; değişimi yine PR ile yaparsınız. Böylece hem kontrol sizde kalır hem de rotasyon unutulmaz.

Yedekleme ve Kurtarma: Kötü Günü İyiye Çevirmek

age kimliklerini nasıl saklamalı?

Şunu asla unutmayın: age kimlikleriniz yoksa, şifreli dosyalarınız yalnızca sanat eseridir. Güzel bakarsınız ama içeriğini göremezsiniz. Bu yüzden kimliği birden fazla güvenli yerde tutun. Bir tanesi şifreli parola kasanızda, bir kopya offline, gerekirse bir YubiKey üstünde. Kurumsal bir depoda şifreli saklamak da mantıklı olabilir.

Yedekleme stratejisinde versiyonlama ve değişmezlik büyük yardımcı. Bu konuda en sevdiğim pratiklerden biri, S3 benzeri depolarda Object Lock ve versioning kullanmak. Eğer bu fikir ilginizi çekerse, şu rehberin yaklaşımı işinize yarar: S3 Object Lock ile fidye yazılıma karşı kale gibi yedek. Böylece yanlışlıkla silinen bir anahtar dosyasını geri getirmek veya kötü niyetli değişikliği geri almak çok daha kolay olur.

İz ve denetim

GitOps’un güzel tarafı burada kendini gösteriyor: PR geçmişi, commit mesajları, release notları size kimin ne zaman, neden bir değişiklik yaptığını anlatır. Süreç şeffaflaştıkça, hatalar daha erken yakalanır. Bir şeyler ters gittiğinde, ‘kim, neyi, nasıl değiştirdi?’ sorusu dosdoğru bir kayda bakılarak yanıtlanır.

Uçtan Uca Mini Senaryo: Node.js Servisini Hayata Döndürelim

Repo ve secrets

Varsayalım basit bir Node.js API’niz var. Repo kökünde secrets/app.env dosyası dursun. İçinde şöyle değerler olsun:

# sops edit ile oluşturduğunuz .env içeriği, kaydedince şifreli saklanır
NODE_ENV=production
PORT=8080
DB_URL=postgres://user:pass@localhost:5432/app
JWT_SECRET=super_gizli_anahtar

Bu dosyayı düz metin commitlemiyorsunuz, sops zaten devrede. PR açınca değişiklik şifreli blob olarak görünür, ama versiyonlama çalışır.

Sunucu hazırlığı

VPS’te age anahtarınız /etc/myapp/agekey.txt altında olsun. İzinleri sıkalım:

sudo mkdir -p /etc/myapp/secrets
sudo chown -R root:root /etc/myapp
sudo chmod 0700 /etc/myapp
sudo chmod 0600 /etc/myapp/agekey.txt

sops’a age kimliğini tanıtmak için ortam değişkeni kullanabilir veya .sops.yaml ile yaşatabilirsiniz. Genelde repo içinde .sops.yaml bulunur. Sunucuda decrypt ederken sops, dosya üstündeki alıcılara bakar; kimlik dosyası ortamda ise çözebilir. Örneğin:

export SOPS_AGE_KEY_FILE=/etc/myapp/agekey.txt
sops -d /etc/myapp/secrets/app.env

systemd servisi

Bir systemd servisi yazalım. Servis, şifreli app.env dosyasını her start’tan önce çözecek, /run/myapp/app.env içine güvenle koyacak, sonra uygulamayı başlatacak.

# /etc/systemd/system/myapp.service
[Unit]
Description=My Node App
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myapp
Group=myapp
Environment=SOPS_AGE_KEY_FILE=/etc/myapp/agekey.txt
EnvironmentFile=/run/myapp/app.env
ExecStartPre=/usr/bin/mkdir -p /run/myapp
ExecStartPre=/usr/bin/chmod 0700 /run/myapp
ExecStartPre=/usr/bin/sops -d /etc/myapp/secrets/app.env > /run/myapp/app.env
ExecStart=/usr/bin/node /srv/myapp/server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

Ardından:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Her restart’ta secrets yeniden çözülür. Deploy pipeline’ınız, şifreli dosyayı güncellediğinde, sadece restart ile yeni değerler devreye girer. Böyle bir akışta SSL, DNS, TCP gibi çevre konularında da tertemiz davranmak işleri daha da sağlamlaştırır. Örneğin yoğun trafiği olan hizmetlerde ağ ayarlarını sakin ve bilinçli yapmak için şu pratik yolculuk hoş bir eşlikçi: Linux TCP tuning ile SYN fırtınasında sakin kalmak.

Günlüklerde sızıntıyı engelleme

Uygulamanızın loglama katmanında, asla env’i topluca dökmeyin. Gereksiz debug modlarını üretimde kapalı tutun. systemd journal’ı dışarıya gönderen forward ayarınız varsa, giden akışta da filtre olduğundan emin olun. Bir satır dalgınlık, haftalarca baş ağrısı demek olabilir.

Pratik İpuçları: Küçük Dokunuşlar, Büyük Ferahlık

Dağıtımda tek yönlü akış

Şifreli dosya repodan tek yönlü akar: CI kopyalar, VPS’te çözülür. Decrypt edilmiş dosyayı repoya, CI çıktısına veya kalıcı diske geri yazmayın. /run gibi uçucu alanlar bu yüzden güzel.

İzinler ve kullanıcılar

Servis kullanıcılarını ayrı tutun. Sadece ihtiyaç duyan servis, sadece ihtiyacı olan dosyaya erişsin. En basitinden, chmod 0600 ve doğru ownership çoğu kazayı engeller. Ayrıca docker veya containerd ile koşuyorsanız, container içinde decrypt yerine host tarafında decrypt edip mount etmek, izleri azaltır.

Rotasyonu takvime bağlayın

Her çeyrekte küçük bir rotasyon yapmak, beklenmedik anlarda aceleye gelmekten iyidir. Küçük PR’lar, küçük riskler demektir. PR açıklamalarına neden ve kapsam notunu eklemek, gelecekte kendinize göndereceğiniz bir teşekkür kartıdır.

Kapanış: Panikten Dünyayı Kurtaran Küçük Alışkanlıklar

Toparlayalım. VPS’te secrets yönetimi, bir kez düzenli kurulduğunda “üzerinde düşünülmeyen” bir konfor alanına dönüşüyor. sops + age ikilisiyle repoda şifreli saklama, GitOps zihniyetiyle PR tabanlı değişim, systemd ile güvenli enjekte etme ve düzenli rotasyon; hepsi bir arada olunca o meşhur .env paniği tarihe karışıyor. Kulağa büyük proje işi gibi gelmesin; küçük bir serviste bile faydasını hemen hissediyorsunuz.

Pratik bir başlangıç için şunu öneririm: Bugün bir age anahtarı üretin, sops’u projeye alın, tek bir secrets dosyasını şifreleyin ve systemd ile /run içine decrypt etmeyi deneyin. Yarın bunu CI akışına bağlayın. Haftaya bir mini rotasyon provası yapın. Hepsi küçük adımlar, ama toplamda güveni ve huzuru artırıyor. İsterseniz bu akışı, Let’s Encrypt’te yaşadığınız otomasyon keyfini kurarken aldığınız tatlı derslerle de benzetebilirsiniz; ben hâlâ çok alan adında SSL alırken izlediğimiz stratejileri bu mantığa yan yana koyarım: düzen, otomasyon, şeffaflık.

Umarım bu rehber, bir sonraki PR’ınızda içinizi ferahlatır. Sorularınız olursa her zamanki gibi yazın; kahve benden, sohbet sizden. Bir dahaki yazıda görüşmek üzere.

Sıkça Sorulan Sorular

sops dosyaları alan bazlı şifrelemeyi kolaylaştırır, age ise hızlı ve sade şifreleme sunar. İkisi birleşince repoda şifreli saklayıp, sadece VPS’te çözerek güvenli ve denetlenebilir bir akış elde edersin.

En güvenlisi sunucuda yapmak. CI sadece şifreli dosyayı taşır. Sunucuda systemd ile servis başlamadan hemen önce çözersin; böylece CI’ın gizli anahtarı görmesi gerekmez.

Doğru planla hayır. Yeni age alıcısını ekleyip dosyaları yeniden şifreledikten sonra deploy edersin. Bir süre eski alıcıyı da tutup kademeli geçiş yaparsan kesinti olmadan rotasyon tamamlanır.