diff --git a/api/v1beta1/azurecluster_webhook.go b/api/v1beta1/azurecluster_webhook.go index 20b1dc75ec8..def1503c053 100644 --- a/api/v1beta1/azurecluster_webhook.go +++ b/api/v1beta1/azurecluster_webhook.go @@ -147,6 +147,13 @@ func (*AzureClusterWebhook) ValidateUpdate(_ context.Context, oldRaw, newObj run allErrs = append(allErrs, err) } + if err := webhookutils.ValidateImmutable( + field.NewPath("spec", "networkSpec", "privateDNSZone"), + old.Spec.NetworkSpec.PrivateDNSZone, + c.Spec.NetworkSpec.PrivateDNSZone); err != nil { + allErrs = append(allErrs, err) + } + // Allow enabling azure bastion but avoid disabling it. if old.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(old.Spec.BastionSpec.AzureBastion, c.Spec.BastionSpec.AzureBastion) { allErrs = append(allErrs, diff --git a/api/v1beta1/azuremanagedcontrolplane_types.go b/api/v1beta1/azuremanagedcontrolplane_types.go index ac0d4336981..8938e26508c 100644 --- a/api/v1beta1/azuremanagedcontrolplane_types.go +++ b/api/v1beta1/azuremanagedcontrolplane_types.go @@ -25,12 +25,19 @@ const ( // ManagedClusterFinalizer allows Reconcile to clean up Azure resources associated with the AzureManagedControlPlane before // removing it from the apiserver. ManagedClusterFinalizer = "azuremanagedcontrolplane.infrastructure.cluster.x-k8s.io" +) + +// PrivateDNSZoneMode determines the creation of Private DNS Zones in a private cluster. +// When unset or set to the default value of PrivateDNSZoneModeSystem, Private DNS Zones are created. +// When set to PrivateDNSZoneModeNone, Private DNS Zones are not created in a private cluster. +type PrivateDNSZoneMode string - // PrivateDNSZoneModeSystem represents mode System for azuremanagedcontrolplane. - PrivateDNSZoneModeSystem string = "System" +const ( + // PrivateDNSZoneModeSystem represents mode System for Private DNS Zones. + PrivateDNSZoneModeSystem PrivateDNSZoneMode = "System" - // PrivateDNSZoneModeNone represents mode None for azuremanagedcontrolplane. - PrivateDNSZoneModeNone string = "None" + // PrivateDNSZoneModeNone represents mode None for Private DNS Zones. + PrivateDNSZoneModeNone PrivateDNSZoneMode = "None" ) // UpgradeChannel determines the type of upgrade channel for automatically upgrading the cluster. diff --git a/api/v1beta1/types.go b/api/v1beta1/types.go index 93bed4fc707..5fa83bcef58 100644 --- a/api/v1beta1/types.go +++ b/api/v1beta1/types.go @@ -116,6 +116,12 @@ type NetworkSpec struct { // +optional AdditionalAPIServerLBPorts []LoadBalancerPort `json:"additionalAPIServerLBPorts,omitempty"` + // PrivateDNSZone enables private dns zone creation modes for a private cluster. + // When unspecified, it defaults to PrivateDNSZoneModeSystem which creates a private DNS zone. + // +kubebuilder:validation:Enum=System;None + // +optional + PrivateDNSZone *PrivateDNSZoneMode `json:"privateDNSZone,omitempty"` + NetworkClassSpec `json:",inline"` } diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index eaa79ca7ebc..036180f4bed 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -3784,6 +3784,11 @@ func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) { *out = make([]LoadBalancerPort, len(*in)) copy(*out, *in) } + if in.PrivateDNSZone != nil { + in, out := &in.PrivateDNSZone, &out.PrivateDNSZone + *out = new(PrivateDNSZoneMode) + **out = **in + } out.NetworkClassSpec = in.NetworkClassSpec } diff --git a/azure/scope/cluster.go b/azure/scope/cluster.go index f7f035f2e7b..b30f1b501a7 100644 --- a/azure/scope/cluster.go +++ b/azure/scope/cluster.go @@ -559,7 +559,7 @@ func (s *ClusterScope) VNetSpec() azure.ASOResourceSpecGetter[*asonetworkv1api20 // PrivateDNSSpec returns the private dns zone spec. func (s *ClusterScope) PrivateDNSSpec() (zoneSpec azure.ResourceSpecGetter, linkSpec, recordSpec []azure.ResourceSpecGetter) { - if s.IsAPIServerPrivate() { + if s.IsAPIServerPrivate() && s.PrivateDNSZoneMode() != infrav1.PrivateDNSZoneModeNone { resourceGroup := s.ResourceGroup() if s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneResourceGroup != "" { resourceGroup = s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneResourceGroup @@ -1251,3 +1251,13 @@ func (s *ClusterScope) getLastAppliedSecurityRules(nsgName string) map[string]in } return lastAppliedSecurityRules } + +// PrivateDNSZoneMode returns the current Private DNS Zone mode. +// When unconfigured, the method returns the default. +// Returned value is used to determine if the Private DNS Zone should be created. +func (s *ClusterScope) PrivateDNSZoneMode() infrav1.PrivateDNSZoneMode { + if s.AzureCluster.Spec.NetworkSpec.PrivateDNSZone == nil { + return infrav1.PrivateDNSZoneModeSystem + } + return *s.AzureCluster.Spec.NetworkSpec.PrivateDNSZone +} diff --git a/azure/scope/cluster_test.go b/azure/scope/cluster_test.go index 6bb5c6ba9e7..cbf58fdf588 100644 --- a/azure/scope/cluster_test.go +++ b/azure/scope/cluster_test.go @@ -216,6 +216,58 @@ func TestAPIServerHost(t *testing.T) { }, want: "apiserver.example.private", }, + { + name: "private apiserver without private dns zone", + azureCluster: infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ + SubscriptionID: fakeSubscriptionID, + IdentityRef: &corev1.ObjectReference{ + Kind: infrav1.AzureClusterIdentityKind, + }, + }, + ControlPlaneEnabled: true, + NetworkSpec: infrav1.NetworkSpec{ + PrivateDNSZone: ptr.To(infrav1.PrivateDNSZoneModeNone), + NetworkClassSpec: infrav1.NetworkClassSpec{ + PrivateDNSZoneName: "", + }, + APIServerLB: &infrav1.LoadBalancerSpec{ + LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ + Type: infrav1.Internal, + }, + }, + }, + }, + }, + want: "apiserver.my-cluster.capz.io", + }, + { + name: "private apiserver with private dns zone", + azureCluster: infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ + SubscriptionID: fakeSubscriptionID, + IdentityRef: &corev1.ObjectReference{ + Kind: infrav1.AzureClusterIdentityKind, + }, + }, + ControlPlaneEnabled: true, + NetworkSpec: infrav1.NetworkSpec{ + PrivateDNSZone: ptr.To(infrav1.PrivateDNSZoneModeSystem), + NetworkClassSpec: infrav1.NetworkClassSpec{ + PrivateDNSZoneName: "", + }, + APIServerLB: &infrav1.LoadBalancerSpec{ + LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ + Type: infrav1.Internal, + }, + }, + }, + }, + }, + want: "apiserver.my-cluster.capz.io", + }, } for _, tc := range tests { @@ -4137,3 +4189,95 @@ func TestAPIServerLBName(t *testing.T) { }) } } + +func TestPrivateDNSSpec(t *testing.T) { + tests := []struct { + name string + clusterName string + azureClusterNetworkSpec infrav1.NetworkSpec + expectPrivateDNSSpec bool + }{ + { + name: "Default PrivateDNSZone (PrivateDNSZoneModeSystem)", + clusterName: "private-default", + azureClusterNetworkSpec: infrav1.NetworkSpec{ + NetworkClassSpec: infrav1.NetworkClassSpec{ + PrivateDNSZoneName: "fake-privateDNSZoneName", + }, + APIServerLB: &infrav1.LoadBalancerSpec{ + FrontendIPs: []infrav1.FrontendIP{ + { + Name: "api-server-lb-internal-ip", + FrontendIPClass: infrav1.FrontendIPClass{ + PrivateIPAddress: infrav1.DefaultInternalLBIPAddress, + }, + }, + }, + LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ + Type: infrav1.Internal, + }, + }, + }, + expectPrivateDNSSpec: true, + }, + { + name: "PrivateDNSZone set to PrivateDNSZoneModeNone", + clusterName: "private-none", + azureClusterNetworkSpec: infrav1.NetworkSpec{ + PrivateDNSZone: ptr.To(infrav1.PrivateDNSZoneModeNone), + NetworkClassSpec: infrav1.NetworkClassSpec{ + PrivateDNSZoneName: "fake-privateDNSZoneName", + }, + APIServerLB: &infrav1.LoadBalancerSpec{ + LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ + Type: infrav1.Internal, + }, + }, + }, + expectPrivateDNSSpec: false, + }, + { + name: "Public LB", + clusterName: "public-none", + azureClusterNetworkSpec: infrav1.NetworkSpec{ + PrivateDNSZone: ptr.To(infrav1.PrivateDNSZoneModeNone), + NetworkClassSpec: infrav1.NetworkClassSpec{ + PrivateDNSZoneName: "fake-privateDNSZoneName", + }, + APIServerLB: &infrav1.LoadBalancerSpec{ + LoadBalancerClassSpec: infrav1.LoadBalancerClassSpec{ + Type: infrav1.Public, + }, + }, + }, + expectPrivateDNSSpec: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + cluster := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.clusterName, + Namespace: "default", + }, + } + azureCluster := &infrav1.AzureCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.clusterName, + }, + Spec: infrav1.AzureClusterSpec{ + NetworkSpec: tc.azureClusterNetworkSpec, + }, + } + + clusterScope := &ClusterScope{ + Cluster: cluster, + AzureCluster: azureCluster, + } + zoneSpec, _, _ := clusterScope.PrivateDNSSpec() + g.Expect(zoneSpec != nil).Should(Equal(tc.expectPrivateDNSSpec)) + }) + } +} diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml index 9a82c35e10e..927feee4b73 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml @@ -926,6 +926,14 @@ spec: description: LBType defines an Azure load balancer Type. type: string type: object + privateDNSZone: + description: |- + PrivateDNSZone enables private dns zone creation modes for a private cluster. + When unspecified, it defaults to PrivateDNSZoneModeSystem which creates a private DNS zone. + enum: + - System + - None + type: string privateDNSZoneName: description: PrivateDNSZoneName defines the zone name for the Azure Private DNS.