github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/vsphere/permission_test.go (about) 1 package vsphere 2 3 import ( 4 "context" 5 "errors" 6 "testing" 7 8 "github.com/golang/mock/gomock" 9 "github.com/stretchr/testify/assert" 10 "github.com/vmware/govmomi/session" 11 "github.com/vmware/govmomi/vim25/mo" 12 vim25types "github.com/vmware/govmomi/vim25/types" 13 "k8s.io/apimachinery/pkg/util/sets" 14 "k8s.io/apimachinery/pkg/util/validation/field" 15 16 "github.com/openshift/installer/pkg/asset/installconfig/vsphere/mock" 17 "github.com/openshift/installer/pkg/types" 18 "github.com/openshift/installer/pkg/types/vsphere" 19 ) 20 21 func buildPermissionGroup(t *testing.T, authManagerMock *mock.MockAuthManager, 22 managedObjectRef vim25types.ManagedObjectReference, 23 username string, 24 group PermissionGroupDefinition, 25 groupName permissionGroup, 26 overrideGroup *permissionGroup, 27 permissionToExcludeSet sets.String) { 28 t.Helper() 29 permissionsToApply := group.Permissions 30 31 if overrideGroup != nil && *overrideGroup == groupName { 32 filteredPermissionsToApply := sets.NewString(group.Permissions...) 33 filteredPermissions := filteredPermissionsToApply.Difference(permissionToExcludeSet) 34 permissionsToApply = filteredPermissions.List() 35 } 36 authManagerMock.EXPECT().FetchUserPrivilegeOnEntities(gomock.Any(), []vim25types.ManagedObjectReference{ 37 managedObjectRef, 38 }, username).Return([]vim25types.UserPrivilegeResult{ 39 { 40 Privileges: permissionsToApply, 41 }, 42 }, nil).AnyTimes() 43 } 44 45 func buildAuthManagerClient(ctx context.Context, 46 t *testing.T, 47 mockCtrl *gomock.Controller, 48 finder Finder, 49 username string, 50 overrideGroup *permissionGroup, 51 permissionsToRemoveFromResource sets.String, 52 permissionsToRemoveFromAvailable sets.String) (*mock.MockAuthManager, error) { 53 t.Helper() 54 authManagerClient := mock.NewMockAuthManager(mockCtrl) 55 authManagerMo := vim25types.ManagedObjectReference{ 56 Type: "auth-manager", 57 Value: "auth-manager", 58 } 59 authManagerClient.EXPECT().Reference().Return(authManagerMo).AnyTimes() 60 61 privilegeListMap := map[string]vim25types.AuthorizationPrivilege{} 62 63 for groupName, group := range permissions { 64 availablePrivileges := sets.NewString(group.Permissions...) 65 availablePrivileges = availablePrivileges.Difference(permissionsToRemoveFromAvailable) 66 for _, availablePrivilege := range availablePrivileges.List() { 67 privilegeListMap[availablePrivilege] = vim25types.AuthorizationPrivilege{ 68 PrivId: availablePrivilege, 69 } 70 } 71 switch groupName { 72 case permissionVcenter: 73 vcenter, err := finder.Folder(ctx, "/") 74 if err != nil { 75 return nil, err 76 } 77 buildPermissionGroup(t, authManagerClient, vcenter.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 78 case permissionDatacenter: 79 datacenters, err := finder.DatacenterList(ctx, "/...") 80 if err != nil { 81 return nil, err 82 } 83 for _, datacenter := range datacenters { 84 buildPermissionGroup(t, authManagerClient, datacenter.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 85 } 86 case permissionDatastore: 87 datastores, err := finder.DatastoreList(ctx, "/...") 88 if err != nil { 89 return nil, err 90 } 91 for _, datastore := range datastores { 92 buildPermissionGroup(t, authManagerClient, datastore.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 93 } 94 case permissionCluster: 95 clusters, err := finder.ClusterComputeResourceList(ctx, "/...") 96 if err != nil { 97 return nil, err 98 } 99 for _, cluster := range clusters { 100 buildPermissionGroup(t, authManagerClient, cluster.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 101 } 102 case permissionPortgroup: 103 networks, err := finder.NetworkList(ctx, "/...") 104 if err != nil { 105 return nil, err 106 } 107 for _, network := range networks { 108 buildPermissionGroup(t, authManagerClient, network.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 109 } 110 case permissionResourcePool: 111 resourcePools := []string{"/DC0/host/DC0_C0/Resources/test-resourcepool", "/DC0/host/DC0_C0/Resources"} 112 for _, resourcePoolPath := range resourcePools { 113 resourcePool, err := finder.ResourcePool(ctx, resourcePoolPath) 114 if err != nil { 115 return nil, err 116 } 117 buildPermissionGroup(t, authManagerClient, resourcePool.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 118 } 119 case permissionFolder: 120 var folders = []string{"/DC0/vm", "/DC0/vm/my-folder"} 121 for _, folder := range folders { 122 folder, err := finder.Folder(ctx, folder) 123 if err != nil { 124 return nil, err 125 } 126 buildPermissionGroup(t, authManagerClient, folder.Reference(), username, group, groupName, overrideGroup, permissionsToRemoveFromResource) 127 } 128 } 129 } 130 privilegeListSlice := make([]vim25types.AuthorizationPrivilege, 0, len(privilegeListMap)) 131 for _, authorizationPrivilege := range privilegeListMap { 132 privilegeListSlice = append(privilegeListSlice, authorizationPrivilege) 133 } 134 135 authorizationManager := mo.AuthorizationManager{ 136 PrivilegeList: privilegeListSlice, 137 } 138 139 authManagerClient.EXPECT().Properties(gomock.Any(), authManagerMo, []string{"privilegeList"}, gomock.Any()).SetArg(3, authorizationManager).AnyTimes() 140 return authManagerClient, nil 141 } 142 143 func validIPIMultiZoneInstallConfig() *types.InstallConfig { 144 installConfig := validIPIInstallConfig() 145 validMultiZonePlatform := validMultiVCenterPlatform() 146 installConfig.VSphere.VCenters = validMultiZonePlatform.VCenters 147 installConfig.VSphere.FailureDomains = validMultiZonePlatform.FailureDomains 148 149 return installConfig 150 } 151 152 func TestPermissionValidate(t *testing.T) { 153 ctx := context.TODO() 154 server, err := mock.StartSimulator(true) 155 if err != nil { 156 t.Error(err) 157 return 158 } 159 defer server.Close() 160 161 client, _, err := mock.GetClient(server) 162 if err != nil { 163 t.Error(err) 164 return 165 } 166 167 mockCtrl := gomock.NewController(t) 168 defer mockCtrl.Finish() 169 170 finder, err := mock.GetFinder(server) 171 if err != nil { 172 t.Error(err) 173 return 174 } 175 176 vmFolder, err := finder.Folder(ctx, "/DC0/vm") 177 if err != nil { 178 t.Error(err) 179 return 180 } 181 182 _, err = vmFolder.CreateFolder(ctx, "my-folder") 183 if err != nil { 184 t.Error(err) 185 return 186 } 187 188 resourcePools, err := finder.ResourcePoolList(ctx, "/DC0/host/DC0_C0") 189 if err != nil { 190 t.Error(err) 191 return 192 } 193 _, err = resourcePools[0].Create(ctx, "test-resourcepool", vim25types.DefaultResourceConfigSpec()) 194 if err != nil { 195 t.Error(err) 196 return 197 } 198 199 validMultiZoneInstallConfig := validIPIMultiZoneInstallConfig() 200 201 sessionMgr := session.NewManager(client) 202 userSession, err := sessionMgr.UserSession(ctx) 203 if err != nil { 204 t.Error(err) 205 return 206 } 207 username := userSession.UserName 208 209 validPermissionsAuthManagerClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, nil, nil, nil) 210 if err != nil { 211 t.Error(err) 212 return 213 } 214 215 missingObjectAttachableDatacenter, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionDatacenter, sets.NewString("InventoryService.Tagging.ObjectAttachable"), nil) 216 if err != nil { 217 t.Error(err) 218 return 219 } 220 221 missingPortgroupPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionPortgroup, sets.NewString("Network.Assign"), nil) 222 if err != nil { 223 t.Error(err) 224 return 225 } 226 227 missingVCenterPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionVcenter, sets.NewString("StorageProfile.View"), nil) 228 if err != nil { 229 t.Error(err) 230 return 231 } 232 233 missingClusterPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionCluster, sets.NewString("VirtualMachine.Config.AddNewDisk"), nil) 234 if err != nil { 235 t.Error(err) 236 return 237 } 238 239 readOnlyClusterPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionCluster, sets.NewString(permissions[permissionCluster].Permissions...), nil) 240 if err != nil { 241 t.Error(err) 242 return 243 } 244 245 missingDatastorePermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionDatastore, sets.NewString("InventoryService.Tagging.ObjectAttachable"), nil) 246 if err != nil { 247 t.Error(err) 248 return 249 } 250 251 missingDatacenterPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionDatacenter, sets.NewString("Folder.Delete"), nil) 252 if err != nil { 253 t.Error(err) 254 return 255 } 256 257 readOnlyDatacenterPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionDatacenter, sets.NewString(permissions[permissionDatacenter].Permissions...), nil) 258 if err != nil { 259 t.Error(err) 260 return 261 } 262 263 missingFolderPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionFolder, sets.NewString("VirtualMachine.Provisioning.DeployTemplate"), nil) 264 if err != nil { 265 t.Error(err) 266 return 267 } 268 269 missingResourcePoolPermissionsClient, err := buildAuthManagerClient(ctx, t, mockCtrl, finder, username, &permissionResourcePool, sets.NewString("VirtualMachine.Config.AddNewDisk"), nil) 270 if err != nil { 271 t.Error(err) 272 return 273 } 274 275 tests := []struct { 276 name string 277 installConfig *types.InstallConfig 278 validationMethod func(*validationContext, *vsphere.FailureDomain, bool) field.ErrorList 279 failureDomain *vsphere.FailureDomain 280 expectErr string 281 authManager AuthManager 282 }{ 283 { 284 name: "multi-zone valid Permissions", 285 installConfig: validMultiZoneInstallConfig, 286 validationMethod: validateFailureDomain, 287 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 288 authManager: validPermissionsAuthManagerClient, 289 }, 290 { 291 name: "multi-zone missing portgroup Permissions", 292 installConfig: validMultiZoneInstallConfig, 293 validationMethod: validateFailureDomain, 294 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 295 authManager: missingPortgroupPermissionsClient, 296 expectErr: "privileges missing for vSphere Port Group: Network.Assign", 297 }, 298 { 299 name: "multi-zone missing vCenter Permissions", 300 installConfig: validMultiZoneInstallConfig, 301 validationMethod: validateFailureDomain, 302 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 303 authManager: missingVCenterPermissionsClient, 304 expectErr: "privileges missing for vSphere vCenter: StorageProfile.View", 305 }, 306 { 307 name: "multi-zone missing cluster Permissions", 308 installConfig: validMultiZoneInstallConfig, 309 validationMethod: validateFailureDomain, 310 failureDomain: func() *vsphere.FailureDomain { 311 failureDomain := validMultiZoneInstallConfig.VSphere.FailureDomains[0] 312 failureDomain.Topology.ResourcePool = "" 313 return &failureDomain 314 }(), 315 authManager: missingClusterPermissionsClient, 316 expectErr: "privileges missing for vSphere vCenter Cluster: VirtualMachine.Config.AddNewDisk", 317 }, 318 { 319 name: "multi-zone resource pool provided, compute cluster can have read-only", 320 installConfig: validMultiZoneInstallConfig, 321 validationMethod: validateFailureDomain, 322 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 323 authManager: readOnlyClusterPermissionsClient, 324 }, 325 { 326 name: "multi-zone missing datacenter Permissions", 327 installConfig: validMultiZoneInstallConfig, 328 validationMethod: validateFailureDomain, 329 failureDomain: func() *vsphere.FailureDomain { 330 failureDomain := validMultiZoneInstallConfig.VSphere.FailureDomains[0] 331 failureDomain.Topology.Folder = "" 332 return &failureDomain 333 }(), 334 authManager: missingDatacenterPermissionsClient, 335 expectErr: "privileges missing for vSphere vCenter Datacenter: Folder.Delete", 336 }, 337 { 338 name: "multi-zone user-defined folder provided, datacenter can have read-only", 339 installConfig: validMultiZoneInstallConfig, 340 validationMethod: validateFailureDomain, 341 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 342 authManager: readOnlyDatacenterPermissionsClient, 343 }, 344 { 345 name: "multi-zone missing datastore Permissions", 346 installConfig: validMultiZoneInstallConfig, 347 validationMethod: validateFailureDomain, 348 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 349 authManager: missingDatastorePermissionsClient, 350 expectErr: "privileges missing for vSphere vCenter Datastore: InventoryService.Tagging.ObjectAttachable", 351 }, 352 { 353 name: "multi-zone missing resource pool Permissions", 354 installConfig: validMultiZoneInstallConfig, 355 validationMethod: validateFailureDomain, 356 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 357 authManager: missingResourcePoolPermissionsClient, 358 expectErr: "privileges missing for vSphere vCenter Resource Pool: VirtualMachine.Config.AddNewDisk", 359 }, 360 { 361 name: "multi-zone missing user-defined folder Permissions but no folder defined", 362 installConfig: validMultiZoneInstallConfig, 363 validationMethod: validateFailureDomain, 364 failureDomain: func() *vsphere.FailureDomain { 365 failureDomain := validMultiZoneInstallConfig.VSphere.FailureDomains[0] 366 failureDomain.Topology.Folder = "" 367 failureDomain.Topology.ResourcePool = "" 368 return &failureDomain 369 }(), 370 authManager: missingFolderPermissionsClient, 371 }, 372 { 373 name: "multi-zone missing user-defined folder Permissions", 374 installConfig: validMultiZoneInstallConfig, 375 validationMethod: validateFailureDomain, 376 failureDomain: &validMultiZoneInstallConfig.VSphere.FailureDomains[0], 377 authManager: missingFolderPermissionsClient, 378 expectErr: "privileges missing for Pre-existing Virtual Machine Folder: VirtualMachine.Provisioning.DeployTemplate", 379 }, 380 { 381 name: "missing datacenter permission InventoryService.Tagging.ObjectAttachable", 382 installConfig: validMultiZoneInstallConfig, 383 validationMethod: validateFailureDomain, 384 failureDomain: func() *vsphere.FailureDomain { 385 failureDomain := validMultiZoneInstallConfig.VSphere.FailureDomains[0] 386 // If folder is empty permissions are checked at the folder level 387 failureDomain.Topology.Folder = "" 388 return &failureDomain 389 }(), 390 391 authManager: missingObjectAttachableDatacenter, 392 expectErr: "privileges missing for vSphere vCenter Datacenter: InventoryService.Tagging.ObjectAttachable", 393 }, 394 { 395 name: "invalid defined datacenter", 396 installConfig: validMultiZoneInstallConfig, 397 validationMethod: validateFailureDomain, 398 failureDomain: func() *vsphere.FailureDomain { 399 failureDomain := validMultiZoneInstallConfig.VSphere.FailureDomains[0] 400 // If folder is empty permissions are checked at the folder level 401 failureDomain.Topology.Datacenter = "invalid" 402 return &failureDomain 403 }(), 404 authManager: validPermissionsAuthManagerClient, 405 expectErr: `platform.vsphere.failureDomains.topology.datacenter: Invalid value: "invalid": datacenter 'invalid' not found`, 406 }, 407 } 408 409 for _, test := range tests { 410 t.Run(test.name, func(t *testing.T) { 411 validationCtx := &validationContext{ 412 AuthManager: test.authManager, 413 Finder: finder, 414 Client: client, 415 } 416 pushPrivileges() 417 err := pruneToAvailablePermissions(ctx, test.authManager) 418 if err != nil { 419 assert.NoError(t, err) 420 } 421 if test.validationMethod != nil { 422 err = test.validationMethod(validationCtx, test.failureDomain, false).ToAggregate() 423 } else { 424 err = errors.New("no test method defined") 425 } 426 if test.expectErr == "" { 427 assert.NoError(t, err) 428 } else { 429 assert.Regexp(t, test.expectErr, err) 430 } 431 popPrivileges() 432 }) 433 } 434 } 435 436 var permissionsBackup = map[permissionGroup]PermissionGroupDefinition{} 437 438 func pushPrivileges() { 439 for permissionGroupKey, permissionGroup := range permissions { 440 var permissions []string 441 permissions = append(permissions, permissionGroup.Permissions...) 442 443 permissionsBackup[permissionGroupKey] = PermissionGroupDefinition{ 444 Permissions: permissions, 445 Description: permissionGroup.Description, 446 } 447 } 448 } 449 450 func popPrivileges() { 451 for permissionGroupKey, permissionGroup := range permissionsBackup { 452 var permissionsSet []string 453 454 permissionsSet = append(permissionsSet, permissionGroup.Permissions...) 455 permissions[permissionGroupKey] = PermissionGroupDefinition{ 456 Permissions: permissionsSet, 457 Description: permissionGroup.Description, 458 } 459 } 460 }