Skip to content

[SQL] Add support for versionless TDE keys#29159

Open
viparek wants to merge 4 commits intoAzure:mainfrom
viparek:viparek/versionlesskeys
Open

[SQL] Add support for versionless TDE keys#29159
viparek wants to merge 4 commits intoAzure:mainfrom
viparek:viparek/versionlesskeys

Conversation

@viparek
Copy link
Contributor

@viparek viparek commented Feb 10, 2026

Description

This PR adds the support for versionless AKV keys for the server and database level TDE cmdlets.

Mandatory Checklist

  • SHOULD update ChangeLog.md file(s) appropriately
    • Update src/{{SERVICE}}/{{SERVICE}}/ChangeLog.md.
      • A snippet outlining the change(s) made in the PR should be written under the ## Upcoming Release header in the past tense.
    • Should not change ChangeLog.md if no new release is required, such as fixing test case only.
  • SHOULD regenerate markdown help files if there is cmdlet API change. Instruction
  • SHOULD have proper test coverage for changes in pull request.
  • SHOULD NOT adjust version of module manually in pull request

Copilot AI review requested due to automatic review settings February 10, 2026 06:18
@azure-client-tools-bot-prd
Copy link

Thanks for your contribution! The pull request validation has started. Please revisit this comment for updated status.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.CreateServerKeyNameFromKeyId to 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.

Comment on lines 106 to 107
$keyVaultName = "akv+$Get-Random"
$keyName = "serverkey+$Get-Random"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

$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.

Suggested change
$keyVaultName = "akv+$Get-Random"
$keyName = "serverkey+$Get-Random"
$randomSuffix = Get-Random -Maximum 1000000
$keyVaultName = "akv$randomSuffix"
$keyName = "serverkey$randomSuffix"

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 116
$rg = Create-ResourceGroupForTest
$serverName = Get-ServerName
$credentials = Get-ServerCredential

$server = New-AzSqlServer -ResourceGroupName $rg.ResourceGroupName -ServerName $serverName -Location $location -SqlAdministratorCredentials $credentials -AssignIdentity
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@isra-fel
Copy link
Member

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

@isra-fel
Copy link
Member

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copilot AI review requested due to automatic review settings February 13, 2026 03:58
@viparek viparek marked this pull request as ready for review February 13, 2026 04:02
@viparek
Copy link
Contributor Author

viparek commented Feb 13, 2026

/azp run

@azure-pipelines
Copy link
Contributor

Commenter does not have sufficient privileges for PR 29159 in repo Azure/azure-powershell

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 11 changed files in this pull request and generated 19 comments.


// 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);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The regex pattern has a potential issue. The pattern (\/[0-9a-zA-Z]+|\/|)$ at the end allows three cases:

  1. /version (versioned key)
  2. / (trailing slash only)
  3. 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.

Copilot uses AI. Check for mistakes.
$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"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
$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"
}

Copilot uses AI. Check for mistakes.
function SetupDynamicTestEnvironmentForDatabaseLevelTDECMKScenariosAndReturnParameters ($location = "eastus2euap")
{
$rg = Create-ResourceGroupForTest
$entraAdmin=$env:USERNAME+"@microsoft.com"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
$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
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
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."

Copilot uses AI. Check for mistakes.
# 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
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.

// 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);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +58

[Fact]
[Trait(Category.RunType, Category.LiveOnly)]
public void TestAddVersionlessServerKeyVaultKey()
{
TestRunner.RunTestScript("Test-AddVersionlessServerKeyVaultKey");
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
[Fact]
[Trait(Category.RunType, Category.LiveOnly)]
public void TestAddVersionlessServerKeyVaultKey()
{
TestRunner.RunTestScript("Test-AddVersionlessServerKeyVaultKey");
}

Copilot uses AI. Check for mistakes.
$server = $params.server
$akvKey = $params.akvKey
$keyVault = $params.keyVault
$umi = "/subscriptions/e64f3e8e-ab91-4a65-8cdd-5cd2f47d00b4/resourceGroups/viparek/providers/Microsoft.ManagedIdentity/userAssignedIdentities/pstestumi"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
$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."
}

Copilot uses AI. Check for mistakes.

Remove-ResourceGroupForTest $rg
$encryptionProtector2 = $akvKey2.Id.Replace(":443", "")
$versionlessKeyId = $encryptionProtector2.Substring(0, $encryptionProtector2.LastIndexOf('/'))
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
$versionlessKeyId = $encryptionProtector2.Substring(0, $encryptionProtector2.LastIndexOf('/'))
$normalizedKeyId = $encryptionProtector2.TrimEnd('/')
$versionlessKeyId = $normalizedKeyId.Substring(0, $normalizedKeyId.LastIndexOf('/'))

Copilot uses AI. Check for mistakes.

<#
.SYNOPSIS
Creates a test Environment for the TDE with AKV tests for server level TDE and returns the parameters of the created environment
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants