Skip to content

Instantly share code, notes, and snippets.

@jcpowermac
Last active November 12, 2020 15:41
Show Gist options
  • Select an option

  • Save jcpowermac/8df504f79559fa5b1cd4ec2052f96a7f to your computer and use it in GitHub Desktop.

Select an option

Save jcpowermac/8df504f79559fa5b1cd4ec2052f96a7f to your computer and use it in GitHub Desktop.
vsphere permissions
package main
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/ssoadmin"
ssotypes "github.com/vmware/govmomi/ssoadmin/types"
"github.com/vmware/govmomi/sts"
//"github.com/vmware/govmomi/nfc"
"github.com/vmware/govmomi/object"
_ "github.com/vmware/govmomi/object"
_ "github.com/vmware/govmomi/ovf"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Platform struct {
// VCenter is the domain name or IP address of the vCenter.
VCenter string `json:"vCenter"`
// Username is the name of the user to use to connect to the vCenter.
Username string `json:"username"`
// Password is the password for the user to use to connect to the vCenter.
Password string `json:"password"`
// Datacenter is the name of the datacenter to use in the vCenter.
Datacenter string `json:"datacenter"`
// DefaultDatastore is the default datastore to use for provisioning volumes.
DefaultDatastore string `json:"defaultDatastore"`
// Folder is the name of the folder that will be used and/or created for
// virtual machines.
Folder string `json:"folder,omitempty"`
// Cluster is the name of the cluster virtual machines will be cloned into.
Cluster string `json:"cluster,omitempty"`
// ClusterOSImage overrides the url provided in rhcos.json to download the RHCOS OVA
ClusterOSImage string `json:"clusterOSImage,omitempty"`
// APIVIP is the virtual IP address for the api endpoint
APIVIP string `json:"apiVIP,omitempty"`
// IngressVIP is the virtual IP address for ingress
IngressVIP string `json:"ingressVIP,omitempty"`
// DNSVIP is the virtual IP address for DNS
DNSVIP string `json:"dnsVIP,omitempty"`
// Network specifies the name of the network to be used by the cluster.
Network string `json:"network,omitempty"` //TODO: determine if this should be omitempty or required
}
// ImportOvaParams contains the vCenter objects required to import a OVA into vSphere.
type ImportOvaParams struct {
ResourcePool *object.ResourcePool
Datacenter *object.Datacenter
Datastore *object.Datastore
Network *object.Network
Host *object.HostSystem
Folder *object.Folder
}
func findImportOvaParams(client *vim25.Client, datacenter, cluster, datastore, network string) (*ImportOvaParams, error) {
var ccrMo mo.ClusterComputeResource
ctx := context.TODO()
importOvaParams := &ImportOvaParams{}
finder := find.NewFinder(client)
dcObj, err := finder.Datacenter(ctx, datacenter)
if err != nil {
return nil, err
}
importOvaParams.Datacenter = dcObj
folders, err := importOvaParams.Datacenter.Folders(ctx)
if err != nil {
return nil, err
}
importOvaParams.Folder = folders.VmFolder
clusterPath := fmt.Sprintf("/%s/host/%s", datacenter, cluster)
clusterComputeResource, err := finder.ClusterComputeResource(ctx, clusterPath)
if err != nil {
return nil, err
}
err = clusterComputeResource.Properties(context.TODO(), clusterComputeResource.Reference(), []string{"network"}, &ccrMo)
if err != nil {
return nil, err
}
for _, networkMoRef := range ccrMo.Network {
networkObj := object.NewNetwork(client, networkMoRef)
networkObjectName, err := networkObj.ObjectName(ctx)
if err != nil {
return nil, err
}
if network == networkObjectName {
importOvaParams.Network = networkObj
break
}
}
datastores, err := clusterComputeResource.Datastores(ctx)
if err != nil {
return nil, err
}
for _, datastoreObj := range datastores {
datastoreObjName, err := datastoreObj.ObjectName(ctx)
if err != nil {
return nil, err
}
if datastore == datastoreObjName {
importOvaParams.Datastore = datastoreObj
break
}
}
hosts, err := clusterComputeResource.Hosts(ctx)
if err != nil {
return nil, err
}
foundDatastore := false
foundNetwork := false
var hostSystemManagedObject mo.HostSystem
for _, hostObj := range hosts {
hostObj.Properties(ctx, hostObj.Reference(), []string{"network", "datastore"}, &hostSystemManagedObject)
if err != nil {
return nil, err
}
for _, dsMoRef := range hostSystemManagedObject.Datastore {
if importOvaParams.Datastore.Reference().Value == dsMoRef.Value {
foundDatastore = true
break
}
}
for _, nMoRef := range hostSystemManagedObject.Network {
if importOvaParams.Network.Reference().Value == nMoRef.Value {
foundNetwork = true
break
}
}
if foundDatastore && foundNetwork {
importOvaParams.Host = hostObj
resourcePool, err := hostObj.ResourcePool(ctx)
if err != nil {
return nil, err
}
importOvaParams.ResourcePool = resourcePool
}
}
return importOvaParams, nil
}
func CreateVSphereClients(ctx context.Context, vcenter, username, password string) (*vim25.Client, *rest.Client, error) {
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
u, err := soap.ParseURL(vcenter)
if err != nil {
return nil, nil, err
}
u.User = url.UserPassword(username, password)
c, err := govmomi.NewClient(ctx, u, true)
if err != nil {
return nil, nil, err
}
restClient := rest.NewClient(c.Client)
err = restClient.Login(ctx, u.User)
if err != nil {
return nil, nil, err
}
return c.Client, restClient, nil
}
type Session struct {
Vim25Client *vim25.Client
RestClient *rest.Client
SsoClient *ssoadmin.Client
}
type VCenterUser struct {
//*ssoadmin.AdminPersonUser
AdminPersonUser *ssotypes.AdminPersonUser
AdminGroups *[]ssotypes.PrincipalId
Roles []int32
}
// GetSession - creates the Session struct
func GetSession(ctx context.Context, p *Platform) (*Session, *VCenterUser, error) {
//spew.Dump(p)
//ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
//defer cancel()
person := &VCenterUser{}
session := &Session{}
header := soap.Header{
Security: &sts.Signer{},
}
u, err := soap.ParseURL(p.VCenter)
if err != nil {
return nil, nil, err
}
u.User = url.UserPassword(p.Username, p.Password)
if u.User == nil {
spew.Dump("user nil")
}
// false in this method disables insecure
// We do not allow insecure connections
govmomiClient, err := govmomi.NewClient(ctx, u, true)
//spew.Dump(c.Client)
vimClient, err := vim25.NewClient(ctx, soap.NewClient(u, true))
stsClient, cerr := sts.NewClient(ctx, vimClient)
if cerr != nil {
return nil, nil, cerr
}
ssoClient, err := ssoadmin.NewClient(ctx, vimClient)
req := sts.TokenRequest{
Certificate: vimClient.Certificate(),
Userinfo: u.User,
}
header.Security, cerr = stsClient.Issue(ctx, req)
if cerr != nil {
return nil, nil, cerr
}
//spew.Dump(header.Security)
if err = ssoClient.Login(ssoClient.WithHeader(ctx, header)); err != nil {
return nil, nil, err
}
// **TODO**
// **TODO**
// **TODO**
// ********
// Do not forget to check for nil
// this is esp. the case when
// vCenter is unhappy
// ********
session.SsoClient = ssoClient
personUser, err := ssoClient.FindPersonUser(ctx, "jcallen@vsphere.local")
if err != nil {
return nil, nil, err
}
//spew.Dump(personUser)
person.AdminPersonUser = personUser
/*
adminGroups, err := ssoClient.FindGroups(ctx, "")
if err != nil {
return nil, err
}
spew.Dump(adminGroups)
*/
groups, err := ssoClient.FindParentGroups(ctx, personUser.Id)
if err != nil {
return nil, nil, err
}
person.AdminGroups = &groups
//spew.Dump(groups)
//spew.Dump(ssoClient)
/*
defer func() {
if err := ssoClient.Logout(ctx); err != nil {
log.Printf("user logout error: %v", err)
}
}()
*/
session.Vim25Client = govmomiClient.Client
//spew.Dump(session)
if err != nil {
return nil, nil, err
}
restClient := rest.NewClient(vimClient)
err = restClient.Login(ctx, u.User)
if err != nil {
return nil, nil, err
}
session.RestClient = restClient
//spew.Dump(session)
return session, person, nil
}
//type PermissionManagedObjectType string
//use ManagedObjectReference type string
//Each ManagedObjectReference
const (
PrivilegesDatacenter string = "Datacenter"
PrivilegesResourcePool string = "ResourcePool"
PrivilegesFolder string = "Folder"
PrivilegesDatastore string = "Datastore"
PrivilegesClusterComputeResource string = "ClusterComputeResource"
// there is no VirtualMachine object to check privilege
)
/*
StorageProfile.View (Profile-driven storage view) vCenter No
*/
var managedObjectRequiredPrivileges = map[string]map[string]bool{
PrivilegesResourcePool: {
"VApp.Import": false,
},
PrivilegesDatacenter: {
"VirtualMachine.Interact.PowerOff": false,
"VirtualMachine.Interact.PowerOn": false,
},
PrivilegesDatastore: {
"Datastore.AllocateSpace": false,
"Datastore.FileManagement": false,
},
PrivilegesFolder: {
"Resource.AssignVMToPool": false,
"VirtualMachine.Config.AddExistingDisk": false,
"VirtualMachine.Config.AddNewDisk": false,
"VirtualMachine.Config.AddRemoveDevice": false,
"VirtualMachine.Config.RemoveDisk": false,
"VirtualMachine.Inventory.Create": false,
"VirtualMachine.Inventory.Delete": false,
"VirtualMachine.Config.Settings": false,
},
PrivilegesClusterComputeResource: {
"Resource.AssignVMToPool": false,
"VirtualMachine.Config.AddExistingDisk": false,
"VirtualMachine.Config.AddNewDisk": false,
"VirtualMachine.Config.AddRemoveDevice": false,
"VirtualMachine.Config.RemoveDisk": false,
"VirtualMachine.Inventory.Create": false,
"VirtualMachine.Inventory.Delete": false,
"VirtualMachine.Config.Settings": false,
},
}
// logon could be a group too
// just needs to be in the form domain/username
func RoleIdsByLogon(authManager *object.AuthorizationManager, ref types.ManagedObjectReference, domain, name string) ([]int32, error) {
ctx := context.TODO()
var roleIds []int32
logon := fmt.Sprintf("%s/%s", strings.ToLower(domain), strings.ToLower(name))
permissions, err := authManager.RetrieveEntityPermissions(ctx, ref, true)
if err != nil {
return nil, err
}
// p.Principal is in the form of domain/username
for _, p := range permissions {
if strings.ToLower(p.Principal) == logon {
roleIds = append(roleIds, p.RoleId)
}
}
return roleIds, nil
}
// TempFunctionName - stop bugging me....
func TempFunctionName(moRef types.ManagedObjectReference, roleList *object.AuthorizationRoleList, roleIds []int32) {
// For all the role ids the domain/name (username)
// is apart of. Get the authorizationRole by id
// for all Privileges contained within the role
//
for _, roleid := range roleIds {
authorizationRole := roleList.ById(roleid)
for _, p := range authorizationRole.Privilege {
// How will I know if I have all my privileges?
if _, ok := managedObjectRequiredPrivileges[moRef.Type][p]; ok {
managedObjectRequiredPrivileges[moRef.Type][p] = true
}
}
}
for rpkey := range managedObjectRequiredPrivileges[moRef.Type] {
if value, ok := managedObjectRequiredPrivileges[moRef.Type][rpkey]; ok {
if !value {
spew.Sprintf("missing privilege: %s", rpkey)
}
}
}
}
func ValidateCreds(ssn *Session, p *Platform, person *VCenterUser) error {
// What is my user account?
// What group(s) is my user account in?
// For the datacenter/host/cluster
// user - role association
// group - role association
// what permissions do the roles contain?
ctx := context.TODO()
authManager := object.NewAuthorizationManager(ssn.Vim25Client)
roleList, err := authManager.RoleList(ctx)
if err != nil {
return err
}
finder := find.NewFinder(ssn.Vim25Client)
datacenter, err := finder.Datacenter(ctx, p.Datacenter)
if err != nil {
return err
}
//permissions, err := authManager.RetrieveEntityPermissions(ctx, datacenter.Reference(), true)
roleIds, err := RoleIdsByLogon(authManager, datacenter.Reference(), "VCENTER.LOCAL", "PowerUsers")
if err != nil {
return err
}
// For all the role ids the domain/name (username)
// is apart of. Get the authorizationRole by id
// for all Privileges contained within the role
//
for _, roleid := range roleIds {
authorizationRole := roleList.ById(roleid)
for _, p := range authorizationRole.Privilege {
// How will I know if I have all my privileges?
if _, ok := managedObjectRequiredPrivileges[datacenter.Reference().Type][p]; ok {
managedObjectRequiredPrivileges[datacenter.Reference().Type][p] = true
}
}
}
for rpkey := range managedObjectRequiredPrivileges[datacenter.Reference().Type] {
if value, ok := managedObjectRequiredPrivileges[datacenter.Reference().Type][rpkey]; ok {
if !value {
spew.Sprintf("missing privilege: %s", rpkey)
}
}
}
/*
if err != nil {
return err
}
spew.Dump(roleList)
*/
/*
personUser, err := ssn.SsoClient.FindPersonUser(ctx, p.Username)
if err != nil {
return err
}
dynamicData := personUser.GetDynamicData()
spew.Dump(dynamicData)
*/
/*
for _, p := range permissions {
spew.Printf("prin: %s roleid: %d group: %t\n", p.Principal, p.RoleId, p.Group)
}
*/
return nil
}
func main() {
//datacenter := "dc1"
//defaultDatastore := "NVMe"
//ctx := context.TODO()
//networkName := "VM Network"
platform := &Platform{
VCenter: "",
Password: "",
Username: "",
Datacenter: "dc1",
Cluster: "cluster1",
}
/*
client, _, err := CreateVSphereClients(
context.TODO(),
"vcenter.virtomation.com", "", "")
//"vcenter.virtomation.com", "", "")
spew.Dump(client)
*/
spew.Dump("Before GetSession")
ssn, person, err := GetSession(context.TODO(), platform)
if err != nil {
spew.Dump(err)
return
}
spew.Dump(person)
spew.Dump("After GetSession, Before ValidateCreds")
//spew.Dump(ssn)
err = ValidateCreds(ssn, platform, person)
if err != nil {
spew.Dump(err)
}
spew.Dump("After ValidateCreds")
//i, err := findImportOvaParams(client, "dc1", "cluster1", "NVMe", "VM Network")
//spew.Dump(i)
//spew.Dump(err)
/*
cachedImage := "rhcos-latest.ova"
ovaTapeArchive := &TapeArchive{Path: cachedImage}
ovaTapeArchive.Client = client
archiveFlag := &ArchiveFlag{}
archiveFlag.Archive = ovaTapeArchive
ovfDescriptor, err := archiveFlag.ReadOvf("desc.ovf")
if err != nil {
spew.Dump(err)
}
ovfEnvelope, err := archiveFlag.ReadEnvelope(ovfDescriptor)
if err != nil {
spew.Dump(errors.Errorf("failed to parse ovf: %s", err))
}
if err != nil {
spew.Dump(err)
return
}
name := "rhcos-test-image"
*/
/*
defaultNetwork, err := finder.Network(context.TODO(), "VM Network")
spew.Dump(defaultNetwork)
if err != nil {
spew.Dump(defaultNetwork)
spew.Dump("default network")
spew.Dump(err)
return
}
datastore, err := finder.Datastore(ctx, defaultDatastore)
if err != nil {
spew.Dump(err)
return
}
host, err := finder.DefaultHostSystem(ctx)
if err != nil {
spew.Dump(err)
return
}
resourcePool, err := host.ResourcePool(ctx)
if err != nil {
spew.Dump(err)
return
}
networkMappings := []types.OvfNetworkMapping{{
Name: ovfEnvelope.Network.Networks[0].Name,
Network: defaultNetwork.Reference(),
}}
cisp := types.OvfCreateImportSpecParams{
EntityName: name,
NetworkMapping: networkMappings,
}
spew.Dump(cisp)
m := ovf.NewManager(client)
spec, err := m.CreateImportSpec(ctx, string(ovfDescriptor),
resourcePool.Reference(),
datastore.Reference(), cisp)
if err != nil {
spew.Dump("CreateImportSpec")
spew.Dump(err)
return
}
if spec.Error != nil {
spew.Dump(errors.New(spec.Error[0].LocalizedMessage))
return
}
if spec.Warning != nil {
for _, w := range spec.Warning {
logrus.Warn(w.LocalizedMessage)
}
}
folder, err := finder.DefaultFolder(context.TODO())
if err != nil {
spew.Dump("folder")
spew.Dump(err)
}
lease, err := resourcePool.ImportVApp(ctx, spec.ImportSpec, folder, host)
if err != nil {
spew.Dump("ImportVApp")
spew.Dump(err)
return
}
spew.Dump(lease)
spew.Dump(spec.FileItem)
info, err := lease.Wait(ctx, spec.FileItem)
if err != nil {
spew.Dump("wait")
spew.Dump(err)
return
}
//return errors.Errorf("folder: %s", folder)
u := lease.StartUpdater(ctx, info)
defer u.Done()
for _, i := range info.Items {
err = Upload(ctx, archiveFlag, lease, i)
if err != nil {
spew.Dump("upload")
spew.Dump(err)
return
}
}
err = lease.Complete(ctx)
if err != nil {
spew.Dump("lease complete")
spew.Dump(err)
return
}
vm := object.NewVirtualMachine(client, info.Entity)
spew.Dump(vm.Name)
*/
return
}
/*
func Upload(ctx context.Context, archive *ArchiveFlag, lease *nfc.Lease, item nfc.FileItem) error {
file := item.Path
f, size, err := archive.Open(file)
if err != nil {
return err
}
defer f.Close()
// logger := cmd.ProgressLogger(fmt.Sprintf("Uploading %s... ", path.Base(file)))
// defer logger.Wait()
opts := soap.Upload{
ContentLength: size,
// Progress: logger,
}
return lease.Upload(ctx, item, f, opts)
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment