sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azurecluster_webhook.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1beta1 18 19 import ( 20 "reflect" 21 22 apierrors "k8s.io/apimachinery/pkg/api/errors" 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/util/validation/field" 25 webhookutils "sigs.k8s.io/cluster-api-provider-azure/util/webhook" 26 ctrl "sigs.k8s.io/controller-runtime" 27 "sigs.k8s.io/controller-runtime/pkg/webhook" 28 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 29 ) 30 31 // SetupWebhookWithManager sets up and registers the webhook with the manager. 32 func (c *AzureCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { 33 return ctrl.NewWebhookManagedBy(mgr). 34 For(c). 35 Complete() 36 } 37 38 // +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azurecluster,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azureclusters,versions=v1beta1,name=validation.azurecluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 39 // +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-azurecluster,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=azureclusters,versions=v1beta1,name=default.azurecluster.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 40 41 var _ webhook.Validator = &AzureCluster{} 42 var _ webhook.Defaulter = &AzureCluster{} 43 44 // Default implements webhook.Defaulter so a webhook will be registered for the type. 45 func (c *AzureCluster) Default() { 46 c.setDefaults() 47 } 48 49 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. 50 func (c *AzureCluster) ValidateCreate() (admission.Warnings, error) { 51 return c.validateCluster(nil) 52 } 53 54 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. 55 func (c *AzureCluster) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) { 56 var allErrs field.ErrorList 57 old := oldRaw.(*AzureCluster) 58 59 if err := webhookutils.ValidateImmutable( 60 field.NewPath("Spec", "ResourceGroup"), 61 old.Spec.ResourceGroup, 62 c.Spec.ResourceGroup); err != nil { 63 allErrs = append(allErrs, err) 64 } 65 66 if err := webhookutils.ValidateImmutable( 67 field.NewPath("Spec", "SubscriptionID"), 68 old.Spec.SubscriptionID, 69 c.Spec.SubscriptionID); err != nil { 70 allErrs = append(allErrs, err) 71 } 72 73 if err := webhookutils.ValidateImmutable( 74 field.NewPath("Spec", "Location"), 75 old.Spec.Location, 76 c.Spec.Location); err != nil { 77 allErrs = append(allErrs, err) 78 } 79 80 if old.Spec.ControlPlaneEndpoint.Host != "" && c.Spec.ControlPlaneEndpoint.Host != old.Spec.ControlPlaneEndpoint.Host { 81 allErrs = append(allErrs, 82 field.Invalid(field.NewPath("spec", "ControlPlaneEndpoint", "Host"), 83 c.Spec.ControlPlaneEndpoint.Host, "field is immutable"), 84 ) 85 } 86 87 if old.Spec.ControlPlaneEndpoint.Port != 0 && c.Spec.ControlPlaneEndpoint.Port != old.Spec.ControlPlaneEndpoint.Port { 88 allErrs = append(allErrs, 89 field.Invalid(field.NewPath("spec", "ControlPlaneEndpoint", "Port"), 90 c.Spec.ControlPlaneEndpoint.Port, "field is immutable"), 91 ) 92 } 93 94 if !reflect.DeepEqual(c.Spec.AzureEnvironment, old.Spec.AzureEnvironment) { 95 // The equality failure could be because of default mismatch between v1alpha3 and v1beta1. This happens because 96 // the new object `r` will have run through the default webhooks but the old object `old` would not have so. 97 // This means if the old object was in v1alpha3, it would not get the new defaults set in v1beta1 resulting 98 // in object inequality. To workaround this, we set the v1beta1 defaults here so that the old object also gets 99 // the new defaults. 100 old.setAzureEnvironmentDefault() 101 102 // if it's still not equal, return error. 103 if !reflect.DeepEqual(c.Spec.AzureEnvironment, old.Spec.AzureEnvironment) { 104 allErrs = append(allErrs, 105 field.Invalid(field.NewPath("spec", "AzureEnvironment"), 106 c.Spec.AzureEnvironment, "field is immutable"), 107 ) 108 } 109 } 110 111 if err := webhookutils.ValidateImmutable( 112 field.NewPath("Spec", "NetworkSpec", "PrivateDNSZoneName"), 113 old.Spec.NetworkSpec.PrivateDNSZoneName, 114 c.Spec.NetworkSpec.PrivateDNSZoneName); err != nil { 115 allErrs = append(allErrs, err) 116 } 117 118 // Allow enabling azure bastion but avoid disabling it. 119 if old.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(old.Spec.BastionSpec.AzureBastion, c.Spec.BastionSpec.AzureBastion) { 120 allErrs = append(allErrs, 121 field.Invalid(field.NewPath("spec", "BastionSpec", "AzureBastion"), 122 c.Spec.BastionSpec.AzureBastion, "azure bastion cannot be removed from a cluster"), 123 ) 124 } 125 126 if err := webhookutils.ValidateImmutable( 127 field.NewPath("Spec", "NetworkSpec", "ControlPlaneOutboundLB"), 128 old.Spec.NetworkSpec.ControlPlaneOutboundLB, 129 c.Spec.NetworkSpec.ControlPlaneOutboundLB); err != nil { 130 allErrs = append(allErrs, err) 131 } 132 133 allErrs = append(allErrs, c.validateSubnetUpdate(old)...) 134 135 if len(allErrs) == 0 { 136 return c.validateCluster(old) 137 } 138 139 return nil, apierrors.NewInvalid(GroupVersion.WithKind(AzureClusterKind).GroupKind(), c.Name, allErrs) 140 } 141 142 // validateSubnetUpdate validates a ClusterSpec.NetworkSpec.Subnets for immutability. 143 func (c *AzureCluster) validateSubnetUpdate(old *AzureCluster) field.ErrorList { 144 var allErrs field.ErrorList 145 146 oldSubnetMap := make(map[string]SubnetSpec, len(old.Spec.NetworkSpec.Subnets)) 147 oldSubnetIndex := make(map[string]int, len(old.Spec.NetworkSpec.Subnets)) 148 for i, subnet := range old.Spec.NetworkSpec.Subnets { 149 oldSubnetMap[subnet.Name] = subnet 150 oldSubnetIndex[subnet.Name] = i 151 } 152 for i, subnet := range c.Spec.NetworkSpec.Subnets { 153 if oldSubnet, ok := oldSubnetMap[subnet.Name]; ok { 154 // Verify the CIDR blocks haven't changed for an owned Vnet. 155 // A non-owned Vnet's CIDR block can change based on what's 156 // defined in the spec vs what's been loaded from Azure directly. 157 // This technically allows the cidr block to be modified in the brief 158 // moments before the Vnet is created (because the tags haven't been 159 // set yet) but once the Vnet has been created it becomes immutable. 160 if old.Spec.NetworkSpec.Vnet.Tags.HasOwned(old.Name) && !reflect.DeepEqual(subnet.CIDRBlocks, oldSubnet.CIDRBlocks) { 161 allErrs = append(allErrs, 162 field.Invalid(field.NewPath("spec", "networkSpec", "subnets").Index(oldSubnetIndex[subnet.Name]).Child("CIDRBlocks"), 163 c.Spec.NetworkSpec.Subnets[i].CIDRBlocks, "field is immutable"), 164 ) 165 } 166 if subnet.RouteTable.Name != oldSubnet.RouteTable.Name { 167 allErrs = append(allErrs, 168 field.Invalid(field.NewPath("spec", "networkSpec", "subnets").Index(oldSubnetIndex[subnet.Name]).Child("RouteTable").Child("Name"), 169 c.Spec.NetworkSpec.Subnets[i].RouteTable.Name, "field is immutable"), 170 ) 171 } 172 if (subnet.NatGateway.Name != oldSubnet.NatGateway.Name) && (oldSubnet.NatGateway.Name != "") { 173 allErrs = append(allErrs, 174 field.Invalid(field.NewPath("spec", "networkSpec", "subnets").Index(oldSubnetIndex[subnet.Name]).Child("NatGateway").Child("Name"), 175 c.Spec.NetworkSpec.Subnets[i].NatGateway.Name, "field is immutable"), 176 ) 177 } 178 if subnet.SecurityGroup.Name != oldSubnet.SecurityGroup.Name { 179 allErrs = append(allErrs, 180 field.Invalid(field.NewPath("spec", "networkSpec", "subnets").Index(oldSubnetIndex[subnet.Name]).Child("SecurityGroup").Child("Name"), 181 c.Spec.NetworkSpec.Subnets[i].SecurityGroup.Name, "field is immutable"), 182 ) 183 } 184 } 185 } 186 187 return allErrs 188 } 189 190 // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. 191 func (c *AzureCluster) ValidateDelete() (admission.Warnings, error) { 192 return nil, nil 193 }