Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-mslearn/toolkit/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The following section lists features and enhancements that are currently in deve

- **Added**
- Added VNet and private network modes, including opt-in NAT Gateway support for private mode; NAT Gateway incurs additional cost when enabled ([#2163](https://github.com/microsoft/finops-toolkit/pull/2163)).
- Added an opt-in `enableRbacAuthorization` parameter to switch the remote hub Key Vault from access policies to Azure RBAC, satisfying Cloud Adoption Framework / Enterprise-Scale landing zone guardrails that require RBAC-authorized key vaults; the Data Factory managed identity is granted an equivalent Key Vault Secrets User role assignment so secret access keeps working when enabled ([#1067](https://github.com/microsoft/finops-toolkit/issues/1067)).
- **Changed**
- Replaced redundant `tolower()` comparisons in hub KQL with case-insensitive operators (`has`, `=~`, `!~`) so the engine can use the term index instead of scanning every row ([#2213](https://github.com/microsoft/finops-toolkit/issues/2213)).
- Replaced whole-term `contains` matches with `has` across hub KQL and the query catalog (resource ID paths, licensing phrases, SKU description terms) and added a per-row operator-equivalence regression harness with unit test coverage ([#2220](https://github.com/microsoft/finops-toolkit/pull/2220)).
Expand Down
10 changes: 9 additions & 1 deletion src/templates/finops-hub/createUiDefinition.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,14 @@
"name": "enablePurgeProtection",
"type": "Microsoft.Common.CheckBox",
"label": "Enable Key Vault purge protection",
"toolTip": "Enables purge protection for the Key Vault used to store the remote hub storage key. Purge protection prevents permanent deletion of the Key Vault for 90 days after deletion. Note: If the key is lost, you can regenerate it from the remote hub's storage account.",
"toolTip": "Enables purge protection for the Key Vault used to store the remote hub storage key. Purge protection prevents permanent deletion of the Key Vault for 90 days after deletion. Note: If the key is lost, you can regenerate it from the remote hub's storage account. This cannot be disabled once enabled.",
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]"
},
{
"name": "enableRbacAuthorization",
"type": "Microsoft.Common.CheckBox",
"label": "Enable Key Vault RBAC authorization",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens on existing deployments? Will it end up with both, rbac and access policies, as we are not removing access policies via bicep I guess.

"toolTip": "Enables Azure RBAC instead of access policies to authorize access to the Key Vault used to store the remote hub storage key. Required by some organizations for policy compliance (e.g., Cloud Adoption Framework guardrails). Enable this if you are deploying to a subscription that enforces this requirement.",
"visible": "[equals(basics('analyticsBackend').analyticsEngine, 'remote')]"
}
],
Expand Down Expand Up @@ -1013,6 +1020,7 @@
"remoteHubStorageUri": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), basics('analyticsBackend').remoteHubStorageUri, '')]",
"remoteHubStorageKey": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), basics('analyticsBackend').remoteHubStorageKey, '')]",
"enablePurgeProtection": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), coalesce(basics('analyticsBackend').enablePurgeProtection, false), false)]",
"enableRbacAuthorization": "[if(equals(basics('analyticsBackend').analyticsEngine, 'remote'), coalesce(basics('analyticsBackend').enableRbacAuthorization, false), false)]",
"tagsByResource": "[steps('tags').tagsByResource]"
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/templates/finops-hub/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ param storageSku string = 'Premium_LRS'
@description('Optional. Enable infrastructure encryption on the storage account. Default = false.')
param enableInfrastructureEncryption bool = false

@description('Optional. Enable purge protection for the Key Vault. Default: false.')
@description('Optional. Enable purge protection for the Key Vault. Once enabled on a vault, purge protection cannot be disabled. Default: false.')
param enablePurgeProtection bool = false

@description('Optional. Enable Azure RBAC for authorizing access to the Key Vault instead of access policies. Required by some organizations for policy compliance (e.g., Cloud Adoption Framework guardrails). Switching an existing vault from access policies to RBAC has migration implications, so this defaults to false for backward compatibility with existing deployments; review before enabling on an upgrade. Default: false.')
param enableRbacAuthorization bool = false

@description('Optional. Storage account to push data to for ingestion into a remote hub.')
param remoteHubStorageUri string = ''

Expand Down Expand Up @@ -179,6 +182,7 @@ module hub 'modules/hub.bicep' = {
storageSku: storageSku
enableInfrastructureEncryption: enableInfrastructureEncryption
enablePurgeProtection: enablePurgeProtection
enableRbacAuthorization: enableRbacAuthorization
enableManagedExports: enableManagedExports
enableRecommendations: enableRecommendations
enableAHBRecommendations: enableAHBRecommendations
Expand Down
23 changes: 20 additions & 3 deletions src/templates/finops-hub/modules/fx/hub-app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ var storageInfrastructureEncryptionProperties = !app.hub.options.storageInfrastr
}
}

