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