CI/CD Pipeline’ından Cosmos DB’ye Secret’sız Erişim
Uygulamanızı managed identity ile Azure Cosmos DB’ye bağladınız, hesap anahtarlarını kapattınız ve disableLocalAuth: true ayarını yaptınız. Ama build pipeline’ınıza baktığınızda AZURE_CLIENT_SECRET ve COSMOS_CONNECTION_STRING gibi değerlerin hala durduğunu görüyorsunuz. Uygulamada temizlediğiniz uzun ömürlü kimlik bilgilerini CI ayarlarında bırakmak, yapılan güvenlik çalışmasını büyük ölçüde geri alır. Bu yazıda pipeline’ı Cosmos DB’ye hiçbir secret saklamadan bağlamanın yolunu, yani workload identity federation yaklaşımını ele alıyoruz.
Sorunun özü: pipeline’da duran secret’lar
Local auth kapatıldığında repository secret’larındaki connection string zaten çalışmaz hale gelir. Client secret ise tam olarak uygulamadan sildiğiniz türde uzun ömürlü bir kimlik bilgisidir: ayar sayfasında durur, runbook’lara kopyalanır, sohbetlere yapıştırılır ve bir gün gecenin ortasında bir release sırasında süresi dolar.
Çözüm secret’ı “daha güvenli bir yerde saklamak” değil, hiç saklamamaktır. Workload identity federation ile pipeline, GitHub veya Azure DevOps tarafından üretilen kısa ömürlü bir OIDC token ile kimliğini kanıtlar; Entra ID bu token’ı Azure erişim token’ına çevirir. Ortada saklanan bir secret, süresi dolan bir kimlik veya rotate edilmesi gereken bir değer kalmaz.
Zihinsel model: aynı anda sağlanması gereken üç şey
YAML dosyalarına dokunmadan önce şu resmi netleştirmek gerekiyor. Pipeline Cosmos DB ile konuştuğunda üç ayrı koşulun sağlanması gerekir:
- Güven (Trust): Entra ID, CI platformunun ürettiği token’a güvenmelidir. Bunu federated credential sağlar.
- Control plane yetkilendirmesi: Pipeline altyapı deploy ediyorsa (hesap, veritabanı, container oluşturma) DocumentDB Account Contributor gibi bir Azure RBAC rolüne ihtiyaç duyar.
- Data plane yetkilendirmesi: Pipeline item okuyor, yazıyor veya seed veri yüklüyorsa Cosmos DB’nin kendi data plane rol atamasına ihtiyaç duyar.
Çoğu ekip birinci adımı halledip az login‘in çalışmasına sevinir, sonra üçüncüyü atladığı için 403 hatası alır. Control plane ve data plane’in neden ayrı sistemler olduğunu daha ayrıntılı okumak isterseniz serinin önceki yazısı iyi bir başlangıçtır.
GitHub Actions ile adım adım kurulum
1. Pipeline için kimlik oluşturma
Federasyonu ister bir app registration ister user-assigned managed identity üzerinden kurabilirsiniz. App registration en yaygın seçenektir; managed identity ise altyapıyla aynı resource group içinde tutulmak istendiğinde iyi bir alternatiftir.
APP_ID=$(az ad app create --display-name "gh-actions-myrepo" --query appId -o tsv)
az ad sp create --id "$APP_ID"
2. Federated credential eklemek
Bu adımda Entra ID’ye hangi workflow’un bu kimliği kullanabileceğini söylersiniz. En kritik alan subject: GitHub’ın ürettiği OIDC token’ıyla harfi harfine eşleşmek zorundadır.
az ad app federated-credential create \
--id "$APP_ID" \
--parameters '{
"name": "github-prod-env",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:my-org/my-repo:environment:production",
"audiences": ["api://AzureADTokenExchange"]
}'
Subject seçiminde bilinmesi gerekenler:
- Belirli bir branch:
repo:my-org/my-repo:ref:refs/heads/main - GitHub environment:
repo:my-org/my-repo:environment:production - Pull request build’leri:
repo:my-org/my-repo:pull_request
Branch tabanlı subject’ler wildcard desteklemediği için her yeni release branch başka bir credential gerektirir. Environment tabanlı subject’ler daha yönetilebilir, approval ve branch protection ile uyumlu, branch’lerin gelip gitmesinden bağımsızdır. Bir kimliğe en fazla 20 federated credential atanabildiğini de unutmayın.
3. Repository’nin subject formatını doğrulama
Bu ayrıntı yeni bir davranış getiriyor ve mevcut kurulumları sessizce bozabiliyor. OIDC spec’i, subject claim’lerinin yerel olarak benzersiz olmasını ve tekrar atanmamasını gerektirir. Eski format yalnızca organizasyon ve repository adlarını içerdiği için, geri dönüştürülmüş bir namespace farklı bir sahip altında aynı subject değerini üretebiliyordu.
Bu boşluğu kapatmak için 15 Temmuz 2026 tarihinden sonra oluşturulan repository’ler, sahip ID’sini ve repository ID’sini içeren immutable (değişmez) subject formatını kullanır.
- Önceki format:
repo:sample-org/octo-repo:ref:refs/heads/main - Immutable format:
repo:sample-org@123456/octo-repo@456789:ref:refs/heads/main
@ ayırıcısı, GitHub kullanıcı adı veya repository adında bulunamayacağı için tercih edilmiştir. 15 Temmuz 2026’dan önce oluşturulan repository’ler, OIDC ayarları arayüzünden veya REST API’sinden organizasyon ya da repository düzeyinde opt-in yapmadıkları sürece eski formatta kalır.
İki pratik sonuç var: yeni bir repo yeni formatı gerektirir; eski bir repo’daki federated credential’ı yeni bir repo’ya kopyalarsanız subject eşleşmez ve token exchange başarısız olur. Ayrıca 15 Temmuz 2026 sonrası rename veya transfer işlemleri repository’yi immutable formata taşır; bu durumda credential’ı düzenlemek yetmez, yeniden yazmak gerekir.
Immutable subject claim’leri GitHub Enterprise Server’da mevcut değildir. Claim’lerinizi include_claim_keys ile özelleştiriyorsanız, immutable formattaki repository’ler için repo segmentinde sahip ve repo ID’leri her zaman yer alır; kaldırmak mümkün değildir.
Güvenilir yaklaşım tahmin yürütmek değil, workflow’un ürettiği gerçek subject’i loglayıp o değeri kaydetmektir.
4. Control plane erişimi (yalnızca altyapı deploy ediliyorsa)
az role assignment create \
--assignee "$APP_ID" \
--role "DocumentDB Account Contributor" \
--scope "/subscriptions/$SUB_ID/resourceGroups/$RESOURCE_GROUP"
Pipeline yalnızca veri işliyorsa bu adımı atlayın. Her pipeline’ın hesabı yönetmesi gerekmez.
5. Data plane erişimi (en sık atlanan adım)
PRINCIPAL_ID=$(az ad sp show --id "$APP_ID" --query id -o tsv)
az cosmosdb sql role assignment create \
--account-name "$ACCOUNT" \
--resource-group "$RESOURCE_GROUP" \
--role-definition-id "00000000-0000-0000-0000-000000000002" \
--scope "/dbs/orders-test" \
--principal-id "$PRINCIPAL_ID"
Buradaki role definition ID, yerleşik Cosmos DB Built-in Data Contributor rolüne karşılık gelir. Pipeline yalnızca read-only smoke test çalıştırıyorsa …0001 (Data Reader) kullanmak daha uygundur. Scope’un / yerine test veritabanına yönlendirildiğine dikkat edin; her push’ta unattended çalışan CI kimliği, hesap genelinde yazma erişimine sahip olmaması gereken kimliktir.
6. Tanımlayıcıları saklamak (secret değil)
Repository veya environment değişkenlerine şunları ekleyin:
AZURE_CLIENT_ID— 1. adımdaki app IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_ID
Bunların hiçbiri secret değil, birer tanımlayıcı. Sizin repository ve environment’ınızdan gelen bir token olmadan işe yaramazlar. Alışkanlıkla secrets bölümüne koymak sorun değil, ama bu değerlerin hassas olmadığını kavramak zaten bu işin özü.
7. Workflow tanımı
name: Integration tests
on:
push:
branches: [main]
permissions:
id-token: write # OIDC token istemek için gerekli
contents: read
jobs:
test:
runs-on: ubuntu-latest
environment: production # federated credential subject'i ile eşleşmeli
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Run integration tests against Cosmos DB
env:
COSMOS_ENDPOINT: https://my-account.documents.azure.com:443/
run: dotnet test
Eksik olana dikkat: client-secret yok. Test kodu da uygulamanızdakinden farksız:
var client = new CosmosClient(
accountEndpoint: Environment.GetEnvironmentVariable("COSMOS_ENDPOINT"),
tokenCredential: new DefaultAzureCredential());
azure/login@v2, runner’da Azure CLI oturumunu açar; DefaultAzureCredential bunu Azure CLI credential üzerinden yakalar. CI’da, üretimde ve geliştiricinin makinesinde aynı kod çalışır.
Bu adımın en sık başarısız olma nedeni permissions: id-token: write izninin unutulmasıdır ve verilen hata mesajı bunu net söylemez.
Azure Pipelines: aynı fikir, daha az adım
Azure DevOps bu işlerin büyük kısmını sizin yerinize yapar. Azure Resource Manager tipinde bir service connection oluşturup Workload Identity federation (automatic) seçeneğini işaretlediğinizde Azure DevOps, app registration ile federated credential’ı otomatik oluşturur ve subject’i projenize ve service connection’a bağlar.
Rol atamalarını yine yukarıdaki gibi yapmanız gerekir: service connection’ın principal ID’sini Entra ID’de bulup aynı az cosmosdb sql role assignment create komutunu çalıştırırsınız. Azure DevOps yalnızca güven kısmını halleder; Cosmos DB data plane rolleri konusunda bir bilgisi yoktur.
trigger:
branches:
include: [main]
steps:
- task: AzureCLI@2
displayName: Run integration tests
inputs:
azureSubscription: 'my-workload-identity-connection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
dotnet test
env:
COSMOS_ENDPOINT: https://my-account.documents.azure.com:443/
AzureCLI@2 içinde çalıştırılan her şey CLI oturumunu miras alır; DefaultAzureCredential GitHub Actions’daki gibi çalışır. Test süreci bu task dışında çalışıyorsa credential’a erişemez.
Yönetsel bir not: eski service principal + secret ile oluşturulmuş service connection’lar hala Azure DevOps’ta bir secret ve son kullanma tarihi taşır. Bunları yerinde workload identity federation’a dönüştürmek desteklenen bir işlemdir ve yapılmaya değer.
Sık karşılaşılan beş sorun
AADSTS70021: No matching federated identity record found
Subject eşleşmiyor. Bu bir string karşılaştırmasıdır ve hoşgörüsüzdür. Kritik detay şu: yanlış bir subject ile federated identity credential oluşturursanız, işlem hatasız başarıyla tamamlanır. Entra ID onu doğrulayacak bir referansa sahip değildir. Hata ancak gerçek bir token exchange sırasında ortaya çıkar.
Bu yüzden gözle karşılaştırmayın; workflow’un ürettiği gerçek subject’i loglayıp karakter karakter kayıtlı olanla kıyaslayın. Sık rastlanan uyumsuzluklar:
environment:productionkaydettiniz ama job’daenvironment:anahtarı yok veyaenvironment: prodyazıyor.ref:refs/heads/mainkaydettiniz ama run bir tag ile tetiklendi; buref:refs/tags/v1.0üretir.- Job bir environment referansı içeriyor; bu durumda subject branch yerine environment’ı taşır. Environment kazanır.
- Repo 15 Temmuz 2026 sonrası rename veya transfer edildi ve immutable formata geçti; eski subject artık eşleşmiyor.
- Repo 15 Temmuz 2026 sonrası oluşturuldu ama subject’i eski formatlı bir repodan kopyaladınız.
- Environment adı iki nokta içeriyor. Metadata değerindeki
:karakteri%3Aolarak escape edilir;Production:V1, subject’teProduction%3AV1olarak görünür.
az login çalışıyor ama Cosmos DB 403 dönüyor
Güven tarafı sorunsuz, yetkilendirme değil. Muhtemelen Azure RBAC atamasını yapıp data plane atamasını atladınız. Şununla kontrol edin:
az cosmosdb sql role assignment list \
--account-name "$ACCOUNT" \
--resource-group "$RESOURCE_GROUP"
Pipeline’ın principal ID’si bu çıktıda yoksa sorun tam olarak orada. Data plane atamaları az role assignment list çıktısında veya portalın Access control (IAM) alanında görünmez; ayrı bir sistemdir ve ayrı komutlarla yönetilir.
Credential bir adımda çalışıyor, diğerinde çalışmıyor
OIDC oturumu runner üzerindeki shell oturumuna aittir. Testleriniz o shell’in erişemediği bir yerde çalışıyorsa (kendi başlattığınız bir Docker container’ının içinde, başka bir job’da, farklı bir runner’da) credential onları takip etmez.
Container’lar için ya token’ı açıkça geçirin ya da ilgili environment değişkenlerini map edin. Ayrı job’lar için her job’ın kendi azure/login adımı ve kendi id-token: write izni olması gerekir. Job’lar birbirinin oturumunu miras almaz.
Bicep veya ARM template’iniz hala listKeys() çağırıyor
Bu tuzak Cosmos DB’ye özgüdür. Birçok template şu satırla biter:
output connectionString string = listKeys(cosmosAccount.id, '2024-11-15').primaryMasterKey
disableLocalAuthtrue yapıldıktan sonra bu çağrı başarısız olur ve altyapı değişiklikleri kısmen uygulandıktan sonra tüm deployment’ı çökertir. Template’lerinizde listKeys, listConnectionStrings ve primaryMasterKey arayarak bu output’ları temizleyin; uygulamanızın artık ihtiyacı yok.
Fork’lardan gelen PR build’leri boş dönüyor
Fork’tan tetiklenen workflow, tasarım gereği sizin repository’niz için OIDC token almaz. PR doğrulamanız Cosmos DB’ye ihtiyaç duyuyorsa bu testleri yalnızca ana repo içindeki branch’lerde çalıştırın veya fork PR’ları için Azure Cosmos DB emulator‘ünü kullanıp gerçek hesabı merge sonrasına bırakın. Bunu bir secret ekleyerek aşmaya çalışmak, federasyonun kapatmak için var olduğu boşluğu geri açar.
Hızlı teşhis sırası
Pipeline hata verdiğinde şu listeyi sırayla geçin:
- Job’da
permissions: id-token: writetanımlı mı? - Federated credential üzerindeki
subject, run’ın ürettiği değerle birebir eşleşiyor mu? - Pipeline’ın principal’ı yalnızca Azure RBAC değil, data plane rol ataması da almış mı?
- Data plane scope’u, pipeline’ın gerçekten dokunduğu veritabanı ve container’ı kapsıyor mu?
- Başarısız olan adım, Azure login’in yapıldığı shell’in dışında mı çalışıyor?
- Template’lerde veya script’lerde hala anahtar isteyen bir çağrı var mı?
Toparlarken
Pipeline erişime ihtiyaç duyduğunda ilk refleks secret üretmektir çünkü pipeline’lar hep bunu kullandı. Oysa CI/CD kimliği federasyon için biçilmiş kaftan: bilinen bir yerde, bilinen olaylarla, bilinen bir repository adına çalışır. OIDC token’ının taşıdığı bilgi tam olarak budur; Entra ID’nin herhangi bir şey saklamadan doğrulayabileceği şey de yine budur.
Bir kez kurulduktan sonra rotate edilecek, sızacak veya gecenin ortasında süresi dolacak bir şey kalmaz. Pipeline uygulamanızın kimlik doğrulama şeklini paylaşır, kod bile aynıdır; “üretim verisine kim erişebiliyor” sorusunun sorgulanabilir bir cevabı olur.
Sıra sizde
- CI ayarlarınızı şimdi açın ve
AZURE_CLIENT_SECRET,COSMOS_CONNECTION_STRINGveya içindeAccountKeygeçen değerleri arayın. Bulduğunuz her şey ilk göç adayınız. - Federasyonu önce üretim dışı bir pipeline üzerinde kurun ve data plane rolünü
/yerine bir test veritabanına scope’layın. disableLocalAuth‘u açmadan önce Bicep ve ARM template’lerinizdelistKeysaramayı unutmayın.
Kaynaklar ve İleri Okuma
- Iria Osara ve Sudhanshu Khera — I Need My CI/CD Pipeline to Access Cosmos DB Without Using a Secret (Azure Cosmos DB Blog)
- I Enabled RBAC and Everything Broke — What Did I Do Wrong? (serinin önceki yazısı)
- Azure Cosmos DB Emulator (Microsoft Learn)
- Azure Cosmos DB Blog
- Azure Cosmos DB YouTube kanalı
- Azure Cosmos DB vNext Emulator: Yerelde Gerçek Gibi Test Etmek
- Azure Developer CLI Sonunda Olmuş: Uzantılar, Foundry ve Pipeline Devrimi







3 comments