Read in 15 min read, written by Emre Cebeci
TABLE OF CONTENTS
- 1.appsettings.json İçerisine Placeholder Değerleri Ekleyin#appsettingsjson-i̇çerisine-placeholder-değerleri-ekleyin
- 2.Variable Group Oluşturun ve Değerleri Ekleyin#variable-group-oluşturun-ve-değerleri-ekleyin
- 3.Azure Pipeline’ı Düzenleyelim#azure-pipelineı-düzenleyelim
- 3.1.tokenPattern Formatı#tokenpattern-formatı
- 4.appsettings.Development.json Dosyasını .gitignore’a Ekleyin#appsettingsdevelopmentjson-dosyasını-gitignorea-ekleyin
- 5.Credential Bilgileri İçeren Dosyaların Repository'den Temizleyin#credential-bilgileri-i̇çeren-dosyaların-repositoryden-temizleyin
- 5.1.UYARI!#uyari
- 6.Son adım: Dokümantasyonu güncelleyin#son-adım-dokümantasyonu-güncelleyin
Tek appsettings.json, Çoklu Ortam: .NET ve Azure DevOps ile Pratik Güvenli Ortam Yönetimi
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]
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:
appsettings.json
içerisine placeholder değerlerinin eklenmesi- Azure DevOps üzerinde ortama özel
Variable Group
yapılandırması - Azure Pipeline üzerinde
Replace Tokens
entegrasyonu - 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.
Ortamlara göre ayrı Variable Group’lar oluşturun Örnek:
ProjectExample-Test-Variables
ProjectExample-Prod-Variables
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
Secret olan değişkenleri kilitleyin (Lock simgesiyle).
Save butonunu tıklayın.
Kaydettikten sonra_Pipeline permissions_
butonu aktifleşecektir. Bu butona tıklayın.
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.
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:
- 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.
- Secret rotation politikaları aktifse Parola, key veya sertifikalar otomatik yenileniyorsa ve bu işlemi yöneten merkezi bir sistemi kullanmak gerekiyorsa.
- Birden fazla uygulama aynı secret’ı paylaşıyorsa Farklı microservice’ler veya projeler aynı credential’ı kullanıyor ve merkezi bir yerde tutulması isteniyorsa.
- Role-based access control (RBAC) ile detaylı yetkilendirme isteniyorsa Hangi uygulamanın hangi secret’a erişeceği detaylı şekilde kontrol edilmek isteniyorsa.
- Auditing ve erişim logları gerekiyorsa Secret’a kim, ne zaman, nasıl erişmiş gibi detaylı izleme/loglama ihtiyacı varsa.
- 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.