// KeyVault access policies
var keyVaultAccessPolicies = [
// KeyVault access policies -- only used when the vault uses the legacy access-policy auth model
// (RBAC-authorized vaults must have an empty accessPolicies array; Azure rejects a non-empty array otherwise)
var keyVaultAccessPolicies = app.hub.options.keyVaultEnableRbacAuthorization ? [] : [
{
#disable-next-line BCP318 // Null safety warning for conditional resource access // Null safety warning for conditional resource access
objectId: dataFactory.identity.principalId
Expand All @@ -93,6 +94,10 @@ var keyVaultAccessPolicies = [
}
]

// Built-in role definition IDs used for Key Vault RBAC role assignments
// Key Vault Secrets User -- https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#key-vault-secrets-user
var keyVaultSecretsUserRoleId = '4633458b-17de-408a-b874-0445c86b69e6'


//==============================================================================
// Resources
Expand Down Expand Up @@ -484,7 +489,7 @@ resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = if (usesKeyVault) {
softDeleteRetentionInDays: 90
// Use null instead of false when purge protection is disabled - Azure requires null to indicate the property should not be set
enablePurgeProtection: app.hub.options.keyVaultEnablePurgeProtection ? true : null
enableRbacAuthorization: false
enableRbacAuthorization: app.hub.options.keyVaultEnableRbacAuthorization
createMode: 'default'
tenantId: subscription().tenantId
accessPolicies: keyVaultAccessPolicies
Expand All @@ -495,6 +500,18 @@ resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = if (usesKeyVault) {
}
}

// Grant ADF identity RBAC access to read secrets when the vault uses RBAC instead of access policies
resource keyVaultRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (usesKeyVault && usesDataFactory && app.hub.options.keyVaultEnableRbacAuthorization) {
name: guid(keyVault.id, keyVaultSecretsUserRoleId, dataFactory.id)
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', keyVaultSecretsUserRoleId)
#disable-next-line BCP318 // Null safety warning for conditional resource access
principalId: dataFactory.identity.principalId
principalType: 'ServicePrincipal'
}
}

resource keyVaultPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = if (usesKeyVault && app.hub.options.privateRouting) {
name: 'privatelink${replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')}' // cSpell:ignore privatelink, vaultcore
location: 'global'
Expand Down
6 changes: 6 additions & 0 deletions src/templates/finops-hub/modules/fx/hub-types.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type HubRoutingProperties = {
enableTelemetry: 'Indicates whether telemetry should be enabled for deployments.'
keyVaultSku: 'KeyVault SKU. Allowed values: "standard", "premium".'
keyVaultEnablePurgeProtection: 'Indicates whether purge protection is enabled for the Key Vault. When enabled, deleted Key Vault and its secrets cannot be permanently deleted until the retention period expires, which is required for compliance in some environments.'
keyVaultEnableRbacAuthorization: 'Indicates whether the Key Vault uses Azure RBAC instead of access policies to authorize access to secrets. When enabled, access policies are ignored and callers need an RBAC role assignment (e.g., Key Vault Secrets User) on the vault, which is required for compliance in some environments.'
networkAddressPrefix: 'Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.'
natGateway: 'Indicates whether a NAT Gateway should be deployed for controlled outbound internet access. When enabled, subnets disable Azure default outbound access and route through the NAT Gateway.'
privateRouting: 'Indicates whether private network routing is enabled.'
Expand All @@ -96,6 +97,7 @@ type HubProperties = {
enableTelemetry: bool
keyVaultSku: string
keyVaultEnablePurgeProtection: bool
keyVaultEnableRbacAuthorization: bool
networkAddressPrefix: string
natGateway: bool
privateRouting: bool
Expand Down Expand Up @@ -192,6 +194,7 @@ func newHubInternal(
storageSku string,
keyVaultSku string,
keyVaultEnablePurgeProtection bool,
keyVaultEnableRbacAuthorization bool,
enableInfrastructureEncryption bool,
enablePublicAccess bool,
enableNatGateway bool,
Expand All @@ -213,6 +216,7 @@ func newHubInternal(
enableTelemetry: isTelemetryEnabled ?? true
keyVaultSku: keyVaultSku
keyVaultEnablePurgeProtection: keyVaultEnablePurgeProtection
keyVaultEnableRbacAuthorization: keyVaultEnableRbacAuthorization
networkAddressPrefix: networkAddressPrefix
natGateway: !enablePublicAccess && enableNatGateway
privateRouting: !enablePublicAccess
Expand Down Expand Up @@ -253,6 +257,7 @@ func newHub(
storageSku string,
keyVaultSku string,
keyVaultEnablePurgeProtection bool,
keyVaultEnableRbacAuthorization bool,
enableInfrastructureEncryption bool,
enablePublicAccess bool,
enableNatGateway bool,
Expand All @@ -268,6 +273,7 @@ func newHub(
storageSku,
keyVaultSku,
keyVaultEnablePurgeProtection,
keyVaultEnableRbacAuthorization,
enableInfrastructureEncryption,
enablePublicAccess,
enableNatGateway,
Expand Down
6 changes: 5 additions & 1 deletion src/templates/finops-hub/modules/hub.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ param enableInfrastructureEncryption bool = false
])
param keyVaultSku string = 'premium'

@description('Optional. Enable purge protection for the Key Vault. Default: false.')
@description('Optional. Enable purge protection for the Key Vault. Once enabled on a vault, purge protection cannot be disabled. Default: false.')
param enablePurgeProtection bool = false

@description('Optional. Enable Azure RBAC for authorizing access to the Key Vault instead of access policies. Required by some organizations for policy compliance (e.g., Cloud Adoption Framework guardrails). Switching an existing vault from access policies to RBAC has migration implications, so this defaults to false for backward compatibility with existing deployments; review before enabling on an upgrade. Default: false.')
param enableRbacAuthorization bool = false

@description('Optional. Remote storage account for ingestion dataset.')
param remoteHubStorageUri string = ''

Expand Down Expand Up @@ -195,6 +198,7 @@ var hub = newHub(
storageSku,
keyVaultSku,
enablePurgeProtection,
enableRbacAuthorization,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this name too generic? This isn't clear it's for Key Vault only.

enableInfrastructureEncryption,
enablePublicAccess,
enableNatGateway,
Expand Down
Loading