Skip to content

Commit bec65c4

Browse files
robgrameCopilot
andcommitted
fix(azure): wire AppConfiguration in Functions + scope KV crypto
Rubber-duck of Phase 3 flagged two blockers that would have made the deploy functionally inert. 1) Functions reads `Encryption:KeyVault:Uri` and `:SealKeyName` from `IConfiguration` but no AppConfiguration source was wired. The `KeyVault__Uri` env var set in functions.bicep maps to `KeyVault:Uri`, not `Encryption:KeyVault:Uri` - so Phase 2 KeyVaultKekProvider and Phase 3 DekSealingService were silently NOT registered in production. Fix: register Microsoft.Extensions.Configuration.AzureAppConfiguration in Functions Program.cs with KeyFilter `blkmon:*`, label = AZURE_ENV_NAME (default = builder.Environment.EnvironmentName), TrimKeyPrefix `blkmon:`, and ConfigureKeyVault for KV-reference resolution. The endpoint comes from `AzureAppConfig__Endpoint` which functions.bicep already sets. Added new AZURE_ENV_NAME env var (sourced from main.bicep environmentName param) so the App Configuration label filter selects environment-scoped keys. 2) keyvault.bicep granted vault-wide Key Vault Crypto User to BOTH funcIdentity AND webIdentity. Web.Azure today is an empty Blazor scaffold that performs no crypto operations directly - so this gave Web the ability to wrap/unwrap with blkmon-kek and decrypt with the Phase 3 blkmon-agent-seal key, violating least privilege. Fix: split keyvault.bicep `consumerPrincipalIds` into `cryptoConsumerPrincipalIds` (funcIdentity only, retains operational KEK access) and `secretsConsumerPrincipalIds` (funcIdentity + webIdentity, needed for KV-reference resolution of the SQL connection string via App Configuration). main.bicep updated accordingly. Tests: Azure slnx 86 + 15 + 8 = 109 green. On-prem slnx 86 green. `az bicep build infra/main.bicep` compiles clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6b0b625 commit bec65c4

4 files changed

Lines changed: 47 additions & 5 deletions

File tree

infra/main.bicep

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ module keyvault 'modules/keyvault.bicep' = {
7272
keyVaultName: '${abbrs.keyVaultVaults}${resourceToken}'
7373
tenantId: tenantId
7474
deployerPrincipalId: principalId
75-
consumerPrincipalIds: [
75+
cryptoConsumerPrincipalIds: [
76+
identity.outputs.funcIdentityPrincipalId
77+
]
78+
secretsConsumerPrincipalIds: [
7679
identity.outputs.webIdentityPrincipalId
7780
identity.outputs.funcIdentityPrincipalId
7881
]
@@ -168,6 +171,7 @@ module functions 'modules/functions.bicep' = {
168171
funcIdentityClientId: identity.outputs.funcIdentityClientId
169172
appConfigEndpoint: appConfig.outputs.endpoint
170173
keyVaultUri: keyvault.outputs.keyVaultUri
174+
environmentName: environmentName
171175
}
172176
}
173177

infra/modules/functions.bicep

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ param funcIdentityResourceId string
99
param funcIdentityClientId string
1010
param appConfigEndpoint string
1111
param keyVaultUri string
12+
@description('Short environment name (e.g. dev / prod). Surfaced to the Functions runtime as AZURE_ENV_NAME so App Configuration label filtering selects the right environment-scoped keys.')
13+
param environmentName string
1214

1315
resource storage 'Microsoft.Storage/storageAccounts@2024-01-01' existing = {
1416
name: storageAccountName
@@ -96,6 +98,10 @@ resource functionApp 'Microsoft.Web/sites@2024-04-01' = {
9698
name: 'KeyVault__Uri'
9799
value: keyVaultUri
98100
}
101+
{
102+
name: 'AZURE_ENV_NAME'
103+
value: environmentName
104+
}
99105
]
100106
}
101107
}

