Cloud'da Identity ve Erişim: Entra ID, RBAC, Managed Identity ve Access Key'siz Uygulama
Access key'siz uygulama, managed identity ile Entra ID'den kısa ömürlü token alıp yalnız ihtiyaç duyduğu scope'ta veri rolüne sahip olan uygulamadır. Kontrol ve veri düzlemi farkını, Azure RBAC'ın principal-rol-scope modelini, managed identity'nin yerel token endpoint'ini taklit bir servisle, DefaultAzureCredential zincirinin gerçek çıktısını, Bookstore.Api'nin key taşımayan Blob istemcisini ve covers container'ına verilen rolü anlatıyoruz.
Sürüm notu: Bu yazıdaki örnekler .NET 10 (SDK 10.0.404), Azure.Identity 1.21.0, Azure.Storage.Blobs 12.30.1, xUnit v3 4.0.3 ve Azure CLI 2.92.0 ile denenmiştir. Azure'da kaynak ya da rol ataması oluşturulmadı; managed identity'nin token endpoint'i yerelde sahte bir servisle canlandırıldı, Azure tarafındaki davranış Microsoft Learn'den (Kasım 2026) aktarıldı.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) kitap kapakları bir storage account'ta, covers container'ında duruyor. Bookstore.Api'nin managed identity'sine storage account'un bulunduğu resource group'ta Contributor rolü verdin; Contributor "her şeyi değiştirebilir" diye biliniyor. Uygulama Entra ID token'ıyla covers içindeki bir blob'u okuyabilir mi? İkinci soru: yarın bu rolü geri aldığında uygulamanın erişimi hemen mi kesilir? Cevaplar yazının içinde.
Kontrol Düzlemi ve Veri Düzlemi
Azure'da bir kaynakla iki farklı yoldan konuşursun. Kontrol düzlemi (control plane), kaynağın kendisini yönetir: storage account oluşturmak, bir ayarı değiştirmek, container eklemek. Bu istekler management.azure.com'a, yani 2. yazıda gördüğümüz Azure Resource Manager'a gider. Veri düzlemi (data plane) ise kaynağın içindeki veriyle çalışır: blob yüklemek, kuyruğa mesaj yazmak, veritabanında sorgu çalıştırmak. Bu istekler doğrudan servisin endpoint'ine, örneğin stbookstoredevweu.blob.core.windows.net'e gider.
Azure RBAC'ta (Role-Based Access Control; rol tabanlı yetkilendirme) bir rol tanımı bu iki dünyayı ayrı alanlarda tutar: Actions kontrol düzlemindeki işlemleri, DataActions veri düzlemindeki işlemleri listeler. Contributor rolünün Actions listesi neredeyse her şeyi kapsar ama DataActions listesi boştur. Tahmin sorusunun cevabı bu yüzden hayır: Contributor, Entra ID token'ıyla blob okuyamaz; bunun için "Storage Blob Data Reader" ya da "Storage Blob Data Contributor" gibi bir veri rolü gerekir. Ama işin ters bir yüzü var: Contributor, storage account'un access key'lerini listeleyebilir (bu bir kontrol düzlemi işlemi). Key'i alan herkes ise RBAC'a hiç uğramadan bütün veriye erişir. Bu yüzden "veri rolü vermedim, öyleyse veriye erişemez" demek, shared key erişimi kapalı değilse doğru değildir.
Azure RBAC ve Kimlik Türleri
Bir rol ataması üç parçadan oluşur: kim (security principal), ne yapabilir (role definition) ve nerede (scope). Scope, 2. yazıdaki ağacın herhangi bir seviyesi olabilir: management group, subscription, resource group, tek bir kaynak, hatta storage'da tek bir container. Atama verdiğin scope'ta ve altındaki her şeyde geçerlidir. Built-in roller arasında en çok kullanılanlar Reader (yalnız okur), Contributor (yönetir ama yetki dağıtamaz) ve Owner (yetki de dağıtır). Yetki dağıtmayı ayrı vermek istiyorsan "Role Based Access Control Administrator" rolü, yalnız rol atamalarını yönetme hakkını verir. En az yetki ilkesi pratikte iki kurala iner: veri rollerini olabilecek en dar scope'ta ver ve Owner'ı kişilere değil, az sayıda yöneticiden oluşan bir gruba ver.
Security principal dört türlü olabilir. Kullanıcı ve group, Entra ID'deki insanlardır; rolleri tek tek kişilere değil gruplara vermek, ekipten biri ayrıldığında temizliği kolaylaştırır. Service principal, bir uygulamanın Entra ID'deki kimliğidir ve kendi secret'ı ya da sertifikası vardır; secret'ı saklamak ve döndürmek senin işindir. Managed identity, Azure'un senin yerine yönettiği bir service principal'dır: secret'ı yoktur, token'ı yalnız Azure'da çalışan kaynak alabilir. System-assigned managed identity kaynağa bağlıdır ve kaynak silinince silinir; user-assigned olan ayrı bir kaynaktır, birden çok uygulamaya bağlanabilir ve uygulamayı yeniden oluştursan da kimliği ve rol atamaları yerinde kalır. Bookstore'da user-assigned id-bookstore-dev-weu kullanıyoruz. Son tür, workload identity federation: Azure dışında çalışan bir işin (örneğin bir GitHub Actions job'unun) kendi sağlayıcısından aldığı OIDC (OpenID Connect; kimliği imzalı bir token'la kanıtlamaya yarayan açık standart) token'ını Entra ID token'ıyla takas etmesi. GitHub Actions serisinin "GitHub Actions ile Deploy 1: OIDC ile Azure App Service ve Şifresiz Cloud Erişimi" yazısı bu akışı adım adım kuruyor. Aşağıdaki tablo bu kavramların AWS (Amazon Web Services; Amazon'un sunduğu bulut platformu) karşılıklarını veriyor; tablodaki kısaltmalar IAM (Identity and Access Management; AWS'nin kimlik ve erişim yönetimi servisi), SSO (Single Sign-On; tek oturumla birden çok uygulamaya giriş), EC2 (AWS'nin sanal makine servisi) ve ECS (AWS'nin container çalıştırma servisi).
| Azure | AWS karşılığı | Not |
|---|---|---|
| Entra ID kullanıcısı ve group | IAM Identity Center kullanıcısı, IAM user ve group | İnsanlar; uzun ömürlü access key yerine SSO tercih edilir |
| Role definition | IAM policy | AWS'de izinler policy dokümanında, rol ayrı bir kimlik |
| Role assignment (principal, rol, scope) | Policy'yi bir user, group ya da role'e bağlamak | AWS'de scope, policy içindeki Resource alanıyla verilir |
| Managed identity | EC2 instance profile, ECS task role, Lambda execution role | Secret'sız, platformun verdiği geçici kimlik bilgisi |
| Workload identity federation | IAM OIDC identity provider ve AssumeRoleWithWebIdentity | Dış sistemin token'ını bulut kimliğiyle takas etmek |
Managed Identity'nin İçi: Yerel Token Endpoint'i
Managed identity'nin arkasında basit bir mekanizma var. App Service ve Functions, managed identity açık bir uygulamaya iki environment variable verir: IDENTITY_ENDPOINT (yalnız o uygulamanın erişebildiği yerel bir token servisi) ve IDENTITY_HEADER (SSRF'e, yani bir saldırganın uygulamayı kendi adına iç isteklere yönlendirmesine karşı her istekte gönderilen, platformun döndürdüğü bir değer). Uygulama bu adrese bir GET isteği atar, karşılığında kısa ömürlü bir Entra ID token'ı alır. Bunu görmek için Azure gerekmiyor; Learn'deki protokol tarifine göre davranan yerel bir taklit yazdık. Gerçek token üretmiyor, yalnız isteği gösteriyor:
// App Service'in token endpoint'inin (IDENTITY_ENDPOINT) yerel taklidi; yalnız protokolü görmek için.
// Sahte bir token döner; burada hiçbir şey Microsoft Entra ID ile konuşmaz.
// Çalıştır: dotnet run tools/cloud/fake-identity-endpoint.cs
#:sdk Microsoft.NET.Sdk.Web
#:property PublishAot=false
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://127.0.0.1:5051");
builder.Logging.SetMinimumLevel(LogLevel.Warning);
WebApplication app = builder.Build();
const string ExpectedHeader = "local-test-header";
app.MapGet("/msi/token", (HttpRequest request) =>
{
Console.WriteLine($"GET {request.Path}{request.QueryString}");
Console.WriteLine($" X-IDENTITY-HEADER present: {request.Headers.ContainsKey("X-IDENTITY-HEADER")}");
if (request.Headers["X-IDENTITY-HEADER"] != ExpectedHeader)
{
return Results.Unauthorized();
}
long expiresOn = DateTimeOffset.UtcNow.AddHours(24).ToUnixTimeSeconds();
return Results.Json(new Dictionary<string, string>
{
["access_token"] = "fake-token-for-local-test",
["expires_on"] = expiresOn.ToString(),
["resource"] = request.Query["resource"].ToString(),
["token_type"] = "Bearer",
["client_id"] = request.Query["client_id"].ToString(),
});
});
app.Run();
Karşısına, Bookstore.Api'nin yapacağı çağrıyı yapan küçük bir uygulama koyduk. Storage için token istiyor ve hangi credential'ın ne yaptığını yazıyor:
// Bookstore.Api'nin yaptığı gibi Microsoft Entra ID'den Storage token'ı ister ve ne olduğunu yazar.
// Çalıştır: dotnet run tools/cloud/identity-probe.cs -- [default|managed]
#:package Azure.Identity
using Azure.Core;
using Azure.Identity;
string mode = args.Length > 0 ? args[0] : "default";
string? clientId = Environment.GetEnvironmentVariable("BOOKSTORE_IDENTITY_CLIENT_ID");
TokenCredential credential = mode == "managed"
? new ManagedIdentityCredential(clientId is null ? ManagedIdentityId.SystemAssigned : ManagedIdentityId.FromUserAssignedClientId(clientId))
: new DefaultAzureCredential();
TokenRequestContext request = new(["https://storage.azure.com/.default"]);
try
{
AccessToken token = await credential.GetTokenAsync(request, CancellationToken.None);
Console.WriteLine($"{credential.GetType().Name}: token received, {token.Token.Length} chars, expires {token.ExpiresOn:u}");
}
catch (AuthenticationFailedException ex)
{
Console.WriteLine($"{credential.GetType().Name} failed:");
Console.WriteLine(ex.Message);
}
Dosya tabanlı uygulamalar varsayılan olarak Native AOT (Ahead-of-Time; kodu çalıştırmadan önce makine koduna derleme) analizini açtığı ve repo'daki Directory.Build.props uyarıları hataya çevirdiği için, reflection'la JSON üreten taklit serviste PublishAot=false satırı gerekiyor. Taklidi bir terminalde başlatıp, ikinci terminalde App Service'in vereceği environment variable'larla probe'u çalıştırınca:
$ export IDENTITY_ENDPOINT=http://127.0.0.1:5051/msi/token
$ export IDENTITY_HEADER=local-test-header
$ export BOOKSTORE_IDENTITY_CLIENT_ID=00000000-0000-0000-0000-000000000000
$ dotnet run tools/cloud/identity-probe.cs -- managed
ManagedIdentityCredential: token received, 25 chars, expires 2026-11-15 19:45:31Z
Taklit servisin konsolunda gelen istek görünüyor:
GET /msi/token?api-version=2019-08-01&resource=https://storage.azure.com&client_id=00000000-0000-0000-0000-000000000000
X-IDENTITY-HEADER present: True
İki ayrıntıya dikkat. Kodda istenen scope https://storage.azure.com/.default, endpoint'e giden ise resource=https://storage.azure.com: Azure.Identity, managed identity protokolünün beklediği eski biçime çeviriyor. İkincisi client_id: user-assigned identity kullanıyorsan hangisini istediğini bu parametreyle söylemen gerekir; vermezsen istek system-assigned identity'ye gider ve o yoksa başarısız olur. Bu yüzden Bookstore.Api, client ID'yi configuration'dan okuyor.
DefaultAzureCredential'ı Okumak
Örneklerin çoğu new DefaultAzureCredential() ile başlar. Bu, sırayla birçok credential'ı deneyen hazır bir zincir. Hiçbir oturumun açık olmadığı bir makinede probe'u varsayılan modda çalıştırınca zincirin tamamı görünüyor:
$ dotnet run tools/cloud/identity-probe.cs
DefaultAzureCredential failed:
DefaultAzureCredential failed to retrieve a token from the included credentials. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/defaultazurecredential/troubleshoot
- EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot
- WorkloadIdentityCredential authentication unavailable. The workload options are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/workloadidentitycredential/troubleshoot
- ManagedIdentityCredential authentication unavailable. No response received from the managed identity endpoint.
- VisualStudioCredential authentication failed: Visual Studio Token provider can't be accessed at /home/user/.IdentityService/AzureServiceAuth/tokenprovider.json
- VisualStudioCodeCredential requires the Azure.Identity.Broker package to be referenced from the project. VS Code Azure See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/vscodecredential/troubleshoot
- AzureCliCredential authentication failed: Azure CLI not installed
- AzurePowerShellCredential authentication failed: PowerShell is not installed.
- AzureDeveloperCliCredential authentication failed: Azure Developer CLI could not be found.
- The BrokerCredential requires the Azure.Identity.Broker package to be referenced. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/brokercredential/troubleshoot
Sıra önemli: önce environment variable'lar, sonra workload identity, sonra managed identity, en son geliştirici araçları (Visual Studio, Visual Studio Code, az, PowerShell, azd). Kendi makinende az login yaptıysan zincir AzureCliCredential'da durur; App Service'te ise üçüncü adımda managed identity bulunur. Bu esneklik geliştirme için rahat ama production için risk: Azure SDK for .NET'in kendi rehberi, deploy edilmiş uygulamada DefaultAzureCredential yerine ManagedIdentityCredential gibi belirli bir credential kullanmayı öneriyor. Gerekçeler hata ayıklamanın zorlaşması, her denemenin gecikme eklemesi ve sunucuya birinin AZURE_CLIENT_SECRET gibi bir değişken koymasıyla uygulamanın sessizce başka bir kimliğe geçebilmesi. Zinciri tutmak istersen AZURE_TOKEN_CREDENTIALS environment variable'ı ile prod, dev ya da tek bir credential adı vererek daraltabilirsin.
Bookstore.Api: Access Key'siz Storage İstemcisi
Bu kuralı Bookstore.Api'ye uyguluyoruz. Kapak yükleme endpoint'i 7. yazının konusu; burada yalnız istemcinin nasıl kurulduğunu belirliyoruz. Ayarlar tek bir section'da:
namespace Bookstore.Storage;
public sealed class BlobStorageOptions
{
public const string SectionName = "BlobStorage";
// Azure: https://<account>.blob.core.windows.net, managed identity ile kullanılır
public Uri? ServiceUri { get; init; }
// User-assigned managed identity'nin client ID'si; boşsa system-assigned
public string? ManagedIdentityClientId { get; init; }
// Yalnız yerel geliştirme (Azurite); Azure'da asla doldurulmaz
public string? ConnectionString { get; init; }
public string CoversContainer { get; init; } = "covers";
}
using Azure.Core;
using Azure.Identity;
using Azure.Storage.Blobs;
namespace Bookstore.Storage;
public static class BlobStorageExtensions
{
public static IServiceCollection AddBlobStorage(this IServiceCollection services, IConfiguration configuration)
{
BlobStorageOptions options = configuration.GetSection(BlobStorageOptions.SectionName).Get<BlobStorageOptions>() ?? new();
services.AddSingleton(options);
// İlk kullanımda oluşur; storage ayarlı olmasa da API açılır
services.AddSingleton(_ => CreateServiceClient(options));
services.AddSingleton(sp => sp.GetRequiredService<BlobServiceClient>().GetBlobContainerClient(options.CoversContainer));
return services;
}
public static BlobServiceClient CreateServiceClient(BlobStorageOptions options)
{
if (options.ConnectionString is { Length: > 0 } connectionString)
{
return new BlobServiceClient(connectionString);
}
Uri serviceUri = options.ServiceUri
?? throw new InvalidOperationException("BlobStorage:ServiceUri is missing (see README.md).");
return new BlobServiceClient(serviceUri, CreateCredential(options));
}
// Azure: her zaman user-assigned identity, yedek zincir yok.
// Geliştirici makinesi: DefaultAzureCredential, az login / Visual Studio hesabını kullanır.
public static TokenCredential CreateCredential(BlobStorageOptions options) =>
options.ManagedIdentityClientId is { Length: > 0 } clientId
? new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(clientId))
: new DefaultAzureCredential();
}
Azure'da ManagedIdentityClientId dolu olduğu için zincir yok, doğrudan user-assigned identity kullanılıyor. Geliştirici makinesinde iki seçenek var: appsettings.Development.json'daki "ConnectionString": "UseDevelopmentStorage=true" ile yerel Azurite emülatörüne bağlanmak ya da client ID'yi boş bırakıp kendi az login hesabıyla gerçek bir dev storage account'a gitmek. Program.cs'te tek satır değişti:
builder.Services
.AddCatalogModule()
.AddInventoryModule()
.AddOrderingModule()
.AddBlobStorage(builder.Configuration);
Bu kodun en önemli özelliği bir testle doğrulanabiliyor. Azure.Storage.Blobs'taki BlobServiceClient.CanGenerateAccountSasUri, istemcinin account key taşıyıp taşımadığını söyler; key taşıyan istemci kendi başına, hiçbir RBAC kontrolüne takılmadan bütün hesaba erişen SAS (Shared Access Signature; süresi ve yetkisi sınırlı erişim adresi) URL'leri üretebilir:
using Azure.Identity;
using Azure.Storage.Blobs;
using Bookstore.Storage;
namespace Bookstore.Api.Tests;
public class BlobStorageTests
{
private static readonly Uri ServiceUri = new("https://stbookstoredevweu.blob.core.windows.net");
[Fact]
public void Managed_identity_client_has_no_account_key()
{
BlobStorageOptions options = new() { ServiceUri = ServiceUri, ManagedIdentityClientId = "00000000-0000-0000-0000-000000000000" };
BlobServiceClient client = BlobStorageExtensions.CreateServiceClient(options);
Assert.IsType<ManagedIdentityCredential>(BlobStorageExtensions.CreateCredential(options));
Assert.False(client.CanGenerateAccountSasUri);
}
[Fact]
public void Connection_string_client_holds_the_account_key()
{
BlobStorageOptions options = new() { ConnectionString = "UseDevelopmentStorage=true" };
BlobServiceClient client = BlobStorageExtensions.CreateServiceClient(options);
Assert.True(client.CanGenerateAccountSasUri);
}
[Fact]
public void Missing_service_uri_fails_with_a_clear_message()
{
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
() => BlobStorageExtensions.CreateServiceClient(new BlobStorageOptions()));
Assert.Contains("BlobStorage:ServiceUri", ex.Message);
}
}
$ dotnet test
Test run summary: Passed!
total: 13
failed: 0
succeeded: 13
skipped: 0
duration: 2s 478ms
On test, referans uygulamadan gelen katalog ve sipariş testleri; üçü yeni. Testler, referans uygulamanın README'sinde anlatıldığı gibi bir SQL Server container'ına karşı çalışıyor.
Yetkiyi Vermek: Container Scope'unda Role Assignment
Azure tarafındaki karşılığı bir script. Storage account'u shared key erişimi kapalı açıyor, identity'yi oluşturuyor ve veri rolünü yalnız covers container'ına veriyor:
#!/usr/bin/env bash
# Bookstore.Api kimliği: yalnız covers container'ını okuyup yazabilen user-assigned managed identity.
set -euo pipefail
: "${AZURE_SUBSCRIPTION_ID:?Set AZURE_SUBSCRIPTION_ID to the target subscription}"
ENVIRONMENT="${1:?Usage: 03-identity.sh <dev|staging|prod>}"
LOCATION="westeurope"
RG="rg-bookstore-${ENVIRONMENT}-weu"
IDENTITY="id-bookstore-${ENVIRONMENT}-weu"
STORAGE="stbookstore${ENVIRONMENT}weu"
az account set --subscription "$AZURE_SUBSCRIPTION_ID"
# Yalnız Microsoft Entra ID kabul eden storage account: account key yok, anonim erişim yok
az storage account create \
--name "$STORAGE" --resource-group "$RG" --location "$LOCATION" \
--sku Standard_LRS --kind StorageV2 --min-tls-version TLS1_2 \
--allow-shared-key-access false --allow-blob-public-access false
# Kontrol düzlemi: container'ı oluşturmak ARM'den geçer, veri rolü gerekmez
az storage container-rm create --storage-account "$STORAGE" --name covers
az identity create --name "$IDENTITY" --resource-group "$RG" --location "$LOCATION"
PRINCIPAL_ID=$(az identity show --name "$IDENTITY" --resource-group "$RG" --query principalId --output tsv)
CLIENT_ID=$(az identity show --name "$IDENTITY" --resource-group "$RG" --query clientId --output tsv)
STORAGE_ID=$(az storage account show --name "$STORAGE" --resource-group "$RG" --query id --output tsv)
# Veri düzlemi: en dar scope container'dır, hesap ya da resource group değil
az role assignment create \
--assignee-object-id "$PRINCIPAL_ID" --assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope "${STORAGE_ID}/blobServices/default/containers/covers"
echo "BlobStorage__ServiceUri=https://${STORAGE}.blob.core.windows.net"
echo "BlobStorage__ManagedIdentityClientId=${CLIENT_ID}"
Üç karar var. --allow-shared-key-access false, Contributor'ın key listeleyip RBAC'ı aşması yolunu kapatır; artık bu hesaba yalnız Entra ID token'ıyla erişilir. Container'ı oluşturan az storage container-rm create ARM (Azure Resource Manager; yönetim isteklerini karşılayan katman) üzerinden çalışır, yani script'i çalıştıran kişinin veri rolü olmasına gerek yoktur; bu, kontrol ve veri düzleminin ayrı olmasının pratikteki karşılığı. Son olarak rol ataması .../blobServices/default/containers/covers scope'unda: Bookstore.Api ileride aynı hesaba eklenecek exports container'ını okuyamaz. Script'i ShellCheck ile denetledik ama Azure'a karşı çalıştırmadık.
Tahmin sorusunun ikinci cevabı da burada. Learn'e göre managed identity'lerin arka plandaki servisleri token'ları kaynak başına yaklaşık 24 saat cache'te tutabiliyor ve token'ı erken yenilemeye zorlamanın bir yolu yok. Yani bir rolü geri aldığında uygulamanın erişimi hemen kesilmeyebilir; acil bir durumda daha hızlı sonuç veren yol, kaynak tarafında erişimi kapatmaktır (ağ kuralı, container'ı silmek ya da identity'yi uygulamadan ayırmak).
Hesap Güvenliği: MFA, Break-Glass ve PIM
Uygulama kimlikleri secret'sız olabilir ama insan hesapları hâlâ en büyük risk. Microsoft, Azure için MFA'yı (Multi-Factor Authentication; paroladan başka bir doğrulama adımı daha istenmesi) zorunlu hale getirdi: birinci aşama Ekim 2024'ten itibaren Azure portalı ve yönetim merkezlerini, ikinci aşama 1 Ekim 2025'ten itibaren Azure CLI, PowerShell, IaC araçları ve REST (HTTP üzerinden kaynak odaklı çağrı yapılan API tarzı) API üzerinden yapılan oluşturma, güncelleme ve silme işlemlerini kapsıyor. Managed identity ve service principal gibi workload identity'ler bu zorunluluktan etkilenmiyor; bir otomasyon hâlâ bir kullanıcı hesabıyla çalışıyorsa, onu workload identity'ye taşımanın tam zamanı.
Break-glass (acil durum) hesabı, normal yönetici hesaplarına erişilemediğinde, örneğin Conditional Access kuralı yanlış yazıldığında, tenant'a girebilmek için ayrılmış bir veya iki hesaptır. Bu hesaplar da MFA ile girer; Microsoft passkey (FIDO2) ya da sertifika tabanlı doğrulamayı öneriyor. Kullanıldıklarında alarm üretmeleri gerekir. PIM (Privileged Identity Management; ayrıcalıklı rolleri kalıcı yerine istenince ve süreli veren servis) ise Owner gibi rolleri kalıcı atamak yerine "uygun" (eligible) olarak tanımlar: Ayşe prod'a müdahale etmesi gerektiğinde rolü gerekçesiyle birkaç saatliğine etkinleştirir, iş bitince rol kendiliğinden düşer. PIM için Entra ID'nin ücretli lisansı gerekiyor.
Üretimde Dikkat
Rol atamalarını kod gibi gözden geçir. Kimin hangi scope'ta hangi rolü olduğunu az role assignment list --scope ... --include-inherited ile düzenli olarak dök; miras gelen atamaları görmeden "bu kişinin prod'a erişimi yok" demek mümkün değil.
User-assigned identity'yi uygulamadan bağımsız yönet. Bookstore.Api'yi silip yeniden oluşturduğunda identity ve rol atamaları yerinde kalır; system-assigned identity ise yeni bir kimlikle geri gelir ve bütün rol atamalarını yeniden yapman gerekir.
Shared key'i kapattıktan sonra her şeyin Entra ID ile çalıştığını kontrol et. Eski bir rapor job'u, bir portal eklentisi ya da Azure Storage Explorer'daki kaydedilmiş bağlantı hâlâ key kullanıyor olabilir; bunlar key kapandığı gün 403 almaya başlar.
Sık Düşülen Tuzaklar
Geliştiriciye subscription seviyesinde Owner vermek. Owner hem her şeyi silebilir hem de bu yetkiyi başkalarına dağıtabilir. Geliştiricinin günlük işi için dev resource group'unda Contributor ve gerekiyorsa dar scope'lu veri rolleri yeter; prod için PIM ile süreli yetki.
Connection string'i ya da access key'i koda veya app setting'e düz yazmak. Key, sahibinin kim olduğunu söylemez, süresi dolmaz ve kim kopyaladıysa onda kalır. Managed identity'ye geçmek mümkün değilse en azından Key Vault referansı kullan; ayrıntısı Configuration ve Secret Management serisinde.
Contributor vermeyi "veriye erişimi yok" diye yorumlamak. Shared key açıkken Contributor key'i alır ve her şeyi okur.
User-assigned identity kullanıp client ID'yi vermemek. DefaultAzureCredential ya da ManagedIdentityCredential client ID'siz çağrıldığında system-assigned identity'yi arar; uygulamada yalnız user-assigned varsa token alınamaz.
Küçük Bir Deneme
★☆☆ Taklit servisi çalıştırıp probe'u IDENTITY_HEADER değişkeni yanlışken çalıştır. Credential'ın hangi hatayı verdiğini ve taklit servisin konsolunda ne gördüğünü not et.
★★☆ Probe'a Azure.Core.Diagnostics.AzureEventSourceListener ekleyip Azure-Identity event'lerini konsola yaz; varsayılan modda zincirin her adımının ne kadar sürdüğünü ölç. AZURE_TOKEN_CREDENTIALS=dev verince süre nasıl değişiyor?
★★★ Bookstore için bir yetki matrisi çıkar: Selin, Emre, Ayşe, GitHub Actions'ın deploy kimliği ve Bookstore.Api'nin managed identity'si hangi scope'ta hangi role sahip olmalı? Her satır için neden daha geniş bir rolün yanlış olacağını bir cümleyle yaz.
Bir sonraki yazıda Bookstore'u nerede çalıştıracağımıza geçiyoruz: VM, App Service, Container Apps, AKS (Azure Kubernetes Service; yönetilen Kubernetes) ve Functions arasındaki fark, AWS'deki karşılıkları ve cold start'ın Bookstore.Api'de gerçekte ne kadar sürdüğü.