sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremachine_validation.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 "encoding/base64" 21 "fmt" 22 23 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5" 24 "github.com/google/uuid" 25 "golang.org/x/crypto/ssh" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure" 28 ) 29 30 // ValidateAzureMachineSpec checks an AzureMachineSpec and returns any validation errors. 31 func ValidateAzureMachineSpec(spec AzureMachineSpec) field.ErrorList { 32 var allErrs field.ErrorList 33 34 if errs := ValidateImage(spec.Image, field.NewPath("image")); len(errs) > 0 { 35 allErrs = append(allErrs, errs...) 36 } 37 38 if errs := ValidateOSDisk(spec.OSDisk, field.NewPath("osDisk")); len(errs) > 0 { 39 allErrs = append(allErrs, errs...) 40 } 41 42 if errs := ValidateConfidentialCompute(spec.OSDisk.ManagedDisk, spec.SecurityProfile, field.NewPath("securityProfile")); len(errs) > 0 { 43 allErrs = append(allErrs, errs...) 44 } 45 46 if errs := ValidateSSHKey(spec.SSHPublicKey, field.NewPath("sshPublicKey")); len(errs) > 0 { 47 allErrs = append(allErrs, errs...) 48 } 49 50 if errs := ValidateUserAssignedIdentity(spec.Identity, spec.UserAssignedIdentities, field.NewPath("userAssignedIdentities")); len(errs) > 0 { 51 allErrs = append(allErrs, errs...) 52 } 53 54 if errs := ValidateDataDisks(spec.DataDisks, field.NewPath("dataDisks")); len(errs) > 0 { 55 allErrs = append(allErrs, errs...) 56 } 57 58 if errs := ValidateDiagnostics(spec.Diagnostics, field.NewPath("diagnostics")); len(errs) > 0 { 59 allErrs = append(allErrs, errs...) 60 } 61 62 if errs := ValidateNetwork(spec.SubnetName, spec.AcceleratedNetworking, spec.NetworkInterfaces, field.NewPath("networkInterfaces")); len(errs) > 0 { 63 allErrs = append(allErrs, errs...) 64 } 65 66 if errs := ValidateSystemAssignedIdentityRole(spec.Identity, spec.RoleAssignmentName, spec.SystemAssignedIdentityRole, field.NewPath("systemAssignedIdentityRole")); len(errs) > 0 { 67 allErrs = append(allErrs, errs...) 68 } 69 70 return allErrs 71 } 72 73 // ValidateNetwork validates the network configuration. 74 func ValidateNetwork(subnetName string, acceleratedNetworking *bool, networkInterfaces []NetworkInterface, fldPath *field.Path) field.ErrorList { 75 if (networkInterfaces != nil) && len(networkInterfaces) > 0 && subnetName != "" { 76 return field.ErrorList{field.Invalid(fldPath, networkInterfaces, "cannot set both networkInterfaces and machine subnetName")} 77 } 78 79 if (networkInterfaces != nil) && len(networkInterfaces) > 0 && acceleratedNetworking != nil { 80 return field.ErrorList{field.Invalid(fldPath, networkInterfaces, "cannot set both networkInterfaces and machine acceleratedNetworking")} 81 } 82 83 for _, nic := range networkInterfaces { 84 if nic.PrivateIPConfigs < 1 { 85 return field.ErrorList{field.Invalid(fldPath, networkInterfaces, "number of privateIPConfigs per interface must be at least 1")} 86 } 87 } 88 89 return field.ErrorList{} 90 } 91 92 // ValidateSSHKey validates an SSHKey. 93 func ValidateSSHKey(sshKey string, fldPath *field.Path) field.ErrorList { 94 allErrs := field.ErrorList{} 95 96 decoded, err := base64.StdEncoding.DecodeString(sshKey) 97 if err != nil { 98 allErrs = append(allErrs, field.Invalid(fldPath, sshKey, "the SSH public key is not properly base64 encoded")) 99 return allErrs 100 } 101 102 if _, _, _, _, err := ssh.ParseAuthorizedKey(decoded); err != nil { 103 allErrs = append(allErrs, field.Invalid(fldPath, sshKey, "the SSH public key is not valid")) 104 return allErrs 105 } 106 107 return allErrs 108 } 109 110 // ValidateSystemAssignedIdentity validates the system-assigned identities list. 111 func ValidateSystemAssignedIdentity(identityType VMIdentity, oldIdentity, newIdentity string, fldPath *field.Path) field.ErrorList { 112 allErrs := field.ErrorList{} 113 114 if identityType == VMIdentitySystemAssigned { 115 if _, err := uuid.Parse(newIdentity); err != nil { 116 allErrs = append(allErrs, field.Invalid(fldPath, newIdentity, "Role assignment name must be a valid GUID. It is optional and will be auto-generated when not specified.")) 117 } 118 if oldIdentity != "" && oldIdentity != newIdentity { 119 allErrs = append(allErrs, field.Invalid(fldPath, newIdentity, "Role assignment name should not be modified after AzureMachine creation.")) 120 } 121 } else if newIdentity != "" { 122 allErrs = append(allErrs, field.Forbidden(fldPath, "Role assignment name should only be set when using system assigned identity.")) 123 } 124 125 return allErrs 126 } 127 128 // ValidateUserAssignedIdentity validates the user-assigned identities list. 129 func ValidateUserAssignedIdentity(identityType VMIdentity, userAssignedIdentities []UserAssignedIdentity, fldPath *field.Path) field.ErrorList { 130 allErrs := field.ErrorList{} 131 132 if identityType == VMIdentityUserAssigned { 133 if len(userAssignedIdentities) == 0 { 134 allErrs = append(allErrs, field.Required(fldPath, "must be specified for the 'UserAssigned' identity type")) 135 } 136 for _, identity := range userAssignedIdentities { 137 if identity.ProviderID != "" { 138 if _, err := azureutil.ParseResourceID(identity.ProviderID); err != nil { 139 allErrs = append(allErrs, field.Invalid(fldPath, identity.ProviderID, "must be a valid Azure resource ID")) 140 } 141 } 142 } 143 } 144 145 return allErrs 146 } 147 148 // ValidateSystemAssignedIdentityRole validates the system-assigned identity role. 149 func ValidateSystemAssignedIdentityRole(identityType VMIdentity, roleAssignmentName string, role *SystemAssignedIdentityRole, fldPath *field.Path) field.ErrorList { 150 var allErrs field.ErrorList 151 if roleAssignmentName != "" && role != nil && role.Name != "" { 152 allErrs = append(allErrs, field.Invalid(fldPath, role.Name, "cannot set both roleAssignmentName and systemAssignedIdentityRole.name")) 153 } 154 if identityType == VMIdentitySystemAssigned { 155 if role.DefinitionID == "" { 156 allErrs = append(allErrs, field.Invalid(field.NewPath("Spec", "SystemAssignedIdentityRole", "DefinitionID"), role.DefinitionID, "the definitionID field cannot be empty")) 157 } 158 if role.Scope == "" { 159 allErrs = append(allErrs, field.Invalid(field.NewPath("Spec", "SystemAssignedIdentityRole", "Scope"), role.Scope, "the scope field cannot be empty")) 160 } 161 } 162 if identityType != VMIdentitySystemAssigned && role != nil { 163 allErrs = append(allErrs, field.Forbidden(field.NewPath("Spec", "Role"), "systemAssignedIdentityRole can only be set when identity is set to SystemAssigned")) 164 } 165 return allErrs 166 } 167 168 // ValidateDataDisks validates a list of data disks. 169 func ValidateDataDisks(dataDisks []DataDisk, fieldPath *field.Path) field.ErrorList { 170 allErrs := field.ErrorList{} 171 lunSet := make(map[int32]struct{}) 172 nameSet := make(map[string]struct{}) 173 for _, disk := range dataDisks { 174 // validate that the disk size is between 4 and 32767. 175 if disk.DiskSizeGB < 4 || disk.DiskSizeGB > 32767 { 176 allErrs = append(allErrs, field.Invalid(fieldPath.Child("DiskSizeGB"), "", "the disk size should be a value between 4 and 32767")) 177 } 178 179 // validate that all names are unique 180 if disk.NameSuffix == "" { 181 allErrs = append(allErrs, field.Required(fieldPath.Child("NameSuffix"), "the name suffix cannot be empty")) 182 } 183 if _, ok := nameSet[disk.NameSuffix]; ok { 184 allErrs = append(allErrs, field.Duplicate(fieldPath, disk.NameSuffix)) 185 } else { 186 nameSet[disk.NameSuffix] = struct{}{} 187 } 188 189 // validate optional managed disk option 190 if disk.ManagedDisk != nil { 191 if errs := validateManagedDisk(disk.ManagedDisk, fieldPath.Child("managedDisk"), false); len(errs) > 0 { 192 allErrs = append(allErrs, errs...) 193 } 194 } 195 196 // validate that all LUNs are unique and between 0 and 63. 197 if disk.Lun == nil { 198 allErrs = append(allErrs, field.Required(fieldPath, "LUN should not be nil")) 199 } else if *disk.Lun < 0 || *disk.Lun > 63 { 200 allErrs = append(allErrs, field.Invalid(fieldPath, disk.Lun, "logical unit number must be between 0 and 63")) 201 } else if _, ok := lunSet[*disk.Lun]; ok { 202 allErrs = append(allErrs, field.Duplicate(fieldPath, disk.Lun)) 203 } else { 204 lunSet[*disk.Lun] = struct{}{} 205 } 206 207 // validate cachingType 208 allErrs = append(allErrs, validateCachingType(disk.CachingType, fieldPath, disk.ManagedDisk)...) 209 } 210 return allErrs 211 } 212 213 // ValidateOSDisk validates the OSDisk spec. 214 func ValidateOSDisk(osDisk OSDisk, fieldPath *field.Path) field.ErrorList { 215 allErrs := field.ErrorList{} 216 217 if osDisk.DiskSizeGB != nil { 218 if *osDisk.DiskSizeGB <= 0 || *osDisk.DiskSizeGB > 2048 { 219 allErrs = append(allErrs, field.Invalid(fieldPath.Child("DiskSizeGB"), "", "the Disk size should be a value between 1 and 2048")) 220 } 221 } 222 223 if osDisk.OSType == "" { 224 allErrs = append(allErrs, field.Required(fieldPath.Child("OSType"), "the OS type cannot be empty")) 225 } 226 227 allErrs = append(allErrs, validateCachingType(osDisk.CachingType, fieldPath, osDisk.ManagedDisk)...) 228 229 if osDisk.ManagedDisk != nil { 230 if errs := validateManagedDisk(osDisk.ManagedDisk, fieldPath.Child("managedDisk"), true); len(errs) > 0 { 231 allErrs = append(allErrs, errs...) 232 } 233 } 234 235 if osDisk.DiffDiskSettings != nil && osDisk.ManagedDisk != nil && osDisk.ManagedDisk.DiskEncryptionSet != nil { 236 allErrs = append(allErrs, field.Invalid( 237 fieldPath.Child("managedDisks").Child("diskEncryptionSet"), 238 osDisk.ManagedDisk.DiskEncryptionSet.ID, 239 "diskEncryptionSet is not supported when diffDiskSettings.option is 'Local'", 240 )) 241 } 242 243 return allErrs 244 } 245 246 // validateManagedDisk validates updates to the ManagedDiskParameters field. 247 func validateManagedDisk(m *ManagedDiskParameters, fieldPath *field.Path, isOSDisk bool) field.ErrorList { 248 allErrs := field.ErrorList{} 249 250 if m != nil { 251 allErrs = append(allErrs, validateStorageAccountType(m.StorageAccountType, fieldPath.Child("StorageAccountType"), isOSDisk)...) 252 253 // DiskEncryptionSet can only be set when SecurityEncryptionType is set to DiskWithVMGuestState 254 // https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/create-or-update?tabs=HTTP#securityencryptiontypes 255 if isOSDisk && m.SecurityProfile != nil && m.SecurityProfile.DiskEncryptionSet != nil { 256 if m.SecurityProfile.SecurityEncryptionType != SecurityEncryptionTypeDiskWithVMGuestState { 257 allErrs = append(allErrs, field.Invalid( 258 fieldPath.Child("securityProfile").Child("diskEncryptionSet"), 259 m.SecurityProfile.DiskEncryptionSet.ID, 260 "diskEncryptionSet is only supported when securityEncryptionType is set to DiskWithVMGuestState", 261 )) 262 } 263 } 264 } 265 266 return allErrs 267 } 268 269 // ValidateDataDisksUpdate validates updates to Data disks. 270 func ValidateDataDisksUpdate(oldDataDisks, newDataDisks []DataDisk, fieldPath *field.Path) field.ErrorList { 271 allErrs := field.ErrorList{} 272 273 diskErrMsg := "adding/removing data disks after machine creation is not allowed" 274 fieldErrMsg := "modifying data disk's fields after machine creation is not allowed" 275 276 if len(oldDataDisks) != len(newDataDisks) { 277 allErrs = append(allErrs, field.Invalid(fieldPath, newDataDisks, diskErrMsg)) 278 return allErrs 279 } 280 281 oldDisks := make(map[string]DataDisk) 282 283 for _, disk := range oldDataDisks { 284 oldDisks[disk.NameSuffix] = disk 285 } 286 287 for i, newDisk := range newDataDisks { 288 if oldDisk, ok := oldDisks[newDisk.NameSuffix]; ok { 289 if newDisk.DiskSizeGB != oldDisk.DiskSizeGB { 290 allErrs = append(allErrs, field.Invalid(fieldPath.Index(i).Child("diskSizeGB"), newDataDisks, fieldErrMsg)) 291 } 292 293 allErrs = append(allErrs, validateManagedDisksUpdate(oldDisk.ManagedDisk, newDisk.ManagedDisk, fieldPath.Index(i).Child("managedDisk"))...) 294 295 if (newDisk.Lun != nil && oldDisk.Lun != nil) && (*newDisk.Lun != *oldDisk.Lun) { 296 allErrs = append(allErrs, field.Invalid(fieldPath.Index(i).Child("lun"), newDataDisks, fieldErrMsg)) 297 } else if (newDisk.Lun != nil && oldDisk.Lun == nil) || (newDisk.Lun == nil && oldDisk.Lun != nil) { 298 allErrs = append(allErrs, field.Invalid(fieldPath.Index(i).Child("lun"), newDataDisks, fieldErrMsg)) 299 } 300 301 if newDisk.CachingType != oldDisk.CachingType { 302 allErrs = append(allErrs, field.Invalid(fieldPath.Index(i).Child("cachingType"), newDataDisks, fieldErrMsg)) 303 } 304 } else { 305 allErrs = append(allErrs, field.Invalid(fieldPath.Index(i).Child("nameSuffix"), newDataDisks, diskErrMsg)) 306 } 307 } 308 309 return allErrs 310 } 311 312 func validateManagedDisksUpdate(oldDiskParams, newDiskParams *ManagedDiskParameters, fieldPath *field.Path) field.ErrorList { 313 allErrs := field.ErrorList{} 314 fieldErrMsg := "changing managed disk options after machine creation is not allowed" 315 316 if newDiskParams != nil && oldDiskParams != nil { 317 if newDiskParams.StorageAccountType != oldDiskParams.StorageAccountType { 318 allErrs = append(allErrs, field.Invalid(fieldPath.Child("storageAccountType"), newDiskParams, fieldErrMsg)) 319 } 320 if newDiskParams.DiskEncryptionSet != nil && oldDiskParams.DiskEncryptionSet != nil { 321 if newDiskParams.DiskEncryptionSet.ID != oldDiskParams.DiskEncryptionSet.ID { 322 allErrs = append(allErrs, field.Invalid(fieldPath.Child("diskEncryptionSet").Child("ID"), newDiskParams, fieldErrMsg)) 323 } 324 } else if (newDiskParams.DiskEncryptionSet != nil && oldDiskParams.DiskEncryptionSet == nil) || (newDiskParams.DiskEncryptionSet == nil && oldDiskParams.DiskEncryptionSet != nil) { 325 allErrs = append(allErrs, field.Invalid(fieldPath.Child("diskEncryptionSet"), newDiskParams, fieldErrMsg)) 326 } 327 } else if (newDiskParams != nil && oldDiskParams == nil) || (newDiskParams == nil && oldDiskParams != nil) { 328 allErrs = append(allErrs, field.Invalid(fieldPath, newDiskParams, fieldErrMsg)) 329 } 330 331 return allErrs 332 } 333 334 func validateStorageAccountType(storageAccountType string, fieldPath *field.Path, isOSDisk bool) field.ErrorList { 335 allErrs := field.ErrorList{} 336 337 if isOSDisk && storageAccountType == string(armcompute.StorageAccountTypesUltraSSDLRS) { 338 allErrs = append(allErrs, field.Invalid(fieldPath.Child("managedDisks").Child("storageAccountType"), storageAccountType, "UltraSSD_LRS can only be used with data disks, it cannot be used with OS Disks")) 339 } 340 341 if storageAccountType == "" { 342 allErrs = append(allErrs, field.Required(fieldPath, "the Storage Account Type for Managed Disk cannot be empty")) 343 return allErrs 344 } 345 346 for _, possibleStorageAccountType := range armcompute.PossibleDiskStorageAccountTypesValues() { 347 if string(possibleStorageAccountType) == storageAccountType { 348 return allErrs 349 } 350 } 351 allErrs = append(allErrs, field.Invalid(fieldPath, "", fmt.Sprintf("allowed values are %v", armcompute.PossibleDiskStorageAccountTypesValues()))) 352 return allErrs 353 } 354 355 func validateCachingType(cachingType string, fieldPath *field.Path, managedDisk *ManagedDiskParameters) field.ErrorList { 356 allErrs := field.ErrorList{} 357 cachingTypeChildPath := fieldPath.Child("CachingType") 358 359 if managedDisk != nil && managedDisk.StorageAccountType == string(armcompute.StorageAccountTypesUltraSSDLRS) { 360 if cachingType != string(armcompute.CachingTypesNone) { 361 allErrs = append(allErrs, field.Invalid(cachingTypeChildPath, cachingType, fmt.Sprintf("cachingType '%s' is not supported when storageAccountType is '%s'. Allowed values are: '%s'", cachingType, armcompute.StorageAccountTypesUltraSSDLRS, armcompute.CachingTypesNone))) 362 } 363 } 364 365 for _, possibleCachingType := range armcompute.PossibleCachingTypesValues() { 366 if string(possibleCachingType) == cachingType { 367 return allErrs 368 } 369 } 370 371 allErrs = append(allErrs, field.Invalid(cachingTypeChildPath, cachingType, fmt.Sprintf("allowed values are %v", armcompute.PossibleCachingTypesValues()))) 372 return allErrs 373 } 374 375 // ValidateDiagnostics validates the Diagnostic spec. 376 func ValidateDiagnostics(diagnostics *Diagnostics, fieldPath *field.Path) field.ErrorList { 377 var allErrs field.ErrorList 378 379 if diagnostics != nil && diagnostics.Boot != nil { 380 switch diagnostics.Boot.StorageAccountType { 381 case UserManagedDiagnosticsStorage: 382 if diagnostics.Boot.UserManaged == nil { 383 allErrs = append(allErrs, field.Required(fieldPath.Child("UserManaged"), 384 fmt.Sprintf("userManaged must be specified when storageAccountType is '%s'", UserManagedDiagnosticsStorage))) 385 } else if diagnostics.Boot.UserManaged.StorageAccountURI == "" { 386 allErrs = append(allErrs, field.Required(fieldPath.Child("StorageAccountURI"), 387 fmt.Sprintf("StorageAccountURI cannot be empty when storageAccountType is '%s'", UserManagedDiagnosticsStorage))) 388 } 389 case ManagedDiagnosticsStorage: 390 if diagnostics.Boot.UserManaged != nil && 391 diagnostics.Boot.UserManaged.StorageAccountURI != "" { 392 allErrs = append(allErrs, field.Invalid(fieldPath.Child("StorageAccountURI"), diagnostics.Boot.UserManaged.StorageAccountURI, 393 fmt.Sprintf("StorageAccountURI cannot be set when storageAccountType is '%s'", 394 ManagedDiagnosticsStorage))) 395 } 396 case DisabledDiagnosticsStorage: 397 if diagnostics.Boot.UserManaged != nil && 398 diagnostics.Boot.UserManaged.StorageAccountURI != "" { 399 allErrs = append(allErrs, field.Invalid(fieldPath.Child("StorageAccountURI"), diagnostics.Boot.UserManaged.StorageAccountURI, 400 fmt.Sprintf("StorageAccountURI cannot be set when storageAccountType is '%s'", 401 ManagedDiagnosticsStorage))) 402 } 403 } 404 } 405 406 return allErrs 407 } 408 409 // ValidateConfidentialCompute validates the configuration options when the machine is a Confidential VM. 410 // https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/create-or-update?tabs=HTTP#vmdisksecurityprofile 411 // https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/create-or-update?tabs=HTTP#securityencryptiontypes 412 // https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/create-or-update?tabs=HTTP#uefisettings 413 func ValidateConfidentialCompute(managedDisk *ManagedDiskParameters, profile *SecurityProfile, fieldPath *field.Path) field.ErrorList { 414 var allErrs field.ErrorList 415 416 var securityEncryptionType SecurityEncryptionType 417 418 if managedDisk != nil && managedDisk.SecurityProfile != nil { 419 securityEncryptionType = managedDisk.SecurityProfile.SecurityEncryptionType 420 } 421 422 if profile != nil && securityEncryptionType != "" { 423 // SecurityEncryptionType can only be set for Confindential VMs 424 if profile.SecurityType != SecurityTypesConfidentialVM { 425 allErrs = append(allErrs, field.Invalid(fieldPath.Child("SecurityType"), profile.SecurityType, 426 fmt.Sprintf("SecurityType should be set to '%s' when securityEncryptionType is defined", SecurityTypesConfidentialVM))) 427 } 428 429 // Confidential VMs require vTPM to be enabled, irrespective of the SecurityEncryptionType used 430 if profile.UefiSettings == nil { 431 allErrs = append(allErrs, field.Invalid(fieldPath.Child("UefiSettings"), profile.UefiSettings, 432 "UefiSettings should be set when securityEncryptionType is defined")) 433 } 434 435 if profile.UefiSettings != nil && (profile.UefiSettings.VTpmEnabled == nil || !*profile.UefiSettings.VTpmEnabled) { 436 allErrs = append(allErrs, field.Invalid(fieldPath.Child("VTpmEnabled"), profile.UefiSettings.VTpmEnabled, 437 "VTpmEnabled should be set to true when securityEncryptionType is defined")) 438 } 439 440 if securityEncryptionType == SecurityEncryptionTypeDiskWithVMGuestState { 441 // DiskWithVMGuestState encryption type is not compatible with EncryptionAtHost 442 if profile.EncryptionAtHost != nil && *profile.EncryptionAtHost { 443 allErrs = append(allErrs, field.Invalid(fieldPath.Child("EncryptionAtHost"), profile.EncryptionAtHost, 444 fmt.Sprintf("EncryptionAtHost cannot be set to 'true' when securityEncryptionType is set to '%s'", SecurityEncryptionTypeDiskWithVMGuestState))) 445 } 446 447 // DiskWithVMGuestState encryption type requires SecureBoot to be enabled 448 if profile.UefiSettings != nil && (profile.UefiSettings.SecureBootEnabled == nil || !*profile.UefiSettings.SecureBootEnabled) { 449 allErrs = append(allErrs, field.Invalid(fieldPath.Child("SecureBootEnabled"), profile.UefiSettings.SecureBootEnabled, 450 fmt.Sprintf("SecureBootEnabled should be set to true when securityEncryptionType is set to '%s'", SecurityEncryptionTypeDiskWithVMGuestState))) 451 } 452 } 453 } 454 455 return allErrs 456 }