infra/modules/keyvault.bicep

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ param keyVaultName string
44
param tenantId string
55
@description('Object ID of the deployer principal (gets Key Vault Administrator). Empty disables this assignment.')
66
param deployerPrincipalId string
7-
@description('Object IDs of workload identities (web, func) that need wrapKey/unwrapKey for the KEK and Get for secrets resolved via App Configuration KV-references.')
8-
param consumerPrincipalIds array
7+
@description('Object IDs of identities that need wrapKey/unwrapKey on the operational KEK. Typically the Functions UAMI only — Web does not perform crypto operations directly.')
8+
param cryptoConsumerPrincipalIds array
9+
@description('Object IDs of identities that need Get on KV secrets (e.g. SQL connection string resolved via App Configuration KV-references). Typically Functions + Web.')
10+
param secretsConsumerPrincipalIds array
911
@description('Object IDs of identities that need encrypt rights on the agent-seal key (Phase 3 Functions producer). Typically the Functions UAMI. Empty disables.')
1012
param sealKeyEncryptPrincipalIds array = []
1113
@description('Object IDs of identities that need decrypt rights on the agent-seal key (Phase 3 HybridAgent consumer). Typically the HybridAgent UAMI / SPN. Empty disables — provision the agent identity then re-run azd.')
@@ -89,7 +91,7 @@ resource adminRa 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!emp
8991
}
9092
}
9193

92-
resource cryptoUserRa 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for pid in consumerPrincipalIds: {
94+
resource cryptoUserRa 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for pid in cryptoConsumerPrincipalIds: {
9395
scope: keyVault
9496
name: guid(keyVault.id, pid, kvCryptoUserRoleId)
9597
properties: {
@@ -99,7 +101,7 @@ resource cryptoUserRa 'Microsoft.Authorization/roleAssignments@2022-04-01' = [fo
99101
}
100102
}]
101103

102-
resource secretsUserRa 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for pid in consumerPrincipalIds: {
104+
resource secretsUserRa 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for pid in secretsConsumerPrincipalIds: {
103105
scope: keyVault
104106
name: guid(keyVault.id, pid, kvSecretsUserRoleId)
105107
properties: {

src/BitLockerKeyMonitor.Functions/Program.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using Microsoft.Azure.Functions.Worker;
66
using Microsoft.Azure.Functions.Worker.Builder;
77
using Microsoft.Azure.Functions.Worker.OpenTelemetry;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
810
using Microsoft.Extensions.DependencyInjection;
911
using Microsoft.Extensions.Hosting;
1012
using OpenTelemetry;
@@ -13,6 +15,34 @@
1315

1416
builder.ConfigureFunctionsWebApplication();
1517

18+
// ---------------- Azure App Configuration ----------------
19+
// Wires builder.Configuration to read keys seeded by infra/modules/appconfig.bicep. Keys are
20+
// stored with the "blkmon:" prefix and an environment-name label (e.g. dev / prod); TrimKeyPrefix
21+
// strips the prefix so callers can read "Encryption:KeyVault:Uri" directly. ConfigureKeyVault
22+
// resolves App Configuration KV-references (e.g. SQL connection string) via the same managed
23+
// identity. Without this block, the Phase 2 KeyVaultKekProvider and Phase 3 DekSealingService
24+
// would silently fail to register because their config keys would always resolve to null.
25+
26+
var appConfigEndpoint = builder.Configuration["AzureAppConfig:Endpoint"]
27+
?? Environment.GetEnvironmentVariable("AzureAppConfig__Endpoint");
28+
var envLabel = builder.Configuration["AzureAppConfig:Label"]
29+
?? Environment.GetEnvironmentVariable("AZURE_ENV_NAME")
30+
?? builder.Environment.EnvironmentName;
31+
32+
if (!string.IsNullOrEmpty(appConfigEndpoint))
33+
{
34+
var appConfigCredential = new DefaultAzureCredential();
35+
builder.Configuration.AddAzureAppConfiguration(options =>
36+
{
37+
options
38+
.Connect(new Uri(appConfigEndpoint), appConfigCredential)
39+
.Select("blkmon:*", LabelFilter.Null)
40+
.Select("blkmon:*", envLabel)
41+
.TrimKeyPrefix("blkmon:")
42+
.ConfigureKeyVault(kv => kv.SetCredential(appConfigCredential));
43+
});
44+
}
45+
1646
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING")))
1747
{
1848
builder.Services.AddOpenTelemetry()

0 commit comments

Comments
 (0)