Read in 15 min read, written by Emre Cebeci

TABLE OF CONTENTS

Tek appsettings.json, Çoklu Ortam: .NET ve Azure DevOps ile Pratik Güvenli Ortam Yönetimi

Photo by Ferenc Almasi on Unsplash

Projelerde yeni özellikler ekler, bug fix’ler yapar, performansı iyileştiririz. Günler kod içinde geçer. Bir gün sonra testler başlar. Güvenlik taramaları devreye girer. Ve uyarı gelir:

Credential bilgileri dosyada açık kalmış. OWASP A02 riski.

İlk tepki çoğu zaman:

Ama repo private, kimse göremez ki?

Fakat bir dosya bir kez commitlendiyse, Git geçmişine yazılır. Branch silinse de, dosya silinse de, PR kapatılsa da fark etmez. Eski bir fork, eski bir clone, bir yedek dosya… Hepsi potansiyel sızıntı kaynağıdır.

Dahası, bu sızıntılar çoğu zaman aylarca fark edilmez.

Bu yüzden, versiyon kontrolüne giren her credential bilgisi doğrudan bir güvenlik zafiyetidir. Ve bu, geliştiricinin sorumluluğundadır.

Güvenliğin ilk adımı geliştiricide başlar

  • Uygulamanın konfigürasyonunu yazarken,
  • Veritabanı bağlantısını tanımlarken,
  • JWT secret’ını bir dosyaya eklerken…

Ben de bir geliştirici olarak şunu düşündüm:

Bu iş karmaşık olmamalı.

  • Yeni gelen yazılımcı yapıyı kolayca anlayabilmeli.
  • Secret’lar koda karışmadan, kendi ortamında sorunsuzca çalıştırabilmeli.
  • Sistemi bozmadan, güvenliği riske atmadan geliştirmeye devam edebilmeli.

Birden fazla ortam için appsettings.Development.json, appsettings.Staging.json ve appsettings.Production.json gibi dosyaları yönetmeye çalışmak kısa sürede hem zahmetli hem hataya açık hale gelir.

Üstelik bu dosyalarda credential bilgilerini barındırmak, proje dizinine erişimi olan herkesin kritik verilere ulaşabilmesi anlamına geliyor.

Bu durum, OWASP Top 10 listesinde 2. sırada yer alan A02:2021 -Cryptographic Failures başlığı kapsamında ciddi bir güvenlik zafiyetidir.

Ayrıca SonarQube gibi statik analiz araçları tarafından da "Security Hotspot" olarak işaretlenir. [1] [2]

Hiçbirimiz güvenlikte problem istemeyiz, değil mi?

Bu yazıda, yalnızca tek bir appsettings.json dosyası kullanarak, Azure DevOps pipeline üzerinden ortam bazlı yapılandırmanın nasıl güvenli ve sade şekilde yönetileceğini adım adım anlatacağım.

Secret’lar Azure DevOps Variable Group yapısıyla tanımlanacak, pipeline sırasında Replace Tokens task’ı ile dosyaya otomatik olarak inject edilecektir.

Yöntem .NET projelerine odaklı olsa da, aynı prensipleri farklı dillerde ve CI/CD sistemlerinde de kolaylıkla uygulayabilirsiniz.

Yöntem

Bu yapı dört ana aşamadan oluşur:

  1. appsettings.json içerisine placeholder değerlerinin eklenmesi
  2. Azure DevOps üzerinde ortama özel Variable Group yapılandırması
  3. Azure Pipeline üzerinde Replace Tokens entegrasyonu
  4. Geliştirici ortamı ve dokümantasyonun standardizasyonu

Her adım, ortam ayrımı ve secret yönetimini sadeleştirirken güvenlik ilkelerini de ihmal etmeden uygulanabilir bir yapı sunmaktadır.

appsettings.json İçerisine Placeholder Değerleri Ekleyin

Aşağıdaki gibi $(VariableName) formatında değerleri tanımlayın:

json{
  "ConnectionStrings": {
    "DefaultConnection": "$(DefaultConnectionString)"
  },
  "Jwt": {
    "Issuer": "$(JwtIssuer)",
    "Audience": "$(JwtAudience)",
    "SecretKey": "$(JwtSecretKey)"
  }
}

Not: $(...) formatı Replace Tokens task’ında "tokenPattern: azpipelines" ile eşleşir. Farklı bir token formatı tercih ediyorsanız, Azure Pipeline’ı Düzenleyelim adımında diğer seçenekler açıklanmıştır.

