Skip to content
Draft
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
9 changes: 6 additions & 3 deletions docs-mslearn/toolkit/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: FinOps toolkit changelog
description: Review the latest features and enhancements in the FinOps toolkit, including updates to FinOps hubs, Power BI reports, and more.
author: MSBrett
ms.author: brettwil
ms.date: 10/28/2026
ms.date: 10/29/2026
ms.topic: reference
ms.service: finops
ms.subservice: finops-toolkit
Expand All @@ -18,14 +18,17 @@ ms.reviewer: brettwil

This article summarizes the features and enhancements in each release of the FinOps toolkit.

<!--
<br>

## Unreleased

The following section lists features and enhancements that are currently in development.

-->
### [FinOps hubs](hubs/finops-hubs-overview.md)

- **Added**
- Added an opt-in `disableStorageSharedKeyAccess` parameter (and matching `-DisableStorageSharedKeyAccess` switch in [Deploy-FinOpsHub](powershell/hubs/deploy-finopshub.md)) to disable Shared Key (storage account key) authentication on hub storage accounts. Data Factory already authenticates using managed identity and RBAC, so enabling this does not affect data ingestion. Default: false, to avoid changing the authentication model on existing deployments ([#2312](https://github.com/microsoft/finops-toolkit/issues/2312)).


<br><a name="latest"></a>

Expand Down
12 changes: 12 additions & 0 deletions src/powershell/Public/Deploy-FinOpsHub.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
.PARAMETER EnablePurgeProtection
Optional. Enable purge protection for the Key Vault. Default = false.

.PARAMETER DisableStorageSharedKeyAccess
Optional. Disable Shared Key (storage account key) authentication on hub storage accounts. When enabled, only Microsoft Entra ID and managed identity authentication are allowed. Data Factory already authenticates using managed identity and RBAC, so this does not affect data ingestion. Do not enable this on a hub that receives data from other hubs (remote hub ingestion authenticates with the storage account key). The private-mode deployment script storage account always allows Shared Key access because deployment scripts require it. Default = false.

.PARAMETER RemoteHubStorageUri
Optional. Storage account to push data to for ingestion into a remote hub.

Expand Down Expand Up @@ -145,6 +148,10 @@ function Deploy-FinOpsHub
[switch]
$EnablePurgeProtection,

[Parameter()]
[switch]
$DisableStorageSharedKeyAccess,

[Parameter()]
[string]
$RemoteHubStorageUri,
Expand Down Expand Up @@ -343,6 +350,11 @@ function Deploy-FinOpsHub
$parameterSplat.TemplateParameterObject.Add('enablePurgeProtection', $EnablePurgeProtection.IsPresent)
}

if ($Version -eq 'latest' -or [version]$Version -ge '16.0')
{
$parameterSplat.TemplateParameterObject.Add('disableStorageSharedKeyAccess', $DisableStorageSharedKeyAccess.IsPresent)
}

# Only pass enableNatGateway when private mode is requested. This keeps public/vnet
# deployments compatible with template versions that predate the parameter.
if ($effectiveNetworkMode -eq 'private' -and ($Version -eq 'latest' -or $templateVersion -ge '15.0'))
Expand Down
33 changes: 33 additions & 0 deletions src/powershell/Tests/Unit/Deploy-FinOpsHub.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,38 @@ InModuleScope 'FinOpsToolkit' {
} -Times 1
}
}

Context 'Storage shared key access' {
BeforeAll {
Mock -CommandName 'Get-AzResourceGroup' -MockWith { return @{ ResourceGroupName = $rgName } }
Mock -CommandName 'New-AzResourceGroup'
Mock -CommandName 'Save-FinOpsHubTemplate'
Mock -CommandName 'Initialize-FinOpsHubDeployment'
$templateFile = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'FinOps/finops-hub-v1.0.0/main.bicep'
Mock -CommandName 'Get-ChildItem' -MockWith { return @{ FullName = $templateFile } }
Mock -CommandName 'New-AzResourceGroupDeployment'
}

It 'Should default to allowing Shared Key access' {
{ Deploy-FinOpsHub -Name $hubName -ResourceGroup $rgName -Location $location -Version 'latest' } | Should -Not -Throw
Should -Invoke -CommandName 'New-AzResourceGroupDeployment' -ParameterFilter {
$TemplateParameterObject.disableStorageSharedKeyAccess -eq $false
} -Times 1
}

It 'Should pass disableStorageSharedKeyAccess when -DisableStorageSharedKeyAccess is specified' {
{ Deploy-FinOpsHub -Name $hubName -ResourceGroup $rgName -Location $location -DisableStorageSharedKeyAccess -Version 'latest' } | Should -Not -Throw
Should -Invoke -CommandName 'New-AzResourceGroupDeployment' -ParameterFilter {
$TemplateParameterObject.disableStorageSharedKeyAccess -eq $true
} -Times 1
}

It 'Should not pass disableStorageSharedKeyAccess when targeting a version older than 16.0' {
{ Deploy-FinOpsHub -Name $hubName -ResourceGroup $rgName -Location $location -DisableStorageSharedKeyAccess -Version '15.0' } | Should -Not -Throw
Should -Invoke -CommandName 'New-AzResourceGroupDeployment' -ParameterFilter {
-not $TemplateParameterObject.ContainsKey('disableStorageSharedKeyAccess')
} -Times 1
}
}
}
}
23 changes: 23 additions & 0 deletions src/templates/finops-hub/createUiDefinition.json
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,28 @@
],
"visible": true
},
{
"name": "storageSecurity",
"type": "Microsoft.Common.Section",
"label": "Storage account security",
"elements": [
{
"name": "disableStorageSharedKeyAccessIntro",
"type": "Microsoft.Common.TextBlock",
"visible": true,
"options": {
"text": "Data Factory always connects to hub storage accounts using its managed identity, so disabling Shared Key access does not affect data ingestion. Do not enable this on a hub that receives data from other hubs. This can be changed after deployment."
}
},
{
"name": "disableStorageSharedKeyAccess",
"type": "Microsoft.Common.CheckBox",
"label": "Disable Shared Key access",
"toolTip": "Disables Shared Key (storage account key) authentication on hub storage accounts, requiring Microsoft Entra ID or managed identity authentication instead. Recommended for compliance requirements that prohibit key-based storage access."
}
],
"visible": true
},
{
"name": "managedExports",
"type": "Microsoft.Common.Section",
Expand Down Expand Up @@ -998,6 +1020,7 @@
"fabricQueryUri": "[if(equals(basics('analyticsBackend').analyticsEngine, 'fabric'), basics('analyticsBackend').fabricQueryUri, '')]",
"fabricCapacityUnits": "[if(equals(basics('analyticsBackend').analyticsEngine, 'fabric'), basics('analyticsBackend').fabricCapacity, '')]",
"enableInfrastructureEncryption": "[steps('advanced').storage.enableInfrastructureEncryption]",
"disableStorageSharedKeyAccess": "[steps('advanced').storageSecurity.disableStorageSharedKeyAccess]",
"enableManagedExports": "[steps('advanced').managedExports.enableManagedExports]",
"enableRecommendations": "[steps('recommendations').enableRecommendations]",
"enableAHBRecommendations": "[steps('recommendations').optional.enableAHBRecommendations]",
Expand Down
4 changes: 4 additions & 0 deletions src/templates/finops-hub/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ param storageSku string = 'Premium_LRS'
@description('Optional. Enable infrastructure encryption on the storage account. Default = false.')
param enableInfrastructureEncryption bool = false

@description('Optional. Disable Shared Key (storage account key) authentication on hub storage accounts. When enabled, only Microsoft Entra ID and managed identity authentication are allowed. Data Factory already authenticates using managed identity and RBAC, so this does not affect data ingestion. Do not enable this on a hub that receives data from other hubs (remote hub ingestion authenticates with the storage account key). The private-mode deployment script storage account always allows Shared Key access because deployment scripts require it. Default = false.')
param disableStorageSharedKeyAccess bool = false

@description('Optional. Enable purge protection for the Key Vault. Default: false.')
param enablePurgeProtection bool = false

Expand Down Expand Up @@ -178,6 +181,7 @@ module hub 'modules/hub.bicep' = {
// eventGridLocation: eventGridLocation
storageSku: storageSku
enableInfrastructureEncryption: enableInfrastructureEncryption
disableStorageSharedKeyAccess: disableStorageSharedKeyAccess
enablePurgeProtection: enablePurgeProtection
enableManagedExports: enableManagedExports
enableRecommendations: enableRecommendations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ resource scriptStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = i
tags: getHubTags(hub, 'Microsoft.Storage/storageAccounts')
properties: {
supportsHttpsTrafficOnly: true
allowSharedKeyAccess: true
allowSharedKeyAccess: true // Required: deployment scripts mount this storage account in Azure Container Instances using the account key. See https://learn.microsoft.com/azure/azure-resource-manager/bicep/deployment-script-develop#use-an-existing-storage-account
isHnsEnabled: false
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
Expand Down
2 changes: 1 addition & 1 deletion src/templates/finops-hub/modules/fx/hub-app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = if (use
properties: {
...storageInfrastructureEncryptionProperties
supportsHttpsTrafficOnly: true
allowSharedKeyAccess: true
allowSharedKeyAccess: !app.hub.options.disableStorageSharedKeyAccess
isHnsEnabled: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
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 @@ -77,6 +77,7 @@ type HubRoutingProperties = {
natGateway: 'Indicates whether a NAT Gateway should be deployed for controlled outbound internet access. When enabled, the script and Data Explorer subnets route outbound traffic through the NAT Gateway.'
privateRouting: 'Indicates whether private network routing is enabled.'
publisherIsolation: 'Indicates whether FinOps hub resources should be separated by publisher for advanced security.'
disableStorageSharedKeyAccess: 'Indicates whether Shared Key (storage account key) authentication is disabled on hub storage accounts. When enabled, only Microsoft Entra ID and managed identity authentication are allowed. This does not affect Data Factory, which already authenticates via managed identity and RBAC. Does not apply to the private-mode deployment script storage account, which requires Shared Key access.'
storageInfrastructureEncryption: 'Indicates whether infrastructure encryption is enabled for the storage account.'
storageSku: 'Storage account SKU. Allowed values: "Premium_LRS", "Premium_ZRS".'
}
Expand All @@ -102,6 +103,7 @@ type HubProperties = {
natGateway: bool
privateRouting: bool
publisherIsolation: bool
disableStorageSharedKeyAccess: bool
storageInfrastructureEncryption: bool
storageSku: string
}
Expand Down Expand Up @@ -195,6 +197,7 @@ func newHubInternal(
keyVaultSku string,
keyVaultEnablePurgeProtection bool,
enableInfrastructureEncryption bool,
disableStorageSharedKeyAccess bool,
enablePublicAccess bool,
enableNatGateway bool,
networkName string,
Expand All @@ -219,6 +222,7 @@ func newHubInternal(
natGateway: !enablePublicAccess && enableNatGateway
privateRouting: !enablePublicAccess
publisherIsolation: false // TODO: Expose publisher isolation option
disableStorageSharedKeyAccess: disableStorageSharedKeyAccess
storageInfrastructureEncryption: enableInfrastructureEncryption
storageSku: storageSku
}
Expand Down Expand Up @@ -257,6 +261,7 @@ func newHub(
keyVaultSku string,
keyVaultEnablePurgeProtection bool,
enableInfrastructureEncryption bool,
disableStorageSharedKeyAccess bool,
enablePublicAccess bool,
enableNatGateway bool,
networkAddressPrefix string,
Expand All @@ -272,6 +277,7 @@ func newHub(
keyVaultSku,
keyVaultEnablePurgeProtection,
enableInfrastructureEncryption,
disableStorageSharedKeyAccess,
enablePublicAccess,
enableNatGateway,
'${safeStorageName(name)}-vnet-${location}', // networkName, cSpell:ignore vnet
Expand Down
4 changes: 4 additions & 0 deletions src/templates/finops-hub/modules/hub.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ param storageSku string = 'Premium_LRS'
@description('Optional. Enable infrastructure encryption on the storage account. Default = false.')
param enableInfrastructureEncryption bool = false

@description('Optional. Disable Shared Key (storage account key) authentication on hub storage accounts. When enabled, only Microsoft Entra ID and managed identity authentication are allowed. Data Factory already authenticates using managed identity and RBAC, so this does not affect data ingestion. Do not enable this on a hub that receives data from other hubs (remote hub ingestion authenticates with the storage account key). The private-mode deployment script storage account always allows Shared Key access because deployment scripts require it. Default = false.')
param disableStorageSharedKeyAccess bool = false

@description('Optional. SKU to use for the KeyVault instance, if enabled. Allowed values: "standard", "premium". Default: "premium".')
@allowed([
'premium'
Expand Down Expand Up @@ -196,6 +199,7 @@ var hub = newHub(
keyVaultSku,
enablePurgeProtection,
enableInfrastructureEncryption,
disableStorageSharedKeyAccess,
enablePublicAccess,
enableNatGateway,
virtualNetworkAddressPrefix,
Expand Down
Loading