{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"},{"name":"Jendrik Brack","url":"https://daily-devops.net/authors/jendrik/"}],"description":"Recent content in Cloud Native Development with .NET \u0026 Azure on Daily DevOps \u0026 .NET","favicon":"https://daily-devops.net/images/logo_hu_6465d873dfa490cf.png","feed_url":"https://daily-devops.net/tags/cloudnative/feed.json","home_page_url":"https://daily-devops.net/tags/cloudnative/","icon":"https://daily-devops.net/images/logo_hu_5926de77762241ba.png","items":[{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003e\u0026ldquo;Just use a Service Principal,\u0026rdquo; they said. \u0026ldquo;Store the secret in Key Vault,\u0026rdquo; they said. So you did. And now that secret has been in your Git history since 2019, copied to three different environments, and nobody remembers which applications actually use it.\u003c/p\u003e\n\u003cp\u003eEvery Azure subscription I\u0026rsquo;ve worked with contains at least a dozen connection strings with embedded credentials scattered across configuration files, Key Vault secrets that still contain passwords, and Service Principal credentials checked into Git history. The credential sprawl is real. It\u0026rsquo;s not because developers are careless. It\u0026rsquo;s because the traditional authentication patterns we learned for on-premises systems don\u0026rsquo;t translate to cloud environments.\u003c/p\u003e\n\u003cp\u003eThe \u0026ldquo;create a service account, store the password somewhere secure\u0026rdquo; approach made sense when \u0026ldquo;somewhere secure\u0026rdquo; was a locked filing cabinet in the server room. In the cloud, that password ends up in CI/CD variables, Docker image layers, application logs, and that one Slack channel where someone shared the connection string \u0026ldquo;just for testing.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eAzure Managed Identity and Role-Based Access Control (RBAC) provide the solution. Not a workaround. Not a mitigation. An actual architectural pattern that makes credential leakage technically impossible for Azure resource authentication. Let\u0026rsquo;s examine why the traditional approach fails and how to implement credential-free authentication properly in .NET applications.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-fatal-pattern-credential-sprawl-in-azure\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#the-fatal-pattern-credential-sprawl-in-azure\" title=\"The Fatal Pattern: Credential Sprawl in Azure\"\u003eThe Fatal Pattern: Credential Sprawl in Azure\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eBefore we examine the correct implementation, let\u0026rsquo;s be explicit about what we\u0026rsquo;re trying to eliminate. These patterns appear in production systems daily, each representing a potential breach vector.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"service-principal-credentials-in-configuration\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#service-principal-credentials-in-configuration\" title=\"Service Principal Credentials in Configuration\"\u003eService Principal Credentials in Configuration\u003c/a\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Fatal: Service Principal credentials in appsettings.json\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;AzureAd\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;ClientId\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;a1b2c3d4-e5f6-7890-abcd-ef1234567890\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;ClientSecret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;P@ssw0rd123!SuperSecret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;TenantId\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;12345678-90ab-cdef-1234-567890abcdef\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;StorageAccount\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;ConnectionString\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;DefaultEndpointsProtocol=https;AccountName=mystorageacct;AccountKey=abcd1234...==\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis configuration file contains everything an attacker needs to authenticate as your application. If this file appears in your Git history, Docker image layers, or application logs, you\u0026rsquo;ve handed over the keys to your infrastructure.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve lost count of how many times I\u0026rsquo;ve seen this exact pattern in production. The justification is always the same: \u0026ldquo;We need the credentials for local development\u0026rdquo; or \u0026ldquo;The deployment pipeline requires them.\u0026rdquo; Both are false. Both have better solutions. But the path of least resistance wins, and suddenly you have permanent credentials embedded in your codebase.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"storage-account-access-keys-in-code\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#storage-account-access-keys-in-code\" title=\"Storage Account Access Keys in Code\"\u003eStorage Account Access Keys in Code\u003c/a\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Fatal: Hardcoded storage credentials\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eDocumentService\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003eStorageAccountKey\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;abcdef...==\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003easync\u003c/span\u003e \u003cspan class=\"n\"\u003eTask\u003c/span\u003e \u003cspan class=\"n\"\u003eUploadDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e \u003cspan class=\"n\"\u003edocument\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003efileName\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ecredentials\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStorageSharedKeyCredential\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;mystorageacct\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eStorageAccountKey\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eblobClient\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eBlobServiceClient\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eUri\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://mystorageacct.blob.core.windows.net\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003ecredentials\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// Upload logic...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eStorage account access keys provide unrestricted access to all operations on all containers. They cannot be scoped. Once leaked, the only remediation is key rotation, which breaks all other applications using that key. You\u0026rsquo;re responding to a security incident by creating an availability incident.\u003c/p\u003e\n\u003cp\u003eThe irony? Storage account keys are the most commonly leaked credentials in Azure breaches. They\u0026rsquo;re also the easiest to eliminate with Managed Identity. Yet teams cling to them because \u0026ldquo;that\u0026rsquo;s how we\u0026rsquo;ve always done it.\u0026rdquo;\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"sql-connection-strings-with-passwords\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#sql-connection-strings-with-passwords\" title=\"SQL Connection Strings with Passwords\"\u003eSQL Connection Strings with Passwords\u003c/a\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Fatal: SQL credentials in connection string\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eoptionsBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUseSqlServer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s\"\u003e\u0026#34;Server=tcp:myserver.database.windows.net;Database=mydb;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s\"\u003e\u0026#34;User ID=sqladmin;Password=P@ssw0rd123!;Encrypt=true;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSQL authentication credentials are typically shared across environments, can\u0026rsquo;t be rotated without updating every application instance, and audit logs show all activity as the same user account. You can\u0026rsquo;t trace actions to specific applications.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-correct-pattern-managed-identity-and-rbac\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#the-correct-pattern-managed-identity-and-rbac\" title=\"The Correct Pattern: Managed Identity and RBAC\"\u003eThe Correct Pattern: Managed Identity and RBAC\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAzure Managed Identity eliminates credentials from application code by leveraging Azure AD\u0026rsquo;s OAuth 2.0 implementation. The Azure platform manages the credential lifecycle, token acquisition, and rotation automatically.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the key insight that changes everything: instead of your application proving identity by presenting a secret (which can be stolen), Azure proves your application\u0026rsquo;s identity through platform-level cryptographic attestation. The token never leaves Azure\u0026rsquo;s infrastructure. There\u0026rsquo;s nothing to leak because there\u0026rsquo;s nothing stored in your code.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"system-assigned-managed-identity\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#system-assigned-managed-identity\" title=\"System-Assigned Managed Identity\"\u003eSystem-Assigned Managed Identity\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe simplest pattern uses a system-assigned Managed Identity, which creates a service principal tied to a specific Azure resource\u0026rsquo;s lifecycle.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bicep\" data-lang=\"bicep\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// App Service with system-assigned Managed Identity\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eresource\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003eappService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;Microsoft.Web/sites@2022-09-01\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003eappServiceName\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003elocation\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003elocation\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003eidentity\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;SystemAssigned\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c1\"\u003e// Creates managed identity automatically\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nv\"\u003eserverFarmId\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003eappServicePlan\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nv\"\u003eid\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nv\"\u003ehttpsOnly\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// RBAC: Grant least-privilege access to storage\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eresource\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003estorageBlobDataContributor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;Microsoft.Authorization/roleAssignments@2022-04-01\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eguid\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003estorageAccount\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nv\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003eappService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nv\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;ba92f5b4-2d11-453d-a403-e96b0029c9fe\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003escope\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003estorageAccount\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nv\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// Storage Blob Data Contributor - not full Contributor!\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nv\"\u003eroleDefinitionId\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esubscriptionResourceId\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;Microsoft.Authorization/roleDefinitions\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;ba92f5b4-2d11-453d-a403-e96b0029c9fe\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nv\"\u003eprincipalId\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nv\"\u003eappService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nv\"\u003eidentity\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nv\"\u003eprincipalId\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nv\"\u003eprincipalType\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;ServicePrincipal\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe identity is automatically deleted when the App Service is deleted, preventing orphaned credentials.\u003c/p\u003e\n\u003cp\u003eNotice the RBAC assignment uses \u003ccode\u003eStorage Blob Data Contributor\u003c/code\u003e, not \u003ccode\u003eContributor\u003c/code\u003e. This is the principle of least privilege in action. The application can read and write blobs, but it cannot delete the storage account, modify network rules, or access Table/Queue storage. If compromised, the blast radius is limited to blob operations on this specific storage account.\u003c/p\u003e\n\u003cp\u003eI see teams assign Contributor or Owner roles \u0026ldquo;because it\u0026rsquo;s easier.\u0026rdquo; Easier until the security audit. Easier until the breach. The few extra lines of Bicep are worth the reduced attack surface.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"net-application-using-defaultazurecredential\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#net-application-using-defaultazurecredential\" title=\".NET Application Using DefaultAzureCredential\"\u003e.NET Application Using DefaultAzureCredential\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe Azure SDK for .NET provides \u003ccode\u003eDefaultAzureCredential\u003c/code\u003e, which implements a credential chain that works across local development and production environments without code changes.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eAzure.Identity\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eAzure.Storage.Blobs\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eAzureResourceService\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eBlobServiceClient\u003c/span\u003e \u003cspan class=\"n\"\u003e_blobServiceClient\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"n\"\u003eAzureResourceService\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eIConfiguration\u003c/span\u003e \u003cspan class=\"n\"\u003econfiguration\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// DefaultAzureCredential tries in order:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// 1. Environment variables (CI/CD)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// 2. Workload Identity (AKS)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// 3. Managed Identity (Azure resources)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// 4. Azure CLI (local dev)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ecredential\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eDefaultAzureCredential\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003estorageUri\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eUri\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econfiguration\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Azure:StorageAccountUri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]!);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003e_blobServiceClient\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eBlobServiceClient\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estorageUri\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecredential\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003easync\u003c/span\u003e \u003cspan class=\"n\"\u003eTask\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003ebyte\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eDownloadDocumentAsync\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003econtainerName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003eblobName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCancellationToken\u003c/span\u003e \u003cspan class=\"n\"\u003ect\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003edefault\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003econtainer\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003e_blobServiceClient\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetBlobContainerClient\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econtainerName\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eblob\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003econtainer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetBlobClient\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eblobName\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eblob\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDownloadContentAsync\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ect\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eValue\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eContent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eToArray\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eConfiguration (appsettings.json) - Notice: no credentials\u003c/strong\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;Azure\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;StorageAccountUri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://mystorageacct.blob.core.windows.net\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;KeyVaultUri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://mykeyvault.vault.azure.net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;ConnectionStrings\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;DefaultConnection\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Server=tcp:myserver.database.windows.net;Database=mydb;Encrypt=true;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe connection string contains no \u003ccode\u003eUser ID\u003c/code\u003e or \u003ccode\u003ePassword\u003c/code\u003e. Same code works locally via Azure CLI and in production via Managed Identity.\u003c/p\u003e\n\u003cp\u003eThis is the beauty of \u003ccode\u003eDefaultAzureCredential\u003c/code\u003e: zero code changes between environments. No \u003ccode\u003e#if DEBUG\u003c/code\u003e blocks. No environment-specific configuration files with different credential strategies. The SDK figures out which credential type to use based on where it\u0026rsquo;s running. Your code stays clean.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"local-development-without-credential-management\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#local-development-without-credential-management\" title=\"Local Development Without Credential Management\"\u003eLocal Development Without Credential Management\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOne of \u003ccode\u003eDefaultAzureCredential\u003c/code\u003e\u0026rsquo;s significant advantages is enabling developers to work with Azure resources locally without managing credentials in configuration files.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDevelopment workflow\u003c/strong\u003e:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eDeveloper authenticates to Azure CLI: \u003ccode\u003eaz login\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eApplication uses \u003ccode\u003eDefaultAzureCredential\u003c/code\u003e, which detects Azure CLI credentials\u003c/li\u003e\n\u003cli\u003eSame code runs in production using Managed Identity\u003c/li\u003e\n\u003cli\u003eNo credentials in \u003ccode\u003eappsettings.Development.json\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eDevelopers use their individual Azure AD accounts, maintaining a proper audit trail. No shared development credentials.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"sql-database-configuration\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#sql-database-configuration\" title=\"SQL Database Configuration\"\u003eSQL Database Configuration\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAzure SQL requires additional configuration to enable Managed Identity authentication:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- Execute as Azure AD admin on the database\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCREATE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eUSER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eEXTERNAL\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ePROVIDER\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eALTER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eROLE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb_datareader\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eADD\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMEMBER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eALTER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eROLE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb_datawriter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eADD\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMEMBER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNote: RBAC assignments can take several minutes to propagate. If you get 403 errors right after deployment, wait a few minutes or implement retry logic.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-migration-path\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#the-migration-path\" title=\"The Migration Path\"\u003eThe Migration Path\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIf you\u0026rsquo;re starting with the fatal patterns shown earlier, here\u0026rsquo;s a pragmatic migration path:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePhase 1 - Key Vault Migration\u003c/strong\u003e (Days):\nMove existing credentials to Azure Key Vault, access Key Vault using Managed Identity. This eliminates credentials from code without changing application authentication logic.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePhase 2 - Managed Identity for Azure Resources\u003c/strong\u003e (Weeks):\nConvert storage, Service Bus, and other Azure resource authentication to use Managed Identity. This is the highest-value change. Most credential leaks involve storage keys.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePhase 3 - SQL Managed Identity\u003c/strong\u003e (Weeks):\nMigrate SQL authentication to Managed Identity. Requires coordination with DBAs for permission configuration but eliminates SQL credentials.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePhase 4 - Custom RBAC Roles\u003c/strong\u003e (Months):\nReplace built-in roles with custom role definitions scoped to minimum necessary permissions. This is optimization. The security improvement from Phase 1-3 is already substantial.\u003c/p\u003e\n\u003cp\u003eThe total effort? Weeks, not months. The hardest part isn\u0026rsquo;t the technology. It\u0026rsquo;s convincing teams to abandon patterns they\u0026rsquo;ve used for years.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-objections-and-why-they-dont-hold-up\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#the-objections-and-why-they-dont-hold-up\" title=\"The Objections (And Why They Don\u0026rsquo;t Hold Up)\"\u003eThe Objections (And Why They Don\u0026rsquo;t Hold Up)\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve implemented this pattern across multiple Azure environments, and the resistance is predictable.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u0026ldquo;How do we rotate credentials now?\u0026rdquo;\u003c/strong\u003e You don\u0026rsquo;t. Azure manages the credential lifecycle. The operational burden decreases while security posture improves.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u0026ldquo;But what about local development?\u0026rdquo;\u003c/strong\u003e Solved. Azure CLI authentication. Same code, different credential provider. Developers use their individual accounts, maintaining proper audit trails.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u0026ldquo;Our CI/CD pipeline needs credentials.\u0026rdquo;\u003c/strong\u003e Use Workload Identity Federation for GitHub Actions or Azure DevOps service connections. Still no static credentials.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u0026ldquo;It\u0026rsquo;s too much work to migrate.\u0026rdquo;\u003c/strong\u003e More work than responding to a credential leak? More work than explaining to your CISO why production secrets are in Git history? The migration is measured in weeks. The breach response is measured in months.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve heard every excuse. None of them hold up against the fundamental reality: static credentials are a liability. Every credential you embed is a credential that can be extracted. Every secret you store is a secret that can leak.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"/posts/managed-identity-rbac-azure-resources/#conclusion\" title=\"Conclusion\"\u003eConclusion\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe patterns shown here work in production systems today, handling millions of requests without a single credential in code or configuration. That\u0026rsquo;s not compliance theater. That\u0026rsquo;s fundamental security architecture that makes credential leakage technically impossible for Azure resource authentication.\u003c/p\u003e\n\u003cp\u003eCredentials are hazardous material. Treat them accordingly: contained, time-limited, and scoped to minimum necessary permissions. Or better yet, eliminate them entirely with Managed Identity.\u003c/p\u003e\n\u003cp\u003eYour future self (and your security team) will thank you.\u003c/p\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2026-04-14T17:00:00+02:00","id":"https://daily-devops.net/posts/managed-identity-rbac-azure-resources/","language":"en","summary":"That ClientSecret has been in your Git history since 2019. Here's how Azure Managed Identity eliminates credentials from your .NET apps entirely.\n","tags":["security","azure","cloudnative","dotnet","csharp"],"title":"\"We Store Secrets in appsettings.json\": A Horror Story in Five Acts\n","url":"https://daily-devops.net/posts/managed-identity-rbac-azure-resources/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eI\u0026rsquo;ve watched the relationship between developers and ISO standards evolve dramatically over 15 years. In the early 2010s, ISO/IEC 27001 certification was something that happened in conference rooms I never entered. Compliance teams handled audits. Security officers filled out spreadsheets. The certification badge appeared on corporate websites. We developers kept shipping code, blissfully unaware that any of it related to our daily work.\u003c/p\u003e\n\u003cp\u003eThen cloud-native development changed everything.\u003c/p\u003e\n\u003cp\u003eOver the past five years, I\u0026rsquo;ve watched Infrastructure as Code, CI/CD pipelines, and Azure\u0026rsquo;s shared responsibility model fundamentally transform who owns ISO compliance. It\u0026rsquo;s no longer abstract policy work happening in separate departments. ISO/IEC 27001, 27017, and 27701 controls are now concrete technical decisions embedded directly in the code I review every week. The connection string you write, the authentication you implement, the logging statement you add—each one either implements or violates specific ISO requirements.\u003c/p\u003e\n\u003cp\u003eThis isn\u0026rsquo;t theoretical. Last month alone, I\u0026rsquo;ve seen three production systems with hardcoded secrets, two APIs processing sensitive data without authentication, and one logging credit card numbers in plain text to Application Insights. Every single one had ISO certifications displayed prominently on their company websites. The disconnect hasn\u0026rsquo;t disappeared—it\u0026rsquo;s just become significantly more dangerous.\u003c/p\u003e\n\u003cp\u003eLet me show you exactly what changed, and why your next pull request is an ISO compliance decision whether you realize it or not.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-historical-disconnect-standards-as-theater\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#the-historical-disconnect-standards-as-theater\" title=\"The Historical Disconnect: Standards as Theater\"\u003eThe Historical Disconnect: Standards as Theater\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTen years ago, ISO/IEC 27001 compliance typically meant this: your company hired consultants, documented processes, implemented policies, passed an audit, and displayed a certification badge. Developers rarely saw any of this. Security was handled by a separate team. Infrastructure lived in a data center managed by operations. Compliance was someone else\u0026rsquo;s problem.\u003c/p\u003e\n\u003cp\u003eThis created what I call \u0026ldquo;compliance theater\u0026rdquo;—the appearance of security without meaningful engineering impact. Everyone clapped when the ISO certificate arrived, then went back to hardcoding production passwords in appsettings.json. ISO standards became associated with bureaucracy rather than real protection. Developers learned to ignore them because they seemed disconnected from actual technical work.\u003c/p\u003e\n\u003cp\u003eThat model made sense when developers didn\u0026rsquo;t control infrastructure, didn\u0026rsquo;t deploy to production, and didn\u0026rsquo;t make decisions about data storage, encryption, or access control. But cloud-native development changed all of that.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-changed-the-cloud-native-reality\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#what-changed-the-cloud-native-reality\" title=\"What Changed: The Cloud-Native Reality\"\u003eWhat Changed: The Cloud-Native Reality\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eCloud-native .NET development on Azure broke this model completely. Now \u003cem\u003eyou\u003c/em\u003e define infrastructure. \u003cem\u003eYou\u003c/em\u003e control secrets. \u003cem\u003eYou\u003c/em\u003e decide encryption. Now developers:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eDefine infrastructure\u003c/strong\u003e through Bicep, Terraform, or ARM templates\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eControl secrets management\u003c/strong\u003e by choosing Key Vault integration or hardcoding credentials\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDetermine data encryption\u003c/strong\u003e by selecting Azure SQL configurations or accepting defaults\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eConfigure authentication\u003c/strong\u003e by implementing Azure AD or rolling custom solutions\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSet logging levels\u003c/strong\u003e that capture sensitive data or properly classify it\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDeploy directly to production\u003c/strong\u003e via CI/CD pipelines they build\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEvery single one of these is a technical decision that \u003cem\u003edirectly implements\u003c/em\u003e (or violates) ISO/IEC 27001, 27017, and 27701 controls. These aren\u0026rsquo;t abstract compliance requirements anymore. They\u0026rsquo;re engineering choices embedded in your code, your configuration, and your deployment automation.\u003c/p\u003e\n\u003cp\u003eLet me demonstrate with a concrete example that shows exactly how ISO standards map to everyday .NET development decisions.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-pattern-i-keep-seeing\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#the-pattern-i-keep-seeing\" title=\"The Pattern I Keep Seeing\"\u003eThe Pattern I Keep Seeing\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThree weeks ago, I reviewed a production API processing financial transactions. No authentication. Connection string hardcoded in Program.cs. Credit card numbers logged to Application Insights. When I asked about ISO compliance, the team pointed to their certificate. That disconnect—between the certificate on the wall and the code in production—hasn\u0026rsquo;t gone away. It\u0026rsquo;s just gotten more dangerous.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"the-anti-pattern-code\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#the-anti-pattern-code\" title=\"The Anti-Pattern Code\"\u003eThe Anti-Pattern Code\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eHere\u0026rsquo;s what I see constantly in real codebases—well-intentioned .NET APIs built without considering ISO requirements:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Program.cs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eWebApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCreateBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Hardcoded connection string\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddDbContext\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eApplicationDbContext\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003eoptions\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eoptions\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUseSqlServer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s\"\u003e\u0026#34;Server=myserver.database.windows.net;Database=mydb;User Id=admin;Password=P@ssw0rd123!;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eapp\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eBuild\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// No authentication required\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUseHttpsRedirection\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eMapControllers\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eRun\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// OrderController.cs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003e[HttpPost]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003easync\u003c/span\u003e \u003cspan class=\"n\"\u003eTask\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eIActionResult\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eCreateOrder\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"n\"\u003eFromBody\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"n\"\u003eOrderRequest\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Plain text logging of sensitive data\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003e_logger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLogInformation\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e$\u0026#34;Creating order for customer: {request.CustomerEmail}, \u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                         \u003cspan class=\"s\"\u003e$\u0026#34;Card: {request.CreditCardNumber}, Amount: {request.Amount}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003e_context\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eOrders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eOrder\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eCustomerEmail\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCustomerEmail\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eAmount\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAmount\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003e_context\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSaveChangesAsync\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eOk\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\n\n\u003ch3 id=\"whats-wrong-here\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#whats-wrong-here\" title=\"What\u0026rsquo;s Wrong Here\"\u003eWhat\u0026rsquo;s Wrong Here\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThis code compiles. It runs. It might work in production for months. But it violates three fundamental security principles:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eHardcoded secrets\u003c/strong\u003e: Database credentials in source code. Anyone with repository access has production database access.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNo authentication\u003c/strong\u003e: Anyone on the internet can call this API. No identity verification before processing transactions.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSensitive data in logs\u003c/strong\u003e: Credit card numbers logged in plain text. When this flows to Application Insights, you\u0026rsquo;ve created a compliance disaster.\u003c/p\u003e\n\u003cp\u003eThis is the kind of code that leads to breach notifications and very uncomfortable conversations with auditors.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-compliant-code-actually-looks-like\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#what-compliant-code-actually-looks-like\" title=\"What Compliant Code Actually Looks Like\"\u003eWhat Compliant Code Actually Looks Like\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s the same functionality, but built with security standards directly into the architecture:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Program.cs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eWebApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCreateBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Secrets from Azure Key Vault\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ekeyVaultUri\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eUri\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eConfiguration\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;KeyVault:Uri\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]!);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eConfiguration\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddAzureKeyVault\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekeyVaultUri\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eDefaultAzureCredential\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddDbContext\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eApplicationDbContext\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003eoptions\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eoptions\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUseSqlServer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eConfiguration\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;ConnectionStrings:Database\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Azure AD authentication required\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddAuthentication\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eJwtBearerDefaults\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationScheme\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddMicrosoftIdentityWebApi\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eConfiguration\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetSection\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;AzureAd\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eapp\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eBuild\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUseAuthentication\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUseAuthorization\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eMapControllers\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eRun\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// OrderController.cs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003e[Authorize]\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// Require authentication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eOrderController\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eControllerBase\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003e    [HttpPost]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003easync\u003c/span\u003e \u003cspan class=\"n\"\u003eTask\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eIActionResult\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eCreateOrder\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"n\"\u003eFromBody\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"n\"\u003eOrderRequest\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// Never log sensitive data\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003e_logger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLogInformation\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"s\"\u003e\u0026#34;Creating order. UserId={UserId}, Amount={Amount}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFindFirstValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eClaimTypes\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eNameIdentifier\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAmount\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eorder\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eOrder\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eUserId\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003euserId\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eAmount\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAmount\u003c/span\u003e \u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003e_context\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eOrders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eorder\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003e_context\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSaveChangesAsync\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eOk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eOrderId\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eorder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eId\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\n\n\u003ch3 id=\"what-changed\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#what-changed\" title=\"What Changed\"\u003eWhat Changed\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003eKey Vault for secrets\u003c/strong\u003e: Credentials never exist in source code. Azure AD controls who can access them.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAzure AD authentication\u003c/strong\u003e: \u003ccode\u003e[Authorize]\u003c/code\u003e attribute requires valid JWT tokens. Every request is identified and auditable.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStructured logging\u003c/strong\u003e: Use identifiers, not personal data. No email addresses or credit card numbers in logs.\u003c/p\u003e\n\u003cp\u003eThe key insight: \u003cstrong\u003eyou were already making these decisions\u003c/strong\u003e. ISO-compliant code just means choosing Key Vault over hardcoding, Azure AD over no authentication, and identifiers over sensitive data.\u003c/p\u003e\n\u003cp\u003eDetailed implementation comes in the follow-up articles.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"how-iso-standards-map-to-code-decisions\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#how-iso-standards-map-to-code-decisions\" title=\"How ISO Standards Map to Code Decisions\"\u003eHow ISO Standards Map to Code Decisions\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eISO/IEC 27001, 27017, and 27701 aren\u0026rsquo;t separate concerns from your daily development work. Every architectural decision you make either implements or violates specific requirements:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eWhere you store secrets\u003c/strong\u003e → Key Vault vs. hardcoding\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHow you authenticate users\u003c/strong\u003e → Azure AD vs. custom auth\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWhat you log\u003c/strong\u003e → Structured logging with data classification\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHow you encrypt data\u003c/strong\u003e → Managed encryption vs. rolling your own\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHow you handle PII\u003c/strong\u003e → Data minimization, purpose limitation\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWhere you deploy\u003c/strong\u003e → Understanding shared responsibility\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEach of these is a technical decision that appears in your code, your Infrastructure as Code templates, and your CI/CD pipelines. The upcoming articles in this series will show you exactly how to implement each requirement correctly in .NET applications.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-shared-responsibility-model-why-this-matters-now\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#the-shared-responsibility-model-why-this-matters-now\" title=\"The Shared Responsibility Model: Why This Matters Now\"\u003eThe Shared Responsibility Model: Why This Matters Now\u003c/a\u003e\u003c/h2\u003e\n\n\n\n\n\u003ch3 id=\"what-microsoft-handles-vs-what-you-handle\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#what-microsoft-handles-vs-what-you-handle\" title=\"What Microsoft Handles vs. What You Handle\"\u003eWhat Microsoft Handles vs. What You Handle\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eMicrosoft\u0026rsquo;s \u003ca href=\"https://learn.microsoft.com/en-us/azure/security/fundamentals/shared-responsibility\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eshared responsibility model\u003c/a\u003e fundamentally changed who is responsible for implementing ISO controls. In traditional on-premises infrastructure, your organization controlled everything—physical security, network security, operating systems, applications, data.\u003c/p\u003e\n\u003cp\u003eIn Azure, Microsoft handles:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePhysical data center security (locks, guards, cameras)\u003c/li\u003e\n\u003cli\u003eNetwork infrastructure (DDoS protection, network isolation)\u003c/li\u003e\n\u003cli\u003eHypervisor security (compute isolation between tenants)\u003c/li\u003e\n\u003cli\u003ePlatform patching (Azure SQL, App Service, Key Vault patches)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou still handle:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIdentity and access management (Azure AD configuration)\u003c/li\u003e\n\u003cli\u003eApplication security (authentication, authorization, input validation)\u003c/li\u003e\n\u003cli\u003eData classification and encryption (choosing encryption options)\u003c/li\u003e\n\u003cli\u003eNetwork security (firewall rules, private endpoints)\u003c/li\u003e\n\u003cli\u003eSecret management (using Key Vault correctly)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eThe critical insight:\u003c/strong\u003e ISO auditors now evaluate whether you correctly used the platform\u0026rsquo;s security features, not whether you built those features yourself. If Azure provides Key Vault and you hardcoded secrets anyway, that\u0026rsquo;s a finding. If Azure provides Azure AD and you built custom authentication with known vulnerabilities, that\u0026rsquo;s a finding. If Azure provides encryption at rest and you stored plaintext sensitive data, that\u0026rsquo;s a finding.\u003c/p\u003e\n\u003cp\u003eThis is why ISO standards now directly affect your code: \u003cstrong\u003eyou\u0026rsquo;re responsible for the integration layer between your application and Azure\u0026rsquo;s security services.\u003c/strong\u003e That integration \u003cem\u003eis\u003c/em\u003e your code.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-three-standards-what-you-need-to-know\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#the-three-standards-what-you-need-to-know\" title=\"The Three Standards: What You Need to Know\"\u003eThe Three Standards: What You Need to Know\u003c/a\u003e\u003c/h2\u003e\n\n\n\n\n\u003ch3 id=\"isoiec-27001-information-security-management\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#isoiec-27001-information-security-management\" title=\"ISO/IEC 27001: Information Security Management\"\u003eISO/IEC 27001: Information Security Management\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThis is the foundation—how organizations protect information. It defines 114 controls across 14 categories. The ones that affect your daily code:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eA.9 (Access Control):\u003c/strong\u003e Who can access your systems and data\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA.10 (Cryptography):\u003c/strong\u003e How you protect data at rest and in transit\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA.12 (Operations Security):\u003c/strong\u003e Logging, monitoring, vulnerability management\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA.14 (System Acquisition, Development, and Maintenance):\u003c/strong\u003e Secure development practices\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEvery authentication decision, every encryption choice, every logging statement relates to specific 27001 controls.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"isoiec-27017-cloud-specific-security-controls\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#isoiec-27017-cloud-specific-security-controls\" title=\"ISO/IEC 27017: Cloud-Specific Security Controls\"\u003eISO/IEC 27017: Cloud-Specific Security Controls\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThis extends 27001 with cloud-specific guidance. It adds controls that didn\u0026rsquo;t exist in traditional infrastructure:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eShared responsibility model:\u003c/strong\u003e Clarity on who manages what\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eVirtual machine isolation:\u003c/strong\u003e How cloud providers isolate tenants\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCloud service customer monitoring:\u003c/strong\u003e What visibility you have into the platform\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eVirtual network security:\u003c/strong\u003e How you secure communication between services\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWhen you configure Azure Virtual Networks, implement private endpoints, or use managed identities, you\u0026rsquo;re implementing 27017 controls.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"isoiec-27701-privacy-information-management\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#isoiec-27701-privacy-information-management\" title=\"ISO/IEC 27701: Privacy Information Management\"\u003eISO/IEC 27701: Privacy Information Management\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThis extends 27001 with privacy-specific requirements that map directly to GDPR, CCPA, and other privacy regulations:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eData minimization:\u003c/strong\u003e Collect only what you need\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePurpose limitation:\u003c/strong\u003e Use data only for stated purposes\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eData subject rights:\u003c/strong\u003e Support access, deletion, portability requests\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePrivacy by design:\u003c/strong\u003e Build privacy into architecture from the start\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWhen you decide what data to log, how long to retain it, and whether to store email addresses or just user IDs, you\u0026rsquo;re making privacy decisions that these standards address.\u003c/p\u003e\n\u003cp\u003eWhy all three matter: modern applications touch all three domains. You\u0026rsquo;re building information systems (27001) on cloud platforms (27017) that process personal data (27701).\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"where-to-start\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#where-to-start\" title=\"Where to Start\"\u003eWhere to Start\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIf you\u0026rsquo;re recognizing these patterns in your codebase, here\u0026rsquo;s what to do first:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStep 1: Audit Your Secrets (1-2 hours)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSearch your codebase for hardcoded credentials:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit grep -i \u003cspan class=\"s2\"\u003e\u0026#34;password\\s*=\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit grep -i \u003cspan class=\"s2\"\u003e\u0026#34;connectionstring\\s*=\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit grep -i \u003cspan class=\"s2\"\u003e\u0026#34;apikey\\s*=\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eMigrate every secret to Azure Key Vault. Use \u003ccode\u003eDefaultAzureCredential\u003c/code\u003e for local development and managed identity in production. This single change addresses the most common ISO finding I see in .NET applications.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStep 2: Add Authentication to Every API (2-4 hours)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eIf you have public APIs without authentication, add Azure AD integration:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edotnet add package Microsoft.Identity.Web\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eApply the \u003ccode\u003e[Authorize]\u003c/code\u003e attribute to every controller that processes sensitive data. This isn\u0026rsquo;t just compliance—it\u0026rsquo;s basic security hygiene.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStep 3: Review Your Logging (2-3 hours)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSearch for logged sensitive data:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit grep -i \u003cspan class=\"s2\"\u003e\u0026#34;LogInformation.*email\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit grep -i \u003cspan class=\"s2\"\u003e\u0026#34;LogInformation.*password\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit grep -i \u003cspan class=\"s2\"\u003e\u0026#34;LogInformation.*card\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eReplace with structured logging that uses identifiers instead of personal data. Use \u003ccode\u003eLoggerMessage\u003c/code\u003e source generators for consistent, performant logging.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStep 4: Enable Encryption at Rest (30 minutes)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eFor Azure SQL, enable Transparent Data Encryption (it\u0026rsquo;s often on by default, but verify):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eaz sql db tde \u003cspan class=\"nb\"\u003eset\u003c/span\u003e --status Enabled --resource-group mygroup --server myserver --database mydb\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFor Blob Storage, verify encryption at rest is enabled (also default, but confirm your configuration).\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStep 5: Document Your Shared Responsibility (1 hour)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eCreate a simple table documenting what Azure handles vs. what your code handles. This clarifies exactly where ISO controls apply to your development work and becomes invaluable during audits.\u003c/p\u003e\n\u003cp\u003eEach of these steps takes hours, not weeks. Each addresses real ISO findings I\u0026rsquo;ve seen in production systems. And each makes your application genuinely more secure, not just compliant on paper.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"standards-as-engineering-not-paperwork\"\u003e\u003ca href=\"/posts/iso-standards-intro-dotnet-developers/#standards-as-engineering-not-paperwork\" title=\"Standards as Engineering, Not Paperwork\"\u003eStandards as Engineering, Not Paperwork\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe disconnect between ISO certifications and actual code made sense in a different era. But in cloud-native .NET development, standards and code merged. Every technical decision you make—where you store secrets, how you authenticate, what you log—either aligns with these standards or violates them.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;re making these choices anyway. The standards just give you a framework for making better ones.\u003c/p\u003e\n\u003cp\u003eThis isn\u0026rsquo;t about satisfying auditors. It\u0026rsquo;s about building systems that actually protect customer data and survive real threats. The code in this article isn\u0026rsquo;t \u0026ldquo;compliance code\u0026rdquo;—it\u0026rsquo;s just better code.\u003c/p\u003e\n\u003cp\u003eThe next PR you review will contain one of these patterns. You can catch it now while it\u0026rsquo;s easy to fix, or explain it to auditors later when it\u0026rsquo;s woven throughout production. I know which conversation I\u0026rsquo;d rather have.\u003c/p\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2026-01-22T17:00:00+01:00","id":"https://daily-devops.net/posts/iso-standards-intro-dotnet-developers/","language":"en","summary":"ISO/IEC 27001, 27017, and 27701 aren't compliance theater anymore—they're engineering requirements in cloud-native .NET that affect every code decision.\n","tags":["security","compliance","dotnet","cloud","cloudnative","azure"],"title":"Why ISO Standards Actually Matter for .NET Developers","url":"https://daily-devops.net/posts/iso-standards-intro-dotnet-developers/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eKubernetes has transitioned from a technical option to an assumed default. In organizations and projects I\u0026rsquo;ve worked with, discussions no longer start with whether Kubernetes is appropriate. They start with migration timelines. I\u0026rsquo;ve sat through planning sessions where the question wasn\u0026rsquo;t \u0026ldquo;Should we use Kubernetes?\u0026rdquo; but rather \u0026ldquo;When can we have everything moved over?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eThis shift isn\u0026rsquo;t driven by application requirements. It\u0026rsquo;s driven by narrative. Consulting decks and reference architectures present \u003cem\u003e\u003cstrong\u003eKubernetes as a universal platform\u003c/strong\u003e\u003c/em\u003e that absorbs governance, security, scalability, observability, recovery, and operational responsibility. The implicit promise: once your software runs on Kubernetes, the hard parts are handled. I\u0026rsquo;ve watched teams adopt this belief wholesale, only to discover the gaps six months into production.\u003c/p\u003e\n\u003cp\u003eThat promise is incomplete. Kubernetes primarily addresses \u003cstrong\u003eone phase\u003c/strong\u003e: runtime orchestration. Most architectural risk, cost overruns, and operational failures occur \u003cstrong\u003ebefore\u003c/strong\u003e runtime during design and delivery, or \u003cstrong\u003eafter\u003c/strong\u003e runtime when incidents happen and systems evolve. I\u0026rsquo;ve debugged production incidents where Kubernetes ran flawlessly while the system failed spectacularly because architectural problems existed upstream and downstream of container orchestration.\u003c/p\u003e\n\u003cp\u003eTreating Kubernetes as a lifecycle platform rather than a runtime component introduces complexity that stays invisible during planning and becomes unavoidable in production. The demos look clean. The reference architectures are elegant. Then you hit reality.\u003c/p\u003e\n\u003cp\u003eTwo questions matter: Not whether Kubernetes works (it does, consistently, in its domain), but where its responsibility ends and whether your organization can handle what lies beyond those boundaries.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"kubernetes-in-the-net-reality\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#kubernetes-in-the-net-reality\" title=\"Kubernetes in the .NET Reality\"\u003eKubernetes in the .NET Reality\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes clusters rarely host a single, clean workload type in practice. They become convergence points: ASP.NET Core APIs, background workers, event-driven processors, migrated Windows Services, and platform components all sharing infrastructure. I\u0026rsquo;ve inherited clusters running everything from modern microservices to decade-old .NET Framework services wrapped in Windows containers, all competing for the same resources.\u003c/p\u003e\n\u003cp\u003eFor stateless, Linux-based ASP.NET Core services, Kubernetes is genuinely strong. Deployments are predictable. Rollouts are controlled. Health checks integrate cleanly. You implement a simple health endpoint:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eWebApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCreateBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddHealthChecks\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eapp\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eBuild\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eMapHealthChecks\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/health\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eRun\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen you deploy 3 replicas and Kubernetes does what you asked: it keeps exactly 3 running, rolling out updates without downtime, removing failed pods from traffic automatically. You push a new image and watch the update complete—no manual intervention, no traffic loss, no coordination overhead.\u003c/p\u003e\n\u003cp\u003eThis is where Kubernetes works exactly as intended: the application exposes its state honestly, and the platform responds intelligently. Three replicas means three replicas, constantly. A pod fails, it gets replaced within seconds. A rolling update happens seamlessly because Kubernetes orchestrates the transition and the application cooperates through its health endpoint. The first time you watch this happen without manually managing anything, it feels like magic.\u003c/p\u003e\n\u003cp\u003eThis experience—predictable, reliable, hands-off—becomes the template in your mind for how Kubernetes should work everywhere.\u003c/p\u003e\n\u003cp\u003eThe mistake begins when this success gets generalized. I\u0026rsquo;ve seen this pattern repeatedly: success with stateless APIs leads to confidence that everything belongs in Kubernetes. Then the complexity arrives.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"governance-structure-without-enforcement\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#governance-structure-without-enforcement\" title=\"Governance: Structure Without Enforcement\"\u003eGovernance: Structure Without Enforcement\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes offers namespaces, labels, and RBAC. These are primitives, not governance. Real enterprise governance requires enforceable policy, auditability, cost attribution, and environmental separation. In Azure-centric environments, these concerns traditionally live at the subscription, management group, and Azure Policy layer, where they\u0026rsquo;re auditable, mandatory, and enforced at the platform level.\u003c/p\u003e\n\u003cp\u003eIntroducing Kubernetes adds a second governance plane. Without deliberate policy enforcement, clusters drift. I\u0026rsquo;ve seen production and experimental workloads coexist in the same cluster because namespace isolation felt sufficient. It wasn\u0026rsquo;t. Cost attribution becomes opaque. Who actually paid for that node pool? Which business unit owns this? When incidents happen, these questions waste critical time.\u003c/p\u003e\n\u003cp\u003eIn one organization, we discovered experimental ML workloads running on production infrastructure because someone had \u003ccode\u003ekubectl\u003c/code\u003e access and \u0026ldquo;just needed to test something quickly.\u0026rdquo; The namespace separation existed. The policy enforcement didn\u0026rsquo;t.\u003c/p\u003e\n\u003cp\u003eKubernetes doesn\u0026rsquo;t prevent this drift. It accelerates it by making deployment so frictionless that governance becomes an afterthought.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"identity-kubernetes-stops-where-entra-id-starts\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#identity-kubernetes-stops-where-entra-id-starts\" title=\"Identity: Kubernetes Stops Where Entra ID Starts\"\u003eIdentity: Kubernetes Stops Where Entra ID Starts\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e.NET applications rely on Entra ID (formerly Azure AD) for authentication, authorization, managed identities, and conditional access. Kubernetes has no native concept of enterprise identity. It doesn\u0026rsquo;t integrate with Entra ID\u0026rsquo;s policy layer, conditional access rules, or compliance tracking. This isn\u0026rsquo;t a limitation; it\u0026rsquo;s architectural reality.\u003c/p\u003e\n\u003cp\u003eKubernetes RBAC governs access to cluster resources: who can deploy pods, create services, read secrets. But application identity—the identity your code runs under, the services it authenticates to, the permissions it holds—that\u0026rsquo;s entirely separate. Kubernetes facilitates the technical handshake (workload identity token exchange), but the authority making identity decisions lives outside the cluster in Entra ID. Your application integrates with Entra ID directly, not through Kubernetes.\u003c/p\u003e\n\u003cp\u003eThis boundary is invisible until you\u0026rsquo;re three months into production and security asks about conditional access policies, device compliance rules, or audit trails. Kubernetes doesn\u0026rsquo;t track any of that. It can\u0026rsquo;t. The identity system is external, and Kubernetes merely provides the plumbing to connect to it.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve worked with teams who expected Kubernetes to handle enterprise identity because it handled everything else. It doesn\u0026rsquo;t. That realization typically arrives when security reviews surface the integration gaps.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"networking-where-kubernetes-abstraction-fails-first\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#networking-where-kubernetes-abstraction-fails-first\" title=\"Networking: Where Kubernetes Abstraction Fails First\"\u003eNetworking: Where Kubernetes Abstraction Fails First\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNetworking is where Kubernetes myths collapse fastest. I\u0026rsquo;ve seen the most preventable production incidents here. Kubernetes introduces its own networking model, but it doesn\u0026rsquo;t replace enterprise networking. It operates \u003cstrong\u003einside\u003c/strong\u003e it. This distinction matters when things go wrong.\u003c/p\u003e\n\u003cp\u003eIn Azure-based architectures, your first line of defense exists outside the cluster:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eVirtual networks and subnet isolation\u003c/li\u003e\n\u003cli\u003eUser-defined routing (UDR)\u003c/li\u003e\n\u003cli\u003eAzure Firewall or Network Virtual Appliance (NVA)\u003c/li\u003e\n\u003cli\u003eApplication Gateway or Front Door with Web Application Firewall (WAF)\u003c/li\u003e\n\u003cli\u003ePrivate endpoints and service endpoints\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIngress controllers route traffic. They don\u0026rsquo;t defend the network. They\u0026rsquo;re application-layer components running inside pods, not hardened network appliances.\u003c/p\u003e\n\u003cp\u003eTreating Kubernetes ingress as your security perimeter shifts responsibility from hardened network controls to application-level components that were never designed to absorb hostile traffic at scale. I\u0026rsquo;ve seen this assumption lead to security incidents where attackers bypassed ingress controllers by targeting services directly once they gained cluster access.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"azure-cni-and-ip-exhaustion\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#azure-cni-and-ip-exhaustion\" title=\"Azure CNI and IP Exhaustion\"\u003eAzure CNI and IP Exhaustion\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eWith Azure CNI, every pod consumes a real IP address from your virtual network subnet. Scaling pods means scaling IP consumption linearly. Poor subnet sizing surfaces late—usually in production when teams suddenly can\u0026rsquo;t scale further and the error message is cryptic. Kubernetes schedules pods until the network says no, then fails silently.\u003c/p\u003e\n\u003cp\u003eThis isn\u0026rsquo;t a Kubernetes failure. It\u0026rsquo;s a networking responsibility that Kubernetes exposes. I\u0026rsquo;ve debugged this scenario more times than I\u0026rsquo;d like to admit, always with the same root cause: network planning happened before anyone calculated peak pod counts under load.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"east-west-traffic-and-lateral-movement\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#east-west-traffic-and-lateral-movement\" title=\"East-West Traffic and Lateral Movement\"\u003eEast-West Traffic and Lateral Movement\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eKubernetes networking is flat by default. Every pod can reach every other pod within the cluster. Network policies are optional and frequently incomplete. In organizations without dedicated platform teams, they\u0026rsquo;re often absent entirely.\u003c/p\u003e\n\u003cp\u003eFor multi-service .NET systems, this makes lateral movement trivial once any single pod is compromised. An attacker who gains access to a frontend pod can immediately probe backend services, database connections, and internal APIs. Kubernetes provides the mechanism (network policies) but doesn\u0026rsquo;t enforce discipline. I worked on an incident response where a compromised pod accessed 12 different internal services before we detected it. Network policies existed in the repository. They weren\u0026rsquo;t applied.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"egress-control\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#egress-control\" title=\"Egress Control\"\u003eEgress Control\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIngress gets constant attention: WAF rules, TLS certificates, rate limiting. Egress almost never does. By default, all pods can reach the internet: any destination, any port. In regulated environments, that\u0026rsquo;s unacceptable. Egress control requires forced routing through Azure Firewall and explicit allow-listing of destinations.\u003c/p\u003e\n\u003cp\u003eKubernetes has no native concept of allowed destinations. You build this external to the cluster, then spend weeks troubleshooting why perfectly valid application calls fail because someone forgot to allow-list a critical API endpoint.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"security-responsibility-is-concentrated-not-removed\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#security-responsibility-is-concentrated-not-removed\" title=\"Security: Responsibility Is Concentrated, Not Removed\"\u003eSecurity: Responsibility Is Concentrated, Not Removed\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes provides security mechanisms. Almost none are enabled by default. A .NET application on Azure App Service benefits from opinionated defaults: automatic image scanning, encrypted secrets, preconfigured network isolation, integrated runtime monitoring.\u003c/p\u003e\n\u003cp\u003eIn Kubernetes, every guarantee requires deliberate recreation:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eImage provenance through admission controllers and policy enforcement\u003c/li\u003e\n\u003cli\u003eSecret handling through external secret stores (Azure Key Vault integration)\u003c/li\u003e\n\u003cli\u003eNetwork segmentation through network policies and firewall rules\u003c/li\u003e\n\u003cli\u003eRuntime monitoring through service mesh sidecars or host-level agents\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEach added controller or sidecar increases capability and attack surface simultaneously. I\u0026rsquo;ve reviewed Kubernetes configurations where security controls outnumbered application pods. The cluster became a security platform that happened to run some software.\u003c/p\u003e\n\u003cp\u003eKubernetes doesn\u0026rsquo;t reduce security effort. It concentrates it into your platform team, assuming you have one.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"cicd-and-supply-chain-kubernetes-consumes-trust\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#cicd-and-supply-chain-kubernetes-consumes-trust\" title=\"CI/CD and Supply Chain: Kubernetes Consumes Trust\"\u003eCI/CD and Supply Chain: Kubernetes Consumes Trust\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes consumes artifacts. It doesn\u0026rsquo;t produce trust. CI pipelines, artifact promotion, image immutability, and signing decisions all happen long before Kubernetes schedules a pod. A broken supply chain can\u0026rsquo;t be repaired at runtime. If a malicious image makes it to your registry, Kubernetes will happily deploy it.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve worked with a team who discovered their CI pipeline had been compromised for three weeks. Kubernetes deployed every malicious image perfectly—on schedule, with zero-downtime rolling updates. The orchestration worked flawlessly. The supply chain didn\u0026rsquo;t. Kubernetes enforces desired state but doesn\u0026rsquo;t validate how that state was produced. That validation is your responsibility in your build pipelines, artifact registries, and admission controllers.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"observability-infrastructure-metrics-are-not-insight\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#observability-infrastructure-metrics-are-not-insight\" title=\"Observability: Infrastructure Metrics Are Not Insight\"\u003eObservability: Infrastructure Metrics Are Not Insight\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes emits metrics and logs: CPU usage per pod, memory consumption, network I/O. These describe platform health, not system behavior. .NET systems require application-level observability—distributed tracing across service boundaries, dependency tracking to external systems, structured logging with correlation IDs.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eServices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddOpenTelemetry\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWithTracing\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddAspNetCoreInstrumentation\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAddHttpClientInstrumentation\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWithout integration into Azure Monitor and Application Insights, incidents become reconstruction exercises. I\u0026rsquo;ve sat in war rooms where Kubernetes dashboards stayed green—all pods healthy, all nodes operational—while users experienced cascading timeouts. Pod restarts hide underlying failures instead of surfacing them. A pod that crashes and restarts every 30 seconds looks \u0026ldquo;healthy\u0026rdquo; to Kubernetes if it passes health checks between crashes.\u003c/p\u003e\n\u003cp\u003eObservability requires design. You bring it, or you debug blind.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"scalability-kubernetes-scales-pods-not-systems\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#scalability-kubernetes-scales-pods-not-systems\" title=\"Scalability: Kubernetes Scales Pods, Not Systems\"\u003eScalability: Kubernetes Scales Pods, Not Systems\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes scales replicas, not architectures. Database contention, synchronous dependencies, external API limits—they all remain regardless of how many pod copies you create. Kubernetes can amplify bottlenecks just as effectively as it amplifies capacity.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve watched auto-scaling create 50 pod replicas, all waiting for the same database connection pool that maxed out at 100 connections. More pods didn\u0026rsquo;t solve the problem—they made it worse by consuming resources while waiting.\u003c/p\u003e\n\u003cp\u003eEvent-driven scaling improves this, but only with architectural redesign. Kubernetes enables the \u003cstrong\u003emechanism\u003c/strong\u003e for elasticity—you can scale replicas based on external signals. But the architecture determines whether that mechanism translates into actual scalability. Scaling 50 pods won\u0026rsquo;t help if they\u0026rsquo;re all waiting on the same bottleneck. That\u0026rsquo;s a design problem, not an orchestration problem.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"backup-and-recovery-kubernetes-stops-completely\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#backup-and-recovery-kubernetes-stops-completely\" title=\"Backup and Recovery: Kubernetes Stops Completely\"\u003eBackup and Recovery: Kubernetes Stops Completely\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes restarts containers. It doesn\u0026rsquo;t restore systems. State lives outside the cluster in databases, message queues, caches, and storage accounts. Backup and recovery remain responsibilities of data platforms and operational processes. Kubernetes has no concept of business continuity or disaster recovery beyond \u0026ldquo;restart the pod.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eHigh availability masks failure. It doesn\u0026rsquo;t undo it. A corrupted database doesn\u0026rsquo;t care how many pod replicas exist or how fast Kubernetes can reschedule them. I\u0026rsquo;ve responded to incidents where Kubernetes performed perfectly—immediate failover, health-driven routing—while the underlying data corruption spread across all replicas.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"windows-containers-on-kubernetes-a-strong-architectural-smell\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#windows-containers-on-kubernetes-a-strong-architectural-smell\" title=\"Windows Containers on Kubernetes: A Strong Architectural Smell\"\u003eWindows Containers on Kubernetes: A Strong Architectural Smell\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWindows containers are supported but introduce slower startup times (minutes versus seconds), limited ecosystem support, and operational asymmetry—separate node pools, different update cadence, higher costs. They\u0026rsquo;re frequently used to avoid refactoring legacy workloads, turning Kubernetes into a compatibility layer rather than a platform.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve seen .NET Framework applications from 2010 wrapped in Windows containers and deployed to Kubernetes because \u0026ldquo;we\u0026rsquo;re moving to cloud-native.\u0026rdquo; The workload hadn\u0026rsquo;t changed. The infrastructure complexity increased dramatically. They function, they complicate operations, and they rarely age well.\u003c/p\u003e\n\u003cp\u003eEvery Windows container deployment I\u0026rsquo;ve reviewed eventually became a maintenance burden. The startup time alone makes scaling problematic. Windows licensing costs amplify infrastructure expenses. And the operational split between Linux and Windows node pools fragments your platform team\u0026rsquo;s expertise.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"cost-and-organizational-economics\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#cost-and-organizational-economics\" title=\"Cost and Organizational Economics\"\u003eCost and Organizational Economics\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes isn\u0026rsquo;t cost-neutral—a realization that typically arrives 3-6 months after initial deployment when finance asks why cloud costs doubled. It shifts cost visibility from infrastructure to organization: platform teams grow from 2 to 8 people, node pools sit idle waiting for burst capacity that happens twice a month, Windows nodes amplify costs through licensing and compute, observability instrumentation adds runtime overhead and egress costs.\u003c/p\u003e\n\u003cp\u003eTechnical efficiency—improved resource utilization through bin-packing and scheduling—often comes at \u003cstrong\u003eorganizational expense\u003c/strong\u003e: larger platform teams, slower iteration velocity (every change needs cluster-wide validation), distributed debugging complexity (which of the 15 services in the trace actually caused the timeout?).\u003c/p\u003e\n\u003cp\u003eThe calculation isn\u0026rsquo;t universal. It depends on workload mix, team structure, organizational tolerance for operational complexity. For companies running 200+ microservices with dedicated SRE teams, Kubernetes pays dividends. For companies running 8 services with 3 developers, it\u0026rsquo;s often overhead.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"conclusion-kubernetes-concentrates-architectural-responsibility\"\u003e\u003ca href=\"/posts/kubernetes-not-platform-strategy/#conclusion-kubernetes-concentrates-architectural-responsibility\" title=\"Conclusion: Kubernetes Concentrates Architectural Responsibility\"\u003eConclusion: Kubernetes Concentrates Architectural Responsibility\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eKubernetes is powerful and, in specific scenarios, the right choice: stateless Linux-based APIs with clean 12-factor design, event-driven background workers that scale horizontally, organizations with dedicated platform teams who can absorb operational complexity, and standardized workload portfolios where 80%+ of applications fit predictable patterns.\u003c/p\u003e\n\u003cp\u003eOutside these boundaries, Kubernetes doesn\u0026rsquo;t remove responsibility. It concentrates it. The responsibilities I\u0026rsquo;ve outlined (governance, identity, networking, security, observability, backup) don\u0026rsquo;t disappear. They become explicit architectural decisions that someone on your team must own, implement, and maintain.\u003c/p\u003e\n\u003cp\u003eKubernetes is not governance. That lives at the subscription, policy, and organizational level. It\u0026rsquo;s not identity. That authority is Entra ID. It\u0026rsquo;s not the security perimeter. That\u0026rsquo;s the network, the firewall, and the defense-in-depth controls you build around the cluster. It\u0026rsquo;s not backup and recovery. That responsibility belongs to data platforms and business continuity planning. It\u0026rsquo;s not observability. That\u0026rsquo;s an application design concern requiring deliberate instrumentation.\u003c/p\u003e\n\u003cp\u003eKubernetes orchestrates workloads, and it does this extremely well.\u003c/p\u003e\n\u003cp\u003eFrom an architect\u0026rsquo;s perspective—someone who has designed, deployed, and maintained these systems in production—Kubernetes can be the most visible component of a hosting solution but never the \u003cstrong\u003ewhole\u003c/strong\u003e solution. The promise that it absorbs the software lifecycle is marketing, not engineering reality.\u003c/p\u003e\n\u003cp\u003eThat distinction isn\u0026rsquo;t theoretical. It\u0026rsquo;s operational reality I\u0026rsquo;ve experienced across multiple organizations, multiple industries, multiple failure modes.\u003c/p\u003e\n\u003cp\u003eThe question isn\u0026rsquo;t whether Kubernetes works—it does, consistently, predictably, within its domain. The question is whether your organization can handle everything Kubernetes \u003cstrong\u003edoesn\u0026rsquo;t\u003c/strong\u003e do, and whether the complexity trade-off makes sense for your specific context, team capability, and workload characteristics.\u003c/p\u003e\n\u003cp\u003eAnswer that question honestly before committing your platform strategy.\u003c/p\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2026-01-13T17:00:00+01:00","id":"https://daily-devops.net/posts/kubernetes-not-platform-strategy/","language":"en","summary":"Kubernetes orchestrates containers brilliantly. But governance, identity, and recovery live elsewhere—and ignoring those boundaries breaks production.\n","tags":["kubernetes","architecture","platform-engineering","dotnet","cloudnative"],"title":"Kubernetes Is Not a Platform Strategy\n","url":"https://daily-devops.net/posts/kubernetes-not-platform-strategy/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eLet me tell you what I\u0026rsquo;ve learned over the years from watching teams deploy logging strategies that looked great on paper and failed spectacularly at 3 AM when production burned.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s not that they didn\u0026rsquo;t know the theory. They\u0026rsquo;d read the Azure documentation. They\u0026rsquo;d seen the structured logging samples. They\u0026rsquo;d studied distributed tracing. The real problem was different: they knew \u003cem\u003ewhat\u003c/em\u003e to do but had no idea \u003cem\u003ewhy\u003c/em\u003e it mattered until production broke catastrophically.\u003c/p\u003e\n\u003cp\u003eThis article isn\u0026rsquo;t about generic \u0026ldquo;best practices\u0026rdquo; or theoretical frameworks. Instead, it\u0026rsquo;s about the specific, concrete ways logging strategies fail in real production systems—why teams log things that don\u0026rsquo;t actually help, miss logging things that critically do, and build expensive observability infrastructure that doesn\u0026rsquo;t deliver when it matters most.\u003c/p\u003e\n\u003cp\u003eAnd I\u0026rsquo;m quite confident that your team is already doing at least two of these things right now.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-core-problem-logging-isnt-about-logging\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#the-core-problem-logging-isnt-about-logging\" title=\"The Core Problem: Logging Isn\u0026rsquo;t About Logging\"\u003eThe Core Problem: Logging Isn\u0026rsquo;t About Logging\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s the fundamental issue: most teams approach logging in a fundamentally backward way. They start by asking themselves: \u0026ldquo;What should we log?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s completely wrong. The right question—the one that changes everything—is: \u0026ldquo;What information do we absolutely need to diagnose a production failure when everything is burning?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eBecause logging isn\u0026rsquo;t a feature. It\u0026rsquo;s insurance. And like all insurance, you want to pay the minimum premium for maximum coverage. You don\u0026rsquo;t insure against every possible outcome; you insure against the catastrophic ones.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"anti-pattern-1-logging-everything-just-in-case\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#anti-pattern-1-logging-everything-just-in-case\" title=\"Anti-Pattern 1: Logging Everything \u0026ldquo;Just in Case\u0026rdquo;\"\u003eAnti-Pattern 1: Logging Everything \u0026ldquo;Just in Case\u0026rdquo;\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve seen applications log 50+ MB per request. Developers reasoned with apparent logic: \u0026ldquo;More data = better debugging.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eThis is not just wrong. It\u0026rsquo;s catastrophically wrong. And I can prove it with concrete math and real-world consequences.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eThe Reality of Excessive Logging\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s walk through a concrete example. Consider a typical e-commerce order processing request that touches multiple services. A well-intentioned developer adds \u0026ldquo;detailed diagnostic logging\u0026rdquo; at every single step—serializing objects, logging variable states, capturing full request/response payloads. It seems reasonable. It looks thorough. It feels safe.\u003c/p\u003e\n\u003cp\u003eThen production hits real load. Assume 100 requests per second, each with 5 MB of unfiltered diagnostic data. That\u0026rsquo;s 500 MB per second of logs flowing into your systems. Your log ingestion pipeline starts struggling. You\u0026rsquo;re either dropping logs or compressing aggressively (and losing critical detail). Your monthly storage bill—depending on your tool and retention policy—can easily escalate from a comfortable $200 to several thousand dollars. The actual impact varies depending on your setup: Application Insights charges per GB ingested, Datadog per host/span volume, Elasticsearch per GB stored. It\u0026rsquo;s not always catastrophic, but it\u0026rsquo;s significant enough to force painful cost-cutting decisions.\u003c/p\u003e\n\u003cp\u003eBut more importantly than cost, here\u0026rsquo;s what actually happens in practice:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eSearch becomes genuinely frustrating.\u003c/strong\u003e With gigabytes of noise, finding a specific error means sifting through thousands of irrelevant entries. A query for \u0026ldquo;payment timeout\u0026rdquo; returns 500 results. Which one is actually yours? You don\u0026rsquo;t know.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLogs stop being useful entirely.\u003c/strong\u003e Not because they\u0026rsquo;re stored badly, but because finding signal in the noise takes longer than just restarting the service and hoping it works. So teams gradually stop using logs for diagnosis and instead use luck.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eReal problems hide effectively.\u003c/strong\u003e The actual error is there somewhere, buried in noise about every intermediate step, every variable assignment, every function entry. By the time you find it, the incident is already over and customers are angry.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eYou\u0026rsquo;re paying for data nobody uses.\u003c/strong\u003e Not $13,000/day in runaway costs, but definitely enough to notice and enough to make management ask questions.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThis is exactly what happens when you optimize for \u003cem\u003ecompleteness\u003c/em\u003e instead of \u003cem\u003esignal\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eThe solution is surprisingly simple:\u003c/strong\u003e Log only what you\u0026rsquo;d actually need to diagnose a failure. Not what \u003cem\u003emight\u003c/em\u003e be useful someday. Not \u0026ldquo;this function was called.\u0026rdquo; Not \u0026ldquo;this variable is 42.\u0026rdquo; Only things that directly help answer: \u0026ldquo;Why did this critical operation fail?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eIn concrete terms: when an order fails, you truly need to know \u003cem\u003ewhat\u003c/em\u003e failed and \u003cem\u003ewhy\u003c/em\u003e. Did validation reject it? Did payment timeout? Did the warehouse queue overflow? Did inventory run out? Each failure mode has a completely different cause and a different fix. So you log specifically for those scenarios, not for everything in between.\u003c/p\u003e\n\u003cp\u003eA typical refactoring looks like this: instead of logging every intermediate step (retrieved order, started validation, started payment, called warehouse), you log only outcome points (order complete, order failed with specific reason X). This cuts noise by roughly 80% while actually \u003cem\u003eimproving\u003c/em\u003e diagnostic value. You know what mattered. You can find it in seconds.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"anti-pattern-2-fire-and-forget-observability\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#anti-pattern-2-fire-and-forget-observability\" title=\"Anti-Pattern 2: Fire-and-Forget Observability\"\u003eAnti-Pattern 2: Fire-and-Forget Observability\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou\u0026rsquo;ve attended a cloud architecture conference. You heard talks about observability and its importance. You read the Microsoft Learn documentation on Application Insights. You diligently configured it—set up the Azure SDK, added OpenTelemetry, made sure logs flow reliably to the cloud.\u003c/p\u003e\n\u003cp\u003eYou check the box: \u0026ldquo;Observability: Done.\u0026rdquo; Problem solved, right?\u003c/p\u003e\n\u003cp\u003eThen production breaks at 2 AM. You wake up. You go to Application Insights and\u0026hellip; find nothing useful. No signal, just noise. So you deploy a quick fix with logging at DEBUG level. Now you have terabytes of noise flooding in. You restart the service and hope it doesn\u0026rsquo;t happen again. Problem \u0026ldquo;fixed\u0026rdquo; (until it does).\u003c/p\u003e\n\u003cp\u003eThis pattern happens constantly. Not because Application Insights is fundamentally bad. Not because you\u0026rsquo;re incompetent. But because observability was never actually designed for \u003cem\u003eyour specific\u003c/em\u003e application and \u003cem\u003eyour specific\u003c/em\u003e failure modes. You bought expensive tools. You installed them correctly. You patted yourself on the back. Then you walked away without thinking deeply.\u003c/p\u003e\n\u003cp\u003eObservability without genuine understanding isn\u0026rsquo;t observability. It\u0026rsquo;s just expensive logging theater—looking good in slides but useless when it matters.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eReal observability requires answering three critical questions:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eFirst: What are the critical paths in your system? Not every code path. The ones that, if they break, create real incidents and wake people up. In e-commerce: order placement, payment processing, inventory updates. In SaaS: user authentication, data export, billing operations. In APIs: request validation, database queries, external service calls. You need to identify and understand these before you write a single log statement.\u003c/p\u003e\n\u003cp\u003eSecond: What can go wrong on each of these paths? Not everything theoretically possible. The specific failure modes you\u0026rsquo;ve actually seen in production or can reasonably expect based on your architecture. Payment timeout? Insufficient funds? Database deadlock? API rate limiting? Service unavailable? Malformed request? Rate limit exceeded? Each has a completely different diagnosis path and different fix. So you log for each of these specific scenarios, not for the thousands of things that don\u0026rsquo;t go wrong.\u003c/p\u003e\n\u003cp\u003eThird: What minimum information do I need to diagnose each specific failure? Not \u0026ldquo;all the data.\u0026rdquo; Not the entire request. The minimum information that tells you which specific failure mode occurred and \u003cem\u003ewhy\u003c/em\u003e. For a payment timeout, you need: order ID, amount, payment provider, timeout duration, retry count. You don\u0026rsquo;t need the entire customer object serialized. You don\u0026rsquo;t need the full response payload. You need the signal, not the noise.\u003c/p\u003e\n\u003cp\u003eThen—and only then—you instrument for exactly those scenarios. Not generically. Specifically and intentionally.\u003c/p\u003e\n\u003cp\u003eIn practice, this means source-generated log methods (using LoggerMessage) for each specific failure mode. Not generic \u0026ldquo;OrderProcessingStarted\u0026rdquo; and \u0026ldquo;OrderProcessingEnded\u0026rdquo; messages. Instead: \u0026ldquo;PaymentTimeout,\u0026rdquo; \u0026ldquo;PaymentDeclined,\u0026rdquo; \u0026ldquo;WarehouseQueueFull,\u0026rdquo; \u0026ldquo;InventoryInsufficient.\u0026rdquo; Each log message tells you exactly what state the system entered and what concrete cause triggered it.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"anti-pattern-3-logging-without-correlation\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#anti-pattern-3-logging-without-correlation\" title=\"Anti-Pattern 3: Logging Without Correlation\"\u003eAnti-Pattern 3: Logging Without Correlation\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eA customer reports: \u0026ldquo;My order didn\u0026rsquo;t process.\u0026rdquo; In a microservices architecture, that single request touched four different services. Now you\u0026rsquo;re essentially a detective trying to solve a mystery.\u003c/p\u003e\n\u003cp\u003eWithout correlation IDs, finding the relevant logs across four different services becomes tedious, frustrating detective work. You search for \u0026ldquo;order timeout\u0026rdquo; and get 6 different orders from across the entire day. Which one is actually theirs? You cross-reference timestamps. You check payment logs. You check warehouse logs. You piece together a story. 30 minutes later, you finally find it. By then, the incident is already over. The customer has called your support team twice. You\u0026rsquo;re exhausted.\u003c/p\u003e\n\u003cp\u003eWith proper correlation, one single trace ID connects everything together. ASP.NET Core generates this automatically—it\u0026rsquo;s called HttpContext.TraceIdentifier. The same trace ID flows through every log entry for that specific request, across every service it touches. When a customer reports \u0026ldquo;my order didn\u0026rsquo;t process,\u0026rdquo; you search by that one trace ID and see every step: API received it, validation passed, payment service timed out, warehouse was never notified. Done. You understand the entire story in 30 seconds instead of 30 minutes.\u003c/p\u003e\n\u003cp\u003eThe W3C Trace Context standard makes this correlation work across service boundaries. It\u0026rsquo;s built into ASP.NET Core natively. You get it for free. But there\u0026rsquo;s a crucial requirement: you have to structure your logs so the trace ID is actually queryable—which means using structured logging (key-value pairs, not free-form text blobs).\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"anti-pattern-4-logging-performance-secrets\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#anti-pattern-4-logging-performance-secrets\" title=\"Anti-Pattern 4: Logging Performance Secrets\"\u003eAnti-Pattern 4: Logging Performance Secrets\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s a pattern I\u0026rsquo;ve seen derail production performance more often than most people admit: logging that hurts performance so severely that teams simply disable observability rather than pay the performance cost.\u003c/p\u003e\n\u003cp\u003eYour application runs beautifully on your local machine. You ship it to production. Suddenly in production, it feels sluggish. Latency starts climbing. P95 latency goes from 50ms to 200ms. Users complain. You add more logging to debug the slow path. Now it\u0026rsquo;s even slower. Much, much slower. You profile the application and find the surprising culprit: the logging itself is the bottleneck.\u003c/p\u003e\n\u003cp\u003eThis is the moment most teams give up on observability entirely. \u0026ldquo;It\u0026rsquo;s too expensive,\u0026rdquo; they say. What they really mean: \u0026ldquo;We instrumented it wrong and now we\u0026rsquo;re paying the performance price.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eThe culprit: string formatting and object serialization happening automatically regardless of whether anyone is listening. You\u0026rsquo;re serializing objects, building strings, allocating temporary memory—all of it discarded if the log level isn\u0026rsquo;t even enabled. This is particularly insidious because it only hurts production performance (where logging is at higher levels) while looking perfectly fine in local testing (where you control the verbosity level).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// KILLER: Always executes expensive work\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLogDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Processing user. FullDetails: {Details}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eJsonConvert\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSerializeObject\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecomplexUser\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// BETTER: Guards it, but still wasteful\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eIsEnabled\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eLogLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLogDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Processing user. FullDetails: {Details}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eJsonConvert\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSerializeObject\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecomplexUser\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// BEST: Source-generated logging—zero overhead when disabled\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003e[LoggerMessage(Level = LogLevel.Debug, Message = \u0026#34;Processing user. UserId={UserId}\u0026#34;)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kd\"\u003epartial\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eProcessingUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e \u003cspan class=\"n\"\u003eILogger\u003c/span\u003e \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003euserId\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn production with Debug logging disabled, the first version \u003cem\u003estill executes the expensive serialization anyway\u003c/em\u003e. That\u0026rsquo;s performance death by a thousand cuts. The template parser runs. The object is serialized. The memory is allocated. Only \u003cem\u003ethen\u003c/em\u003e does the code check \u0026ldquo;is debug level enabled?\u0026rdquo; and discard the entire result. Wasted CPU cycles. Wasted memory. And this happens repeated thousands of times per second.\u003c/p\u003e\n\u003cp\u003eThis is exactly the kind of hidden performance killer that shows up and hurts production but not in load tests. Because load tests usually don\u0026rsquo;t add this kind of logging to their code paths.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eThe Solution: Source-Generated Logging\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSource-generated logging (LoggerMessage attribute, .NET 6+) completely flips this on its head. The compiler generates code at build time that knows: \u0026ldquo;this parameter matters, that one doesn\u0026rsquo;t. Here\u0026rsquo;s the most efficient way to capture and format it.\u0026rdquo; No runtime template parsing. No boxing. No wasted string allocation. Zero overhead when disabled.\u003c/p\u003e\n\u003cp\u003eA clarification: the performance gain is primarily noticeable in high-frequency logging scenarios (thousands of calls per second). For low-frequency events like error logging or rare business events, the difference is measurable but not dramatic. The real power of LoggerMessage is its consistency across high-volume paths. Also worth noting: LoggerMessage requires \u003ccode\u003epartial\u003c/code\u003e methods, which means you can\u0026rsquo;t use it everywhere—instance methods on regular classes need to be static partials, which limits where you can apply this pattern.\u003c/p\u003e\n\u003cp\u003eI wrote extensively about this pattern in my \u003ca href=\"/posts/compositeformat-performance-boost/\"\u003eCompositeFormat article\u003c/a\u003e, where I showed concretely how parsing overhead compounds at scale. The same principle applies here: parse once (at compile time), use a thousand times (at runtime). Source-generated logging is the logging equivalent of that core optimization. It delivers measurably better performance. It means measurably lower CPU usage. And the code is even cleaner and more maintainable.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"anti-pattern-5-unstructured-logs-in-structured-systems\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#anti-pattern-5-unstructured-logs-in-structured-systems\" title=\"Anti-Pattern 5: Unstructured Logs in Structured Systems\"\u003eAnti-Pattern 5: Unstructured Logs in Structured Systems\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou\u0026rsquo;ve set up Application Insights correctly. You\u0026rsquo;re sending structured logs to the cloud. But then someone does this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// DON\u0026#39;T: Free-form text—not queryable or searchable\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLogError\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e$\u0026#34;Order 12345 failed. Payment service returned 429...\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// DO: Structured data—queryable and analyzable\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLogError\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Payment rate limited. OrderId={OrderId}, StatusCode={StatusCode}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eorderId\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatusCode\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe second version is queryable. The first version is just noise that wastes storage.\u003c/p\u003e\n\u003cp\u003eApplication Insights, Datadog, Elasticsearch—all of these powerful tools only work effectively because logs are structured. When you log unstructured text, you throw away the tool\u0026rsquo;s entire value proposition. You might as well be writing to a flat file somewhere. You\u0026rsquo;ve spent significant money on enterprise observability and gained nothing from it.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-practical-path-forward\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#the-practical-path-forward\" title=\"The Practical Path Forward\"\u003eThe Practical Path Forward\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSo how do you actually fix these patterns? The answer isn\u0026rsquo;t more generic best practices. It\u0026rsquo;s not buying more tools. It\u0026rsquo;s building deliberate, intentional, carefully designed observability built specifically for your application.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"step-1-identify-your-critical-paths\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#step-1-identify-your-critical-paths\" title=\"Step 1: Identify Your Critical Paths\"\u003eStep 1: Identify Your Critical Paths\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eWrite down the 3-5 user flows that actually matter in your system. Not every single code path. The ones where failure creates real incidents and angry customers.\u003c/p\u003e\n\u003cp\u003eFor an e-commerce system: order placement → payment processing → warehouse notification.\nFor a SaaS platform: user sign-up → authentication → data access → export.\nFor an API service: request validation → business logic → response serialization → client response.\u003c/p\u003e\n\u003cp\u003eYou\u0026rsquo;ll complete this exercise in an afternoon or two. It immediately clarifies what\u0026rsquo;s actually important in your system and what you should care about.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"step-2-map-failure-modes\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#step-2-map-failure-modes\" title=\"Step 2: Map Failure Modes\"\u003eStep 2: Map Failure Modes\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eFor each critical path, list concretely what can go wrong. Not everything theoretically possible. The specific failures you\u0026rsquo;ve actually dealt with in production:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePayment timeout (how long does it take to decide? What\u0026rsquo;s the timeout value?)\u003c/li\u003e\n\u003cli\u003eInsufficient funds (is this handled gracefully? Do you notify the user?)\u003c/li\u003e\n\u003cli\u003eService unavailable (do you have fallbacks? Do you retry?)\u003c/li\u003e\n\u003cli\u003eRate limiting (do you respect backoff headers? Do you queue?)\u003c/li\u003e\n\u003cli\u003eInvalid input (where\u0026rsquo;s the validation boundary? What gets validated?)\u003c/li\u003e\n\u003cli\u003eDatabase deadlock (how often does it happen? What query triggers it?)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis exercise takes longer than step one, but it\u0026rsquo;s where the real insight happens. You\u0026rsquo;re not speculating about what \u003cem\u003ecould\u003c/em\u003e theoretically go wrong. You\u0026rsquo;re building on what actually has gone wrong in production.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"step-3-instrument-deliberately\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#step-3-instrument-deliberately\" title=\"Step 3: Instrument Deliberately\"\u003eStep 3: Instrument Deliberately\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eNow you log only when something meaningful happens:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eA critical path step completes (success or specific failure)\u003c/li\u003e\n\u003cli\u003eAn operation enters a retry/fallback state (you\u0026rsquo;re doing something non-standard)\u003c/li\u003e\n\u003cli\u003eA threshold is crossed (queue is full, latency exceeds SLA, rate limit triggered, circuit breaker opened)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eNothing else. Not method entry/exit. Not variable assignments. Not successful intermediate steps that didn\u0026rsquo;t fail. Only things that directly help answer: \u0026ldquo;Why did this critical path fail?\u0026rdquo;\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"step-4-make-logs-actionable\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#step-4-make-logs-actionable\" title=\"Step 4: Make Logs Actionable\"\u003eStep 4: Make Logs Actionable\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eHere\u0026rsquo;s the test: when someone reads a log line at 3 AM during an incident, can they immediately understand what was happening and what went wrong? Or do they need to cross-reference five other services, query the database, check five other log systems, and piece together a story?\u003c/p\u003e\n\u003cp\u003eIf it\u0026rsquo;s the latter, restructure your log. Make it self-contained. Include the context that matters. Make it so someone can understand what happened without detective work.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"step-5-use-sampling-for-scale\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#step-5-use-sampling-for-scale\" title=\"Step 5: Use Sampling for Scale\"\u003eStep 5: Use Sampling for Scale\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eYou can\u0026rsquo;t keep every single log entry. But you actually don\u0026rsquo;t need to. Use context-aware, intelligent sampling:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eKeep 100% of errors and warnings (these are rare and valuable)\u003c/li\u003e\n\u003cli\u003eFor information logs, consider adaptive sampling: sample heavily on errors (100%), moderately on warnings (50%), lightly on success paths (5-10%)\u003c/li\u003e\n\u003cli\u003eDisable debug logs in production entirely (add them on-demand when troubleshooting a specific incident)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eImportant note: Sampling must be consistent across all services in a distributed trace (W3C Trace Context propagates the \u003ccode\u003esampled\u003c/code\u003e flag for this reason). If one service samples at 10% and another at 50%, you\u0026rsquo;ll have incomplete and inconsistent traces. Either all services honor the same sampling decision, or you lose correlation.\u003c/p\u003e\n\u003cp\u003eWith this approach, you might sample 1 out of every 10 successful order completions. But you\u0026rsquo;ll still see 100 order completions per second even with sampling. You see the patterns. You see the anomalies. You catch bugs. And you\u0026rsquo;re not paying for 90% noise.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"real-example-the-safe-approach\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#real-example-the-safe-approach\" title=\"Real Example: The Safe Approach\"\u003eReal Example: The Safe Approach\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhen you combine all these principles—deliberate instrumentation, source-generated logging, correlation IDs, specific failure modes—the result looks like this:\u003c/p\u003e\n\u003cp\u003eYou log only when a critical path step completes. If it succeeds, one single log entry confirms it happened. If it fails, you log the specific failure mode (timeout, rate limit, validation error) with enough context to diagnose immediately. You use ActivitySource to track the operation through services. You keep the happy path silent—no noise about intermediate steps that didn\u0026rsquo;t fail.\u003c/p\u003e\n\u003cp\u003eInstead of sprawling code with dozens of unnecessary log statements, you have surgical, intentional instrumentation. Each log line earns its place because it answers a specific diagnostic question. You use W3C Trace Context headers (traceparent/tracestate) to correlate across services automatically. The result: when something breaks at 3 AM, you don\u0026rsquo;t sift through chaos. You have a clear narrative: here\u0026rsquo;s what the request tried to do, here\u0026rsquo;s where it failed in which service, here\u0026rsquo;s why. One single trace ID connects everything.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"conclusion-know-why-before-you-know-what\"\u003e\u003ca href=\"/posts/dotnet-advanced-logging/#conclusion-know-why-before-you-know-what\" title=\"Conclusion: Know Why Before You Know What\"\u003eConclusion: Know Why Before You Know What\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe difference between teams that own production and teams that merely survive it isn\u0026rsquo;t logging volume. It\u0026rsquo;s logging intelligence and intention.\u003c/p\u003e\n\u003cp\u003eThe teams with genuinely healthy observability don\u0026rsquo;t log more. They log smarter. They understand their failure modes deeply. They instrument not for completeness, but for purpose. They keep logs queryable because they know they\u0026rsquo;ll search them under pressure. They use sampling strategically instead of trying to keep everything.\u003c/p\u003e\n\u003cp\u003eMost importantly: they make every log line \u003cem\u003ecount\u003c/em\u003e. There\u0026rsquo;s no filler. No speculation. No \u0026ldquo;this might be useful someday.\u0026rdquo; Every log line answers a question.\u003c/p\u003e\n\u003cp\u003eMeanwhile, other teams are paying extra storage fees for logs nobody reads. They\u0026rsquo;re adding more logging and watching performance tank. They\u0026rsquo;re frustrated because diagnosis takes hours instead of minutes.\u003c/p\u003e\n\u003cp\u003eIt doesn\u0026rsquo;t have to be this way.\u003c/p\u003e\n\u003cp\u003eStart with the hardest question: \u0026ldquo;What would I need to see in a log line to immediately understand why this customer\u0026rsquo;s order failed? Why this API call timed out? Why this background job got stuck?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eThen instrument for exactly that. Nothing more. Nothing less.\u003c/p\u003e\n\u003cp\u003eWhen a bug escapes to production—and it will—you won\u0026rsquo;t be digging through gigabytes of noise hoping to find something relevant. You\u0026rsquo;ll have the signal right there in front of you. You\u0026rsquo;ll see what failed, why it failed, and what the system tried to do about it.\u003c/p\u003e\n\u003cp\u003eAt 3 AM, when production is burning and everyone is exhausted and frustrated, that\u0026rsquo;s the difference between \u0026ldquo;we found it in minutes and fixed it\u0026rdquo; and \u0026ldquo;we flew blind for hours and lost customers.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eBuild for that moment. Your future self will thank you.\u003c/p\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2025-12-23T17:00:00+01:00","id":"https://daily-devops.net/posts/dotnet-advanced-logging/","language":"en","summary":"Most .NET teams log 50MB per request and still can't diagnose the 3 AM outage. Fix the anti-patterns that turn observability into expensive noise.","tags":["observability","dotnet","csharp","architecture","bestpractices","cloudnative","performance"],"title":"Why Your Logging Strategy Fails in Production","url":"https://daily-devops.net/posts/dotnet-advanced-logging/"}],"language":"en","title":"Cloud Native Development with .NET \u0026 Azure on Daily DevOps \u0026 .NET","version":"https://jsonfeed.org/version/1.1"}