Repository: openshift/hypershift
Pull request #6048: CNTRLPLANE-111: Support KMSv2 encryption encryption for ARO HCP, with MIv3
Author: login: bryan-cox
Labels: name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: lgtm
description: Indicates that a PR is ready to be merged.
name: jira/valid-reference
description: Indicates that this PR references a valid Jira ticket of any type.
name: area/control-plane-operator
description: Indicates the PR includes changes for the control plane operator - in an OCP release
name: area/hypershift-operator
description: Indicates the PR includes changes for the hypershift operator and API - outside an OCP release
name: area/documentation
description: Indicates the PR includes changes for documentation
name: area/cli
description: Indicates the PR includes changes for CLI
name: area/testing
description: Indicates the PR includes changes for e2e testing
Created: 2025-04-17T11:25:56Z | Merged: 2025-04-24T18:44:12Z

**What this PR does / why we need it**:
This PR:
- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
- reorders some of the Azure how-to pages in our docs
- adds a how-to section on setting up and verifying etcd encryption

**Which issue(s) this PR fixes**:
Fixes [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111)

**Notes to Reviewers**:
- The first commit contains the actual code changes. The other commits are docs related.
- CacheSize: ptr.To\[int32\]\(100\), was removed because it is not valid for v2

**Checklist**
- [x] Subject and description added to both, commit and PR.
- [x] Relevant issues have been referenced.
- [x] This change includes docs. 
- [ ] This change includes unit tests.


Diff:
diff --git a/contrib/managed-azure/setup_etcd_kv.sh b/contrib/managed-azure/setup_etcd_kv.sh
new file mode 100644
index 00000000000..31464a112c0
--- /dev/null
+++ b/contrib/managed-azure/setup_etcd_kv.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -x
+
+# Prerequisites: define these constants
+LOCATION="eastus"
+KV_RG_NAME="example-kms"
+KV_NAME="example-key-vault"
+KEY_NAME="example-key"
+SUBSCRIPTION_ID="<your-subscription-id-here>"
+
+# This is the object ID of the KMS Managed Identity. This object ID can be found under the enterprise application for
+# your KMS Managed Identity.
+USER_OBJECT_ID="<fill-me-out>"
+
+# This is the object ID of the KMS Managed Identity. This object ID can be found under the enterprise application for
+# your KMS Managed Identity.
+OBJECT_ID="<fill-me-out>"
+
+# Create a resource group to hold the Key Vault
+az group create --name $KV_RG_NAME --location $LOCATION
+
+# Create the Key Vault
+az keyvault create --name $KV_NAME --resource-group $KV_RG_NAME --location $LOCATION --enable-rbac-authorization
+
+## Associate your SP/Account with the Key Vault; this is so you can use your SP/Account can use the CLI to create the key in the key vault
+az role assignment create \
+    --assignee ${USER_OBJECT_ID} \
+    --scope /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/$KV_RG_NAME/providers/Microsoft.KeyVault/vaults/${KV_NAME} \
+    --role "Key Vault Administrator"
+
+#Create a key in the Key Vault
+export KEY_ID=$(az keyvault key create --vault-name $KV_NAME --name $KEY_NAME --protection software --kty RSA --query key.kid -o tsv)
+
+# Assign the Key Vault Crypto User role to the KMS Managed Identity
+az role assignment create --assignee $OBJECT_ID --role "Key Vault Crypto User" --scope "$(az keyvault show --name $KV_NAME --query "resourceGroup" -o tsv | xargs -I{} az group show --name {} --query "id" -o tsv)"
+
+set +x
+
+set +x
\ No newline at end of file
diff --git a/control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go b/control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
index b42f4b8e7e3..f7b98aeb5ac 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
@@ -3261,28 +3261,12 @@ func (r *HostedControlPlaneReconciler) reconcileKubeAPIServer(ctx context.Contex
 			if hcp.Spec.SecretEncryption.KMS == nil {
 				return fmt.Errorf("kms metadata not specified")
 			}
-			if _, err := createOrUpdate(ctx, r, encryptionConfigFile, func() error {
-				return kas.ReconcileKMSEncryptionConfig(encryptionConfigFile, p.OwnerRef, hcp.Spec.SecretEncryption.KMS)
-			}); err != nil {
-				return fmt.Errorf("failed to reconcile kms encryption config secret: %w", err)
-			}
 
 			if _, err := createOrUpdate(ctx, r, encryptionConfigFile, func() error {
 				return kas.ReconcileKMSEncryptionConfig(encryptionConfigFile, p.OwnerRef, hcp.Spec.SecretEncryption.KMS)
 			}); err != nil {
 				return fmt.Errorf("failed to reconcile kms encryption config secret: %w", err)
 			}
-
-			if hyperazureutil.IsAroHCP() {
-				// Reconcile the SecretProviderClass
-				kmsSecretProviderClass := manifests.ManagedAzureSecretProviderClass(config.ManagedAzureKMSSecretProviderClassName, hcp.Namespace)
-				if _, err := createOrUpdate(ctx, r, kmsSecretProviderClass, func() error {
-					secretproviderclass.ReconcileManagedAzureSecretProviderClass(kmsSecretProviderClass, hcp, hcp.Spec.SecretEncryption.KMS.Azure.KMS, true)
-					return nil
-				}); err != nil {
-					return fmt.Errorf("failed to reconcile KMS SecretProviderClass: %w", err)
-				}
-			}
 		}
 	}
 
