Table of Contents
Introduction
Why Managed Identities Are Non-Negotiable in Critical Infrastructure
Real-World Scenario: Real-Time Grid Load Balancing System
How Managed Identities Work Under the Hood
End-to-End Implementation in .NET 8
Secure Integration with Azure Key Vault and Cosmos DB
Testing and Validation Strategy
Enterprise Operational Best Practices
Conclusion
Introduction
In the world of critical infrastructure—like national power grids—every secret is a liability. A leaked connection string or API key doesn’t just risk data; it risks blackouts, safety systems, and public trust.
As a senior cloud architect who’s modernized grid control systems for major utilities, I enforce one principle:
“If your code handles credentials, you’ve already failed.”
This article shows how Managed Identities in Azure Functions eliminate secrets entirely, using a real-time smart grid load-balancing system as our live scenario.
Why Managed Identities Are Non-Negotiable in Critical Infrastructure
Utilities operate under NERC CIP and ISO 27001. They require:
Managed Identities solve this by giving your Azure Function an identity in Azure AD, which can be granted permissions to other Azure services—without passwords, keys, or certificates.
Real-World Scenario: Real-Time Grid Load Balancing System
A national utility uses Azure Functions to:
Ingest real-time telemetry from 50,000+ smart meters (via Event Hubs)
Analyze grid load using Cosmos DB for historical patterns
Fetch dynamic pricing rules from Azure Key Vault
Dispatch load-shedding commands to substations
The mandate?
“The function must access Cosmos DB and Key Vault without any secrets in code, config, or deployment pipelines.”
Managed Identity is the only compliant solution.
![PlantUML Diagram]()
How Managed Identities Work Under the Hood
When you enable System-Assigned Managed Identity on a Function App:
Azure creates a service principal in your Azure AD tenant
The runtime automatically acquires an access token from the Instance Metadata Service (IMDS)
SDKs like Azure.Identity
use this token to authenticate to Azure services
No credentials. No rotation. No risk.
End-to-End Implementation in .NET 8
Step 1: Enable Managed Identity (via Azure CLI)
az functionapp identity assign \
--name grid-load-balancer-func \
--resource-group energy-rg \
--query principalId -o tsv
Step 2: Grant Permissions
# Get Cosmos DB account name
COSMOS_ACCOUNT="grid-telemetry-cosmos"
# Grant Data Reader role
az cosmosdb sql role assignment create \
--account-name $COSMOS_ACCOUNT \
--resource-group energy-rg \
--scope "/" \
--principal-id <principalId-from-above> \
--role-definition-id 00000000-0000-0000-0000-000000000001 # Built-in Cosmos DB Reader
# Grant Key Vault secret reader
az keyvault set-policy \
--name grid-config-kv \
--object-id <principalId> \
--secret-permissions get list
Step 3: .NET 8 Function Code (GridBalancerFunction.cs
)
using System.Text.Json;
using Azure.Identity;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Cosmos;
namespace Energy.Grid.Functions;
public class GridBalancerFunction
{
private readonly CosmosClient _cosmosClient;
private readonly SecretClient _secretClient;
private readonly ILogger<GridBalancerFunction> _logger;
public GridBalancerFunction(
ILogger<GridBalancerFunction> logger,
// Managed Identity is used automatically by Azure SDKs
CosmosClient cosmosClient,
SecretClient secretClient)
{
_logger = logger;
_cosmosClient = cosmosClient;
_secretClient = secretClient;
}
[Function("BalanceGridLoad")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
_logger.LogInformation("Grid balancing triggered");
try
{
// 1. Fetch dynamic pricing rules from Key Vault
var pricingSecret = await _secretClient.GetSecretAsync("DynamicPricingRule");
var pricingRule = JsonSerializer.Deserialize<PricingRule>(pricingSecret.Value.Value);
// 2. Query Cosmos DB for real-time load
var container = _cosmosClient.GetContainer("GridTelemetry", "Meters");
var query = $"SELECT VALUE c.load FROM c WHERE c.timestamp > {(DateTimeOffset.UtcNow.AddMinutes(-5).ToUnixTimeSeconds())}";
var loads = new List<double>();
using var feed = container.GetItemQueryStreamIterator(query);
while (feed.HasMoreResults)
{
var response = await feed.ReadNextAsync();
var results = await JsonSerializer.DeserializeAsync<List<double>>(response.Content);
loads.AddRange(results);
}
var avgLoad = loads.Count > 0 ? loads.Average() : 0;
// 3. Apply business logic
var action = avgLoad > pricingRule.Threshold
? "Initiate load shedding"
: "Maintain normal operation";
// 4. Return result
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
await response.WriteStringAsync(JsonSerializer.Serialize(new {
action,
averageLoad = avgLoad,
ruleApplied = pricingRule.Name
}));
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Grid balancing failed");
var errorResponse = req.CreateResponse(System.Net.HttpStatusCode.InternalServerError);
await errorResponse.WriteStringAsync("Processing failed");
return errorResponse;
}
}
}
public record PricingRule(string Name, double Threshold);
Step 4: Program.cs
– Register Azure Clients with Managed Identity
using Azure.Identity;
using Microsoft.Azure.Cosmos;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Azure;
var builder = Host.CreateApplicationBuilder(args);
builder.ConfigureFunctionsWorkerDefaults();
// Use DefaultAzureCredential (Managed Identity in Azure, CLI/auth in dev)
var credential = new DefaultAzureCredential();
// Register Cosmos DB client
builder.Services.AddSingleton<CosmosClient>(sp =>
new CosmosClient(
accountEndpoint: "https://grid-telemetry-cosmos.documents.azure.com:443/",
tokenCredential: credential
));
// Register Key Vault client
builder.Services.AddSingleton<SecretClient>(sp =>
new SecretClient(
vaultUri: new Uri("https://grid-config-kv.vault.azure.net/"),
credential: credential
));
var host = builder.Build();
host.Run();
Step 5: local.settings.json
(for local development)
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}
Run az login
as a user with Key Vault and Cosmos DB access. DefaultAzureCredential
will use your CLI identity automatically.
Secure Integration with Azure Key Vault and Cosmos DB
Key Vault: Stores DynamicPricingRule
as a JSON secret
Cosmos DB: Uses RBAC (not primary keys) with fine-grained roles
All communication: Uses Azure AD tokens—no connection strings anywhere
![1]()
![2]()
Testing and Validation Strategy
Local
Azure
Trigger function → check Application Insights for logs
Verify Key Vault audit logs show GetSecret
by Function identity
Confirm Cosmos DB requests appear in diagnostic logs
Break-glass test:
Enterprise Operational Best Practices
Prefer System-Assigned over User-Assigned unless sharing identity across resources
Enable diagnostic logs on Key Vault and Cosmos DB for full audit trail
Use least-privilege roles: e.g., Cosmos DB Built-in Data Reader
, not Owner
Rotate secrets in Key Vault without redeploying code
Fail fast: Validate connectivity at startup using IHostedService
Conclusion
In critical systems like smart grids, security isn’t a feature—it’s a requirement. Managed Identities in Azure Functions deliver zero-secret authentication that’s simple, scalable, and compliant. By leveraging DefaultAzureCredential
and Azure RBAC, we’ve built a system where:
No secrets exist in code, config, or pipelines
Every data access is auditable
Credentials rotate automatically
This isn’t just best practice—it’s how modern critical infrastructure stays resilient, secure, and always on. As cloud architects, our job is to make the secure path the only path. Managed Identities make that possible.