Skip to content

Commit bef9f3f

Browse files
committed
[AKS] Implement platform-managed-keys (PMK) awared validation for KMS customer-managed-key (CMK)
1 parent c58fd97 commit bef9f3f

File tree

5 files changed

+1964
-16
lines changed

5 files changed

+1964
-16
lines changed

src/aks-preview/azext_aks_preview/_validators.py

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
RequiredArgumentMissingError)
3232
from azure.cli.core.commands.validators import validate_tag
3333
from azure.cli.core.util import CLIError
34-
from azure.mgmt.core.tools import is_valid_resource_id
34+
from azure.mgmt.core.tools import is_valid_resource_id, parse_resource_id
3535
from knack.log import get_logger
3636

3737
logger = get_logger(__name__)
@@ -701,26 +701,100 @@ def validate_crg_id(namespace):
701701
def validate_azure_keyvault_kms_key_id(namespace):
702702
key_id = namespace.azure_keyvault_kms_key_id
703703
if key_id:
704-
err_msg = (
705-
"--azure-keyvault-kms-key-id is not a valid Key Vault key ID. "
706-
"See https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name" # pylint: disable=line-too-long
704+
# Check if PMK (Platform-Managed Keys) is enabled
705+
is_pmk_enabled = (
706+
hasattr(namespace, 'kms_infrastructure_encryption') and
707+
namespace.kms_infrastructure_encryption == "Enabled"
707708
)
708709

709710
https_prefix = "https://"
710711
if not key_id.startswith(https_prefix):
712+
err_msg = (
713+
"--azure-keyvault-kms-key-id is not a valid Key Vault key ID. "
714+
"See https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name" # pylint: disable=line-too-long
715+
)
711716
raise InvalidArgumentValueError(err_msg)
712717

713718
segments = key_id[len(https_prefix):].split("/")
714-
if len(segments) != 4 or segments[1] != "keys":
715-
raise InvalidArgumentValueError(err_msg)
719+
720+
if is_pmk_enabled:
721+
# PMK enabled (K2P): Only accept versionless key ID (3 segments: vault.net/keys/key-name)
722+
if len(segments) != 3 or segments[1] != "keys":
723+
err_msg = (
724+
"--azure-keyvault-kms-key-id is not a valid versionless Key Vault key ID for PMK. "
725+
"Valid format is https://{key-vault-url}/keys/{key-name}. "
726+
"See https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name" # pylint: disable=line-too-long
727+
)
728+
raise InvalidArgumentValueError(err_msg)
729+
else:
730+
# PMK disabled (KMS v2): Accept versioned key ID (4 segments)
731+
if len(segments) != 4 or segments[1] != "keys":
732+
err_msg = (
733+
"--azure-keyvault-kms-key-id is not a valid Key Vault key ID. "
734+
"See https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name" # pylint: disable=line-too-long
735+
)
736+
raise InvalidArgumentValueError(err_msg)
716737

717738

718739
def validate_azure_keyvault_kms_key_vault_resource_id(namespace):
719740
key_vault_resource_id = namespace.azure_keyvault_kms_key_vault_resource_id
720-
if key_vault_resource_id is None or key_vault_resource_id == '':
741+
742+
# Check if PMK (Platform-Managed Keys) is enabled
743+
is_pmk_enabled = (
744+
hasattr(namespace, 'kms_infrastructure_encryption') and
745+
namespace.kms_infrastructure_encryption == "Enabled"
746+
)
747+
748+
# Check if CMK (Customer-Managed Keys) is enabled
749+
is_cmk_enabled = (
750+
hasattr(namespace, 'enable_azure_keyvault_kms') and
751+
namespace.enable_azure_keyvault_kms
752+
)
753+
754+
# Validate key vault resource ID requirements
755+
key_vault_missing = key_vault_resource_id is None or key_vault_resource_id == ''
756+
757+
if not is_cmk_enabled:
758+
if key_vault_missing:
759+
return
760+
raise RequiredArgumentMissingError(
761+
'"--azure-keyvault-kms-key-vault-resource-id" requires "--enable-azure-keyvault-kms".'
762+
)
763+
764+
if key_vault_missing:
765+
if is_pmk_enabled:
766+
raise RequiredArgumentMissingError(
767+
"--azure-keyvault-kms-key-vault-resource-id is required when "
768+
"--kms-infrastructure-encryption is set to Enabled (PMK)."
769+
)
721770
return
771+
722772
if not is_valid_resource_id(key_vault_resource_id):
723-
raise InvalidArgumentValueError("--azure-keyvault-kms-key-vault-resource-id is not a valid Azure resource ID.")
773+
raise InvalidArgumentValueError(
774+
"--azure-keyvault-kms-key-vault-resource-id is not a valid Azure resource ID."
775+
)
776+
777+
try:
778+
parsed = parse_resource_id(key_vault_resource_id)
779+
provider = parsed.get('namespace', '').lower()
780+
if provider != 'microsoft.keyvault':
781+
raise InvalidArgumentValueError(
782+
"--azure-keyvault-kms-key-vault-resource-id must reference a "
783+
"Microsoft.KeyVault resource."
784+
)
785+
resource_type = parsed.get('type', '').lower()
786+
if resource_type not in ['vaults', 'managedhsms']:
787+
raise InvalidArgumentValueError(
788+
"--azure-keyvault-kms-key-vault-resource-id must reference a Key Vault "
789+
"(vaults) or Managed HSM (managedHSMs)."
790+
)
791+
except InvalidArgumentValueError:
792+
# Re-raise our validation errors
793+
raise
794+
except Exception as ex:
795+
raise InvalidArgumentValueError(
796+
f"--azure-keyvault-kms-key-vault-resource-id parsing failed: {str(ex)}"
797+
)
724798

725799

726800
def validate_bootstrap_container_registry_resource_id(namespace):

src/aks-preview/azext_aks_preview/managed_cluster_decorator.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,29 @@ def get_kms_infrastructure_encryption(self) -> str:
12881288

12891289
return kms_infrastructure_encryption
12901290

1291+
def get_azure_keyvault_kms_key_vault_resource_id(self) -> Union[str, None]:
1292+
"""Obtain the value of azure_keyvault_kms_key_vault_resource_id.
1293+
1294+
:return: bool
1295+
"""
1296+
# read the original value passed by the command
1297+
azure_keyvault_kms_key_vault_resource_id = self.raw_param.get(
1298+
"azure_keyvault_kms_key_vault_resource_id"
1299+
)
1300+
if self.decorator_mode == DecoratorMode.CREATE:
1301+
if (
1302+
self.mc and
1303+
hasattr(self.mc, "security_profile") and # backward compatibility
1304+
self.mc.security_profile and
1305+
self.mc.security_profile.azure_key_vault_kms and
1306+
self.mc.security_profile.azure_key_vault_kms.key_vault_resource_id is not None
1307+
):
1308+
azure_keyvault_kms_key_vault_resource_id = (
1309+
self.mc.security_profile.azure_key_vault_kms.key_vault_resource_id
1310+
)
1311+
1312+
return azure_keyvault_kms_key_vault_resource_id
1313+
12911314
def get_cluster_snapshot_id(self) -> Union[str, None]:
12921315
"""Obtain the values of cluster_snapshot_id.
12931316
@@ -3313,8 +3336,9 @@ def set_up_image_integrity(self, mc: ManagedCluster) -> ManagedCluster:
33133336

33143337
return mc
33153338

3316-
def set_up_kms_infrastructure_encryption(self, mc: ManagedCluster) -> ManagedCluster:
3317-
"""Set up security profile KubernetesResourceObjectEncryptionProfile for the ManagedCluster object.
3339+
def set_up_kms_pmk_and_cmk(self, mc: ManagedCluster) -> ManagedCluster:
3340+
"""Set up security profile KubernetesResourceObjectEncryptionProfile and AzureKeyVaultKms for
3341+
the ManagedCluster object.
33183342
33193343
:return: the ManagedCluster object
33203344
"""
@@ -3335,6 +3359,17 @@ def set_up_kms_infrastructure_encryption(self, mc: ManagedCluster) -> ManagedClu
33353359
# pylint: disable=line-too-long
33363360
mc.security_profile.kubernetes_resource_object_encryption_profile.infrastructure_encryption = kms_infrastructure_encryption
33373361

3362+
if self.context.get_enable_azure_keyvault_kms():
3363+
key_id = self.context.get_azure_keyvault_kms_key_id()
3364+
if key_id:
3365+
if mc.security_profile is None:
3366+
mc.security_profile = self.models.ManagedClusterSecurityProfile()
3367+
mc.security_profile.azure_key_vault_kms = self.models.AzureKeyVaultKms(
3368+
enabled=True,
3369+
key_id=key_id,
3370+
key_vault_resource_id=self.context.get_azure_keyvault_kms_key_vault_resource_id(),
3371+
)
3372+
33383373
return mc
33393374

33403375
def set_up_creationdata_of_cluster_snapshot(self, mc: ManagedCluster) -> ManagedCluster:
@@ -3906,7 +3941,7 @@ def construct_mc_profile_preview(self, bypass_restore_defaults: bool = False) ->
39063941
# set up image integrity
39073942
mc = self.set_up_image_integrity(mc)
39083943
# set up KMS infrastructure encryption
3909-
mc = self.set_up_kms_infrastructure_encryption(mc)
3944+
mc = self.set_up_kms_pmk_and_cmk(mc)
39103945
# set up cluster snapshot
39113946
mc = self.set_up_creationdata_of_cluster_snapshot(mc)
39123947
# set up app routing profile

0 commit comments

Comments
 (0)