[SQL] Add support for versionless TDE keys#29159
Conversation
| Thanks for your contribution! The pull request validation has started. Please revisit this comment for updated status. |
There was a problem hiding this comment.
Pull request overview
Adds support in the Az.Sql module for versionless Azure Key Vault key IDs when configuring SQL Server TDE / server key vault keys, aligning cmdlet behavior with newer service capabilities.
Changes:
- Updated
TdeKeyHelper.CreateServerKeyNameFromKeyIdto accept both versioned and versionless Key Vault KeyIds. - Added a new live-only scenario test covering adding a server key vault key with a versionless KeyId.
- Updated AutoRest input-file API versions for ServerKeys / EncryptionProtectors / Databases in the Sql.Management.Sdk generator config.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/Sql/Sql/Common/TdeKeyHelper.cs |
Extends KeyId validation/parsing to support versionless Key Vault key URIs and adjusts server key name formatting accordingly. |
src/Sql/Sql.Test/ScenarioTests/ServerKeyVaultKeyTests.ps1 |
Adds a new scenario test that creates a KV + key and calls Add-AzSqlServerKeyVaultKey using a versionless KeyId. |
src/Sql/Sql.Test/ScenarioTests/ServerKeyVaultKeyTests.cs |
Registers the new PowerShell scenario test in the xUnit runner (LiveOnly). |
src/Sql/Sql.Management.Sdk/README.md |
Bumps specific swagger inputs to newer preview API versions for code generation. |
| $keyVaultName = "akv+$Get-Random" | ||
| $keyName = "serverkey+$Get-Random" |
There was a problem hiding this comment.
$keyVaultName and $keyName are built as literal strings ("akv+$Get-Random" / "serverkey+$Get-Random"). In PowerShell this does not call Get-Random, and it also introduces +, $, and - characters that violate Azure Key Vault naming rules (vault names must be 3–24 chars and contain only letters, numbers, and hyphens). Generate these names using Get-Random (or existing getAssetName helpers) and ensure they meet Key Vault naming constraints.
| $keyVaultName = "akv+$Get-Random" | |
| $keyName = "serverkey+$Get-Random" | |
| $randomSuffix = Get-Random -Maximum 1000000 | |
| $keyVaultName = "akv$randomSuffix" | |
| $keyName = "serverkey$randomSuffix" |
| $rg = Create-ResourceGroupForTest | ||
| $serverName = Get-ServerName | ||
| $credentials = Get-ServerCredential | ||
|
|
||
| $server = New-AzSqlServer -ResourceGroupName $rg.ResourceGroupName -ServerName $serverName -Location $location -SqlAdministratorCredentials $credentials -AssignIdentity |
There was a problem hiding this comment.
This test creates the resource group via Create-ResourceGroupForTest (defaults to westcentralus), but then creates the SQL server/key vault in eastus2euap. Consider passing $location into Create-ResourceGroupForTest (or using $rg.Location) to keep resources co-located and reduce live-test flakiness.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/azp run |
|
Commenter does not have sufficient privileges for PR 29159 in repo Azure/azure-powershell |
|
|
||
| // Validate that the url is a keyvault url and has a key and version | ||
| Regex r = new Regex(@"https://(.)+\.(managedhsm.azure.net|managedhsm-preview.azure.net|vault.azure.net|vault-int.azure-int.net|vault.azure.cn|managedhsm.azure.cn|vault.usgovcloudapi.net|managedhsm.usgovcloudapi.net|vault.microsoftazure.de|managedhsm.microsoftazure.de|vault.cloudapi.eaglex.ic.gov|vault.cloudapi.microsoft.scloud)(:443)?\/keys/[^\/]+\/[0-9a-zA-Z]+$", RegexOptions.IgnoreCase); | ||
| Regex r = new Regex(@"^https://(?!.*\.\.)[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]\.(managedhsm.azure.net|managedhsm-preview.azure.net|vault.azure.net|vault-int.azure-int.net|vault.azure.cn|managedhsm.azure.cn|vault.usgovcloudapi.net|managedhsm.usgovcloudapi.net|vault.microsoftazure.de|managedhsm.microsoftazure.de|vault.cloudapi.eaglex.ic.gov|vault.cloudapi.microsoft.scloud|mdep.azure.net)(:443)?\/keys/[^\/]+(\/[0-9a-zA-Z]+|\/|)$", RegexOptions.IgnoreCase); |
There was a problem hiding this comment.
The regex pattern has a potential issue. The pattern (\/[0-9a-zA-Z]+|\/|)$ at the end allows three cases:
/version(versioned key)/(trailing slash only)- Empty string (no trailing slash)
However, this creates ambiguity and allows invalid URLs like https://vault.vault.azure.net/keys/keyname/ (with trailing slash but no version). According to the documentation comment, valid versionless keys should be https://YourVaultName.vault.azure.net/keys/YourKeyName (no trailing slash). The regex should be more strict to only allow either /[0-9a-zA-Z]+ for versioned keys or nothing for versionless keys, not allowing a trailing slash without a version. Consider changing the pattern to (\/[0-9a-zA-Z]+)?$ to only allow an optional version segment, not an empty trailing slash.
| $serverName = Get-ServerName | ||
| $server = New-AzSqlServer -ResourceGroupName $rg.ResourceGroupName -Location $location -ServerName $serverName -ServerVersion "12.0" -ExternalAdminName $entraAdmin -EnableActiveDirectoryOnlyAuthentication -AssignIdentity | ||
|
|
||
| $umiObjectId = "89cdfc0e-3f82-4e08-86d4-d0092eb4cd6e" |
There was a problem hiding this comment.
The hardcoded UMI object ID "89cdfc0e-3f82-4e08-86d4-d0092eb4cd6e" may not exist in all test environments, which will cause test failures. This setup function is used by multiple tests and the hardcoded value makes it brittle. Consider either creating a UMI dynamically as part of the test setup, using an environment variable, or documenting that this specific UMI must exist in the test subscription for these tests to work.
| $umiObjectId = "89cdfc0e-3f82-4e08-86d4-d0092eb4cd6e" | |
| # Use a configurable UMI object ID if provided; fall back to the legacy default for | |
| # compatibility with existing test environments. | |
| if ($env:AZURE_SQL_TDE_UMI_OBJECT_ID -and $env:AZURE_SQL_TDE_UMI_OBJECT_ID.Trim()) { | |
| $umiObjectId = $env:AZURE_SQL_TDE_UMI_OBJECT_ID.Trim() | |
| } | |
| else { | |
| $umiObjectId = "89cdfc0e-3f82-4e08-86d4-d0092eb4cd6e" | |
| } |
| function SetupDynamicTestEnvironmentForDatabaseLevelTDECMKScenariosAndReturnParameters ($location = "eastus2euap") | ||
| { | ||
| $rg = Create-ResourceGroupForTest | ||
| $entraAdmin=$env:USERNAME+"@microsoft.com" |
There was a problem hiding this comment.
The Entra admin email is constructed using $env:USERNAME+"@microsoft.com", which assumes the test is running in a Microsoft environment. This will fail in non-Microsoft environments or when USERNAME doesn't correspond to a valid Microsoft email address. This makes the test environment setup brittle and not portable. Consider using a test account, environment variable, or parameter to specify the admin email, or document that these tests require a specific Microsoft test environment.
| $versionlessKeyId = $akvKey.Id.Substring(0, $akvKey.Id.LastIndexOf('/')) | ||
| $keyResult = Add-AzSqlServerKeyVaultKey -ServerName $server.ServerName -ResourceGroupName $rg.ResourceGroupName -KeyId $versionlessKeyId | ||
| Assert-NotNull $keyResult.Uri | ||
| Assert-AreNotEqual $versionlessKeyId $keyResult.Uri # The key added is versionless, but the result should have the full key url |
There was a problem hiding this comment.
The comment states "The key added is versionless, but the result should have the full key url". This is confusing because it suggests the API automatically resolves a versionless key ID to a versioned one. However, the assertion Assert-AreNotEqual $versionlessKeyId $keyResult.Uri doesn't clearly validate this behavior - it only checks they're different, not that the result is a properly versioned URL. Consider adding a more specific assertion that validates the result URI has a version component (e.g., checking that it matches the pattern with a version, or extracting and validating the version exists).
| Assert-AreNotEqual $versionlessKeyId $keyResult.Uri # The key added is versionless, but the result should have the full key url | |
| # The key added is versionless, but the result should have the full key url (including a version segment) | |
| Assert-AreNotEqual $versionlessKeyId $keyResult.Uri | |
| $fullKeyUri = [string]$keyResult.Uri | |
| $expectedPrefix = $versionlessKeyId + '/' | |
| Assert-True ($fullKeyUri.StartsWith($expectedPrefix)) "Expected key URI to start with versionless key id followed by a slash." | |
| $versionPart = $fullKeyUri.Substring($expectedPrefix.Length) | |
| Assert-True (-not [string]::IsNullOrEmpty($versionPart)) "Expected key URI to contain a non-empty version segment." |
| # Verify the protector is using the versionless key | ||
| $encProtector2 = Get-AzSqlServerTransparentDataEncryptionProtector -ResourceGroupName $rg.ResourceGroupName -ServerName $server.ServerName | ||
| Assert-AreEqual AzureKeyVault $encProtector2.Type | ||
| Assert-AreEqual $encProtector.ServerKeyVaultKeyName $encProtector2.ServerKeyVaultKeyName |
There was a problem hiding this comment.
The test doesn't validate that the ServerKeyVaultKeyName follows the expected format for versionless keys. According to the code changes in TdeKeyHelper.cs, versionless keys should have the format "vault_keyname" (without version), while versioned keys have "vault_keyname_version". Consider adding an assertion to verify the ServerKeyVaultKeyName doesn't contain a version component, or matches the expected versionless pattern.
|
|
||
| // Validate that the url is a keyvault url and has a key and version | ||
| Regex r = new Regex(@"https://(.)+\.(managedhsm.azure.net|managedhsm-preview.azure.net|vault.azure.net|vault-int.azure-int.net|vault.azure.cn|managedhsm.azure.cn|vault.usgovcloudapi.net|managedhsm.usgovcloudapi.net|vault.microsoftazure.de|managedhsm.microsoftazure.de|vault.cloudapi.eaglex.ic.gov|vault.cloudapi.microsoft.scloud)(:443)?\/keys/[^\/]+\/[0-9a-zA-Z]+$", RegexOptions.IgnoreCase); | ||
| Regex r = new Regex(@"^https://(?!.*\.\.)[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]\.(managedhsm.azure.net|managedhsm-preview.azure.net|vault.azure.net|vault-int.azure-int.net|vault.azure.cn|managedhsm.azure.cn|vault.usgovcloudapi.net|managedhsm.usgovcloudapi.net|vault.microsoftazure.de|managedhsm.microsoftazure.de|vault.cloudapi.eaglex.ic.gov|vault.cloudapi.microsoft.scloud|mdep.azure.net)(:443)?\/keys/[^\/]+(\/[0-9a-zA-Z]+|\/|)$", RegexOptions.IgnoreCase); |
There was a problem hiding this comment.
The regex adds a new domain mdep.azure.net to the list of valid Key Vault/Managed HSM domains. While this may be a legitimate new Azure service domain, there's no explanation in the PR description or comments about why this domain was added. Consider adding a comment explaining what this domain is for (e.g., "mdep.azure.net is for [specific Azure service/environment]") or verifying this is an officially supported Key Vault domain.
|
|
||
| [Fact] | ||
| [Trait(Category.RunType, Category.LiveOnly)] | ||
| public void TestAddVersionlessServerKeyVaultKey() | ||
| { | ||
| TestRunner.RunTestScript("Test-AddVersionlessServerKeyVaultKey"); | ||
| } |
There was a problem hiding this comment.
The test function Test-AddVersionlessServerKeyVaultKey is referenced in the test runner but does not exist in the ServerKeyVaultKeyTests.ps1 file. This will cause the test to fail at runtime with a "command not found" error. Please add the missing test function implementation to ServerKeyVaultKeyTests.ps1, or remove the test runner entry if the test is not needed.
| [Fact] | |
| [Trait(Category.RunType, Category.LiveOnly)] | |
| public void TestAddVersionlessServerKeyVaultKey() | |
| { | |
| TestRunner.RunTestScript("Test-AddVersionlessServerKeyVaultKey"); | |
| } |
| $server = $params.server | ||
| $akvKey = $params.akvKey | ||
| $keyVault = $params.keyVault | ||
| $umi = "/subscriptions/e64f3e8e-ab91-4a65-8cdd-5cd2f47d00b4/resourceGroups/viparek/providers/Microsoft.ManagedIdentity/userAssignedIdentities/pstestumi" |
There was a problem hiding this comment.
Hardcoded subscription ID and resource group name are being used for the User Managed Identity (UMI) resource ID. This will cause the test to fail if run in a different subscription or if the hardcoded resource group/UMI doesn't exist. This appears to be test-only code, but using dynamic values or parameterized test environment would make the test more portable and maintainable. Consider using environment variables, test fixtures, or creating the UMI dynamically as part of the test setup.
| $umi = "/subscriptions/e64f3e8e-ab91-4a65-8cdd-5cd2f47d00b4/resourceGroups/viparek/providers/Microsoft.ManagedIdentity/userAssignedIdentities/pstestumi" | |
| $umi = $env:AZURE_SQL_TEST_UMI_RESOURCE_ID | |
| if (-not $umi) { | |
| throw "Environment variable 'AZURE_SQL_TEST_UMI_RESOURCE_ID' must be set to the resource ID of a User Assigned Managed Identity for Test-DatabaseUpdateWithPerDBCMK." | |
| } |
|
|
||
| Remove-ResourceGroupForTest $rg | ||
| $encryptionProtector2 = $akvKey2.Id.Replace(":443", "") | ||
| $versionlessKeyId = $encryptionProtector2.Substring(0, $encryptionProtector2.LastIndexOf('/')) |
There was a problem hiding this comment.
Using LastIndexOf('/') to create a versionless key ID could fail if the key URL has a trailing slash or other edge cases. This same pattern appears in multiple places. Consider creating a helper function or using a more robust URL parsing approach to extract the versionless key URL consistently across all tests.
| $versionlessKeyId = $encryptionProtector2.Substring(0, $encryptionProtector2.LastIndexOf('/')) | |
| $normalizedKeyId = $encryptionProtector2.TrimEnd('/') | |
| $versionlessKeyId = $normalizedKeyId.Substring(0, $normalizedKeyId.LastIndexOf('/')) |
|
|
||
| <# | ||
| .SYNOPSIS | ||
| Creates a test Environment for the TDE with AKV tests for server level TDE and returns the parameters of the created environment |
There was a problem hiding this comment.
The SYNOPSIS comment states "Creates a test Environment for the TDE with AKV tests for server level TDE" which is the same as the function above it (SetupDynamicTestEnvironmentForServerTDEScenariosAndReturnParameters). However, this function is specifically for database-level TDE CMK scenarios, not server level. The comment should accurately reflect this distinction to avoid confusion. Please update to clarify this is for database-level TDE scenarios.
| Creates a test Environment for the TDE with AKV tests for server level TDE and returns the parameters of the created environment | |
| Creates a test Environment for the TDE with AKV tests for database-level TDE using customer-managed keys (CMK) and returns the parameters of the created environment |
Description
This PR adds the support for versionless AKV keys for the server and database level TDE cmdlets.
Mandatory Checklist
Please choose the target release of Azure PowerShell. (⚠️ Target release is a different concept from API readiness. Please click below links for details.)
Check this box to confirm: I have read the Submitting Changes section of
CONTRIBUTING.mdand reviewed the following information:ChangeLog.mdfile(s) appropriatelysrc/{{SERVICE}}/{{SERVICE}}/ChangeLog.md.## Upcoming Releaseheader in the past tense.ChangeLog.mdif no new release is required, such as fixing test case only.