Variable Group Oluşturun ve Değerleri Ekleyin

Azure DevOps arayüzünde Pipelines > Library menüsüne gidin.

captionless image

Ortamlara göre ayrı Variable Group’lar oluşturun Örnek:

  • ProjectExample-Test-Variables
  • ProjectExample-Prod-Variables

captionless image

Her ortamda aynı key isimlerini kullanın, değerleri ortama göre özelleştirin:

  • DefaultConnectionString
  • JwtIssuer
  • JwtAudience
  • JwtSecretKey

Her ortamın kendine ait Variable Group’unda, bu key’ler aynı isimle tanımlanmalı fakat içerikleri ortamın ihtiyacına uygun olmalıdır:

Örnek:

ProjectExample-Test-Variables adında variable grubun içerisine

  • DefaultConnectionString: Server=test-db;...
  • JwtSecretKey: test-secret-key-123

ProjectExample-Prod-Variables adında variable grup içerisine

  • DefaultConnectionString: Server=prod-db;...
  • JwtSecretKey: prod-secret-key-abc

Değerlerini ekleyelim

captionless image

Secret olan değişkenleri kilitleyin (Lock simgesiyle).

captionless image

Save butonunu tıklayın.

Kaydettikten sonra_Pipeline permissions_ butonu aktifleşecektir. Bu butona tıklayın.

captionless image

Açılan ekranda bu Variable Group’a erişmesi gereken repository’leri tanımlayın. Sağ üstteki + butonuyla bir veya daha fazla repository seçebilirsiniz.

captionless image

Diğer ortamlar için de aynı yapıda Variable Group’ları oluşturun.

Azure Pipeline’ı Düzenleyelim

Pipeline dosyasına, ortam bazlı doğru Variable Group’u bağlayarak Replace Tokens task'ını eklememiz gerekir. Her ortam (örneğin Test, Prod) için ayrı stage, ayrı Variable Group kullanılmalıdır.

Örnek yapı:

yamlstages:
  - stage: prodEnv
    displayName: "Prod Environment"
    variables:
      - group: "ProjectExample-Prod-Variables"
    jobs:
      - job: build_test_analyze_and_replace
        displayName: "Build, Analyze, Replace Tokens"
        steps:
          - task: UseDotNet@2 ...
          - task: SonarQubePrepare@7y...
          - task: DotNetCoreCLI@2...
          - task: SonarQubeAnalyze@...
          - task: SonarQubePublish@...
          - task: ReplaceTokens@6
            displayName: "Replace Tokens in appsettings.json"
            inputs:
              targetFiles: "**/appsettings.json"
              encoding: "utf-8"
              addBOM: true
              tokenPattern: "azpipelines"
              missingVarLog: "error"
              telemetryOptout: true
          - task: DockerInstaller@0...

Not: variables.group ifadesi, ilgili ortamın Variable Group'unu bağlamanızı sağlar. Pipeline o stage çalışırken sadece o gruptaki değişkenleri kullanır.

tokenPattern Formatı

Replace Tokens task’ında tokenPattern parametresi, hangi placeholder formatını tanıyacağını belirler. Bu yazıda kullandığımız $(VariableName) formatı için şu şekilde tanımlanır:

tokenPattern: "azpipelines"

İsterseniz farklı formatları da tercih edebilirsiniz. Aşağıda desteklenen seçenekleri bulabilirsiniz:

  • default: #{VariableName}#
  • azpipelines: $(VariableName)
  • doublebraces: {{VariableName}}
  • doubleunderscores: __VariableName__
  • githubactions: #{{VariableName}}
  • octopus: #{VariableName}
  • custom: Özel prefix ve suffix belirtilebilir

Ekip standartlarınıza veya diğer sistemlerle uyum gereksinimlerinize göre bu formatlardan herhangi birini kullanabilirsiniz.

Kaynak: Replace Tokens Marketplace eklentisi

appsettings.Development.json Dosyasını .gitignore’a Ekleyin

Geliştirici ortamına özel credential verileri içeren appsettings.Development.json dosyasını versiyon kontrolüne dahil etmeyin. .gitignore dosyasına aşağıdaki satırı ekleyin:

shell# Local development settings
**/appsettings.Development.json

Credential Bilgileri İçeren Dosyaların Repository'den Temizleyin

