sops-nix: NixOS'ta Şifreli Secret Yönetimi

NixOS’un güçlü yanı, tüm sistem konfigürasyonunu tek bir Git repo’sunda tutabilmektir. Ama bu hemen bir soru doğurur: peki ya şifreler?

Bir self-hosted servis kurduğunuzda — örneğin bir RSS okuyucu, bir veritabanı, bir API servisi — admin şifresinin bir yerde tanımlı olması gerekir. NixOS konfigürasyonuna yazarsanız Git geçmişine girer. Git geçmişine giren bir şey, repo public olsa olmasa, bir kez commit edildiğinde pratik olarak sonsuza dek oradadır.

sops-nix bu sorunu çözüyor: secret’ları şifreli halde Git’e atmanızı, şifre çözme işlemini ise boot sırasında otomatik yapmanızı sağlıyor.

Sops ve Age Nedir?

sops (Secrets OPerationS), Mozilla tarafından geliştirilen bir CLI aracı. YAML, JSON, ENV dosyalarını şifreleyip düzenlemenizi sağlıyor. Şifreleme backend olarak GPG, age veya bulut KMS sistemlerini destekliyor.

age, GPG’ye modern ve sade bir alternatif. GPG’nin karmaşık anahtar yönetimi yerine çok daha basit bir format sunuyor. Önemli bir özelliği: mevcut SSH ed25519 anahtarınızdan otomatik olarak age anahtarı türetilebiliyor. Yani yeni bir anahtar çifti oluşturmanıza gerek yok.

sops-nix ise bu ikisini NixOS’a entegre eden topluluk modülü: şifreli dosyaları boot sırasında çözüp servislerin kullanabileceği formata getiriyor.

Kurulum Mantığı

Üç parça var:

  1. Özel anahtar — Şifre çözme için. Bende SSH ed25519 private key, /persist/kullanici/.ssh/id_ed25519 yolunda. /persist’te olmasının nedeni boot sırası: şifre çözme, /home mount edilmeden önce gerçekleşiyor.

  2. Public key — Şifreleme için. SSH public key’den ssh-to-age komutuyla türetiliyor:

    nix run nixpkgs#ssh-to-age -- -i ~/.ssh/id_ed25519.pub
    # age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  3. Şifreli dosyasecrets/secrets.yaml. Cleartext asla yok; Git’e sadece şifreli hali gidiyor.

Konfigürasyon Dosyaları

.sops.yaml — Kim Şifreleyebilir?

Repo kökünde .sops.yaml dosyası, SOPS’a hangi anahtarlarla hangi dosyaları şifreleyeceğini söylüyor:

keys:
  - &kullanici age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

creation_rules:
  - path_regex: secrets/secrets\.yaml$
    key_groups:
      - age:
        - *kullanici

Bu dosya sayesinde sops secrets/secrets.yaml komutu hangi public key’le şifreleyeceğini biliyor. Birden fazla anahtar tanımlanırsa (örneğin birden fazla makine), tüm anahtarlar dosyayı açabilir.

secrets/secrets.yaml — Şifreli İçerik

Dosyanın içeriği şöyle görünüyor:

miniflux_admin_password: ENC[AES256_GCM,data:cP8iLiOh...,iv:R+UsWV/...,tag:L8eJBodp...,type:str]
searxng_key: ENC[AES256_GCM,data:4hCw4Xmw...,iv:2QO+z04J...,tag:NmJ1Xfwp...,type:str]
sops:
    age:
        - recipient: age1xxxxx...
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            ...
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2026-05-30T13:42:09Z"
    version: 3.12.1

Her değer ENC[AES256_GCM,...] formatında şifreli. Dosya Git’te açıkça duruyor, ama içeriği okumak için private key gerekiyor. sops secrets/secrets.yaml komutu dosyayı otomatik çözüp düzenleyebilir hale getiriyor, kaydedince tekrar şifreliyor.

modules/core/sops.nix — NixOS Entegrasyonu

sops = {
  defaultSopsFile = ../../secrets/secrets.yaml;
  validateSopsFiles = false;
  age.sshKeyPaths = [ "/persist/kullanici/.ssh/id_ed25519" ];
};

age.sshKeyPaths ile sops-nix’e “bu SSH key’i age formatına çevir ve şifre çözme için kullan” diyorsunuz. Ayrıca bir age anahtarı dosyası tanımlamanıza gerek yok — SSH anahtarı yeterli.

validateSopsFiles = false ilk kurulum aşamasında işe yarıyor: modül aktif ama henüz hiç secret tanımlanmamışsa hata vermesini engelliyor.

Secret’ı Servise Vermek

Sadece şifreli dosyayı okumak yetmez; o verinin bir servise ulaşması gerekiyor. sops-nix’in iki mekanizması var:

sops.secrets — Tekil Değer

sops.secrets.miniflux_admin_password = {
  owner = "root";
};

Bu tanım, boot sırasında miniflux_admin_password değerini secrets.yaml’dan çözüp /run/secrets/miniflux_admin_password yoluna yazıyor. Dosya root’a ait, sadece root okuyabilir. /run/secrets/ tmpfs üzerinde — disk’e yazılmıyor, her boot’ta yeniden oluşturuluyor.

sops.templates — Birden Fazla Secret’tan Dosya Oluşturma

Bazı servisler birden fazla değeri tek bir env dosyasında istiyor. Miniflux bunlardan biri — hem kullanıcı adı hem şifre aynı dosyada olmalı:

sops.templates."miniflux-admin.env" = {
  content = ''
    ADMIN_USERNAME=kullanici
    ADMIN_PASSWORD=${config.sops.placeholder.miniflux_admin_password}
  '';
  owner = "root";
};

services.miniflux = {
  adminCredentialsFile = config.sops.templates."miniflux-admin.env".path;
};

config.sops.placeholder.xxx sözdizimi, derleme sırasında secrets.yaml’a dokunmadan bir yer tutucu bırakıyor. Boot sırasında sops-nix bu placeholder’ı gerçek değerle değiştiriyor ve dosyayı /run/secrets.d/ altına yazıyor.

Boot Sırası

Şifre çözme süreci şöyle işliyor:

  1. Boot başlar, /persist mount edilir (neededForBoot = true)
  2. sops-install-secrets systemd servisi çalışır
  3. /persist/kullanici/.ssh/id_ed25519 ile secrets.yaml çözülür
  4. Çözülmüş değerler /run/secrets/ altına yazılır (tmpfs)
  5. Diğer servisler (Miniflux, SearXNG) başlar ve /run/secrets/ dosyalarını okur

Bu sıra önemli: /home bu aşamada henüz mount edilmemiş. Anahtar /home/kullanici/.ssh/ yerine /persist/kullanici/.ssh/ altında olmasının nedeni bu.

Sonuç

Git repo’nuzda plaintext şifre olmadan çalışan bir yapı:

NixOS’un deklaratif yapısı burada iyi oturuyor: neyin hangi servise gideceği konfigürasyonda açıkça tanımlı, elle müdahale yok.


İlgili: Impermanence: NixOS’ta Her Boot’ta Sıfırlanan Bir Root

EOF.