@@ -5650,7 +5634,7 @@ func (r *HostedControlPlaneReconciler) validateAzureKMSConfig(ctx context.Contex
 	azureKmsSpec := hcp.Spec.SecretEncryption.KMS.Azure
 
 	// Retrieve the KMS UserAssignedCredentials path
-	credentialsPath := config.ManagedAzureCertificatePath + hcp.Spec.SecretEncryption.KMS.Azure.KMS.CredentialsSecretName
+	credentialsPath := config.ManagedAzureCredentialsPathForKMS + hcp.Spec.SecretEncryption.KMS.Azure.KMS.CredentialsSecretName
 	cred, err := dataplane.NewUserAssignedIdentityCredential(ctx, credentialsPath, dataplane.WithClientOpts(azcore.ClientOptions{Cloud: cloud.AzurePublic}))
 	if err != nil {
 		conditions.SetFalseCondition(hcp, hyperv1.ValidAzureKMSConfig, hyperv1.InvalidAzureCredentialsReason,
diff --git a/control-plane-operator/controllers/hostedcontrolplane/kas/deployment.go b/control-plane-operator/controllers/hostedcontrolplane/kas/deployment.go
index 41e773c66da..29942aa8264 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/kas/deployment.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/kas/deployment.go
@@ -9,7 +9,6 @@ import (
 
 	hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/aws"
-	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/azure"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/common"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/manifests"
 	"github.com/openshift/hypershift/support/api"
@@ -271,7 +270,7 @@ func ReconcileKubeAPIServerDeployment(deployment *appsv1.Deployment,
 		applyPortieriesConfig(&deployment.Spec.Template.Spec, images.Portieris)
 	}
 	applyNamedCertificateMounts(namedCertificates, &deployment.Spec.Template.Spec)
-	applyCloudConfigVolumeMount(cloudProviderConfigRef, &deployment.Spec.Template.Spec, cloudProviderName)
+	applyCloudConfigVolumeMount(cloudProviderConfigRef, &deployment.Spec.Template.Spec)
 	util.ApplyCloudProviderCreds(&deployment.Spec.Template.Spec, cloudProviderName, cloudProviderCreds, images.TokenMinterImage, kasContainerMain().Name)
 
 	if cloudProviderName == aws.Provider {
@@ -725,22 +724,18 @@ func kasVolumeCloudConfig() *corev1.Volume {
 	}
 }
 
-func buildKASVolumeCloudConfig(configMapName, providerName string) func(v *corev1.Volume) {
+func buildKASVolumeCloudConfig(configMapName string) func(v *corev1.Volume) {
 	return func(v *corev1.Volume) {
-		if providerName == azure.Provider {
-			v.Secret = &corev1.SecretVolumeSource{SecretName: configMapName}
-		} else {
-			v.ConfigMap = &corev1.ConfigMapVolumeSource{
-				LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
-				DefaultMode:          ptr.To[int32](420),
-			}
+		v.ConfigMap = &corev1.ConfigMapVolumeSource{
+			LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
+			DefaultMode:          ptr.To[int32](420),
 		}
 	}
 }
 
-func applyCloudConfigVolumeMount(configRef *corev1.LocalObjectReference, podSpec *corev1.PodSpec, cloudProviderName string) {
+func applyCloudConfigVolumeMount(configRef *corev1.LocalObjectReference, podSpec *corev1.PodSpec) {
 	if configRef != nil && configRef.Name != "" {
-		podSpec.Volumes = append(podSpec.Volumes, util.BuildVolume(kasVolumeCloudConfig(), buildKASVolumeCloudConfig(configRef.Name, cloudProviderName)))
+		podSpec.Volumes = append(podSpec.Volumes, util.BuildVolume(kasVolumeCloudConfig(), buildKASVolumeCloudConfig(configRef.Name)))
 		var container *corev1.Container
 		for i, c := range podSpec.Containers {
 			if c.Name == kasContainerMain().Name {
diff --git a/control-plane-operator/controllers/hostedcontrolplane/kas/kms/azure.go b/control-plane-operator/controllers/hostedcontrolplane/kas/kms/azure.go
index a028118a37e..63e67c47143 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/kas/kms/azure.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/kas/kms/azure.go
@@ -15,7 +15,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
 	v1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
-	"k8s.io/utils/ptr"
 )
 
 const (
@@ -79,7 +78,6 @@ func (p *azureKMSProvider) GenerateKMSEncryptionConfig(apiVersion string) (*v1.E
 			Name:       fmt.Sprintf("%s-%s", azureProviderConfigNamePrefix, activeKeyHash),
 			APIVersion: apiVersion,
 			Endpoint:   azureActiveKMSUnixSocket,
-			CacheSize:  ptr.To[int32](100),
 			Timeout:    &metav1.Duration{Duration: 35 * time.Second},
 		},
 	})
@@ -93,7 +91,6 @@ func (p *azureKMSProvider) GenerateKMSEncryptionConfig(apiVersion string) (*v1.E
 				Name:       fmt.Sprintf("%s-%s", azureProviderConfigNamePrefix, backupKeyHash),
 				APIVersion: apiVersion,
 				Endpoint:   azureBackupKMSUnixSocket,
-				CacheSize:  ptr.To[int32](100),
 				Timeout:    &metav1.Duration{Duration: 35 * time.Second},
 			},
 		})
@@ -137,19 +134,17 @@ func (p *azureKMSProvider) ApplyKMSConfig(podSpec *corev1.PodSpec) error {
 		)
 	}
 
-	var container *corev1.Container
-	for i, c := range podSpec.Containers {
-		if c.Name == KasMainContainerName {
-			container = &podSpec.Containers[i]
-			break
-		}
-	}
+	container := util.FindContainer(KasMainContainerName, podSpec.Containers)
 	if container == nil {
 		panic("main kube apiserver container not found in spec")
 	}
 	container.VolumeMounts = append(container.VolumeMounts,
 		azureKMSVolumeMounts.ContainerMounts(KasMainContainerName)...)
 
+	container = util.FindContainer("azure-kms-provider-active", podSpec.Containers)
+	if container == nil {
+		panic("azure-kms-provider-active container not found in spec")
+	}
 	container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
 		Name:      config.ManagedAzureKMSSecretStoreVolumeName,
 		MountPath: config.ManagedAzureCertificateMountPath,
@@ -226,7 +221,7 @@ func kasVolumeAzureKMSCredentials() *corev1.Volume {
 
 func buildVolumeAzureKMSCredentials(v *corev1.Volume) {
 	v.Secret = &corev1.SecretVolumeSource{
-		SecretName: manifests.AzureProviderConfigWithCredentials("").Name,
+		SecretName: manifests.AzureKMSWithCredentials("").Name,
 		Items: []corev1.KeyToPath{
 			{
 				Key:  azure.CloudConfigKey,
diff --git a/control-plane-operator/controllers/hostedcontrolplane/kas/params.go b/control-plane-operator/controllers/hostedcontrolplane/kas/params.go
index 83a73004e9c..7997d0a5b2f 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/kas/params.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/kas/params.go
@@ -6,7 +6,6 @@ import (
 
 	hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/aws"
-	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/azure"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/imageprovider"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/manifests"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/pki"
@@ -298,12 +297,8 @@ func NewKubeAPIServerParams(ctx context.Context, hcp *hyperv1.HostedControlPlane
 		},
 	}
 
-	switch hcp.Spec.Platform.Type {
-	case hyperv1.AWSPlatform:
+	if hcp.Spec.Platform.Type == hyperv1.AWSPlatform {
 		params.CloudProvider = aws.Provider
-	case hyperv1.AzurePlatform:
-		params.CloudProvider = azure.Provider
-		params.CloudProviderConfig = &corev1.LocalObjectReference{Name: manifests.AzureProviderConfigWithCredentials("").Name}
 	}
 
 	if hcp.Spec.Platform.Type == hyperv1.IBMCloudPlatform {
diff --git a/control-plane-operator/controllers/hostedcontrolplane/kas/secretencryption.go b/control-plane-operator/controllers/hostedcontrolplane/kas/secretencryption.go
index 6fda04dc8b0..04a5b9e0d47 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/kas/secretencryption.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/kas/secretencryption.go
@@ -3,7 +3,7 @@ package kas
 import (
 	hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/manifests"
-	hcpconfig "github.com/openshift/hypershift/support/config"
+	"github.com/openshift/hypershift/support/config"
 
 	corev1 "k8s.io/api/core/v1"
 )
@@ -14,7 +14,7 @@ const (
 )
 
 func ReconcileKMSEncryptionConfig(config *corev1.Secret,
-	ownerRef hcpconfig.OwnerRef,
+	ownerRef config.OwnerRef,
 	encryptionSpec *hyperv1.KMSSpec,
 ) error {
 	ownerRef.ApplyTo(config)
@@ -32,7 +32,7 @@ func ReconcileKMSEncryptionConfig(config *corev1.Secret,
 }
 
 func ReconcileAESCBCEncryptionConfig(config *corev1.Secret,
-	ownerRef hcpconfig.OwnerRef,
+	ownerRef config.OwnerRef,
 	activeKey []byte,
 	backupKey []byte,
 ) error {
diff --git a/control-plane-operator/controllers/hostedcontrolplane/manifests/azure.go b/control-plane-operator/controllers/hostedcontrolplane/manifests/azure.go
index c45fdfbb70b..e2cef7d8058 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/manifests/azure.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/manifests/azure.go
@@ -25,6 +25,15 @@ func AzureProviderConfigWithCredentials(ns string) *corev1.Secret {
 	}
 }
 
+func AzureKMSWithCredentials(ns string) *corev1.Secret {
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "azure-kms-config",
+			Namespace: ns,
+		},
+	}
+}
+
 func AzureDiskConfigWithCredentials(ns string) *corev1.Secret {
 	return &corev1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
index baa9ba68550..d66bdce76a8 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
@@ -83,7 +83,6 @@ func (p *azureKMSProvider) GenerateKMSEncryptionConfig(apiVersion string) (*v1.E
 			Name:       fmt.Sprintf("%s-%s", azureProviderConfigNamePrefix, activeKeyHash),
 			APIVersion: apiVersion,
 			Endpoint:   azureActiveKMSUnixSocket,
-			CacheSize:  ptr.To[int32](100),
 			Timeout:    &metav1.Duration{Duration: 35 * time.Second},
 		},
 	})
@@ -97,7 +96,6 @@ func (p *azureKMSProvider) GenerateKMSEncryptionConfig(apiVersion string) (*v1.E
 				Name:       fmt.Sprintf("%s-%s", azureProviderConfigNamePrefix, backupKeyHash),
 				APIVersion: apiVersion,
 				Endpoint:   azureBackupKMSUnixSocket,
-				CacheSize:  ptr.To[int32](100),
 				Timeout:    &metav1.Duration{Duration: 35 * time.Second},
 			},
 		})
@@ -221,7 +219,7 @@ func kasVolumeAzureKMSCredentials() *corev1.Volume {
 
 func buildVolumeAzureKMSCredentials(v *corev1.Volume) {
 	v.Secret = &corev1.SecretVolumeSource{
-		SecretName: manifests.AzureProviderConfigWithCredentials("").Name,
+		SecretName: manifests.AzureKMSWithCredentials("").Name,
 		Items: []corev1.KeyToPath{
 			{
 				Key:  azure.CloudConfigKey,
diff --git a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/params.go b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/params.go
index 61003f9a7e0..e7953e9ff85 100644
--- a/control-plane-operator/controllers/hostedcontrolplane/v2/kas/params.go
+++ b/control-plane-operator/controllers/hostedcontrolplane/v2/kas/params.go
@@ -5,7 +5,6 @@ import (
 
 	hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/aws"
-	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/azure"
 	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/manifests"
 	"github.com/openshift/hypershift/support/capabilities"
 	"github.com/openshift/hypershift/support/config"
@@ -18,9 +17,7 @@ import (
 )
 
 const (
-	KonnectivityHealthPort      = 2041
 	KonnectivityServerLocalPort = 8090
-	KonnectivityServerPort      = 8091
 
 	defaultMaxRequestsInflight         = 3000
 	defaultMaxMutatingRequestsInflight = 1000
@@ -77,12 +74,8 @@ func NewConfigParams(hcp *hyperv1.HostedControlPlane, featureGates []string) Kub
 		DisableProfiling:             util.StringListContains(hcp.Annotations[hyperv1.DisableProfilingAnnotation], manifests.KASDeployment("").Name),
 	}
 
-	switch hcp.Spec.Platform.Type {
-	case hyperv1.AWSPlatform:
+	if hcp.Spec.Platform.Type == hyperv1.AWSPlatform {
 		kasConfig.CloudProvider = aws.Provider
-	case hyperv1.AzurePlatform:
-		kasConfig.CloudProvider = azure.Provider
-		// kasConfig.CloudProviderConfigRef = &corev1.LocalObjectReference{Name: manifests.AzureProviderConfigWithCredentials("").Name}
 	}
 
 	switch hcp.Spec.Etcd.ManagementType {
diff --git a/docs/content/how-to/azure/create-azure-cluster-with-options.md b/docs/content/how-to/azure/create-azure-cluster-with-options.md
index f4b4965bf24..37e0a8fa8fc 100644
--- a/docs/content/how-to/azure/create-azure-cluster-with-options.md
+++ b/docs/content/how-to/azure/create-azure-cluster-with-options.md
@@ -1,10 +1,10 @@
 # Create an Azure cluster with Additional Options
-This document describes how to set up an Azure cluster with Hypershift with additional flag options.
+This document describes how to set up an Azure cluster with Hypershift with additional options.
 
 Creating an Azure cluster with Hypershift without any additional flag options can be found [here](create-azure-cluster_on_aks.md).
 
 ## Prerequisites
-This assumes you are:
+All sections assume you are:
 1. Using an AKS management cluster
 2. Set up external DNS
 3. Installed the HyperShift Operator
@@ -23,86 +23,6 @@ There are a few prerequisites for encrypting the OS disks on the Azure VMs:
 
 After performing these steps, you just need to provide the DiskEncryptionSet ID when creating a hosted cluster.
 
-## Creating Service Principals for Managed Identities
-Pre-requisites:
-1. Key Vault Administrator role on the Key Vault
-
-
-### Define variables for service principal names
-```
-AZURE_DISK_SP_NAME=<azure-disk-sp-name>
-AZURE_FILE_SP_NAME=<azure-file-sp-name>
-NODEPOOL_MGMT=<nodepool-mgmt-sp-name>
-CLOUD_PROVIDER_SP_NAME=<cloud-provider-sp-name>
-CNCC_NAME=<cncc-sp-name>
-CONTROL_PLANE_SP_NAME=<cpo-sp-name>
-IMAGE_REGISTRY_SP_NAME=<ciro-sp-name>
-INGRESS_SP_NAME=<ingress-sp-name>
-KEY_VAULT_NAME=<name-of-precreated-key-vault>
-KEY_VAULT_TENANT_ID=<tenant-id-of-precreated-key-vault>
-```
-
-### Create service principals and capture app IDs
-```
-DISK_SP_APP_ID=$(az ad sp create-for-rbac --name "${AZURE_DISK_SP_NAME}" --create-cert --cert "${AZURE_DISK_SP_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-FILE_SP_APP_ID=$(az ad sp create-for-rbac --name "${AZURE_FILE_SP_NAME}" --create-cert --cert "${AZURE_FILE_SP_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-NODEPOOL_MGMT_APP_ID=$(az ad sp create-for-rbac --name "${NODEPOOL_MGMT}" --create-cert --cert "${NODEPOOL_MGMT}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-CLOUD_PROVIDER_APP_ID=$(az ad sp create-for-rbac --name "${CLOUD_PROVIDER_SP_NAME}" --create-cert --cert "${CLOUD_PROVIDER_SP_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-CNCC_APP_ID=$(az ad sp create-for-rbac --name "${CNCC_NAME}" --create-cert --cert "${CNCC_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-CONTROL_PLANE_APP_ID=$(az ad sp create-for-rbac --name "${CONTROL_PLANE_SP_NAME}" --create-cert --cert "${CONTROL_PLANE_SP_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-IMAGE_REGISTRY_APP_ID=$(az ad sp create-for-rbac --name "${IMAGE_REGISTRY_SP_NAME}" --create-cert --cert "${IMAGE_REGISTRY_SP_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-INGRESS_APP_ID=$(az ad sp create-for-rbac --name "${INGRESS_SP_NAME}" --create-cert --cert "${INGRESS_SP_NAME}" --keyvault ${KV_NAME} --output json --only-show-errors | jq '.appId' | sed 's/"//g')
-```
-
-### Save service principal IDs and certificate names to a JSON file
-```
-SP_FILE="service-principals.json"
-
-# Save service principal IDs and certificate names to a JSON file
-OUTPUT_FILE="service-principals.json"
-
-cat <<EOF > SP_FILE
-{
-    "cloudProvider": {
-        "certificateName": "${CLOUD_PROVIDER_SP_NAME}",
-        "clientID": "${CLOUD_PROVIDER_APP_ID}"
-    },
-    "controlPlaneOperator": {
-        "certificateName": "${CONTROL_PLANE_SP_NAME}",
-        "clientID": "${CONTROL_PLANE_APP_ID}"
-    },
-    "disk": {
-        "certificateName": "${AZURE_DISK_SP_NAME}",
-        "clientID": "${DISK_SP_APP_ID}"
-    },
-    "file": {
-        "certificateName": "${AZURE_FILE_SP_NAME}",
-        "clientID": "${FILE_SP_APP_ID}"
-    },
-    "imageRegistry": {
-        "certificateName": "${IMAGE_REGISTRY_SP_NAME}",
-        "clientID": "${IMAGE_REGISTRY_APP_ID}"
-    },
-    "ingress": {
-        "certificateName": "${INGRESS_SP_NAME}",
-        "clientID": "${INGRESS_APP_ID}"
-    },
-    "network": {
-        "certificateName": "${CNCC_NAME}",
-        "clientID": "${CNCC_APP_ID}"
-    },
-    "nodePoolManagement": {
-        "certificateName": "${NODEPOOL_MGMT}",
-        "clientID": "${NODEPOOL_MGMT_APP_ID}"
-    },
-    "managedIdentitiesKeyVault": {
-        "name": "${KV_NAME}",
-        "tenantID": "{KV_TENANT_ID}"
-    }
-}
-EOF
-```
-
 ### CLI Example
 ```
 ${HYPERSHIFT_BINARY_PATH}/hypershift create cluster azure \
@@ -225,3 +145,125 @@ hypershift create nodepool azure \
 --instance-type Standard_DS4_v2 \
 --disk-storage-account-type Standard_LRS
 ```
+
+## Enabling KMS encryption
+This section walks through how to:
+
+1. Set up a new resource group, key vault, and key for etcd encryption using KMSv2
+1. Set up the role assignment between the KMS managed identity (MI) and the key vault
+1. Set up the flags needed when creating the Azure HostedCluster
+1. Verify the etcd encryption is setup and working properly 
+
+There is a `setup_etcd_kv.sh` script in the contrib folder in the HyperShift repo to help automate the first couple of 
+steps mentioned above. However, this guide will manually walk through those steps.
+
+1a) Create a resource group for the key vault that will house the key used for etcd encryption. 
+
+!!! note
+
+    It is assumed this key vault is a different key vault, let's call it MI KV, than the one containing all of the 
+    managed identities for the control plane. However, the managed identity for KMS is assumed to be in the MI KV.
+
+```bash
+az group create --name example-kms --location eastus
+```
+
+1b) Create the etcd encryption key vault
+```bash
+az keyvault create --name example-kms --resource-group example-kms --location eastus --enable-rbac-authorization
+```
+
+1c) Create a key in the etcd encryption key vault and capture the ID in a variable, KEY_ID. This will be passed when 
+creating the Azure HostedCluster in a later step below.
+```bash
+KEY_ID=$(az keyvault key create \
+  --vault-name example-kms \
+  --name example-key \
+  --protection software \
+  --kty RSA \
+  --query key.kid \
+  -o tsv)
+```
+
+2) Create a role assignment between the KMS MI and the resource group where the etcd encryption key vault is located so 
+that it can encrypt & decrypt objects.
+
+```bash
+OBJECT_ID="the object ID of the KMS Managed Identity. This object ID can be found under the enterprise application for your KMS Managed Identity"
+
+az role assignment create --assignee $OBJECT_ID --role "Key Vault Crypto User" \
+--scope $(az keyvault show --name example-kms --query "resourceGroup" -o tsv | xargs -I{} az group show --name {} --query "id" -o tsv)
+```
+
+3) Add these flags to your HyperShift CLI command when creating the Azure HostedCluster. KEY_ID is from step 1d.
+```
+`--encryption-key-id $KEY_ID` \
+`--kms-credentials-secret-name <your KMS credentials secret name>`
+```
+
+4) Here are some different things you can do to confirm etcd encryption using KMSv2 is set up properly on the 
+HCP/HostedCluster:
+
+First, confirm the kube-apiserver pod is using the `encryption-provider-config` flag such as:
+```
+--encryption-provider-config=/etc/kubernetes/secret-encryption/config.yaml 
+```
+
+If you look at this data, it should contain something like this:
+```
+apiVersion: apiserver.config.k8s.io/v1
+kind: EncryptionConfiguration
+resources:
+- providers:
+  - kms:
+      apiVersion: v2
+      endpoint: unix:///opt/azurekmsactive.socket
+      name: azure-20514bc7
+      timeout: 35s
+  - identity: {}
+  resources:
+  - secrets
+  - configmaps
+  - routes.route.openshift.io
+  - oauthaccesstokens.oauth.openshift.io
+  - oauthauthorizetokens.oauth.openshift.io
+```
+
+Next, confirm the ` azure-kms-provider-active` container in the kube-apiserver pod is running properly, there are no 
+errors in the log, and the config file is using the KMS MI. The config file path can be found in the flag on the 
+container spec:
+```
+--config-file-path=/etc/kubernetes/azure.json 
+```
+
+If you review this data, you should see the KMS MI credentials secret used within it.
+
+Finally, you can create a secret on the HostedCluster and then check the secret on etcd in the etcd pod on the HCP 
+directly:
+
+1) Create a secret on the HostedCluster. Example `kubectl create secret generic kms-test --from-literal=foo=bar`.
+
+2) Verify you can see the secret contents on the HostedCluster unencrypted.
+
+3) Switch back to the AKS management cluster and exec into the etcd pod, `kubectl exec -it pod/etcd-0 -- /bin/bash`.
+
+4) Run these commands in the etcd pod
+```
+export ETCDCTL_API=3
+export ETCDCTL_CACERT=/etc/etcd/tls/etcd-ca/ca.crt
+export ETCDCTL_CERT=/etc/etcd/tls/client/etcd-client.crt
+export ETCDCTL_KEY=/etc/etcd/tls/client/etcd-client.key
+export ETCDCTL_ENDPOINTS=https://etcd-client:2379
+```
+5) Get the secret created on the HostedCluster `etcdctl get /kubernetes.io/secrets/default/kms-test`. You should see it 
+is encrypted with KMSv2 by the azure provider:
+```
+k8s:enc:kms:v2:azure-8298bce7:
+�d2%&G
+      	E��k(�B�	�H�����6#�]�[���I
+...
+�=�s��h���Fq��a^(��z ��wIȫ�ݹ��,�բRa�A홟��5u�΀��*��᥃��ƚL�$L1Y'�V�Ӧi��.�	R�"                                            �
+version.azure.akv.io1"&
+algorithm.azure.akv.io
+                      RSA-OAEP-256(
+```
diff --git a/docs/content/how-to/azure/scheduler.md b/docs/content/how-to/azure/scheduler.md
index bd8ef1c0a01..d48721efa50 100644
--- a/docs/content/how-to/azure/scheduler.md
+++ b/docs/content/how-to/azure/scheduler.md
@@ -1,4 +1,4 @@
-# Azure Scheduler
+# Using Azure Scheduler
 
 The Azure Scheduler works with the default `ClusterSizingConfiguration` resource and the `HostedClusterSizing` controller.
 
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index d6db286ec86..4abaf36cae8 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -100,8 +100,8 @@ nav:
         - how-to/aws/troubleshooting/debug-nodes.md
         - how-to/aws/troubleshooting/troubleshooting-disaster-recovery.md
   - 'Azure':
-    - how-to/azure/create-azure-cluster-with-options.md
     - how-to/azure/create-azure-cluster_on_aks.md
+    - how-to/azure/create-azure-cluster-with-options.md
     - how-to/azure/scheduler.md
     - 'Troubleshooting':
         - how-to/azure/troubleshooting/index.md
diff --git a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
index ad560f6056d..07464adab49 100644
--- a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
+++ b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
@@ -1935,6 +1935,18 @@ func (r *HostedClusterReconciler) reconcile(ctx context.Context, req ctrl.Reques
 		}); err != nil {
 			return ctrl.Result{}, fmt.Errorf("failed to reconcile nodepool management secret provider class: %w", err)
 		}
+
+		if hcluster.Spec.SecretEncryption.KMS != nil {
+			// Reconcile KMS SecretProviderClass CR
+			kmsSecretProviderClass := cpomanifests.ManagedAzureSecretProviderClass(config.ManagedAzureKMSSecretProviderClassName, hcp.Namespace)
+			if _, err := createOrUpdate(ctx, r, kmsSecretProviderClass, func() error {
+				secretproviderclass.ReconcileManagedAzureSecretProviderClass(kmsSecretProviderClass, hcp, hcp.Spec.SecretEncryption.KMS.Azure.KMS, hcVersion.GE(config.Version419))
+				return nil
+			}); err != nil {
+				return ctrl.Result{}, fmt.Errorf("failed to reconcile KMS SecretProviderClass: %w", err)
+			}
+
+		}
 	}
 
 	if err := r.reconcileKarpenterOperator(ctx, createOrUpdate, hcluster, hcp, r.HypershiftOperatorImage, controlPlaneOperatorImage); err != nil {
@@ -3043,7 +3055,7 @@ func reconcileControlPlaneOperatorDeployment(
 				})
 		}
 
-		// Mount the control plane operator's certificate from the managed Azure key vault. The CPO authenticates with
+		// Mount the control plane operator's credentials from the managed Azure key vault. The CPO authenticates with
 		// the Azure cloud API for validating resource group locations.
 		deployment.Spec.Template.Spec.Containers[0].VolumeMounts = append(deployment.Spec.Template.Spec.Containers[0].VolumeMounts,
 			azureutil.CreateVolumeMountForAzureSecretStoreProviderClass(config.ManagedAzureCPOSecretStoreVolumeName),
@@ -3051,6 +3063,16 @@ func reconcileControlPlaneOperatorDeployment(
 		deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes,
 			azureutil.CreateVolumeForAzureSecretStoreProviderClass(config.ManagedAzureCPOSecretStoreVolumeName, config.ManagedAzureCPOSecretProviderClassName),
 		)
+
+		// Mount the KMS credentials so the HCP reconciliation can validate the Azure KMS configuration.
+		if hcp.Spec.SecretEncryption.KMS != nil {
+			deployment.Spec.Template.Spec.Containers[0].VolumeMounts = append(deployment.Spec.Template.Spec.Containers[0].VolumeMounts,
+				azureutil.CreateVolumeMountForKMSAzureSecretStoreProviderClass(config.ManagedAzureKMSSecretStoreVolumeName),
+			)
+			deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes,
+				azureutil.CreateVolumeForAzureSecretStoreProviderClass(config.ManagedAzureKMSSecretStoreVolumeName, config.ManagedAzureKMSSecretProviderClassName),
+			)
+		}
 	}
 
 	if hcp.Spec.AdditionalTrustBundle != nil {
diff --git a/hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go b/hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
index d4b19aa7652..8cbda741bb9 100644
--- a/hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
+++ b/hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
@@ -2,11 +2,14 @@ package azure
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"os"
 	"strings"
 
 	hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
+	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/azure"
+	"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/manifests"
 	"github.com/openshift/hypershift/support/azureutil"
 	"github.com/openshift/hypershift/support/config"
 	"github.com/openshift/hypershift/support/images"
@@ -197,7 +200,14 @@ func (a Azure) ReconcileCredentials(ctx context.Context, c client.Client, create
 	return nil
 }
 
-func (a Azure) ReconcileSecretEncryption(ctx context.Context, c client.Client, createOrUpdate upsert.CreateOrUpdateFN, hcluster *hyperv1.HostedCluster, controlPlaneNamespace string) error {
+func (a Azure) ReconcileSecretEncryption(ctx context.Context, c client.Client, createOrUpdate upsert.CreateOrUpdateFN, hc *hyperv1.HostedCluster, controlPlaneNamespace string) error {
+	// Reconcile the Azure KMS Config Secret
+	azureKMSConfigSecret := manifests.AzureKMSWithCredentials(controlPlaneNamespace)
+	if _, err := createOrUpdate(ctx, c, azureKMSConfigSecret, func() error {
+		return reconcileKMSConfigSecret(azureKMSConfigSecret, hc)
+	}); err != nil {
+		return fmt.Errorf("failed to reconcile Azure KMS config secret: %w", err)
+	}
 	return nil
 }
 
@@ -298,3 +308,34 @@ func parseCloudType(cloudType string) (string, error) {
 		return "", fmt.Errorf("unsupported cloud type: %s", cloudType)
 	}
 }
+
+// reconcileKMSConfigSecret reconciles the data needed for the KMS configuration secret.
+func reconcileKMSConfigSecret(secret *corev1.Secret, hc *hyperv1.HostedCluster) error {
+	azureConfig := azure.AzureConfig{
+		Cloud:                        hc.Spec.Platform.Azure.Cloud,
+		TenantID:                     hc.Spec.Platform.Azure.TenantID,
+		UseManagedIdentityExtension:  false,
+		SubscriptionID:               hc.Spec.Platform.Azure.SubscriptionID,
+		ResourceGroup:                hc.Spec.Platform.Azure.ResourceGroupName,
+		Location:                     hc.Spec.Platform.Azure.Location,
+		LoadBalancerName:             hc.Spec.InfraID,
+		CloudProviderBackoff:         true,
+		CloudProviderBackoffDuration: 6,
+		UseInstanceMetadata:          false,
+		LoadBalancerSku:              "standard",
+		DisableOutboundSNAT:          true,
+		AADMSIDataPlaneIdentityPath:  config.ManagedAzureCertificatePath + hc.Spec.SecretEncryption.KMS.Azure.KMS.CredentialsSecretName,
+	}
+
+	serializedConfig, err := json.MarshalIndent(azureConfig, "", "  ")
+	if err != nil {
+		return fmt.Errorf("failed to serialize cloudconfig: %w", err)
+	}
+
+	if secret.Data == nil {
+		secret.Data = map[string][]byte{}
+	}
+	secret.Data[azure.CloudConfigKey] = serializedConfig
+
+	return nil
+}
diff --git a/support/azureutil/azureutil.go b/support/azureutil/azureutil.go
index 10e5651031a..1b3b9d203dc 100644
--- a/support/azureutil/azureutil.go
+++ b/support/azureutil/azureutil.go
@@ -207,6 +207,14 @@ func CreateVolumeMountForAzureSecretStoreProviderClass(secretStoreVolumeName str
 	}
 }
 
+func CreateVolumeMountForKMSAzureSecretStoreProviderClass(secretStoreVolumeName string) corev1.VolumeMount {
+	return corev1.VolumeMount{
+		Name:      secretStoreVolumeName,
+		MountPath: config.ManagedAzureCredentialsMountPathForKMS,
+		ReadOnly:  true,
+	}
+}
+
 func CreateVolumeForAzureSecretStoreProviderClass(secretStoreVolumeName, secretProviderClassName string) corev1.Volume {
 	return corev1.Volume{
 		Name: secretStoreVolumeName,
diff --git a/support/config/constants.go b/support/config/constants.go
index 9a4f6b28c8d..408a918e703 100644
--- a/support/config/constants.go
+++ b/support/config/constants.go
@@ -68,7 +68,9 @@ const (
 	ManagedAzureCertificatePathEnvVarKey     = "ARO_HCP_CLIENT_CERTIFICATE_PATH"
 	ManagedAzureSecretProviderClassEnvVarKey = "ARO_HCP_SECRET_PROVIDER_CLASS"
 	ManagedAzureCertificateMountPath         = "/mnt/certs"
+	ManagedAzureCredentialsMountPathForKMS   = "/mnt/kms"
 	ManagedAzureCertificatePath              = "/mnt/certs/"
+	ManagedAzureCredentialsPathForKMS        = "/mnt/kms/"
 	ManagedAzureSecretsStoreCSIDriver        = "secrets-store.csi.k8s.io"
 	ManagedAzureSecretProviderClass          = "secretProviderClass"
 


Reviews:
user: login: bryan-cox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T11:16:25Z
user: login: bryan-cox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T11:17:24Z
user: login: enxebre
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:24:47Z
user: login: enxebre
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:29:57Z
user: login: bryan-cox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T13:00:24Z
user: login: bryan-cox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T13:06:40Z

Review comments:
diff_hunk: @@ -79,7 +78,6 @@ func (p *azureKMSProvider) GenerateKMSEncryptionConfig(apiVersion string) (*v1.E
 			Name:       fmt.Sprintf("%s-%s", azureProviderConfigNamePrefix, activeKeyHash),
 			APIVersion: apiVersion,
 			Endpoint:   azureActiveKMSUnixSocket,
-			CacheSize:  ptr.To[int32](100),
path: control-plane-operator/controllers/hostedcontrolplane/kas/kms/azure.go
user: login: bryan-cox
body: CacheSize: ptr.To[int32], was removed because it is not valid for v2
created_at: 2025-04-22T11:16:25Z
author_association: MEMBER
diff_hunk: @@ -289,12 +288,8 @@ func NewKubeAPIServerParams(ctx context.Context, hcp *hyperv1.HostedControlPlane
 		},
 	}
 
-	switch hcp.Spec.Platform.Type {
-	case hyperv1.AWSPlatform:
+	if hcp.Spec.Platform.Type == hyperv1.AWSPlatform {
 		params.CloudProvider = aws.Provider
-	case hyperv1.AzurePlatform:
-		params.CloudProvider = azure.Provider
path: control-plane-operator/controllers/hostedcontrolplane/kas/params.go
user: login: bryan-cox
body: This doesn't need set for Azure - https://cloud-provider-azure.sigs.k8s.io/install/azure-ccm/#kube-apiserver
created_at: 2025-04-22T11:17:24Z
author_association: MEMBER
diff_hunk: @@ -3178,13 +3173,12 @@ func (r *HostedControlPlaneReconciler) reconcileKubeAPIServer(ctx context.Contex
 			}
 
 			if hyperazureutil.IsAroHCP() {
-				// Reconcile the SecretProviderClass
-				kmsSecretProviderClass := manifests.ManagedAzureSecretProviderClass(config.ManagedAzureKMSSecretProviderClassName, hcp.Namespace)
-				if _, err := createOrUpdate(ctx, r, kmsSecretProviderClass, func() error {
-					secretproviderclass.ReconcileManagedAzureSecretProviderClass(kmsSecretProviderClass, hcp, hcp.Spec.SecretEncryption.KMS.Azure.KMS, true)
-					return nil
+				// Reconcile the Azure KMS Config Secret
path: control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
user: login: enxebre
body: should this be part of hc/internal/platform ReconcileCredentials for consistency with aws? is the cloudNetworkConfigCreds there still needed?
Alternative should we make this part of the kms.IKMSProvider interface?
created_at: 2025-04-23T08:24:47Z
author_association: MEMBER
diff_hunk: @@ -221,7 +219,7 @@ func kasVolumeAzureKMSCredentials() *corev1.Volume {
 
 func buildVolumeAzureKMSCredentials(v *corev1.Volume) {
 	v.Secret = &corev1.SecretVolumeSource{
-		SecretName: manifests.AzureProviderConfigWithCredentials("").Name,
+		SecretName: manifests.AzureKMSWithCredentials("").Name,
path: control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
user: login: enxebre
body: where's the secret being reconciled for cpov2?
created_at: 2025-04-23T08:29:57Z
author_association: MEMBER
diff_hunk: @@ -3178,13 +3173,12 @@ func (r *HostedControlPlaneReconciler) reconcileKubeAPIServer(ctx context.Contex
 			}
 
 			if hyperazureutil.IsAroHCP() {
-				// Reconcile the SecretProviderClass
-				kmsSecretProviderClass := manifests.ManagedAzureSecretProviderClass(config.ManagedAzureKMSSecretProviderClassName, hcp.Namespace)
-				if _, err := createOrUpdate(ctx, r, kmsSecretProviderClass, func() error {
-					secretproviderclass.ReconcileManagedAzureSecretProviderClass(kmsSecretProviderClass, hcp, hcp.Spec.SecretEncryption.KMS.Azure.KMS, true)
-					return nil
+				// Reconcile the Azure KMS Config Secret
path: control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
user: login: bryan-cox
body: cloudNetworkConfigCreds is still needed for CCM/CP so no need to mess with it here.
created_at: 2025-04-23T13:00:24Z
author_association: MEMBER
diff_hunk: @@ -3178,13 +3173,12 @@ func (r *HostedControlPlaneReconciler) reconcileKubeAPIServer(ctx context.Contex
 			}
 
 			if hyperazureutil.IsAroHCP() {
-				// Reconcile the SecretProviderClass
-				kmsSecretProviderClass := manifests.ManagedAzureSecretProviderClass(config.ManagedAzureKMSSecretProviderClassName, hcp.Namespace)
-				if _, err := createOrUpdate(ctx, r, kmsSecretProviderClass, func() error {
-					secretproviderclass.ReconcileManagedAzureSecretProviderClass(kmsSecretProviderClass, hcp, hcp.Spec.SecretEncryption.KMS.Azure.KMS, true)
-					return nil
+				// Reconcile the Azure KMS Config Secret
path: control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
user: login: bryan-cox
body: Maybe we could add this to `ReconcileSecretEncryption`?
created_at: 2025-04-23T13:06:39Z
author_association: MEMBER

PR comments:
user: login: openshift-ci[bot]
created_at: 2025-04-17T11:26:01Z
body: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-04-17T11:26:02Z
body: @bryan-cox: This pull request references [CNTRLPLANE-112](https://issues.redhat.com//browse/CNTRLPLANE-112) which is a valid jira issue.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

><!--
>- Please ensure code changes are split into a series of logically independent commits.
>- Every commit should have a subject/title (What) and a description/body (Why).
>- Every PR must have a description.
>- As an example you can use git commit -m"What" -m"Why" to achieve the requirements above. GitHub automatically recognises the commit description (-m"Why") in single commit PRs and adds it as the PR description.
>- Use the [imperative mood](https://en.wikipedia.org/wiki/Imperative_mood) in the subject line for every commit. E.g `Mark infraID as required` instead of `This patch marks infraID as required` (This follows Git’s own built-in conventions). See https://github.com/openshift/hypershift/pull/485 as an example.
>- See https://hypershift-docs.netlify.app/contribute for more details.
>
>Delete this text before submitting the PR.
>-->
>
>**What this PR does / why we need it**:
>
>**Which issue(s) this PR fixes** *(optional, use `fixes #<issue_number>(, fixes #<issue_number>, ...)` format, where issue_number might be a GitHub issue, or a Jira story*:
>Fixes #
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-04-17T11:26:20Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openshift/hypershift/pull/6048#" title="Author self-approved">bryan-cox</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=openshift%2Fhypershift).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/openshift/hypershift/blob/main/OWNERS)~~ [bryan-cox]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: openshift-ci-robot
created_at: 2025-04-17T11:32:58Z
body: @bryan-cox: This pull request references [CNTRLPLANE-112](https://issues.redhat.com//browse/CNTRLPLANE-112) which is a valid jira issue.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

><!--
>- Please ensure code changes are split into a series of logically independent commits.
>- Every commit should have a subject/title (What) and a description/body (Why).
>- Every PR must have a description.
>- As an example you can use git commit -m"What" -m"Why" to achieve the requirements above. GitHub automatically recognises the commit description (-m"Why") in single commit PRs and adds it as the PR description.
>- Use the [imperative mood](https://en.wikipedia.org/wiki/Imperative_mood) in the subject line for every commit. E.g `Mark infraID as required` instead of `This patch marks infraID as required` (This follows Git’s own built-in conventions). See https://github.com/openshift/hypershift/pull/485 as an example.
>- See https://hypershift-docs.netlify.app/contribute for more details.
>
>Delete this text before submitting the PR.
>-->
>
>**What this PR does / why we need it**:
>
>**Which issue(s) this PR fixes** *(optional, use `fixes #<issue_number>(, fixes #<issue_number>, ...)` format, where issue_number might be a GitHub issue, or a Jira story*:
>Fixes #
>
>**Notes to Reviewers**:
>- I still need to check the KMS configuration for CPOv2
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci-robot
created_at: 2025-04-17T14:42:36Z
body: @bryan-cox: This pull request references [CNTRLPLANE-112](https://issues.redhat.com//browse/CNTRLPLANE-112) which is a valid jira issue.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes** *(optional, use `fixes #<issue_number>(, fixes #<issue_number>, ...)` format, where issue_number might be a GitHub issue, or a Jira story*:
>Fixes #
>
>**Notes to Reviewers**:
>- I still need to check the KMS configuration for CPOv2
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci-robot
created_at: 2025-04-17T14:44:32Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes** *(optional, use `fixes #<issue_number>(, fixes #<issue_number>, ...)` format, where issue_number might be a GitHub issue, or a Jira story*:
>Fixes #
>
>**Notes to Reviewers**:
>- I still need to check the KMS configuration for CPOv2
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci-robot
created_at: 2025-04-17T14:49:27Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes** *(optional, use `fixes #<issue_number>(, fixes #<issue_number>, ...)` format, where issue_number might be a GitHub issue, or a Jira story*:
>Fixes #
>
>**Notes to Reviewers**:
>- This PR needs https://github.com/openshift/azure-kubernetes-kms/pull/14 to merge 
>- I still need to check the KMS configuration for CPOv2
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci-robot
created_at: 2025-04-17T14:55:17Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes**:
>Fixes [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111)
>
>**Notes to Reviewers**:
>- This PR needs https://github.com/openshift/azure-kubernetes-kms/pull/14 to merge 
>- I still need to check the KMS configuration for CPOv2
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: bryan-cox
created_at: 2025-04-18T10:19:25Z
body: /test all
author_association: MEMBER
user: login: cwbotbot
created_at: 2025-04-18T11:35:05Z
body: <!-- Test Results Reporter -->
<!-- Job IDs: 1915436751862632448,1915436756862242816 -->

## Test Results

### e2e-aws
- Status: ✅ PASS
- Started: 2025-04-24T16:05:18Z
- [View Job](https://testgrid-ci-testgrid.apps.rosa.hypershift-ci-2.1xls.p3.openshiftapps.com/?job=1915436756862242816&testName=e2e-aws)
- [View Job History](https://testgrid-ci-testgrid.apps.rosa.hypershift-ci-2.1xls.p3.openshiftapps.com/?pr=6048&testName=e2e-aws)

### e2e-aks
- Status: ✅ PASS
- Started: 2025-04-24T16:05:08Z
- [View Job](https://testgrid-ci-testgrid.apps.rosa.hypershift-ci-2.1xls.p3.openshiftapps.com/?job=1915436751862632448&testName=e2e-aks)
- [View Job History](https://testgrid-ci-testgrid.apps.rosa.hypershift-ci-2.1xls.p3.openshiftapps.com/?pr=6048&testName=e2e-aks)


author_association: NONE
user: login: bryan-cox
created_at: 2025-04-18T13:08:42Z
body: /test e2e-aks
author_association: MEMBER
user: login: openshift-ci-robot
created_at: 2025-04-21T18:37:47Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes**:
>Fixes [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111)
>
>**Notes to Reviewers**:
>- This PR needs https://github.com/openshift/azure-kubernetes-kms/pull/14 to merge 
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [ ] Subject and description added to both, commit and PR.
>- [ ] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: bryan-cox
created_at: 2025-04-21T18:37:49Z
body: /test all
author_association: MEMBER
user: login: bryan-cox
created_at: 2025-04-21T18:38:34Z
body: /retest
author_association: MEMBER
user: login: openshift-ci-robot
created_at: 2025-04-21T18:38:47Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes**:
>Fixes [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111)
>
>**Notes to Reviewers**:
>- This PR needs https://github.com/openshift/azure-kubernetes-kms/pull/14 to merge 
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [x] Subject and description added to both, commit and PR.
>- [x] Relevant issues have been referenced.
>- [ ] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci-robot
created_at: 2025-04-22T11:19:15Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes**:
>Fixes [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111)
>
>**Notes to Reviewers**:
>- The first commit contains the actual code changes. The other commits are docs related.
>- CacheSize: ptr.To[int32](100), was removed because it is not valid for v2
>
>**Checklist**
>- [x] Subject and description added to both, commit and PR.
>- [x] Relevant issues have been referenced.
>- [x] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: openshift-ci-robot
created_at: 2025-04-22T11:19:57Z
body: @bryan-cox: This pull request references [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111) which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.19.0" version, but no target version was set.

<details>

In response to [this](https://github.com/openshift/hypershift/pull/6048):

>**What this PR does / why we need it**:
>This PR:
>- updates Azure KMS implementation by updating the encryption configuration to only support v2 parameters
>- have Azure KMS use it's own Azure authentication configuration; previously, it was reusing CP/CCM configuration
>- reorders some of the Azure how-to pages in our docs
>- adds a how-to section on setting up and verifying etcd encryption
>
>**Which issue(s) this PR fixes**:
>Fixes [CNTRLPLANE-111](https://issues.redhat.com//browse/CNTRLPLANE-111)
>
>**Notes to Reviewers**:
>- The first commit contains the actual code changes. The other commits are docs related.
>- CacheSize: ptr.To\[int32\]\(100\), was removed because it is not valid for v2
>
>**Checklist**
>- [x] Subject and description added to both, commit and PR.
>- [x] Relevant issues have been referenced.
>- [x] This change includes docs. 
>- [ ] This change includes unit tests.


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fhypershift).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: bryan-cox
created_at: 2025-04-23T18:46:38Z
body: /test all
author_association: MEMBER
user: login: bryan-cox
created_at: 2025-04-23T19:11:04Z
body: /test e2e-aks
author_association: MEMBER
user: login: bryan-cox
created_at: 2025-04-24T01:02:36Z
body: /test all
author_association: MEMBER
user: login: enxebre
created_at: 2025-04-24T13:55:49Z
body: /lgtm

author_association: MEMBER
user: login: openshift-ci-robot
created_at: 2025-04-24T14:40:08Z
body: /retest-required

Remaining retests: 0 against base HEAD 77b70b5ebd2d3547b1f628441824ab73da1ab3d7 and 2 for PR HEAD 6b78b09c067b609a210925eb8d8b6fabe6465c14 in total

author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-04-24T18:39:45Z
body: @bryan-cox: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/okd-scos-e2e-aws-ovn | 6b78b09c067b609a210925eb8d8b6fabe6465c14 | [link](https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/openshift_hypershift/6048/pull-ci-openshift-hypershift-main-okd-scos-e2e-aws-ovn/1915403945610579968) | false | `/test okd-scos-e2e-aws-ovn`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=openshift&repo=hypershift&pr=6048). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:bryan-cox).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
author_association: CONTRIBUTOR
user: login: openshift-bot
created_at: 2025-04-25T04:27:30Z
body: **[ART PR BUILD NOTIFIER]**

Distgit: hypershift
This PR has been included in build [ose-hypershift-container-v4.20.0-202504242124.p0.g98cb862.assembly.stream.el9](https://brewweb.engineering.redhat.com/brew/buildinfo?buildID=3619405).
All builds following this will include this PR.
author_association: NONE