Credential bilgileri içeren dosyalar daha önce repository'e commit edildiyse, sadece .gitignore ile saklamak yeterli değildir. Bu dosyaların Git geçmişinden de tamamen silinmesi gerekir. Bunun için git-filter-branch veya BFG Repo-Cleaner araçlarını kullanabilirsiniz

Bu araçlar, tüm Git geçmişini tarayarak belirli dosyaları veya içerikleri temizlemenizi sağlar.

UYARI!

Bu işlem Git ağacını yeniden yazmakta, yani repository geçmişini kökten değiştirmektedir. Temizlik öncesi mutlaka repository'nin yedeğini alın.

Özellikle public repository'lerde credential sızıntısı ciddi bir güvenlik zafiyetidir. Geçmişin temizlenmesi güvenlik açısından kritik bir adımdır.

Son adım: Dokümantasyonu güncelleyin

  • appsettings.Development.json.example adında bir örnek dosya oluşturun. Bu dosya, yeni gelen geliştiricilerin local ortamlarını kolayca hazırlayabilmesini sağlar.
  • Geliştiricilerin kendi appsettings.Development.json dosyalarını bu örneğe göre oluşturmalarını bekleyin. Böylece hem yapı standardize edilir hem de sorumluluk bilinci gelişir.
  • README.md dosyasına, bu yapının nasıl çalıştığını anlatan kısa bir açıklama yazın. Böylece ekip içi bilgi aktarım süreci hızlanır. Hatta bu makalenin bağlantısını da ekleyebilirsiniz.

Bu noktaya kadar ortam bazlı yapılandırmayı appsettings.json ve Azure DevOps Variable Group yapısıyla nasıl yöneteceğimizi adım adım ele aldık.

Neden usersecrets.json yerine appsettings.Development.json kullanmaktayım?

Kişisel birkaç sebebim mevcut:

  • Visual Studio Code entegrasyonu zayıf usersecrets komutları doğrudan Visual Studio için tasarlanmış. VS Code'da bu yapıyı yönetmek karmaşık ve zahmetli.
  • **appsettings.Development.json** geliştirici deneyimi (DX) açısından daha kolay Bu dosya proje dizininde yer alır, doğrudan düzenlenebilir ve versiyon kontrolü dışına alınabilir. Aynı klasörde bulunması, konfigürasyonun derli toplu olmasını sağlar.

Bu yüzden, lokal geliştirme ortamında tutulacak secret’ları doğrudan appsettings.Development.json içinde tutmak, benim için daha okunabilir, taşınabilir bir çözüm sağlamakta. Burada nihai karar elbette size kalmış.

Peki, bu yaklaşım her senaryo için yeterli mi? Daha ileri seviye ihtiyaçlar için hangi çözümler devreye girer?

Azure Key Vault Ne Zaman Gerekir?

Aşağıdaki senaryolarda Key Vault kullanımı projenizde gerekli veya tercih edilebilir:

  1. Uygulama runtime’da secret’a erişiyorsa Örnek: access token, connection string veya API key gibi değerlerin uygulama çalışırken doğrudan Key Vault’tan okunması gerekiyorsa.
  2. Secret rotation politikaları aktifse Parola, key veya sertifikalar otomatik yenileniyorsa ve bu işlemi yöneten merkezi bir sistemi kullanmak gerekiyorsa.
  3. Birden fazla uygulama aynı secret’ı paylaşıyorsa Farklı microservice’ler veya projeler aynı credential’ı kullanıyor ve merkezi bir yerde tutulması isteniyorsa.
  4. Role-based access control (RBAC) ile detaylı yetkilendirme isteniyorsa Hangi uygulamanın hangi secret’a erişeceği detaylı şekilde kontrol edilmek isteniyorsa.
  5. Auditing ve erişim logları gerekiyorsa Secret’a kim, ne zaman, nasıl erişmiş gibi detaylı izleme/loglama ihtiyacı varsa.
  6. Pipeline dışında da erişim varsa Yalnızca DevOps pipeline değil, manuel işlemler, CLI, diğer platformlar da secret’ı kullanıyorsa.

Sonuç

Replace Tokens ve Variable Group yaklaşımı sayesinde:

  • Ortama özel değerler kod dışında tutulur.
  • Secret’lar merkezi ve güvenli biçimde yönetilir.
  • appsettings dosyaları çoğalmaz, tek dosyayla bile ortamlar ayrılmış olur.

Kurumsal ihtiyaçlar arttığında, Azure Key Vault gibi çözümler devreye alınabilir.

Kaynaklar

© M. Emre Cebeci. All Rights Reserved