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  }