diff --git a/Makefile b/Makefile index 5361914c9ef07..b210cc3de9259 100644 --- a/Makefile +++ b/Makefile @@ -418,6 +418,7 @@ mocks: ## Generate mocks ${GOPATH}/bin/mockgen -destination=pkg/providers/tinkerbell/mocks/client.go -package=mocks "github.com/aws/eks-anywhere/pkg/providers/tinkerbell" ProviderKubectlClient,SSHAuthKeyGenerator ${GOPATH}/bin/mockgen -destination=pkg/providers/cloudstack/mocks/client.go -package=mocks "github.com/aws/eks-anywhere/pkg/providers/cloudstack" ProviderCmkClient,ProviderKubectlClient ${GOPATH}/bin/mockgen -destination=pkg/providers/vsphere/mocks/client.go -package=mocks "github.com/aws/eks-anywhere/pkg/providers/vsphere" ProviderGovcClient,ProviderKubectlClient,ClusterResourceSetManager + ${GOPATH}/bin/mockgen -destination=pkg/govmomi/mocks/client.go -package=mocks "github.com/aws/eks-anywhere/pkg/govmomi" VSphereClient,VMOMIAuthorizationManager,VMOMIFinder,VMOMISessionBuilder,VMOMIFinderBuilder,VMOMIAuthorizationManagerBuilder ${GOPATH}/bin/mockgen -destination=pkg/filewriter/mocks/filewriter.go -package=mocks "github.com/aws/eks-anywhere/pkg/filewriter" FileWriter ${GOPATH}/bin/mockgen -destination=pkg/clustermanager/mocks/client_and_networking.go -package=mocks "github.com/aws/eks-anywhere/pkg/clustermanager" ClusterClient,Networking,AwsIamAuth ${GOPATH}/bin/mockgen -destination=pkg/gitops/flux/mocks/client.go -package=mocks "github.com/aws/eks-anywhere/pkg/gitops/flux" FluxClient,KubeClient,GitOpsFluxClient,GitClient,Templater diff --git a/controllers/cluster_controller_test.go b/controllers/cluster_controller_test.go index f28f12d03bd30..b717fa0751321 100644 --- a/controllers/cluster_controller_test.go +++ b/controllers/cluster_controller_test.go @@ -22,6 +22,7 @@ import ( _ "github.com/aws/eks-anywhere/internal/test/envtest" anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/controller/clusters" + "github.com/aws/eks-anywhere/pkg/govmomi" "github.com/aws/eks-anywhere/pkg/networkutils" "github.com/aws/eks-anywhere/pkg/providers/vsphere" "github.com/aws/eks-anywhere/pkg/providers/vsphere/mocks" @@ -46,7 +47,9 @@ func newVsphereClusterReconcilerTest(t *testing.T, objs ...runtime.Object) *vsph cb := fake.NewClientBuilder() cl := cb.WithRuntimeObjects(objs...).Build() - validator := vsphere.NewValidator(govcClient, &networkutils.DefaultNetClient{}) + vcb := govmomi.NewVMOMIClientBuilder() + + validator := vsphere.NewValidator(govcClient, &networkutils.DefaultNetClient{}, vcb) defaulter := vsphere.NewDefaulter(govcClient) reconciler := vspherereconciler.New( diff --git a/go.mod b/go.mod index 11926d9fa6b48..7620a539222d6 100644 --- a/go.mod +++ b/go.mod @@ -149,6 +149,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stmcginnis/gofish v0.12.1-0.20220311113027-6072260f4c8d // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/vmware/govmomi v0.29.0 github.com/xanzy/ssh-agent v0.3.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.7.0 // indirect diff --git a/go.sum b/go.sum index 4203ec2d8b4ad..7728f1013c32f 100644 --- a/go.sum +++ b/go.sum @@ -1677,6 +1677,8 @@ github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:tw github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmware/govmomi v0.27.1/go.mod h1:daTuJEcQosNMXYJOeku0qdBJP9SOLLWB3Mqz8THtv6o= +github.com/vmware/govmomi v0.29.0 h1:SHJQ7DUc4fltFZv16znJNGHR1/XhiDK5iKxm2OqwkuU= +github.com/vmware/govmomi v0.29.0/go.mod h1:F7adsVewLNHsW/IIm7ziFURaXDaHEwcc+ym4r3INMdY= github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714/go.mod h1:jiPk45kn7klhByRvUq5i2vo1RtHKBHj+iWGFpxbXuuI= github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= diff --git a/pkg/config/static/adminPrivs.json b/pkg/config/static/adminPrivs.json new file mode 100644 index 0000000000000..a2e775c3ed316 --- /dev/null +++ b/pkg/config/static/adminPrivs.json @@ -0,0 +1,431 @@ +[ + "Alarm.Acknowledge", + "Alarm.Create", + "Alarm.Delete", + "Alarm.DisableActions", + "Alarm.Edit", + "Alarm.SetStatus", + "Alarm.ToggleEnableOnEntity", + "Authorization.ModifyPermissions", + "Authorization.ModifyPrivileges", + "Authorization.ModifyRoles", + "Authorization.ReassignRolePermissions", + "AutoDeploy.Host.AssociateMachine", + "AutoDeploy.Profile.Create", + "AutoDeploy.Profile.Edit", + "AutoDeploy.Rule.Create", + "AutoDeploy.Rule.Delete", + "AutoDeploy.Rule.Edit", + "AutoDeploy.RuleSet.Activate", + "AutoDeploy.RuleSet.Edit", + "Certificate.Manage", + "CertificateAuthority.Administer", + "CertificateAuthority.Manage", + "CertificateManagement.Administer", + "CertificateManagement.Manage", + "Cns.Searchable", + "ComputePolicy.Manage", + "ContentLibrary.AddCertToTrustStore", + "ContentLibrary.AddLibraryItem", + "ContentLibrary.AddSubscription", + "ContentLibrary.CheckInTemplate", + "ContentLibrary.CheckOutTemplate", + "ContentLibrary.CreateLocalLibrary", + "ContentLibrary.CreateSubscribedLibrary", + "ContentLibrary.DeleteCertFromTrustStore", + "ContentLibrary.DeleteLibraryItem", + "ContentLibrary.DeleteLocalLibrary", + "ContentLibrary.DeleteSubscribedLibrary", + "ContentLibrary.DeleteSubscription", + "ContentLibrary.DownloadSession", + "ContentLibrary.EvictLibraryItem", + "ContentLibrary.EvictSubscribedLibrary", + "ContentLibrary.GetConfiguration", + "ContentLibrary.ImportStorage", + "ContentLibrary.ManageClusterRegistryResource", + "ContentLibrary.ManageRegistry", + "ContentLibrary.ManageRegistryProject", + "ContentLibrary.ProbeSubscription", + "ContentLibrary.PublishLibrary", + "ContentLibrary.PublishLibraryItem", + "ContentLibrary.ReadStorage", + "ContentLibrary.SyncLibrary", + "ContentLibrary.SyncLibraryItem", + "ContentLibrary.TypeIntrospection", + "ContentLibrary.UpdateConfiguration", + "ContentLibrary.UpdateLibrary", + "ContentLibrary.UpdateLibraryItem", + "ContentLibrary.UpdateLocalLibrary", + "ContentLibrary.UpdateSession", + "ContentLibrary.UpdateSubscribedLibrary", + "ContentLibrary.UpdateSubscription", + "Cryptographer.Access", + "Cryptographer.AddDisk", + "Cryptographer.Clone", + "Cryptographer.Decrypt", + "Cryptographer.Encrypt", + "Cryptographer.EncryptNew", + "Cryptographer.ManageEncryptionPolicy", + "Cryptographer.ManageKeyServers", + "Cryptographer.ManageKeys", + "Cryptographer.Migrate", + "Cryptographer.ReadKeyServersInfo", + "Cryptographer.Recrypt", + "Cryptographer.RegisterHost", + "Cryptographer.RegisterVM", + "DVPortgroup.Create", + "DVPortgroup.Delete", + "DVPortgroup.Ipfix", + "DVPortgroup.Modify", + "DVPortgroup.PolicyOp", + "DVPortgroup.ScopeOp", + "DVSwitch.Create", + "DVSwitch.Delete", + "DVSwitch.HostOp", + "DVSwitch.Ipfix", + "DVSwitch.Modify", + "DVSwitch.Move", + "DVSwitch.PolicyOp", + "DVSwitch.PortConfig", + "DVSwitch.PortSetting", + "DVSwitch.ResourceManagement", + "DVSwitch.Vspan", + "Datacenter.Create", + "Datacenter.Delete", + "Datacenter.IpPoolConfig", + "Datacenter.IpPoolQueryAllocations", + "Datacenter.IpPoolReleaseIp", + "Datacenter.Move", + "Datacenter.Reconfigure", + "Datacenter.Rename", + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.Config", + "Datastore.Delete", + "Datastore.DeleteFile", + "Datastore.FileManagement", + "Datastore.Move", + "Datastore.Rename", + "Datastore.UpdateVirtualMachineFiles", + "Datastore.UpdateVirtualMachineMetadata", + "EAM.Config", + "EAM.Modify", + "EAM.View", + "Extension.Register", + "Extension.Unregister", + "Extension.Update", + "ExternalStatsProvider.Register", + "ExternalStatsProvider.Unregister", + "ExternalStatsProvider.Update", + "Folder.Create", + "Folder.Delete", + "Folder.Move", + "Folder.Rename", + "Global.CancelTask", + "Global.CapacityPlanning", + "Global.Diagnostics", + "Global.DisableMethods", + "Global.EnableMethods", + "Global.GlobalTag", + "Global.Health", + "Global.Licenses", + "Global.LogEvent", + "Global.ManageCustomFields", + "Global.Proxy", + "Global.ScriptAction", + "Global.ServiceManagers", + "Global.SetCustomField", + "Global.Settings", + "Global.SystemTag", + "Global.VCServer", + "GuestDataPublisher.GetData", + "HLM.Create", + "HLM.Manage", + "HealthUpdateProvider.Register", + "HealthUpdateProvider.Unregister", + "HealthUpdateProvider.Update", + "Host.Cim.CimInteraction", + "Host.Config.AdvancedConfig", + "Host.Config.AuthenticationStore", + "Host.Config.AutoStart", + "Host.Config.Connection", + "Host.Config.DateTime", + "Host.Config.Firmware", + "Host.Config.GuestStore", + "Host.Config.HyperThreading", + "Host.Config.Image", + "Host.Config.Maintenance", + "Host.Config.Memory", + "Host.Config.NetService", + "Host.Config.Network", + "Host.Config.Nvdimm", + "Host.Config.Patch", + "Host.Config.PciPassthru", + "Host.Config.Power", + "Host.Config.ProductLocker", + "Host.Config.Quarantine", + "Host.Config.Resources", + "Host.Config.Settings", + "Host.Config.Snmp", + "Host.Config.Storage", + "Host.Config.SystemManagement", + "Host.Hbr.HbrManagement", + "Host.Inventory.AddHostToCluster", + "Host.Inventory.AddStandaloneHost", + "Host.Inventory.CreateCluster", + "Host.Inventory.DeleteCluster", + "Host.Inventory.EditCluster", + "Host.Inventory.ManageClusterLifecyle", + "Host.Inventory.MoveCluster", + "Host.Inventory.MoveHost", + "Host.Inventory.RemoveHostFromCluster", + "Host.Inventory.RenameCluster", + "Host.Local.CreateVM", + "Host.Local.DeleteVM", + "Host.Local.InstallAgent", + "Host.Local.ManageUserGroups", + "Host.Local.ReconfigVM", + "Infraprofile.Read", + "Infraprofile.Write", + "IntercomNamespace.Read", + "IntercomNamespace.Write", + "InventoryService.Tagging.AttachTag", + "InventoryService.Tagging.CreateCategory", + "InventoryService.Tagging.CreateTag", + "InventoryService.Tagging.DeleteCategory", + "InventoryService.Tagging.DeleteTag", + "InventoryService.Tagging.EditCategory", + "InventoryService.Tagging.EditTag", + "InventoryService.Tagging.ModifyUsedByForCategory", + "InventoryService.Tagging.ModifyUsedByForTag", + "InventoryService.Tagging.ObjectAttachable", + "Namespaces.Backup", + "Namespaces.Configure", + "Namespaces.Manage", + "Namespaces.ManageCapabilities", + "Namespaces.ManageDisks", + "Namespaces.SelfServiceManage", + "Namespaces.Upgrade", + "Network.Assign", + "Network.Config", + "Network.Delete", + "Network.Move", + "Nsx.Manage", + "Nsx.ModifyAll", + "Nsx.Read", + "Observability.Admin", + "Performance.ModifyIntervals", + "Plugin.Management", + "Profile.Clear", + "Profile.Create", + "Profile.Delete", + "Profile.Edit", + "Profile.Export", + "Profile.View", + "Resource.ApplyRecommendation", + "Resource.AssignVAppToPool", + "Resource.AssignVMToPool", + "Resource.ColdMigrate", + "Resource.CreatePool", + "Resource.DeletePool", + "Resource.EditPool", + "Resource.HotMigrate", + "Resource.MovePool", + "Resource.QueryVMotion", + "Resource.RenamePool", + "ScheduledTask.Create", + "ScheduledTask.Delete", + "ScheduledTask.Edit", + "ScheduledTask.Run", + "ServiceAccount.Administer", + "ServiceAccount.ManageAccount", + "ServiceAccount.ManagePassword", + "Sessions.GlobalMessage", + "Sessions.ImpersonateUser", + "Sessions.TerminateSession", + "Sessions.ValidateSession", + "SettingsStore.Manage", + "StoragePod.Config", + "StorageProfile.Update", + "StorageProfile.View", + "StorageViews.ConfigureService", + "StorageViews.View", + "SupervisorServices.Manage", + "System.Anonymous", + "System.Read", + "System.View", + "Task.Create", + "Task.Update", + "TenantManager.Query", + "TenantManager.Update", + "TransferService.Manage", + "TransferService.Monitor", + "Trust.Administer", + "Trust.Manage", + "TrustedAdmin.ConfigureHostCertificates", + "TrustedAdmin.ConfigureHostMetadata", + "TrustedAdmin.ConfigureTokenConversionPolicy", + "TrustedAdmin.ManageAttestingSSO", + "TrustedAdmin.ManageKMSTrust", + "TrustedAdmin.ManageTrustedHosts", + "TrustedAdmin.ReadAttestingSSO", + "TrustedAdmin.ReadKMSTrust", + "TrustedAdmin.ReadStsInfo", + "TrustedAdmin.ReadTrustedHosts", + "TrustedAdmin.RetrieveHostMetadata", + "TrustedAdmin.RetrieveTPMHostCertificates", + "VApp.ApplicationConfig", + "VApp.AssignResourcePool", + "VApp.AssignVApp", + "VApp.AssignVM", + "VApp.Clone", + "VApp.Create", + "VApp.Delete", + "VApp.Export", + "VApp.ExtractOvfEnvironment", + "VApp.Import", + "VApp.InstanceConfig", + "VApp.ManagedByConfig", + "VApp.Move", + "VApp.PowerOff", + "VApp.PowerOn", + "VApp.PullFromUrls", + "VApp.Rename", + "VApp.ResourceConfig", + "VApp.Suspend", + "VApp.Unregister", + "VcIdentityProviders.Create", + "VcIdentityProviders.Manage", + "VcIdentityProviders.Read", + "VcIntegrity.Baseline.com.vmware.vcIntegrity.AssignBaselines", + "VcIntegrity.Baseline.com.vmware.vcIntegrity.ManageBaselines", + "VcIntegrity.ClusterConfiguration.Export", + "VcIntegrity.ClusterConfiguration.Modify", + "VcIntegrity.ClusterConfiguration.Remediate", + "VcIntegrity.ClusterConfiguration.View", + "VcIntegrity.FileUpload.com.vmware.vcIntegrity.ImportFile", + "VcIntegrity.General.com.vmware.vcIntegrity.Configure", + "VcIntegrity.HardwareCompatibility.Read", + "VcIntegrity.HardwareCompatibility.Write", + "VcIntegrity.Updates.com.vmware.vcIntegrity.Remediate", + "VcIntegrity.Updates.com.vmware.vcIntegrity.Scan", + "VcIntegrity.Updates.com.vmware.vcIntegrity.Stage", + "VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus", + "VcIntegrity.lifecycleDepots.Delete", + "VcIntegrity.lifecycleGeneral.Read", + "VcIntegrity.lifecycleGeneral.Write", + "VcIntegrity.lifecycleHealth.Read", + "VcIntegrity.lifecycleHealth.Write", + "VcIntegrity.lifecycleSettings.Read", + "VcIntegrity.lifecycleSettings.Write", + "VcIntegrity.lifecycleSoftwareRemediation.Read", + "VcIntegrity.lifecycleSoftwareRemediation.Write", + "VcIntegrity.lifecycleSoftwareSpecification.Read", + "VcIntegrity.lifecycleSoftwareSpecification.Write", + "VcLifecycle.Upgrade", + "VcLifecycle.View", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.Annotation", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.ChangeTracking", + "VirtualMachine.Config.DiskExtend", + "VirtualMachine.Config.DiskLease", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.HostUSBDevice", + "VirtualMachine.Config.ManagedBy", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.MksControl", + "VirtualMachine.Config.QueryFTCompatibility", + "VirtualMachine.Config.QueryUnownedFiles", + "VirtualMachine.Config.RawDevice", + "VirtualMachine.Config.ReloadFromPath", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Rename", + "VirtualMachine.Config.ResetGuestInfo", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.Settings", + "VirtualMachine.Config.SwapPlacement", + "VirtualMachine.Config.ToggleForkParent", + "VirtualMachine.Config.UpgradeVirtualHardware", + "VirtualMachine.GuestOperations.Execute", + "VirtualMachine.GuestOperations.Modify", + "VirtualMachine.GuestOperations.ModifyAliases", + "VirtualMachine.GuestOperations.Query", + "VirtualMachine.GuestOperations.QueryAliases", + "VirtualMachine.Hbr.ConfigureReplication", + "VirtualMachine.Hbr.MonitorReplication", + "VirtualMachine.Hbr.ReplicaManagement", + "VirtualMachine.Interact.AnswerQuestion", + "VirtualMachine.Interact.Backup", + "VirtualMachine.Interact.ConsoleInteract", + "VirtualMachine.Interact.CreateScreenshot", + "VirtualMachine.Interact.CreateSecondary", + "VirtualMachine.Interact.DefragmentAllDisks", + "VirtualMachine.Interact.DeviceConnection", + "VirtualMachine.Interact.DisableSecondary", + "VirtualMachine.Interact.DnD", + "VirtualMachine.Interact.EnableSecondary", + "VirtualMachine.Interact.GuestControl", + "VirtualMachine.Interact.MakePrimary", + "VirtualMachine.Interact.Pause", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.PutUsbScanCodes", + "VirtualMachine.Interact.Record", + "VirtualMachine.Interact.Replay", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Interact.SESparseMaintenance", + "VirtualMachine.Interact.SetCDMedia", + "VirtualMachine.Interact.SetFloppyMedia", + "VirtualMachine.Interact.Suspend", + "VirtualMachine.Interact.SuspendToMemory", + "VirtualMachine.Interact.TerminateFaultTolerantVM", + "VirtualMachine.Interact.ToolsInstall", + "VirtualMachine.Interact.TurnOffFaultTolerance", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.CreateFromExisting", + "VirtualMachine.Inventory.Delete", + "VirtualMachine.Inventory.Move", + "VirtualMachine.Inventory.Register", + "VirtualMachine.Inventory.Unregister", + "VirtualMachine.Namespace.Event", + "VirtualMachine.Namespace.EventNotify", + "VirtualMachine.Namespace.Management", + "VirtualMachine.Namespace.ModifyContent", + "VirtualMachine.Namespace.Query", + "VirtualMachine.Namespace.ReadContent", + "VirtualMachine.Provisioning.Clone", + "VirtualMachine.Provisioning.CloneTemplate", + "VirtualMachine.Provisioning.CreateTemplateFromVM", + "VirtualMachine.Provisioning.Customize", + "VirtualMachine.Provisioning.DeployTemplate", + "VirtualMachine.Provisioning.DiskRandomAccess", + "VirtualMachine.Provisioning.DiskRandomRead", + "VirtualMachine.Provisioning.FileRandomAccess", + "VirtualMachine.Provisioning.GetVmFiles", + "VirtualMachine.Provisioning.MarkAsTemplate", + "VirtualMachine.Provisioning.MarkAsVM", + "VirtualMachine.Provisioning.ModifyCustSpecs", + "VirtualMachine.Provisioning.PromoteDisks", + "VirtualMachine.Provisioning.PutVmFiles", + "VirtualMachine.Provisioning.ReadCustSpecs", + "VirtualMachine.State.CreateSnapshot", + "VirtualMachine.State.RemoveSnapshot", + "VirtualMachine.State.RenameSnapshot", + "VirtualMachine.State.RevertToSnapshot", + "VirtualMachineClasses.Manage", + "Vsan.Cluster.ShallowRekey", + "vService.CreateDependency", + "vService.DestroyDependency", + "vService.ReconfigureDependency", + "vService.UpdateDependency", + "vSphereClient.UtilizeVerificationCode", + "vSphereDataProtection.Protection", + "vSphereDataProtection.Recovery", + "vStats.CollectAny", + "vStats.QueryAny", + "vStats.Settings" +] \ No newline at end of file diff --git a/pkg/config/static/cnsDatastorePrivs.json b/pkg/config/static/cnsDatastorePrivs.json new file mode 100644 index 0000000000000..cd5513ae9e4f6 --- /dev/null +++ b/pkg/config/static/cnsDatastorePrivs.json @@ -0,0 +1,6 @@ +[ + "Datastore.FileManagement", + "System.Anonymous", + "System.Read", + "System.View" +] diff --git a/pkg/config/static/cnsHostConfigStorage.json b/pkg/config/static/cnsHostConfigStorage.json new file mode 100644 index 0000000000000..152ddb0779bd1 --- /dev/null +++ b/pkg/config/static/cnsHostConfigStorage.json @@ -0,0 +1,5 @@ +[ + "System.Anonymous", + "System.Read", + "System.View" +] diff --git a/pkg/config/static/cnsSearchAndSpbmPrivs.json b/pkg/config/static/cnsSearchAndSpbmPrivs.json new file mode 100644 index 0000000000000..152ddb0779bd1 --- /dev/null +++ b/pkg/config/static/cnsSearchAndSpbmPrivs.json @@ -0,0 +1,5 @@ +[ + "System.Anonymous", + "System.Read", + "System.View" +] diff --git a/pkg/config/static/cnsVmPrivs.json b/pkg/config/static/cnsVmPrivs.json new file mode 100644 index 0000000000000..012a40fd7ff95 --- /dev/null +++ b/pkg/config/static/cnsVmPrivs.json @@ -0,0 +1,7 @@ +[ + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddRemoveDevice", + "System.Anonymous", + "System.Read", + "System.View" +] diff --git a/pkg/config/static/eksUserPrivs.json b/pkg/config/static/eksUserPrivs.json new file mode 100644 index 0000000000000..86b9f42c1c6c4 --- /dev/null +++ b/pkg/config/static/eksUserPrivs.json @@ -0,0 +1,58 @@ +[ + "ContentLibrary.AddLibraryItem", + "ContentLibrary.CheckInTemplate", + "ContentLibrary.CheckOutTemplate", + "ContentLibrary.CreateLocalLibrary", + "Datastore.AllocateSpace", + "Datastore.Browse", + "Datastore.FileManagement", + "Folder.Create", + "InventoryService.Tagging.AttachTag", + "InventoryService.Tagging.CreateCategory", + "InventoryService.Tagging.CreateTag", + "InventoryService.Tagging.DeleteCategory", + "InventoryService.Tagging.DeleteTag", + "InventoryService.Tagging.EditCategory", + "InventoryService.Tagging.EditTag", + "InventoryService.Tagging.ModifyUsedByForCategory", + "InventoryService.Tagging.ModifyUsedByForTag", + "InventoryService.Tagging.ObjectAttachable", + "Network.Assign", + "Resource.AssignVMToPool", + "ScheduledTask.Create", + "ScheduledTask.Delete", + "ScheduledTask.Edit", + "ScheduledTask.Run", + "StorageProfile.View", + "StorageViews.View", + "System.Anonymous", + "System.Read", + "System.View", + "VApp.Import", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.DiskExtend", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.RawDevice", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Settings", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.CreateFromExisting", + "VirtualMachine.Inventory.Delete", + "VirtualMachine.Provisioning.Clone", + "VirtualMachine.Provisioning.CloneTemplate", + "VirtualMachine.Provisioning.CreateTemplateFromVM", + "VirtualMachine.Provisioning.Customize", + "VirtualMachine.Provisioning.DeployTemplate", + "VirtualMachine.Provisioning.MarkAsTemplate", + "VirtualMachine.Provisioning.ReadCustSpecs", + "VirtualMachine.State.CreateSnapshot", + "VirtualMachine.State.RemoveSnapshot", + "VirtualMachine.State.RevertToSnapshot" +] diff --git a/pkg/config/static/globalPrivs.json b/pkg/config/static/globalPrivs.json new file mode 100644 index 0000000000000..4b9b2c098dfdd --- /dev/null +++ b/pkg/config/static/globalPrivs.json @@ -0,0 +1,19 @@ +[ + "ContentLibrary.AddLibraryItem", + "ContentLibrary.CheckInTemplate", + "ContentLibrary.CheckOutTemplate", + "ContentLibrary.CreateLocalLibrary", + "InventoryService.Tagging.AttachTag", + "InventoryService.Tagging.CreateCategory", + "InventoryService.Tagging.CreateTag", + "InventoryService.Tagging.DeleteCategory", + "InventoryService.Tagging.DeleteTag", + "InventoryService.Tagging.EditCategory", + "InventoryService.Tagging.EditTag", + "InventoryService.Tagging.ModifyUsedByForCategory", + "InventoryService.Tagging.ModifyUsedByForTag", + "InventoryService.Tagging.ObjectAttachable", + "System.Anonymous", + "System.Read", + "System.View" +] diff --git a/pkg/config/static/readOnlyPrivs.json b/pkg/config/static/readOnlyPrivs.json new file mode 100644 index 0000000000000..971f93ecb93a7 --- /dev/null +++ b/pkg/config/static/readOnlyPrivs.json @@ -0,0 +1,5 @@ +[ + "System.Anonymous", + "System.Read", + "System.View" +] diff --git a/pkg/config/vsphereuser.go b/pkg/config/vsphereuser.go new file mode 100644 index 0000000000000..236b37ecc4268 --- /dev/null +++ b/pkg/config/vsphereuser.go @@ -0,0 +1,82 @@ +package config + +import ( + _ "embed" + "os" +) + +const ( + EksavSphereUsernameKey = "EKSA_VSPHERE_USERNAME" + EksavSpherePasswordKey = "EKSA_VSPHERE_PASSWORD" + // Username and password for cloud provider + EksavSphereCPUsernameKey = "EKSA_VSPHERE_CP_USERNAME" + EksavSphereCPPasswordKey = "EKSA_VSPHERE_CP_PASSWORD" + // Username and password for the CSI driver + EksavSphereCSIUsernameKey = "EKSA_VSPHERE_CSI_USERNAME" + EksavSphereCSIPasswordKey = "EKSA_VSPHERE_CSI_PASSWORD" +) + +type VSphereUserConfig struct { + EksaVsphereUsername string + EksaVspherePassword string + EksaVsphereCPUsername string + EksaVsphereCPPassword string + EksaVsphereCSIUsername string + EksaVsphereCSIPassword string +} + +//go:embed static/globalPrivs.json +var VSphereGlobalPrivsFile string + +//go:embed static/eksUserPrivs.json +var VSphereUserPrivsFile string + +//go:embed static/adminPrivs.json +var VSphereAdminPrivsFile string + +//go:embed static/cnsDatastorePrivs.json +var VSphereCnsDatastorePrivsFile string + +//go:embed static/cnsSearchAndSpbmPrivs.json +var VSphereCnsSearchAndSpbmPrivsFile string + +//go:embed static/cnsVmPrivs.json +var VSphereCnsVmPrivsFile string + +//go:embed static/cnsHostConfigStorage.json +var VSphereCnsHostConfigStorageFile string + +//go:embed static/readOnlyPrivs.json +var VSphereReadOnlyPrivs string + +func NewVsphereUserConfig() *VSphereUserConfig { + eksaVsphereUsername := os.Getenv(EksavSphereUsernameKey) + eksaVspherePassword := os.Getenv(EksavSpherePasswordKey) + + // Cloud provider credentials + eksaCPUsername := os.Getenv(EksavSphereCPUsernameKey) + eksaCPPassword := os.Getenv(EksavSphereCPPasswordKey) + + if eksaCPUsername == "" { + eksaCPUsername = eksaVsphereUsername + eksaCPPassword = eksaVspherePassword + } + // CSI driver credentials + eksaCSIUsername := os.Getenv(EksavSphereCSIUsernameKey) + eksaCSIPassword := os.Getenv(EksavSphereCSIPasswordKey) + if eksaCSIUsername == "" { + eksaCSIUsername = eksaVsphereUsername + eksaCSIPassword = eksaVspherePassword + } + + vuc := VSphereUserConfig{ + eksaVsphereUsername, + eksaVspherePassword, + eksaCPUsername, + eksaCPPassword, + eksaCSIUsername, + eksaCSIPassword, + } + + return &vuc +} diff --git a/pkg/config/vsphereuser_test.go b/pkg/config/vsphereuser_test.go new file mode 100644 index 0000000000000..517789164dd38 --- /dev/null +++ b/pkg/config/vsphereuser_test.go @@ -0,0 +1,44 @@ +package config_test + +import ( + "testing" + + "github.com/aws/eks-anywhere/pkg/config" +) + +func TestNewVsphereUserConfig(t *testing.T) { + wantUsername := "FOO" + wantPassword := "BAR" + wantEnv := map[string]string{ + config.EksavSphereUsernameKey: wantUsername, + config.EksavSpherePasswordKey: wantPassword, + config.EksavSphereCPUsernameKey: "", + config.EksavSphereCPPasswordKey: "", + config.EksavSphereCSIUsernameKey: "", + config.EksavSphereCSIPasswordKey: "", + } + for k, v := range wantEnv { + t.Setenv(k, v) + } + vusc := config.NewVsphereUserConfig() + + if vusc.EksaVsphereUsername != wantUsername { + t.Fatalf("vusc.EksaVsphereUsername = %s, want %s", vusc.EksaVsphereUsername, wantUsername) + } + if vusc.EksaVsphereCSIUsername != wantUsername { + t.Fatalf("vusc.EksaVsphereCSIUsername = %s, want %s", vusc.EksaVsphereCSIUsername, wantUsername) + } + if vusc.EksaVsphereCPUsername != wantUsername { + t.Fatalf("vusc.EksaVsphereCPUsername = %s, want %s", vusc.EksaVsphereCPUsername, wantUsername) + } + + if vusc.EksaVspherePassword != wantPassword { + t.Fatalf("vusc.EksaVspherePassword = %s, want %s", vusc.EksaVspherePassword, wantPassword) + } + if vusc.EksaVsphereCSIPassword != wantPassword { + t.Fatalf("vusc.EksaVsphereCSIPassword = %s, want %s", vusc.EksaVsphereCSIPassword, wantPassword) + } + if vusc.EksaVsphereCPPassword != wantPassword { + t.Fatalf("vusc.EksaVsphereCPPassword = %s, want %s", vusc.EksaVsphereCPPassword, wantPassword) + } +} diff --git a/pkg/dependencies/factory.go b/pkg/dependencies/factory.go index e068d4a25a77a..603eec75260f7 100644 --- a/pkg/dependencies/factory.go +++ b/pkg/dependencies/factory.go @@ -25,6 +25,7 @@ import ( "github.com/aws/eks-anywhere/pkg/filewriter" gitfactory "github.com/aws/eks-anywhere/pkg/git/factory" "github.com/aws/eks-anywhere/pkg/gitops/flux" + "github.com/aws/eks-anywhere/pkg/govmomi" "github.com/aws/eks-anywhere/pkg/kubeconfig" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/manifests" @@ -1045,8 +1046,13 @@ func (f *Factory) WithVSphereValidator() *Factory { if f.dependencies.VSphereValidator != nil { return nil } - - f.dependencies.VSphereValidator = vsphere.NewValidator(f.dependencies.Govc, &networkutils.DefaultNetClient{}) + vcb := govmomi.NewVMOMIClientBuilder() + v := vsphere.NewValidator( + f.dependencies.Govc, + &networkutils.DefaultNetClient{}, + vcb, + ) + f.dependencies.VSphereValidator = v return nil }) diff --git a/pkg/executables/executables.go b/pkg/executables/executables.go index bd30321334342..d36b81e9d4db4 100644 --- a/pkg/executables/executables.go +++ b/pkg/executables/executables.go @@ -19,8 +19,8 @@ const ( ) var redactedEnvKeys = []string{ - vSphereUsernameKey, - vSpherePasswordKey, + config.EksavSphereUsernameKey, + config.EksavSpherePasswordKey, decoder.CloudStackCloudConfigB64SecretKey, eksaGithubTokenEnv, config.EksaAccessKeyIdEnv, diff --git a/pkg/executables/govc.go b/pkg/executables/govc.go index b72920e6b888b..3e3fb0e5f3edd 100644 --- a/pkg/executables/govc.go +++ b/pkg/executables/govc.go @@ -18,6 +18,7 @@ import ( "sigs.k8s.io/yaml" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" + "github.com/aws/eks-anywhere/pkg/config" "github.com/aws/eks-anywhere/pkg/filewriter" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/retrier" @@ -33,8 +34,6 @@ const ( govcDatacenterKey = "GOVC_DATACENTER" govcTlsHostsFile = "govc_known_hosts" govcTlsKnownHostsKey = "GOVC_TLS_KNOWN_HOSTS" - vSphereUsernameKey = "EKSA_VSPHERE_USERNAME" - vSpherePasswordKey = "EKSA_VSPHERE_PASSWORD" vSphereServerKey = "VSPHERE_SERVER" byteToGiB = 1073741824.0 DeployOptsFile = "deploy-opts.json" @@ -517,14 +516,14 @@ func (g *Govc) validateAndSetupCreds() (map[string]string, error) { var vSphereUsername, vSpherePassword, vSphereURL string var ok bool var envMap map[string]string - if vSphereUsername, ok = os.LookupEnv(vSphereUsernameKey); ok && len(vSphereUsername) > 0 { + if vSphereUsername, ok = os.LookupEnv(config.EksavSphereUsernameKey); ok && len(vSphereUsername) > 0 { if err := os.Setenv(govcUsernameKey, vSphereUsername); err != nil { return nil, fmt.Errorf("unable to set %s: %v", govcUsernameKey, err) } } else if govcUsername, ok := os.LookupEnv(govcUsernameKey); !ok || len(govcUsername) <= 0 { return nil, fmt.Errorf("%s is not set or is empty: %t", govcUsernameKey, ok) } - if vSpherePassword, ok = os.LookupEnv(vSpherePasswordKey); ok && len(vSpherePassword) > 0 { + if vSpherePassword, ok = os.LookupEnv(config.EksavSpherePasswordKey); ok && len(vSpherePassword) > 0 { if err := os.Setenv(govcPasswordKey, vSpherePassword); err != nil { return nil, fmt.Errorf("unable to set %s: %v", govcPasswordKey, err) } diff --git a/pkg/govmomi/authorizationmanagerbuilder.go b/pkg/govmomi/authorizationmanagerbuilder.go new file mode 100644 index 0000000000000..e9412ba19a24e --- /dev/null +++ b/pkg/govmomi/authorizationmanagerbuilder.go @@ -0,0 +1,12 @@ +package govmomi + +import ( + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25" +) + +type vMOMIAuthorizationManagerBuilder struct{} + +func (*vMOMIAuthorizationManagerBuilder) Build(c *vim25.Client) *object.AuthorizationManager { + return object.NewAuthorizationManager(c) +} diff --git a/pkg/govmomi/client.go b/pkg/govmomi/client.go new file mode 100644 index 0000000000000..e28dce630db81 --- /dev/null +++ b/pkg/govmomi/client.go @@ -0,0 +1,133 @@ +package govmomi + +import ( + "context" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" +) + +const ( + VSphereTypeFolder = "Folder" + VSphereTypeNetwork = "Network" + VSphereTypeResourcePool = "ResourcePool" + VSphereTypeDatastore = "Datastore" + VSphereTypeVirtualMachine = "VirtualMachine" +) + +type VMOMIAuthorizationManager interface { + FetchUserPrivilegeOnEntities(ctx context.Context, entities []types.ManagedObjectReference, userName string) ([]types.UserPrivilegeResult, error) +} + +type VMOMIFinder interface { + Datastore(ctx context.Context, path string) (*object.Datastore, error) + Folder(ctx context.Context, path string) (*object.Folder, error) + Network(ctx context.Context, path string) (object.NetworkReference, error) + ResourcePool(ctx context.Context, path string) (*object.ResourcePool, error) + VirtualMachine(ctx context.Context, path string) (*object.VirtualMachine, error) + Datacenter(ctx context.Context, path string) (*object.Datacenter, error) + SetDatacenter(dc *object.Datacenter) *find.Finder +} + +type VMOMIClient struct { + Gcvm *govmomi.Client + Finder VMOMIFinder + username string + AuthorizationManager VMOMIAuthorizationManager +} + +func NewVMOMIClientCustom(gcvm *govmomi.Client, f VMOMIFinder, username string, am VMOMIAuthorizationManager) *VMOMIClient { + return &VMOMIClient{ + Gcvm: gcvm, + Finder: f, + username: username, + AuthorizationManager: am, + } +} + +func (vsc *VMOMIClient) Username() string { + return vsc.username +} + +func (vsc *VMOMIClient) GetPrivsOnEntity(ctx context.Context, path string, objType string, username string) ([]string, error) { + var vSphereObjectReference types.ManagedObjectReference + emptyResult := []string{} + var err error + + switch objType { + + case VSphereTypeFolder: + vSphereObjectReference, err = vsc.getFolder(ctx, path) + case VSphereTypeNetwork: + vSphereObjectReference, err = vsc.getNetwork(ctx, path) + case VSphereTypeDatastore: + vSphereObjectReference, err = vsc.getDatastore(ctx, path) + case VSphereTypeResourcePool: + vSphereObjectReference, err = vsc.getResourcePool(ctx, path) + case VSphereTypeVirtualMachine: + vSphereObjectReference, err = vsc.getVirtualMachine(ctx, path) + } + + if err != nil { + return emptyResult, err + } + refs := []types.ManagedObjectReference{vSphereObjectReference} + + result, err := vsc.AuthorizationManager.FetchUserPrivilegeOnEntities(ctx, refs, username) + if err != nil { + return emptyResult, err + } + + if len(result) > 0 { + return result[0].Privileges, nil + } else { + return emptyResult, nil + } +} + +func (vsc *VMOMIClient) getFolder(ctx context.Context, path string) (types.ManagedObjectReference, error) { + obj, err := vsc.Finder.Folder(ctx, path) + if err != nil { + return types.ManagedObjectReference{}, err + } else { + return obj.Common.Reference(), nil + } +} + +func (vsc *VMOMIClient) getNetwork(ctx context.Context, path string) (types.ManagedObjectReference, error) { + obj, err := vsc.Finder.Network(ctx, path) + if err != nil { + return types.ManagedObjectReference{}, err + } else { + return obj.Reference(), nil + } +} + +func (vsc *VMOMIClient) getDatastore(ctx context.Context, path string) (types.ManagedObjectReference, error) { + obj, err := vsc.Finder.Datastore(ctx, path) + if err != nil { + return types.ManagedObjectReference{}, err + } else { + return obj.Common.Reference(), nil + } +} + +func (vsc *VMOMIClient) getResourcePool(ctx context.Context, path string) (types.ManagedObjectReference, error) { + obj, err := vsc.Finder.ResourcePool(ctx, path) + if err != nil { + return types.ManagedObjectReference{}, err + } else { + return obj.Common.Reference(), nil + } +} + +func (vsc *VMOMIClient) getVirtualMachine(ctx context.Context, path string) (types.ManagedObjectReference, error) { + obj, err := vsc.Finder.VirtualMachine(ctx, path) + if err != nil { + return types.ManagedObjectReference{}, err + } else { + return obj.Common.Reference(), nil + } +} diff --git a/pkg/govmomi/client_test.go b/pkg/govmomi/client_test.go new file mode 100644 index 0000000000000..070960d82f681 --- /dev/null +++ b/pkg/govmomi/client_test.go @@ -0,0 +1,219 @@ +package govmomi_test + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/golang/mock/gomock" + . "github.com/onsi/gomega" + govmomi_internal "github.com/vmware/govmomi" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/types" + + "github.com/aws/eks-anywhere/pkg/govmomi" + "github.com/aws/eks-anywhere/pkg/govmomi/mocks" +) + +type fields struct { + AuthorizationManager *mocks.MockVMOMIAuthorizationManager + Finder *mocks.MockVMOMIFinder + Path string +} + +func TestGetPrivsOnEntity(t *testing.T) { + ctx := context.Background() + username := "foobar" + wantPrivs := []string{"DoManyThings", "DoFewThings"} + results := []types.UserPrivilegeResult{ + { + Privileges: wantPrivs, + }, + } + errMsg := "No entity found" + + tests := []struct { + name string + objType string + path string + // prepare lets us initialize our mocks within the `tests` slice. Oftentimes it also proves useful for other initialization + prepare func(f *fields) + wantPrivs []string + wantErr string + }{ + { + name: "test folder call happy path", + objType: govmomi.VSphereTypeFolder, + path: "Datacenter/vm/my/directory", + wantPrivs: wantPrivs, + wantErr: "", + prepare: func(f *fields) { + obj := object.Folder{} + objRefs := []types.ManagedObjectReference{obj.Common.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(results, nil) + f.Finder.EXPECT().Folder(ctx, f.Path).Return(&obj, nil) + }, + }, + { + name: "test datastore call happy path", + objType: govmomi.VSphereTypeDatastore, + path: "Datacenter/datastore/LargeDatastore1", + wantPrivs: wantPrivs, + wantErr: "", + prepare: func(f *fields) { + obj := object.Datastore{} + objRefs := []types.ManagedObjectReference{obj.Common.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(results, nil) + f.Finder.EXPECT().Datastore(ctx, f.Path).Return(&obj, nil) + }, + }, + { + name: "test resource pool call happy path", + objType: govmomi.VSphereTypeResourcePool, + path: "Datacenter/host/cluster-02/MyResourcePool", + wantPrivs: wantPrivs, + wantErr: "", + prepare: func(f *fields) { + obj := object.ResourcePool{} + objRefs := []types.ManagedObjectReference{obj.Common.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(results, nil) + f.Finder.EXPECT().ResourcePool(ctx, f.Path).Return(&obj, nil) + }, + }, + { + name: "test virtual machine call happy path", + objType: govmomi.VSphereTypeVirtualMachine, + path: "Datacenter/vm/Templates/MyVMTemplate", + wantPrivs: wantPrivs, + wantErr: "", + prepare: func(f *fields) { + obj := object.VirtualMachine{} + objRefs := []types.ManagedObjectReference{obj.Common.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(results, nil) + f.Finder.EXPECT().VirtualMachine(ctx, f.Path).Return(&obj, nil) + }, + }, + { + name: "test network call happy path", + objType: govmomi.VSphereTypeNetwork, + path: "Datacenter/network/VM Network", + wantPrivs: wantPrivs, + wantErr: "", + prepare: func(f *fields) { + obj := object.Network{} + objRefs := []types.ManagedObjectReference{obj.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(results, nil) + f.Finder.EXPECT().Network(ctx, f.Path).Return(&obj, nil) + }, + }, + { + name: "test network call missing object", + objType: govmomi.VSphereTypeNetwork, + path: "Datacenter/network/VM Network", + wantPrivs: []string{}, + wantErr: errMsg, + prepare: func(f *fields) { + f.Finder.EXPECT().Network(ctx, f.Path).Return(nil, fmt.Errorf(errMsg)) + }, + }, + { + name: "test virtual machine call no privs", + objType: govmomi.VSphereTypeVirtualMachine, + path: "Datacenter/vm/Templates/MyVMTemplate", + wantPrivs: []string{}, + wantErr: errMsg, + prepare: func(f *fields) { + obj := object.VirtualMachine{} + objRefs := []types.ManagedObjectReference{obj.Common.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(nil, fmt.Errorf(errMsg)) + f.Finder.EXPECT().VirtualMachine(ctx, f.Path).Return(&obj, nil) + }, + }, + { + name: "test resource pool call missing object", + objType: govmomi.VSphereTypeResourcePool, + path: "Datacenter/host/cluster-02/MyResourcePool", + wantPrivs: []string{}, + wantErr: errMsg, + prepare: func(f *fields) { + f.Finder.EXPECT().ResourcePool(ctx, f.Path).Return(nil, fmt.Errorf(errMsg)) + }, + }, + { + name: "test folder call empty object results", + objType: govmomi.VSphereTypeFolder, + path: "Datacenter/vm/my/directory", + wantPrivs: []string{}, + wantErr: "", + prepare: func(f *fields) { + obj := object.Folder{} + objRefs := []types.ManagedObjectReference{obj.Common.Reference()} + f.AuthorizationManager.EXPECT().FetchUserPrivilegeOnEntities(ctx, objRefs, username).Return(nil, nil) + f.Finder.EXPECT().Folder(ctx, f.Path).Return(&obj, nil) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + am := mocks.NewMockVMOMIAuthorizationManager(ctrl) + finder := mocks.NewMockVMOMIFinder(ctrl) + f := &fields{ + AuthorizationManager: am, + Finder: finder, + Path: tt.path, + } + tt.prepare(f) + + g := NewWithT(t) + + vsc := govmomi.NewVMOMIClientCustom(nil, finder, username, am) + + privs, err := vsc.GetPrivsOnEntity(ctx, tt.path, tt.objType, username) + if tt.wantErr == "" { + g.Expect(err).To(Succeed()) + if !reflect.DeepEqual(privs, tt.wantPrivs) { + t.Fatalf("privs = %v, want %v", privs, wantPrivs) + } + } else { + g.Expect(err).To(MatchError(ContainSubstring(tt.wantErr))) + } + }, + ) + } +} + +func TestVMOMISessionBuilderBuild(t *testing.T) { + insecure := false + datacenter := "mydatacenter" + datacenterObject := object.Datacenter{} + ctx := context.Background() + ctrl := gomock.NewController(t) + gcb := mocks.NewMockVMOMISessionBuilder(ctrl) + + c := &govmomi_internal.Client{ + Client: &vim25.Client{}, + } + + gcb.EXPECT().Build(ctx, gomock.Any(), insecure).Return(c, nil) + + mockFinder := mocks.NewMockVMOMIFinder(ctrl) + mockFinder.EXPECT().Datacenter(ctx, datacenter).Return(&datacenterObject, nil) + mockFinder.EXPECT().SetDatacenter(gomock.Any()) + + vfb := mocks.NewMockVMOMIFinderBuilder(ctrl) + vfb.EXPECT().Build(c.Client, true).Return(mockFinder) + + amb := mocks.NewMockVMOMIAuthorizationManagerBuilder(ctrl) + amb.EXPECT().Build(c.Client) + + vcb := govmomi.NewVMOMIClientBuilderOverride(vfb, gcb, amb) + _, err := vcb.Build(ctx, "myhost", "myusername", "mypassword", insecure, datacenter) + if err != nil { + t.Fatalf("Failed to build client with %s", err) + } +} diff --git a/pkg/govmomi/clientbuilder.go b/pkg/govmomi/clientbuilder.go new file mode 100644 index 0000000000000..97985b53de68c --- /dev/null +++ b/pkg/govmomi/clientbuilder.go @@ -0,0 +1,69 @@ +package govmomi + +import ( + "context" + "net/url" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/soap" +) + +type VSphereClient interface { + Username() string + GetPrivsOnEntity(ctx context.Context, path string, objType string, username string) ([]string, error) +} + +type VMOMIFinderBuilder interface { + Build(arg0 *vim25.Client, arg1 ...bool) VMOMIFinder +} + +type VMOMISessionBuilder interface { + Build(ctx context.Context, u *url.URL, insecure bool) (*govmomi.Client, error) +} + +type VMOMIAuthorizationManagerBuilder interface { + Build(c *vim25.Client) *object.AuthorizationManager +} + +type vMOMIClientBuilder struct { + vfb VMOMIFinderBuilder + gcb VMOMISessionBuilder + amb VMOMIAuthorizationManagerBuilder +} + +func NewVMOMIClientBuilder() *vMOMIClientBuilder { + return &vMOMIClientBuilder{vfb: &vMOMIFinderBuilder{}, gcb: &vMOMISessionBuilder{}, amb: &vMOMIAuthorizationManagerBuilder{}} +} + +func NewVMOMIClientBuilderOverride(vfb VMOMIFinderBuilder, gcb VMOMISessionBuilder, amb VMOMIAuthorizationManagerBuilder) *vMOMIClientBuilder { + return &vMOMIClientBuilder{vfb: vfb, gcb: gcb, amb: amb} +} + +func (vcb *vMOMIClientBuilder) Build(ctx context.Context, host string, username string, password string, insecure bool, datacenter string) (VSphereClient, error) { + u, err := soap.ParseURL(host) + u.User = url.UserPassword(username, password) + if err != nil { + return nil, err + } + + // start gvmc + gvmc, err := vcb.gcb.Build(ctx, u, insecure) + if err != nil { + return nil, err + } + + f := vcb.vfb.Build(gvmc.Client, true) + + dc, err := f.Datacenter(ctx, datacenter) + if err != nil { + return nil, err + } + + f.SetDatacenter(dc) + + am := vcb.amb.Build(gvmc.Client) + + return &VMOMIClient{gvmc, f, username, am}, nil +} diff --git a/pkg/govmomi/finderbuilder.go b/pkg/govmomi/finderbuilder.go new file mode 100644 index 0000000000000..82ba8e44a0f78 --- /dev/null +++ b/pkg/govmomi/finderbuilder.go @@ -0,0 +1,16 @@ +package govmomi + +import ( + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/vim25" +) + +type vMOMIFinderBuilder struct{} + +func NewVMOMIFinderBuilder() *vMOMIFinderBuilder { + return &vMOMIFinderBuilder{} +} + +func (*vMOMIFinderBuilder) Build(client *vim25.Client, all ...bool) VMOMIFinder { + return find.NewFinder(client, all...) +} diff --git a/pkg/govmomi/mocks/client.go b/pkg/govmomi/mocks/client.go new file mode 100644 index 0000000000000..2965c7e48c387 --- /dev/null +++ b/pkg/govmomi/mocks/client.go @@ -0,0 +1,353 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/eks-anywhere/pkg/govmomi (interfaces: VSphereClient,VMOMIAuthorizationManager,VMOMIFinder,VMOMISessionBuilder,VMOMIFinderBuilder,VMOMIAuthorizationManagerBuilder) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + url "net/url" + reflect "reflect" + + govmomi "github.com/aws/eks-anywhere/pkg/govmomi" + gomock "github.com/golang/mock/gomock" + govmomi0 "github.com/vmware/govmomi" + find "github.com/vmware/govmomi/find" + object "github.com/vmware/govmomi/object" + vim25 "github.com/vmware/govmomi/vim25" + types "github.com/vmware/govmomi/vim25/types" +) + +// MockVSphereClient is a mock of VSphereClient interface. +type MockVSphereClient struct { + ctrl *gomock.Controller + recorder *MockVSphereClientMockRecorder +} + +// MockVSphereClientMockRecorder is the mock recorder for MockVSphereClient. +type MockVSphereClientMockRecorder struct { + mock *MockVSphereClient +} + +// NewMockVSphereClient creates a new mock instance. +func NewMockVSphereClient(ctrl *gomock.Controller) *MockVSphereClient { + mock := &MockVSphereClient{ctrl: ctrl} + mock.recorder = &MockVSphereClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVSphereClient) EXPECT() *MockVSphereClientMockRecorder { + return m.recorder +} + +// GetPrivsOnEntity mocks base method. +func (m *MockVSphereClient) GetPrivsOnEntity(arg0 context.Context, arg1, arg2, arg3 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPrivsOnEntity", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPrivsOnEntity indicates an expected call of GetPrivsOnEntity. +func (mr *MockVSphereClientMockRecorder) GetPrivsOnEntity(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivsOnEntity", reflect.TypeOf((*MockVSphereClient)(nil).GetPrivsOnEntity), arg0, arg1, arg2, arg3) +} + +// Username mocks base method. +func (m *MockVSphereClient) Username() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Username") + ret0, _ := ret[0].(string) + return ret0 +} + +// Username indicates an expected call of Username. +func (mr *MockVSphereClientMockRecorder) Username() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Username", reflect.TypeOf((*MockVSphereClient)(nil).Username)) +} + +// MockVMOMIAuthorizationManager is a mock of VMOMIAuthorizationManager interface. +type MockVMOMIAuthorizationManager struct { + ctrl *gomock.Controller + recorder *MockVMOMIAuthorizationManagerMockRecorder +} + +// MockVMOMIAuthorizationManagerMockRecorder is the mock recorder for MockVMOMIAuthorizationManager. +type MockVMOMIAuthorizationManagerMockRecorder struct { + mock *MockVMOMIAuthorizationManager +} + +// NewMockVMOMIAuthorizationManager creates a new mock instance. +func NewMockVMOMIAuthorizationManager(ctrl *gomock.Controller) *MockVMOMIAuthorizationManager { + mock := &MockVMOMIAuthorizationManager{ctrl: ctrl} + mock.recorder = &MockVMOMIAuthorizationManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVMOMIAuthorizationManager) EXPECT() *MockVMOMIAuthorizationManagerMockRecorder { + return m.recorder +} + +// FetchUserPrivilegeOnEntities mocks base method. +func (m *MockVMOMIAuthorizationManager) FetchUserPrivilegeOnEntities(arg0 context.Context, arg1 []types.ManagedObjectReference, arg2 string) ([]types.UserPrivilegeResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchUserPrivilegeOnEntities", arg0, arg1, arg2) + ret0, _ := ret[0].([]types.UserPrivilegeResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchUserPrivilegeOnEntities indicates an expected call of FetchUserPrivilegeOnEntities. +func (mr *MockVMOMIAuthorizationManagerMockRecorder) FetchUserPrivilegeOnEntities(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchUserPrivilegeOnEntities", reflect.TypeOf((*MockVMOMIAuthorizationManager)(nil).FetchUserPrivilegeOnEntities), arg0, arg1, arg2) +} + +// MockVMOMIFinder is a mock of VMOMIFinder interface. +type MockVMOMIFinder struct { + ctrl *gomock.Controller + recorder *MockVMOMIFinderMockRecorder +} + +// MockVMOMIFinderMockRecorder is the mock recorder for MockVMOMIFinder. +type MockVMOMIFinderMockRecorder struct { + mock *MockVMOMIFinder +} + +// NewMockVMOMIFinder creates a new mock instance. +func NewMockVMOMIFinder(ctrl *gomock.Controller) *MockVMOMIFinder { + mock := &MockVMOMIFinder{ctrl: ctrl} + mock.recorder = &MockVMOMIFinderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVMOMIFinder) EXPECT() *MockVMOMIFinderMockRecorder { + return m.recorder +} + +// Datacenter mocks base method. +func (m *MockVMOMIFinder) Datacenter(arg0 context.Context, arg1 string) (*object.Datacenter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Datacenter", arg0, arg1) + ret0, _ := ret[0].(*object.Datacenter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Datacenter indicates an expected call of Datacenter. +func (mr *MockVMOMIFinderMockRecorder) Datacenter(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Datacenter", reflect.TypeOf((*MockVMOMIFinder)(nil).Datacenter), arg0, arg1) +} + +// Datastore mocks base method. +func (m *MockVMOMIFinder) Datastore(arg0 context.Context, arg1 string) (*object.Datastore, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Datastore", arg0, arg1) + ret0, _ := ret[0].(*object.Datastore) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Datastore indicates an expected call of Datastore. +func (mr *MockVMOMIFinderMockRecorder) Datastore(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Datastore", reflect.TypeOf((*MockVMOMIFinder)(nil).Datastore), arg0, arg1) +} + +// Folder mocks base method. +func (m *MockVMOMIFinder) Folder(arg0 context.Context, arg1 string) (*object.Folder, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Folder", arg0, arg1) + ret0, _ := ret[0].(*object.Folder) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Folder indicates an expected call of Folder. +func (mr *MockVMOMIFinderMockRecorder) Folder(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Folder", reflect.TypeOf((*MockVMOMIFinder)(nil).Folder), arg0, arg1) +} + +// Network mocks base method. +func (m *MockVMOMIFinder) Network(arg0 context.Context, arg1 string) (object.NetworkReference, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Network", arg0, arg1) + ret0, _ := ret[0].(object.NetworkReference) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Network indicates an expected call of Network. +func (mr *MockVMOMIFinderMockRecorder) Network(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*MockVMOMIFinder)(nil).Network), arg0, arg1) +} + +// ResourcePool mocks base method. +func (m *MockVMOMIFinder) ResourcePool(arg0 context.Context, arg1 string) (*object.ResourcePool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ResourcePool", arg0, arg1) + ret0, _ := ret[0].(*object.ResourcePool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResourcePool indicates an expected call of ResourcePool. +func (mr *MockVMOMIFinderMockRecorder) ResourcePool(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourcePool", reflect.TypeOf((*MockVMOMIFinder)(nil).ResourcePool), arg0, arg1) +} + +// SetDatacenter mocks base method. +func (m *MockVMOMIFinder) SetDatacenter(arg0 *object.Datacenter) *find.Finder { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetDatacenter", arg0) + ret0, _ := ret[0].(*find.Finder) + return ret0 +} + +// SetDatacenter indicates an expected call of SetDatacenter. +func (mr *MockVMOMIFinderMockRecorder) SetDatacenter(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDatacenter", reflect.TypeOf((*MockVMOMIFinder)(nil).SetDatacenter), arg0) +} + +// VirtualMachine mocks base method. +func (m *MockVMOMIFinder) VirtualMachine(arg0 context.Context, arg1 string) (*object.VirtualMachine, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VirtualMachine", arg0, arg1) + ret0, _ := ret[0].(*object.VirtualMachine) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VirtualMachine indicates an expected call of VirtualMachine. +func (mr *MockVMOMIFinderMockRecorder) VirtualMachine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VirtualMachine", reflect.TypeOf((*MockVMOMIFinder)(nil).VirtualMachine), arg0, arg1) +} + +// MockVMOMISessionBuilder is a mock of VMOMISessionBuilder interface. +type MockVMOMISessionBuilder struct { + ctrl *gomock.Controller + recorder *MockVMOMISessionBuilderMockRecorder +} + +// MockVMOMISessionBuilderMockRecorder is the mock recorder for MockVMOMISessionBuilder. +type MockVMOMISessionBuilderMockRecorder struct { + mock *MockVMOMISessionBuilder +} + +// NewMockVMOMISessionBuilder creates a new mock instance. +func NewMockVMOMISessionBuilder(ctrl *gomock.Controller) *MockVMOMISessionBuilder { + mock := &MockVMOMISessionBuilder{ctrl: ctrl} + mock.recorder = &MockVMOMISessionBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVMOMISessionBuilder) EXPECT() *MockVMOMISessionBuilderMockRecorder { + return m.recorder +} + +// Build mocks base method. +func (m *MockVMOMISessionBuilder) Build(arg0 context.Context, arg1 *url.URL, arg2 bool) (*govmomi0.Client, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Build", arg0, arg1, arg2) + ret0, _ := ret[0].(*govmomi0.Client) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Build indicates an expected call of Build. +func (mr *MockVMOMISessionBuilderMockRecorder) Build(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockVMOMISessionBuilder)(nil).Build), arg0, arg1, arg2) +} + +// MockVMOMIFinderBuilder is a mock of VMOMIFinderBuilder interface. +type MockVMOMIFinderBuilder struct { + ctrl *gomock.Controller + recorder *MockVMOMIFinderBuilderMockRecorder +} + +// MockVMOMIFinderBuilderMockRecorder is the mock recorder for MockVMOMIFinderBuilder. +type MockVMOMIFinderBuilderMockRecorder struct { + mock *MockVMOMIFinderBuilder +} + +// NewMockVMOMIFinderBuilder creates a new mock instance. +func NewMockVMOMIFinderBuilder(ctrl *gomock.Controller) *MockVMOMIFinderBuilder { + mock := &MockVMOMIFinderBuilder{ctrl: ctrl} + mock.recorder = &MockVMOMIFinderBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVMOMIFinderBuilder) EXPECT() *MockVMOMIFinderBuilderMockRecorder { + return m.recorder +} + +// Build mocks base method. +func (m *MockVMOMIFinderBuilder) Build(arg0 *vim25.Client, arg1 ...bool) govmomi.VMOMIFinder { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Build", varargs...) + ret0, _ := ret[0].(govmomi.VMOMIFinder) + return ret0 +} + +// Build indicates an expected call of Build. +func (mr *MockVMOMIFinderBuilderMockRecorder) Build(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockVMOMIFinderBuilder)(nil).Build), varargs...) +} + +// MockVMOMIAuthorizationManagerBuilder is a mock of VMOMIAuthorizationManagerBuilder interface. +type MockVMOMIAuthorizationManagerBuilder struct { + ctrl *gomock.Controller + recorder *MockVMOMIAuthorizationManagerBuilderMockRecorder +} + +// MockVMOMIAuthorizationManagerBuilderMockRecorder is the mock recorder for MockVMOMIAuthorizationManagerBuilder. +type MockVMOMIAuthorizationManagerBuilderMockRecorder struct { + mock *MockVMOMIAuthorizationManagerBuilder +} + +// NewMockVMOMIAuthorizationManagerBuilder creates a new mock instance. +func NewMockVMOMIAuthorizationManagerBuilder(ctrl *gomock.Controller) *MockVMOMIAuthorizationManagerBuilder { + mock := &MockVMOMIAuthorizationManagerBuilder{ctrl: ctrl} + mock.recorder = &MockVMOMIAuthorizationManagerBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVMOMIAuthorizationManagerBuilder) EXPECT() *MockVMOMIAuthorizationManagerBuilderMockRecorder { + return m.recorder +} + +// Build mocks base method. +func (m *MockVMOMIAuthorizationManagerBuilder) Build(arg0 *vim25.Client) *object.AuthorizationManager { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Build", arg0) + ret0, _ := ret[0].(*object.AuthorizationManager) + return ret0 +} + +// Build indicates an expected call of Build. +func (mr *MockVMOMIAuthorizationManagerBuilderMockRecorder) Build(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockVMOMIAuthorizationManagerBuilder)(nil).Build), arg0) +} diff --git a/pkg/govmomi/sessionbuilder.go b/pkg/govmomi/sessionbuilder.go new file mode 100644 index 0000000000000..c7b229e12fa49 --- /dev/null +++ b/pkg/govmomi/sessionbuilder.go @@ -0,0 +1,18 @@ +package govmomi + +import ( + "context" + "net/url" + + "github.com/vmware/govmomi" +) + +type vMOMISessionBuilder struct{} + +func NewvMOMISessionBuilder() *vMOMIClientBuilder { + return &vMOMIClientBuilder{} +} + +func (*vMOMISessionBuilder) Build(ctx context.Context, u *url.URL, insecure bool) (*govmomi.Client, error) { + return govmomi.NewClient(ctx, u, insecure) +} diff --git a/pkg/providers/vsphere/envars.go b/pkg/providers/vsphere/envars.go index f3071a05c712f..c25bcee1051cc 100644 --- a/pkg/providers/vsphere/envars.go +++ b/pkg/providers/vsphere/envars.go @@ -6,23 +6,24 @@ import ( "strconv" anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" + "github.com/aws/eks-anywhere/pkg/config" ) func SetupEnvVars(datacenterConfig *anywherev1.VSphereDatacenterConfig) error { - if vSphereUsername, ok := os.LookupEnv(EksavSphereUsernameKey); ok && len(vSphereUsername) > 0 { + if vSphereUsername, ok := os.LookupEnv(config.EksavSphereUsernameKey); ok && len(vSphereUsername) > 0 { if err := os.Setenv(vSphereUsernameKey, vSphereUsername); err != nil { - return fmt.Errorf("unable to set %s: %v", EksavSphereUsernameKey, err) + return fmt.Errorf("unable to set %s: %v", config.EksavSphereUsernameKey, err) } } else { - return fmt.Errorf("%s is not set or is empty", EksavSphereUsernameKey) + return fmt.Errorf("%s is not set or is empty", config.EksavSphereUsernameKey) } - if vSpherePassword, ok := os.LookupEnv(EksavSpherePasswordKey); ok && len(vSpherePassword) > 0 { + if vSpherePassword, ok := os.LookupEnv(config.EksavSpherePasswordKey); ok && len(vSpherePassword) > 0 { if err := os.Setenv(vSpherePasswordKey, vSpherePassword); err != nil { - return fmt.Errorf("unable to set %s: %v", EksavSpherePasswordKey, err) + return fmt.Errorf("unable to set %s: %v", config.EksavSpherePasswordKey, err) } } else { - return fmt.Errorf("%s is not set or is empty", EksavSpherePasswordKey) + return fmt.Errorf("%s is not set or is empty", config.EksavSpherePasswordKey) } if err := os.Setenv(vSphereServerKey, datacenterConfig.Spec.Server); err != nil { diff --git a/pkg/providers/vsphere/reconciler/reconciler.go b/pkg/providers/vsphere/reconciler/reconciler.go index 6c9fd145f9fad..6f53278d31d08 100644 --- a/pkg/providers/vsphere/reconciler/reconciler.go +++ b/pkg/providers/vsphere/reconciler/reconciler.go @@ -17,6 +17,7 @@ import ( anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" c "github.com/aws/eks-anywhere/pkg/cluster" + "github.com/aws/eks-anywhere/pkg/config" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/controller" "github.com/aws/eks-anywhere/pkg/controller/clientutil" @@ -77,12 +78,12 @@ func SetupEnvVars(ctx context.Context, vsphereDatacenter *anywherev1.VSphereData vsphereUsername := secret.Data["username"] vspherePassword := secret.Data["password"] - if err := os.Setenv(vsphere.EksavSphereUsernameKey, string(vsphereUsername)); err != nil { - return fmt.Errorf("failed setting env %s: %v", vsphere.EksavSphereUsernameKey, err) + if err := os.Setenv(config.EksavSphereUsernameKey, string(vsphereUsername)); err != nil { + return fmt.Errorf("failed setting env %s: %v", config.EksavSphereUsernameKey, err) } - if err := os.Setenv(vsphere.EksavSpherePasswordKey, string(vspherePassword)); err != nil { - return fmt.Errorf("failed setting env %s: %v", vsphere.EksavSpherePasswordKey, err) + if err := os.Setenv(config.EksavSpherePasswordKey, string(vspherePassword)); err != nil { + return fmt.Errorf("failed setting env %s: %v", config.EksavSpherePasswordKey, err) } if err := vsphere.SetupEnvVars(vsphereDatacenter); err != nil { diff --git a/pkg/providers/vsphere/validator.go b/pkg/providers/vsphere/validator.go index d177163f9a5f7..f90780a77b2a2 100644 --- a/pkg/providers/vsphere/validator.go +++ b/pkg/providers/vsphere/validator.go @@ -2,25 +2,46 @@ package vsphere import ( "context" + _ "embed" + "encoding/json" "errors" "fmt" "net" + "path/filepath" anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" + "github.com/aws/eks-anywhere/pkg/config" + "github.com/aws/eks-anywhere/pkg/govmomi" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/networkutils" "github.com/aws/eks-anywhere/pkg/types" ) +const ( + vsphereRootPath = "/" +) + +type PrivAssociation struct { + objectType string + privsContent string + path string +} + +type VSphereClientBuilder interface { + Build(ctx context.Context, host string, username string, password string, insecure bool, datacenter string) (govmomi.VSphereClient, error) +} + type Validator struct { - govc ProviderGovcClient - netClient networkutils.NetClient + govc ProviderGovcClient + netClient networkutils.NetClient + vSphereClientBuilder VSphereClientBuilder } -func NewValidator(govc ProviderGovcClient, netClient networkutils.NetClient) *Validator { +func NewValidator(govc ProviderGovcClient, netClient networkutils.NetClient, vscb VSphereClientBuilder) *Validator { return &Validator{ - govc: govc, - netClient: netClient, + govc: govc, + netClient: netClient, + vSphereClientBuilder: vscb, } } @@ -385,3 +406,234 @@ func (v *Validator) validateControlPlaneIpUniqueness(spec *Spec) error { } return nil } + +func (v *Validator) collectSpecMachineConfigs(ctx context.Context, spec *Spec) ([]*anywherev1.VSphereMachineConfig, error) { + controlPlaneMachineConfig := spec.controlPlaneMachineConfig() + machineConfigs := []*anywherev1.VSphereMachineConfig{controlPlaneMachineConfig} + + for _, workerNodeGroupConfiguration := range spec.Cluster.Spec.WorkerNodeGroupConfigurations { + workerNodeGroupMachineConfig := spec.workerMachineConfig(workerNodeGroupConfiguration) + machineConfigs = append(machineConfigs, workerNodeGroupMachineConfig) + } + + if spec.Cluster.Spec.ExternalEtcdConfiguration != nil { + etcdMachineConfig := spec.etcdMachineConfig() + machineConfigs = append(machineConfigs, etcdMachineConfig) + } + + return machineConfigs, nil +} + +func (v *Validator) validateUserPrivs(ctx context.Context, spec *Spec, vuc *config.VSphereUserConfig) error { + machineConfigs, err := v.collectSpecMachineConfigs(ctx, spec) + if err != nil { + return err + } + + requiredPrivAssociations := []PrivAssociation{ + // validate global root priv settings are correct + { + objectType: govmomi.VSphereTypeFolder, + privsContent: config.VSphereGlobalPrivsFile, + path: vsphereRootPath, + }, + { + objectType: govmomi.VSphereTypeNetwork, + privsContent: config.VSphereUserPrivsFile, + path: spec.datacenterConfig.Spec.Network, + }, + } + + var pas []PrivAssociation + for _, mc := range machineConfigs { + pas = []PrivAssociation{ + // validate object-level priv settings are correct + { + objectType: govmomi.VSphereTypeDatastore, + privsContent: config.VSphereUserPrivsFile, + path: mc.Spec.Datastore, + }, + { + objectType: govmomi.VSphereTypeResourcePool, + privsContent: config.VSphereUserPrivsFile, + path: mc.Spec.ResourcePool, + }, + // validate Administrator role (all privs) on VM folder and Template folder + { + objectType: govmomi.VSphereTypeFolder, + privsContent: config.VSphereAdminPrivsFile, + path: mc.Spec.Folder, + }, + // ToDo: add more sophisticated validation around a scenario where someone has uploaded templates + // on their own and does not want to allow EKSA user write access to templates + // Verify privs on the template + { + objectType: govmomi.VSphereTypeVirtualMachine, + privsContent: config.VSphereAdminPrivsFile, + path: mc.Spec.Template, + }, + // Verify privs on the template directory + { + objectType: govmomi.VSphereTypeFolder, + privsContent: config.VSphereAdminPrivsFile, + path: filepath.Dir(mc.Spec.Template), + }, + } + + requiredPrivAssociations = append(requiredPrivAssociations, pas...) + + } + + host := spec.datacenterConfig.Spec.Server + datacenter := spec.datacenterConfig.Spec.Datacenter + + vsc, err := v.vSphereClientBuilder.Build( + ctx, + host, + vuc.EksaVsphereUsername, + vuc.EksaVspherePassword, + spec.datacenterConfig.Spec.Insecure, + datacenter, + ) + if err != nil { + return err + } + + return v.validatePrivs(ctx, requiredPrivAssociations, vsc) +} + +func (v *Validator) validateCSIUserPrivs(ctx context.Context, spec *Spec, vuc *config.VSphereUserConfig) error { + requiredPrivAssociations := []PrivAssociation{ + { // CNS-SEARCH-AND-SPBM role + objectType: govmomi.VSphereTypeFolder, + privsContent: config.VSphereCnsSearchAndSpbmPrivsFile, + path: vsphereRootPath, + }, + } + + machineConfigs, err := v.collectSpecMachineConfigs(ctx, spec) + if err != nil { + return err + } + + var pas []PrivAssociation + for _, mc := range machineConfigs { + pas = []PrivAssociation{ + { // CNS-Datastore role + objectType: govmomi.VSphereTypeDatastore, + privsContent: config.VSphereCnsDatastorePrivsFile, + path: mc.Spec.Datastore, + }, + + { // CNS-VM role + objectType: govmomi.VSphereTypeFolder, + privsContent: config.VSphereCnsVmPrivsFile, + path: mc.Spec.Folder, + }, + // CNS-HOST-CONFIG-STORAGE role + { + objectType: govmomi.VSphereTypeDatastore, + privsContent: config.VSphereCnsHostConfigStorageFile, + path: mc.Spec.Datastore, + }, + } + requiredPrivAssociations = append(requiredPrivAssociations, pas...) + } + + host := spec.datacenterConfig.Spec.Server + datacenter := spec.datacenterConfig.Spec.Datacenter + + vsc, err := v.vSphereClientBuilder.Build( + ctx, + host, + vuc.EksaVsphereCSIUsername, + vuc.EksaVsphereCSIPassword, + spec.datacenterConfig.Spec.Insecure, + datacenter, + ) + if err != nil { + return err + } + + return v.validatePrivs(ctx, requiredPrivAssociations, vsc) +} + +func (v *Validator) validateCPUserPrivs(ctx context.Context, spec *Spec, vuc *config.VSphereUserConfig) error { + // CP role just needs read only + privObjs := []PrivAssociation{ + { + objectType: govmomi.VSphereTypeFolder, + privsContent: config.VSphereReadOnlyPrivs, + path: vsphereRootPath, + }, + } + + host := spec.datacenterConfig.Spec.Server + datacenter := spec.datacenterConfig.Spec.Datacenter + + vsc, err := v.vSphereClientBuilder.Build( + ctx, + host, + vuc.EksaVsphereCPUsername, + vuc.EksaVsphereCPPassword, + spec.datacenterConfig.Spec.Insecure, + datacenter, + ) + if err != nil { + return err + } + + return v.validatePrivs(ctx, privObjs, vsc) +} + +func (v *Validator) validatePrivs(ctx context.Context, privObjs []PrivAssociation, vsc govmomi.VSphereClient) error { + var missingPrivs []string + var err error + + for _, obj := range privObjs { + path := obj.path + privsContent := obj.privsContent + t := obj.objectType + missingPrivs, err = v.getMissingPrivs(ctx, vsc, path, t, privsContent, vsc.Username()) + if err != nil { + return err + } else if len(missingPrivs) > 0 { + return fmt.Errorf("User %s missing privileges on %s: %v", vsc.Username(), path, missingPrivs) + } + } + + return nil +} + +func checkRequiredPrivs(requiredPrivs []string, hasPrivs []string) []string { + hp := map[string]interface{}{} + for _, val := range hasPrivs { + hp[val] = 1 + } + + missingPrivs := []string{} + for _, p := range requiredPrivs { + if _, ok := hp[p]; !ok { + missingPrivs = append(missingPrivs, p) + } + } + + return missingPrivs +} + +func (v *Validator) getMissingPrivs(ctx context.Context, vsc govmomi.VSphereClient, path string, objType string, requiredPrivsContent string, username string) ([]string, error) { + var requiredPrivs []string + err := json.Unmarshal([]byte(requiredPrivsContent), &requiredPrivs) + if err != nil { + return nil, err + } + + hasPrivs, err := vsc.GetPrivsOnEntity(ctx, path, objType, username) + if err != nil { + return nil, err + } + + missingPrivs := checkRequiredPrivs(requiredPrivs, hasPrivs) + + return missingPrivs, nil +} diff --git a/pkg/providers/vsphere/validator_test.go b/pkg/providers/vsphere/validator_test.go new file mode 100644 index 0000000000000..5713f44390c1e --- /dev/null +++ b/pkg/providers/vsphere/validator_test.go @@ -0,0 +1,101 @@ +package vsphere + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/golang/mock/gomock" + . "github.com/onsi/gomega" + + "github.com/aws/eks-anywhere/pkg/config" + "github.com/aws/eks-anywhere/pkg/govmomi" + "github.com/aws/eks-anywhere/pkg/govmomi/mocks" +) + +func TestValidatorValidatePrivs(t *testing.T) { + v := Validator{} + + ctrl := gomock.NewController(t) + vsc := mocks.NewMockVSphereClient(ctrl) + + ctx := context.Background() + networkPath := "/Datacenter/network/path/foo" + + objects := []PrivAssociation{ + { + objectType: govmomi.VSphereTypeNetwork, + privsContent: config.VSphereUserPrivsFile, + path: networkPath, + }, + } + + var privs []string + err := json.Unmarshal([]byte(config.VSphereAdminPrivsFile), &privs) + if err != nil { + t.Fatalf("failed to validate privs: %v", err) + } + vsc.EXPECT().Username().Return("foobar") + vsc.EXPECT().GetPrivsOnEntity(ctx, networkPath, govmomi.VSphereTypeNetwork, "foobar").Return(privs, nil) + + err = v.validatePrivs(ctx, objects, vsc) + if err != nil { + t.Fatalf("failed to validate privs: %v", err) + } +} + +func TestValidatorValidatePrivsError(t *testing.T) { + v := Validator{} + + ctrl := gomock.NewController(t) + vsc := mocks.NewMockVSphereClient(ctrl) + + ctx := context.Background() + networkPath := "/Datacenter/network/path/foo" + + objects := []PrivAssociation{ + { + objectType: govmomi.VSphereTypeNetwork, + privsContent: config.VSphereUserPrivsFile, + path: networkPath, + }, + } + + var privs []string + err := json.Unmarshal([]byte(config.VSphereAdminPrivsFile), &privs) + if err != nil { + t.Fatalf("failed to validate privs: %v", err) + } + errMsg := "Could not retrieve privs" + g := NewWithT(t) + vsc.EXPECT().Username().Return("foobar") + vsc.EXPECT().GetPrivsOnEntity(ctx, networkPath, govmomi.VSphereTypeNetwork, "foobar").Return(nil, fmt.Errorf(errMsg)) + + err = v.validatePrivs(ctx, objects, vsc) + g.Expect(err).To(MatchError(ContainSubstring(errMsg))) +} + +func TestValidatorValidatePrivsBadJson(t *testing.T) { + v := Validator{} + + ctrl := gomock.NewController(t) + vsc := mocks.NewMockVSphereClient(ctrl) + vsc.EXPECT().Username().Return("foobar") + + ctx := context.Background() + networkPath := "/Datacenter/network/path/foo" + g := NewWithT(t) + errMsg := "invalid character 'h' in literal true (expecting 'r')" + + objects := []PrivAssociation{ + { + objectType: govmomi.VSphereTypeNetwork, + privsContent: "this is bad json", + path: networkPath, + }, + } + + err := v.validatePrivs(ctx, objects, vsc) + g.Expect(err).To(MatchError(ContainSubstring(errMsg))) +} diff --git a/pkg/providers/vsphere/vsphere.go b/pkg/providers/vsphere/vsphere.go index 8e4fa3cb27d0c..92fe5e4342fa8 100644 --- a/pkg/providers/vsphere/vsphere.go +++ b/pkg/providers/vsphere/vsphere.go @@ -21,10 +21,12 @@ import ( "github.com/aws/eks-anywhere/pkg/bootstrapper" "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/clusterapi" + "github.com/aws/eks-anywhere/pkg/config" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/crypto" "github.com/aws/eks-anywhere/pkg/executables" "github.com/aws/eks-anywhere/pkg/filewriter" + "github.com/aws/eks-anywhere/pkg/govmomi" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/networkutils" "github.com/aws/eks-anywhere/pkg/providers" @@ -37,28 +39,20 @@ import ( ) const ( - CredentialsObjectName = "vsphere-credentials" - EksavSphereUsernameKey = "EKSA_VSPHERE_USERNAME" - EksavSpherePasswordKey = "EKSA_VSPHERE_PASSWORD" - // Username and password for cloud provider - EksavSphereCPUsernameKey = "EKSA_VSPHERE_CP_USERNAME" - EksavSphereCPPasswordKey = "EKSA_VSPHERE_CP_PASSWORD" - // Username and password for the CSI driver - EksavSphereCSIUsernameKey = "EKSA_VSPHERE_CSI_USERNAME" - EksavSphereCSIPasswordKey = "EKSA_VSPHERE_CSI_PASSWORD" - eksaLicense = "EKSA_LICENSE" - vSphereUsernameKey = "VSPHERE_USERNAME" - vSpherePasswordKey = "VSPHERE_PASSWORD" - vSphereServerKey = "VSPHERE_SERVER" - govcDatacenterKey = "GOVC_DATACENTER" - govcInsecure = "GOVC_INSECURE" - expClusterResourceSetKey = "EXP_CLUSTER_RESOURCE_SET" - defaultTemplateLibrary = "eks-a-templates" - defaultTemplatesFolder = "vm/Templates" - bottlerocketDefaultUser = "ec2-user" - ubuntuDefaultUser = "capv" - maxRetries = 30 - backOffPeriod = 5 * time.Second + CredentialsObjectName = "vsphere-credentials" + eksaLicense = "EKSA_LICENSE" + vSphereUsernameKey = "VSPHERE_USERNAME" + vSpherePasswordKey = "VSPHERE_PASSWORD" + vSphereServerKey = "VSPHERE_SERVER" + govcDatacenterKey = "GOVC_DATACENTER" + govcInsecure = "GOVC_INSECURE" + expClusterResourceSetKey = "EXP_CLUSTER_RESOURCE_SET" + defaultTemplateLibrary = "eks-a-templates" + defaultTemplatesFolder = "vm/Templates" + bottlerocketDefaultUser = "ec2-user" + ubuntuDefaultUser = "capv" + maxRetries = 30 + backOffPeriod = 5 * time.Second ) //go:embed config/template-cp.yaml @@ -150,6 +144,14 @@ type ClusterResourceSetManager interface { } func NewProvider(datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, providerGovcClient ProviderGovcClient, providerKubectlClient ProviderKubectlClient, writer filewriter.FileWriter, now types.NowFunc, skipIpCheck bool, resourceSetManager ClusterResourceSetManager) *vsphereProvider { + netClient := &networkutils.DefaultNetClient{} + vcb := govmomi.NewVMOMIClientBuilder() + v := NewValidator( + providerGovcClient, + netClient, + vcb, + ) + return NewProviderCustomNet( datacenterConfig, machineConfigs, @@ -157,14 +159,15 @@ func NewProvider(datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConf providerGovcClient, providerKubectlClient, writer, - &networkutils.DefaultNetClient{}, + netClient, now, skipIpCheck, resourceSetManager, + v, ) } -func NewProviderCustomNet(datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, providerGovcClient ProviderGovcClient, providerKubectlClient ProviderKubectlClient, writer filewriter.FileWriter, netClient networkutils.NetClient, now types.NowFunc, skipIpCheck bool, resourceSetManager ClusterResourceSetManager) *vsphereProvider { +func NewProviderCustomNet(datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, providerGovcClient ProviderGovcClient, providerKubectlClient ProviderKubectlClient, writer filewriter.FileWriter, netClient networkutils.NetClient, now types.NowFunc, skipIpCheck bool, resourceSetManager ClusterResourceSetManager, v *Validator) *vsphereProvider { var controlPlaneMachineSpec, etcdMachineSpec *v1alpha1.VSphereMachineConfigSpec if clusterConfig.Spec.ControlPlaneConfiguration.MachineGroupRef != nil && machineConfigs[clusterConfig.Spec.ControlPlaneConfiguration.MachineGroupRef.Name] != nil { controlPlaneMachineSpec = &machineConfigs[clusterConfig.Spec.ControlPlaneConfiguration.MachineGroupRef.Name].Spec @@ -177,6 +180,7 @@ func NewProviderCustomNet(datacenterConfig *v1alpha1.VSphereDatacenterConfig, ma etcdMachineSpec = &machineConfigs[clusterConfig.Spec.ExternalEtcdConfiguration.MachineGroupRef.Name].Spec } } + retrier := retrier.NewWithMaxRetries(maxRetries, backOffPeriod) return &vsphereProvider{ datacenterConfig: datacenterConfig, @@ -195,7 +199,7 @@ func NewProviderCustomNet(datacenterConfig *v1alpha1.VSphereDatacenterConfig, ma skipIpCheck: skipIpCheck, resourceSetManager: resourceSetManager, Retrier: retrier, - validator: NewValidator(providerGovcClient, netClient), + validator: v, defaulter: NewDefaulter(providerGovcClient), } } @@ -431,6 +435,30 @@ func (p *vsphereProvider) SetupAndValidateCreateCluster(ctx context.Context, clu logger.Info("Skipping check for whether control plane ip is in use") } + vuc := config.NewVsphereUserConfig() + + if err := p.validator.validateUserPrivs(ctx, vSphereClusterSpec, vuc); err != nil { + return err + } else { + logger.MarkPass("%s user vSphere privileges validated", vuc.EksaVsphereUsername) + } + + if len(vuc.EksaVsphereCPUsername) > 0 && vuc.EksaVsphereCPUsername != vuc.EksaVsphereUsername { + if err := p.validator.validateCPUserPrivs(ctx, vSphereClusterSpec, vuc); err != nil { + return err + } else { + logger.MarkPass("%s user vSphere privileges validated", vuc.EksaVsphereCPUsername) + } + } + + if len(vuc.EksaVsphereCSIUsername) > 0 && vuc.EksaVsphereCSIUsername != vuc.EksaVsphereUsername { + if err := p.validator.validateCSIUserPrivs(ctx, vSphereClusterSpec, vuc); err != nil { + return err + } else { + logger.MarkPass("%s user vSphere privileges validated", vuc.EksaVsphereCSIUsername) + } + } + return nil } @@ -695,24 +723,7 @@ func buildTemplateMapCP(clusterSpec *cluster.Spec, datacenterSpec v1alpha1.VSphe controllerManagerExtraArgs := clusterapi.SecureTlsCipherSuitesExtraArgs(). Append(clusterapi.NodeCIDRMaskExtraArgs(&clusterSpec.Cluster.Spec.ClusterNetwork)) - eksaVsphereUsername := os.Getenv(EksavSphereUsernameKey) - eksaVspherePassword := os.Getenv(EksavSpherePasswordKey) - - // Cloud provider credentials - eksaCPUsername := os.Getenv(EksavSphereCPUsernameKey) - eksaCPassword := os.Getenv(EksavSphereCPPasswordKey) - - if eksaCPUsername == "" { - eksaCPUsername = eksaVsphereUsername - eksaCPassword = eksaVspherePassword - } - // CSI driver credentials - eksaCSIUsername := os.Getenv(EksavSphereCSIUsernameKey) - eksaCSIPassword := os.Getenv(EksavSphereCSIPasswordKey) - if eksaCSIUsername == "" { - eksaCSIUsername = eksaVsphereUsername - eksaCSIPassword = eksaVspherePassword - } + vuc := config.NewVsphereUserConfig() values := map[string]interface{}{ "clusterName": clusterSpec.Cluster.Name, @@ -760,12 +771,12 @@ func buildTemplateMapCP(clusterSpec *cluster.Spec, datacenterSpec v1alpha1.VSphe "eksaSystemNamespace": constants.EksaSystemNamespace, "auditPolicy": common.GetAuditPolicy(), "resourceSetName": resourceSetName(clusterSpec), - "eksaVsphereUsername": eksaVsphereUsername, - "eksaVspherePassword": eksaVspherePassword, - "eksaCloudProviderUsername": eksaCPUsername, - "eksaCloudProviderPassword": eksaCPassword, - "eksaCSIUsername": eksaCSIUsername, - "eksaCSIPassword": eksaCSIPassword, + "eksaVsphereUsername": vuc.EksaVsphereUsername, + "eksaVspherePassword": vuc.EksaVspherePassword, + "eksaCloudProviderUsername": vuc.EksaVsphereCPUsername, + "eksaCloudProviderPassword": vuc.EksaVsphereCPPassword, + "eksaCSIUsername": vuc.EksaVsphereCSIUsername, + "eksaCSIPassword": vuc.EksaVsphereCSIPassword, } if clusterSpec.Cluster.Spec.RegistryMirrorConfiguration != nil { diff --git a/pkg/providers/vsphere/vsphere_test.go b/pkg/providers/vsphere/vsphere_test.go index 0286aa6c15459..40c3978eba0d5 100644 --- a/pkg/providers/vsphere/vsphere_test.go +++ b/pkg/providers/vsphere/vsphere_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" _ "embed" + "encoding/json" "errors" "fmt" "math" @@ -28,8 +29,11 @@ import ( "github.com/aws/eks-anywhere/internal/test" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/cluster" + "github.com/aws/eks-anywhere/pkg/config" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/executables" + "github.com/aws/eks-anywhere/pkg/govmomi" + govmomi_mocks "github.com/aws/eks-anywhere/pkg/govmomi/mocks" "github.com/aws/eks-anywhere/pkg/providers/vsphere/mocks" "github.com/aws/eks-anywhere/pkg/types" releasev1alpha1 "github.com/aws/eks-anywhere/release/api/v1alpha1" @@ -267,28 +271,28 @@ func workerNodeGroup2MachineDeployment() *clusterv1.MachineDeployment { } func (tctx *testContext) SaveContext() { - tctx.oldUsername, tctx.isUsernameSet = os.LookupEnv(EksavSphereUsernameKey) - tctx.oldPassword, tctx.isPasswordSet = os.LookupEnv(EksavSpherePasswordKey) + tctx.oldUsername, tctx.isUsernameSet = os.LookupEnv(config.EksavSphereUsernameKey) + tctx.oldPassword, tctx.isPasswordSet = os.LookupEnv(config.EksavSpherePasswordKey) tctx.oldServername, tctx.isServernameSet = os.LookupEnv(vSpherePasswordKey) tctx.oldExpClusterResourceSet, tctx.isExpClusterResourceSetSet = os.LookupEnv(vSpherePasswordKey) - os.Setenv(EksavSphereUsernameKey, expectedVSphereUsername) - os.Setenv(vSphereUsernameKey, os.Getenv(EksavSphereUsernameKey)) - os.Setenv(EksavSpherePasswordKey, expectedVSpherePassword) - os.Setenv(vSpherePasswordKey, os.Getenv(EksavSpherePasswordKey)) + os.Setenv(config.EksavSphereUsernameKey, expectedVSphereUsername) + os.Setenv(vSphereUsernameKey, os.Getenv(config.EksavSphereUsernameKey)) + os.Setenv(config.EksavSpherePasswordKey, expectedVSpherePassword) + os.Setenv(vSpherePasswordKey, os.Getenv(config.EksavSpherePasswordKey)) os.Setenv(vSphereServerKey, expectedVSphereServer) os.Setenv(expClusterResourceSetKey, expectedExpClusterResourceSet) } func (tctx *testContext) RestoreContext() { if tctx.isUsernameSet { - os.Setenv(EksavSphereUsernameKey, tctx.oldUsername) + os.Setenv(config.EksavSphereUsernameKey, tctx.oldUsername) } else { - os.Unsetenv(EksavSphereUsernameKey) + os.Unsetenv(config.EksavSphereUsernameKey) } if tctx.isPasswordSet { - os.Setenv(EksavSpherePasswordKey, tctx.oldPassword) + os.Setenv(config.EksavSpherePasswordKey, tctx.oldPassword) } else { - os.Unsetenv(EksavSpherePasswordKey) + os.Unsetenv(config.EksavSpherePasswordKey) } } @@ -319,6 +323,8 @@ func newProviderTest(t *testing.T) *providerTest { ctrl := gomock.NewController(t) kubectl := mocks.NewMockProviderKubectlClient(ctrl) govc := mocks.NewMockProviderGovcClient(ctrl) + vscb, _ := newMockVSphereClientBuilder(ctrl) + v := NewValidator(govc, &DummyNetClient{}, vscb) resourceSetManager := mocks.NewMockClusterResourceSetManager(ctrl) clusterConfig := givenClusterConfig(t, testClusterConfigMainFilename) datacenterConfig := givenDatacenterConfig(t, testClusterConfigMainFilename) @@ -331,6 +337,7 @@ func newProviderTest(t *testing.T) *providerTest { govc, kubectl, resourceSetManager, + v, ) return &providerTest{ WithT: NewWithT(t), @@ -380,6 +387,37 @@ func (tt *providerTest) setExpectationsForMachineConfigsVCenterValidation() { } func TestNewProvider(t *testing.T) { + mockCtrl := gomock.NewController(t) + clusterConfig := givenClusterConfig(t, testClusterConfigMainFilename) + datacenterConfig := givenDatacenterConfig(t, testClusterConfigMainFilename) + machineConfigs := givenMachineConfigs(t, testClusterConfigMainFilename) + kubectl := mocks.NewMockProviderKubectlClient(mockCtrl) + resourceSetManager := mocks.NewMockClusterResourceSetManager(mockCtrl) + govc := NewDummyProviderGovcClient() + _, writer := test.NewWriter(t) + skipIpCheck := true + + provider := NewProvider( + datacenterConfig, + machineConfigs, + clusterConfig, + govc, + kubectl, + writer, + time.Now, + skipIpCheck, + resourceSetManager, + ) + + if provider == nil { + t.Fatalf("provider object is nil") + } + if provider.validator == nil { + t.Fatalf("validator not configured") + } +} + +func TestNewProviderCustomNet(t *testing.T) { mockCtrl := gomock.NewController(t) clusterConfig := givenClusterConfig(t, testClusterConfigMainFilename) datacenterConfig := givenDatacenterConfig(t, testClusterConfigMainFilename) @@ -400,20 +438,26 @@ func TestNewProvider(t *testing.T) { func newProviderWithKubectl(t *testing.T, datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, kubectl ProviderKubectlClient) *vsphereProvider { ctrl := gomock.NewController(t) + govc := NewDummyProviderGovcClient() + vscb, _ := newMockVSphereClientBuilder(ctrl) + v := NewValidator(govc, &DummyNetClient{}, vscb) resourceSetManager := mocks.NewMockClusterResourceSetManager(ctrl) return newProvider( t, datacenterConfig, machineConfigs, clusterConfig, - NewDummyProviderGovcClient(), + govc, kubectl, resourceSetManager, + v, ) } func newProviderWithGovc(t *testing.T, datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, govc ProviderGovcClient) *vsphereProvider { ctrl := gomock.NewController(t) + vscb, _ := newMockVSphereClientBuilder(ctrl) + v := NewValidator(govc, &DummyNetClient{}, vscb) resourceSetManager := mocks.NewMockClusterResourceSetManager(ctrl) kubectl := mocks.NewMockProviderKubectlClient(ctrl) return newProvider( @@ -424,11 +468,43 @@ func newProviderWithGovc(t *testing.T, datacenterConfig *v1alpha1.VSphereDatacen govc, kubectl, resourceSetManager, + v, ) } -func newProvider(t *testing.T, datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, govc ProviderGovcClient, kubectl ProviderKubectlClient, resourceSetManager ClusterResourceSetManager) *vsphereProvider { +type mockVSphereClientBuilder struct { + vsc *govmomi_mocks.MockVSphereClient +} + +func (mvscb *mockVSphereClientBuilder) Build(ctx context.Context, host string, username string, password string, insecure bool, datacenter string) (govmomi.VSphereClient, error) { + return mvscb.vsc, nil +} + +func setDefaultVSphereClientMock(vsc *govmomi_mocks.MockVSphereClient) error { + vsc.EXPECT().Username().Return("foobar").AnyTimes() + + var privs []string + err := json.Unmarshal([]byte(config.VSphereAdminPrivsFile), &privs) + if err != nil { + return err + } + + vsc.EXPECT().GetPrivsOnEntity(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(privs, nil).AnyTimes() + + return nil +} + +func newMockVSphereClientBuilder(ctrl *gomock.Controller) (VSphereClientBuilder, error) { + vsc := govmomi_mocks.NewMockVSphereClient(ctrl) + err := setDefaultVSphereClientMock(vsc) + mvscb := mockVSphereClientBuilder{vsc} + return &mvscb, err +} + +func newProvider(t *testing.T, datacenterConfig *v1alpha1.VSphereDatacenterConfig, machineConfigs map[string]*v1alpha1.VSphereMachineConfig, clusterConfig *v1alpha1.Cluster, govc ProviderGovcClient, kubectl ProviderKubectlClient, resourceSetManager ClusterResourceSetManager, v *Validator) *vsphereProvider { _, writer := test.NewWriter(t) + netClient := &DummyNetClient{} + return NewProviderCustomNet( datacenterConfig, machineConfigs, @@ -436,10 +512,11 @@ func newProvider(t *testing.T, datacenterConfig *v1alpha1.VSphereDatacenterConfi govc, kubectl, writer, - &DummyNetClient{}, + netClient, test.FakeNow, false, resourceSetManager, + v, ) } @@ -919,8 +996,19 @@ func TestProviderGenerateCAPISpecForCreateWithBottlerocketAndExternalEtcd(t *tes machineConfigs := givenMachineConfigs(t, clusterSpecManifest) ctx := context.Background() govc := NewDummyProviderGovcClient() + vscb, _ := newMockVSphereClientBuilder(mockCtrl) + v := NewValidator(govc, &DummyNetClient{}, vscb) govc.osTag = bottlerocketOSTag - provider := newProvider(t, datacenterConfig, machineConfigs, clusterSpec.Cluster, govc, kubectl, resourceSetManager) + provider := newProvider( + t, + datacenterConfig, + machineConfigs, + clusterSpec.Cluster, + govc, + kubectl, + resourceSetManager, + v, + ) if err := provider.SetupAndValidateCreateCluster(ctx, clusterSpec); err != nil { t.Fatalf("failed to setup and validate: %v", err) @@ -947,8 +1035,19 @@ func TestProviderGenerateDeploymentFileForBottleRocketWithMirrorConfig(t *testin machineConfigs := givenMachineConfigs(t, clusterSpecManifest) ctx := context.Background() govc := NewDummyProviderGovcClient() + vscb, _ := newMockVSphereClientBuilder(mockCtrl) + v := NewValidator(govc, &DummyNetClient{}, vscb) govc.osTag = bottlerocketOSTag - provider := newProvider(t, datacenterConfig, machineConfigs, clusterSpec.Cluster, govc, kubectl, resourceSetManager) + provider := newProvider( + t, + datacenterConfig, + machineConfigs, + clusterSpec.Cluster, + govc, + kubectl, + resourceSetManager, + v, + ) if err := provider.SetupAndValidateCreateCluster(ctx, clusterSpec); err != nil { t.Fatalf("failed to setup and validate: %v", err) } @@ -975,7 +1074,18 @@ func TestProviderGenerateDeploymentFileForBottleRocketWithMirrorAndCertConfig(t ctx := context.Background() govc := NewDummyProviderGovcClient() govc.osTag = bottlerocketOSTag - provider := newProvider(t, datacenterConfig, machineConfigs, clusterSpec.Cluster, govc, kubectl, resourceSetManager) + vscb, _ := newMockVSphereClientBuilder(mockCtrl) + v := NewValidator(govc, &DummyNetClient{}, vscb) + provider := newProvider( + t, + datacenterConfig, + machineConfigs, + clusterSpec.Cluster, + govc, + kubectl, + resourceSetManager, + v, + ) if err := provider.SetupAndValidateCreateCluster(ctx, clusterSpec); err != nil { t.Fatalf("failed to setup and validate: %v", err) } @@ -1116,7 +1226,7 @@ func TestSetupAndValidateCreateClusterNoUsername(t *testing.T) { var tctx testContext tctx.SaveContext() defer tctx.RestoreContext() - os.Unsetenv(EksavSphereUsernameKey) + os.Unsetenv(config.EksavSphereUsernameKey) err := provider.SetupAndValidateCreateCluster(ctx, clusterSpec) @@ -1130,7 +1240,7 @@ func TestSetupAndValidateCreateClusterNoPassword(t *testing.T) { var tctx testContext tctx.SaveContext() defer tctx.RestoreContext() - os.Unsetenv(EksavSpherePasswordKey) + os.Unsetenv(config.EksavSpherePasswordKey) err := provider.SetupAndValidateCreateCluster(ctx, clusterSpec) @@ -1319,7 +1429,7 @@ func TestSetupAndValidateDeleteClusterNoPassword(t *testing.T) { var tctx testContext tctx.SaveContext() defer tctx.RestoreContext() - os.Unsetenv(EksavSpherePasswordKey) + os.Unsetenv(config.EksavSpherePasswordKey) err := provider.SetupAndValidateDeleteCluster(ctx, nil) @@ -1353,7 +1463,7 @@ func TestSetupAndValidateUpgradeClusterNoUsername(t *testing.T) { var tctx testContext tctx.SaveContext() defer tctx.RestoreContext() - os.Unsetenv(EksavSphereUsernameKey) + os.Unsetenv(config.EksavSphereUsernameKey) cluster := &types.Cluster{} err := provider.SetupAndValidateUpgradeCluster(ctx, cluster, clusterSpec, clusterSpec) @@ -1368,7 +1478,7 @@ func TestSetupAndValidateUpgradeClusterNoPassword(t *testing.T) { var tctx testContext tctx.SaveContext() defer tctx.RestoreContext() - os.Unsetenv(EksavSpherePasswordKey) + os.Unsetenv(config.EksavSpherePasswordKey) cluster := &types.Cluster{} err := provider.SetupAndValidateUpgradeCluster(ctx, cluster, clusterSpec, clusterSpec) @@ -3026,21 +3136,21 @@ func TestProviderGenerateCAPISpecForCreateMultipleCredentials(t *testing.T) { { testName: "specify cloud provider credentials", wantCPFile: "testdata/expected_results_main_cp_cloud_provider_credentials.yaml", - envMap: map[string]string{EksavSphereCPUsernameKey: "EksavSphereCPUsername", EksavSphereCPPasswordKey: "EksavSphereCPPassword"}, + envMap: map[string]string{config.EksavSphereCPUsernameKey: "EksavSphereCPUsername", config.EksavSphereCPPasswordKey: "EksavSphereCPPassword"}, }, { testName: "specify CSI credentials", wantCPFile: "testdata/expected_results_main_cp_csi_driver_credentials.yaml", - envMap: map[string]string{EksavSphereCSIUsernameKey: "EksavSphereCSIUsername", EksavSphereCSIPasswordKey: "EksavSphereCSIPassword"}, + envMap: map[string]string{config.EksavSphereCSIUsernameKey: "EksavSphereCSIUsername", config.EksavSphereCSIPasswordKey: "EksavSphereCSIPassword"}, }, { testName: "specify cloud provider and CSI credentials", wantCPFile: "testdata/expected_results_main_cp_cloud_provder_and_csi_driver_credentials.yaml", envMap: map[string]string{ - EksavSphereCSIUsernameKey: "EksavSphereCSIUsername", - EksavSphereCSIPasswordKey: "EksavSphereCSIPassword", - EksavSphereCPUsernameKey: "EksavSphereCPUsername", - EksavSphereCPPasswordKey: "EksavSphereCPPassword", + config.EksavSphereCSIUsernameKey: "EksavSphereCSIUsername", + config.EksavSphereCSIPasswordKey: "EksavSphereCSIPassword", + config.EksavSphereCPUsernameKey: "EksavSphereCPUsername", + config.EksavSphereCPPasswordKey: "EksavSphereCPPassword", }, }, }