You can use a custom Python script to create a restricted vCenter Server user, with optional flags to create a vCenter Server role and apply snapshot, failover, and failback privileges.

This script allows you to create a vCenter Server user with restricted roles that contain the minimum set of privileges required by the Cyber Recovery connector to perform snapshot replication, failover, and failback operations. Run this script from the Cyber Recovery connector VM command line.

Prerequisites

  • GOVC installed on the system where you run this script. You can download GOVC here: https://github.com/vmware/govmomi/tree/master/govc#readme.
  • Set the GOVC_PATH variable in the script to the path to the 'govc' executable on the system where you run this script. The default is /usr/local/bin/govc.
  • Administrator user account and password from the vCenter Server where you are creating this user. You must pass these credentials when you run the script.

Usage

Create a vCenter Server user and role with a minimum set of privileges needed by the Cyber Recovery connector. Optional commands appear inside square brackets [ ].

vcdr-create-vcenter-user.py [-h]

Options:

Option Description

-h, --help

Displays a list of commands and options.

--vcenter VCENTER

IP address of the vCenter Server where you create this user.

--admin-username admin-username

Username of a user withvCenter Server Administrator privileges.

User must have vCenter Server Adminstrator privileges before you run the CLI commands. If not supplied, the script prompts you for this username.

-

[-admin-password admin-password]

Password of a user withvCenter Server Administrator privileges. If not supplied, the script prompts you for this password.

Make sure the password you choose for this user adheres to the VMware vSphere password policy requirements.

--new-username new-username

Use name for the new user.

[--new-password new-password]

Password for the new user.

--vcenter-role vcenter-role

Name of new or existing vCenter Server role to assign to the new user. You can apply --snapshot-privs and/or --failback-privs to this role.

[--keep-existing-privs]

Keep existing privileges on the provided role. Using this option ensures that you do not unecessarily remove privileges from the provided role.

[--create-role-only]

Create role with necessary privileges, but do not create a user account or assign permissions.

[--snapshot-privs]

This option adds snapshot and failover privileges to the user role.

[--failback-privs]

This option adds snapshot, failover, and failback privileges to the user role.

Example

The following example shows how to use the CLI to create a vCenter Server role called vcdr-dkup-role and assign the role snapshot and failover privileges (but no failback). This command then creates a vCenter Server user named [email protected], and globally assigns this new user the new role.

connector-name>> vcdr-create-vcenter-user.py --vcenter 192.0.170.23 --admin-username [email protected] --new-username [email protected] --vcenter-role vcdr-bkup-role --snapshot-privs

Script

#!/usr/bin/env python

"""
Copyright 2021 VMware, Inc.  All rights reserved.

==========================
IMPORT! READ THE FOLLOWING
==========================

This script can be  downloaded and used by customers to create a vCenter user,
role, and permission for use with vCenter registration with the VMware Cloud
Disaster Recover (VCDR) product.

REQUIREMENTS

 - Set the GOVC_PATH variable, below, to the path to the 'govc' executable on
   the system where this script will run. The default is '/usr/local/bin/govc'.

 - GOVC
   GOVC must be installed in order for this script to work.
   https://github.com/vmware/govmomi/tree/master/govc#readme

EXAMPLE

$ vcdr-create-vcenter-user.py --vcenter 10.80.15.23 --admin-username [email protected] \
  --new-username [email protected] --vcenter-role vcdr-bkup-role --snapshot-privs

The above will create a vCenter role called vcdr-dkup-role with the privileges necessary to perform
VM backup (i.e., it will not work for failback). It will also create a vCenter user called
[email protected].  Finally, it will globally assign this new user the above role.

USAGE

$ vcdr-create-vcenter-user.py --help

usage: vcdr-create-vcenter-user.py [-h] --vcenter VCENTER --admin-username
                                  admin-username --new-username new-username
                                  [--admin-password admin-password]
                                  [--new-password new-password] --vcenter-role
                                  vcenter-role [--keep-existing-privs]
                                  [--create-role-only]
                                  [--snapshot-privs | --failback-privs]

Create vCenter user/role/permission with minimal privileges for use with VCDR.

optional arguments:
  -h, --help            show this help message and exit
  --vcenter VCENTER     vCenter IP on which to create/update user
  --admin-username admin-username
                        Admin username at desired vCenter
  --new-username new-username
                        New username to be created in the desired vCenter
  --admin-password admin-password
                        Admin password at desired vCenter (will be prompted if
                        not provided)
  --new-password new-password
                        New password for the new user in the desired vCenter
                        (will be prompted if not provided)
  --vcenter-role vcenter-role
                        Name of new or existing vCenter role to associate to
                        the new user
  --keep-existing-privs
                        Keep existing privileges on provided role (i.e., do
                        not prune unnecessary privileges from provided role)
  --create-role-only    Create role with necessary privileges but do not
                        create a user account or assign permissions
  --snapshot-privs      Create a user with privileges necessary to snapshot
                        VMs and failover (the default)
  --failback-privs      Create a user with privileges necessary to snapshot
                        VMs, failover, and failback

MISC

This script cannot be used to create/update localos users (i.e., the created
VCDR user account must be of the form 'name@domain', e.g., '[email protected]').

This script has been tested with python3.
"""
__author__ = "VMware, Inc"

from getpass import getpass
import json
import logging
import os
import subprocess
import time

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

GOVC_PATH = '/usr/local/bin/govc'
LOG_FILE = '/var/tmp/vcdr-create-vcenter-user'

VCDR_SNAPSHOT_PRIVS = [
    'Datastore.Browse',
    'Datastore.FileManagement',
    'Global.DisableMethods',
    'Global.EnableMethods',
    'System.Anonymous',
    'System.Read',
    'System.View',
    'VirtualMachine.Config.ChangeTracking',
    'VirtualMachine.Config.DiskLease',
    'VirtualMachine.Config.QueryUnownedFiles',
    'VirtualMachine.Config.ReloadFromPath',
    'VirtualMachine.Provisioning.DiskRandomRead',
    'VirtualMachine.Provisioning.GetVmFiles',
    'VirtualMachine.Provisioning.ReadCustSpecs',
    'VirtualMachine.State.CreateSnapshot',
    'VirtualMachine.State.RemoveSnapshot'
]

VCDR_FAILBACK_PRIVS = VCDR_SNAPSHOT_PRIVS + [
    'Datastore.AllocateSpace',
    'Datastore.Config',
    'Datastore.DeleteFile',
    'Datastore.UpdateVirtualMachineFiles',
    'Datastore.UpdateVirtualMachineMetadata',
    'Global.CancelTask',
    'Global.GlobalTag',
    'Global.Licenses',
    'Global.ManageCustomFields',
    'Global.SetCustomField',
    'InventoryService.Tagging.AttachTag',
    'Network.Assign',
    'Resource.AssignVAppToPool',
    'Resource.AssignVMToPool',
    'Sessions.GlobalMessage',
    'Sessions.ValidateSession',
    'VirtualMachine.Config.AddExistingDisk',
    'VirtualMachine.Config.AddNewDisk',
    'VirtualMachine.Config.AddRemoveDevice',
    'VirtualMachine.Config.AdvancedConfig',
    'VirtualMachine.Config.CPUCount',
    'VirtualMachine.Config.DiskExtend',
    'VirtualMachine.Config.EditDevice',
    'VirtualMachine.Config.HostUSBDevice',
    'VirtualMachine.Config.ManagedBy',
    'VirtualMachine.Config.Memory',
    'VirtualMachine.Config.MksControl',
    'VirtualMachine.Config.QueryFTCompatibility',
    'VirtualMachine.Config.RawDevice',
    'VirtualMachine.Config.RemoveDisk',
    'VirtualMachine.Config.Rename',
    'VirtualMachine.Config.ResetGuestInfo',
    'VirtualMachine.Config.Resource',
    'VirtualMachine.Config.Settings',
    'VirtualMachine.Config.SwapPlacement',
    'VirtualMachine.GuestOperations.Execute',
    'VirtualMachine.GuestOperations.Modify',
    'VirtualMachine.GuestOperations.Query',
    'VirtualMachine.Interact.Backup',
    'VirtualMachine.Interact.DeviceConnection',
    'VirtualMachine.Interact.GuestControl',
    'VirtualMachine.Interact.PowerOff',
    'VirtualMachine.Interact.PowerOn',
    'VirtualMachine.Interact.SetCDMedia',
    'VirtualMachine.Interact.SetFloppyMedia',
    'VirtualMachine.Interact.ToolsInstall',
    'VirtualMachine.Inventory.Create',
    'VirtualMachine.Inventory.CreateFromExisting',
    'VirtualMachine.Inventory.Move',
    'VirtualMachine.Inventory.Register',
    'VirtualMachine.Inventory.Unregister',
    'VirtualMachine.Provisioning.Customize',
    'VirtualMachine.Provisioning.DiskRandomAccess',
    'VirtualMachine.Provisioning.FileRandomAccess',
    'VirtualMachine.Provisioning.MarkAsVM',
    'VirtualMachine.Provisioning.ModifyCustSpecs',
    'VirtualMachine.Provisioning.PromoteDisks',
    'VirtualMachine.State.RevertToSnapshot'
]

VCDR_LWD_PRIVS = [
    'vSphereDataProtection.Protection',
    'Host.Config.NetService',
    'vSphereDataProtection.Recovery',
    'System.Read',
    'System.Anonymous',
    'Host.Config.Storage',
]

class GOVC(object):
    def __init__(self, vc_url, vc_username, vc_password, verify_server_cert=False, exit_on_cmd_failure=True):
        """
        The govc URL scheme defaults to https and the URL path defaults to /sdk.
        This means that:
        1. 'host' is equivalent to 'https://<host>/sdk'.
        2. 'username:password@host' is equivalent to 'https://<username:password@host>/sdk'.    # pragma: allowlist secret

        :param vc_url: URL of ESXi or vCenter instance to connect to
        :param vc_username: vCenter or ESXi admin user's username
        :param vc_password: vCenter or ESXi admin user's password
        :param verify_server_cert: Verify vCenter or ESXi server certificate
        :return: None
        """
        self.vc_url = vc_url
        self.vc_username = vc_username
        self.vc_password = vc_password
        self.verify_server_cert = verify_server_cert
        self.exit_on_cmd_failure = exit_on_cmd_failure
        self.set_envs()

    def _run_govc_cli(self, cmd_args, timeout, json=False):
        """
        Run command.

        :param cmd_args: List of command line args
        :param json: Enable JSON output
        :param timeout: command timeout in secs
        :return: command output
        """
        cmd = [GOVC_PATH]
        cmd.extend(cmd_args)
        if json:
            cmd.insert(2, '-json')

        wait = 0
        logger.debug('Run command %s.', cmd)
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        while p.poll() is None and wait < timeout:
            wait += 1
            time.sleep(1)

        if p.poll() is None:
            p.kill()
            rc = 124
        else:
            rc = p.returncode
        stdout, stderr = p.communicate()

        if rc != 0:
            if rc == 124:
                stderr = 'timed out after {} secs, {}'.format(timeout, stderr)
            logger.error('%s failed, rc: %d, stdout: %s, stderr: %s', cmd, rc, stdout, stderr)
            if self.exit_on_cmd_failure:
                exit(rc)
            else:
                raise subprocess.CalledProcessError(rc, cmd, output=stdout+stderr)

        logger.info(stdout)
        return stdout.strip()

    def set_envs(self):
        """
        Set govc login environment variables.

        While using '-u https://user:pass@host/sdk' from the govc command line,    # pragma: allowlist secret
        there is a bug if the username or the password includes special characters.
        Using environment variables instead confirmed works.
        """
        os.environ['GOVC_URL'] = self.vc_url
        os.environ['GOVC_USERNAME'] = self.vc_username
        os.environ['GOVC_PASSWORD'] = self.vc_password
        os.environ['GOVC_INSECURE'] = 'false' if self.verify_server_cert else 'true'
        logger.debug('govc envs: %s', {k: v for k, v in os.environ.items() if k.startswith('GOVC') and k != 'GOVC_PASSWORD'})

    def ssogroup_create(self, name, description=None, timeout=15, json=False, **kwargs):
        """
        Create SSO group.

        :param name: SSO group name
        :param description: SSO group description
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: Command output
        """
        kwargs.clear()

        cmd_args = ['sso.group.create']
        if description:
            cmd_args.extend(['-d', description])
        cmd_args.append(name)

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssogroup_ls(self, timeout=15, json=False, **kwargs):
        """
        List SSO groups.

        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: SSO groups
        """
        kwargs.clear()

        cmd_args = ['sso.group.ls']

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssogroup_rm(self, name, timeout=15, json=False, **kwargs):
        """
        Remove SSO group.

        :param name: SSO group name
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: Command output
        """
        kwargs.clear()

        cmd_args = ['sso.group.rm', name]

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssogroup_update(self, name, description='', add_user=None, remove_user=None,
                        timeout=15, json=False, **kwargs):
        """
        Update SSO group.

        :param name: SSO group name
        :param description: SSO group description
        :param add_user: Add user to group
        :param remove_user: Remove user from group
        :param description: SSO group description
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: Command output
        """
        kwargs.clear()

        cmd_args = ['sso.group.update']
        if description:
            cmd_args.extend(['-d', description])
        if add_user is not None:
            cmd_args.extend(['-a', add_user])
        if remove_user is not None:
            cmd_args.extend(['-r', remove_user])
        cmd_args.append(name)

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssouser_create(self, name, password=None, description=None,
                       solution=False, act_as_user=True, role='Administrator', certificate=None,
                       timeout=15, json=False, **kwargs):
        """
        Create SSO user.

        :param name: SSO user name
        :param password: SSO person user password
        :param description: SSO user description
        :param solution: Whether is solution user
        :param act_as_user: ActAsUser role for solution user WSTrust
        :param role: Role for solution user (RegularUser|Administrator)
        :param certificate: Certificate for solution user
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: Command output
        """
        kwargs.clear()

        cmd_args = ['sso.user.create']
        if description:
            cmd_args.extend(['-d', description])
        if solution:
            assert certificate, 'Solution user certificate is required!'
            assert role in ['RegularUser', 'Administrator'], (
                'Invalid solution user role: {}!'.format(role))
            if act_as_user:
                cmd_args.append('-A')
            cmd_args.extend(['-R', role, '-C', certificate])
        else:
            # Person user.
            assert password, 'SSO user password is required!'
            cmd_args.extend(['-p', password])
        cmd_args.append(name)

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssouser_id(self, name=None, timeout=15, json=False, **kwargs):
        """
        Get SSO user and group IDs.

        :param name: SSO user name
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: SSO user and group IDs
        """
        kwargs.clear()

        cmd_args = ['sso.user.id']
        if name is not None:
            cmd_args.append(name)

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssouser_ls(self, solution=False, timeout=15, json=False, **kwargs):
        """
        List SSO users.

        :param solution: List solution users
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: SSO users
        """
        kwargs.clear()

        cmd_args = ['sso.user.ls']
        if solution:
            cmd_args.append('-s')

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssouser_rm(self, name, timeout=15, json=False, **kwargs):
        """
        Remove SSO user.

        :param name: SSO user name
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: SSO user and group IDs
        :return: Command output
        """
        kwargs.clear()

        cmd_args = ['sso.user.rm', name]

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def ssouser_update(self, name, password=None, description=None,
                       solution=False, act_as_user=True, role=None, certificate=None,
                       timeout=15, json=False, **kwargs):
        """
        Update SSO user.

        :param name: SSO user name
        :param password: SSO person user password
        :param description: SSO user description
        :param solution: Whether is solution user
        :param act_as_user: ActAsUser role for solution user WSTrust
        :param role: Role for solution user (RegularUser|Administrator)
        :param certificate: Certificate for solution user
        :param timeout: command timeout in secs
        :param json: Enable JSON output
        :return: Command output
        """
        kwargs.clear()

        cmd_args = ['sso.user.update']
        if description is not None:
            cmd_args.extend(['-d', description])
        if solution:
            if act_as_user:
                cmd_args.append('-A')
            if role is not None:
                assert role in ['RegularUser', 'Administrator'], (
                    'Invalid solution user role: {}!'.format(role))
                cmd_args.extend(['-R', role])
            if certificate is not None:
                cmd_args.extend(['-C', '{}'.format(certificate)])
        else:
            if password is not None:
                cmd_args.extend(['-p', password])
        cmd_args.append(name)

        return self._run_govc_cli(cmd_args, timeout=timeout, json=json)

    def get_sso_user_privileges(self, username, timeout=15, **kwargs):
        """
        Get privileges associated with an SSO user
        :param name: SSO user name
        :param timeout: command timeout in secs
        :return: List of privileges
        """
        kwargs.clear()

        permission_cmd_args = ['permissions.ls']
        permission_output = self._run_govc_cli(permission_cmd_args, timeout=timeout)
        privileges_list = []
        normalized_user = username.lower()
        for line in permission_output.splitlines()[1:]:
            line_split = line.split('/') #formatted [role, entity, username, propogate]
            user = line_split[1].split()[0].lower() # user formatted either <user> or <domain>\\<user>
            user_split = user.split("\\")
            role = line_split[0].strip()
            constructed_user = user_split[1] + "@" + user_split[0] if len(user_split) == 2 else user
            if normalized_user == constructed_user:
                role_cmd_args = ['role.ls', role]
                privileges_list.extend(self._run_govc_cli(role_cmd_args, timeout=timeout).split())
                break

        return privileges_list

    def set_sso_user_privileges(self, username, role, entity='/', timeout=15, **kwargs):
        """
        Attach a role to a user
        :param username: SSO user name
        :param role: Role to attach to SSO user
        :param entity: Entity to attach privileges on, default root
        :param timeout: command timeout in secs
        """
        kwargs.clear()

        permission_cmd_args = ["permissions.set"]
        permission_cmd_args.extend(['-principal', username])
        permission_cmd_args.extend(['-role', role])
        permission_cmd_args.extend(['-propagate=true'])
        permission_cmd_args.extend([entity])
        self._run_govc_cli(permission_cmd_args, timeout=timeout)

    def role_ls(self, role=None, json=False, timeout=15, **kwargs):
        """
        Get information about roles. Lists names of all roles if a role is
        not provided.
        :param role: name of role to get information about
        :param timeout: command timeout in secs
        """
        kwargs.clear()

        role_cmd_args = ["role.ls"]
        if role:
             role_cmd_args.append(role)
        return self._run_govc_cli(role_cmd_args, timeout=timeout, json=json)

    def role_create(self, role, privileges, timeout=15, **kwargs):
        """
        Get information about roles. Lists names of all roles if a role is
        not provided.
        :param role: name of role to get information about
        :param privileges: list of privileges to attach to role
        :param timeout: command timeout in secs
        """
        kwargs.clear()

        role_cmd_args = ["role.create"]
        role_cmd_args.append(role)
        role_cmd_args.extend(privileges)
        return self._run_govc_cli(role_cmd_args, timeout=timeout)

    def role_update(self, role, privileges_to_add=None, privileges_to_remove=None, timeout=15, **kwargs):
        """
        Get information about roles. Lists names of all roles if a role is
        not provided.
        :param role: name of role to get information about
        :param privileges_to_add: list of privileges to attach to role
        :param privileges_to_remove: list of privileges to remove
        :param timeout: command timeout in secs
        """
        kwargs.clear()

        role_cmd_args = ["role.update"]
        if privileges_to_add:
            role_cmd_args.append("-a")
            role_cmd_args.append(role)
            role_cmd_args.extend(privileges_to_add)
        if privileges_to_remove:
            role_cmd_args.append("-r")
            role_cmd_args.append(role)
            role_cmd_args.extend(privileges_to_remove)
        return self._run_govc_cli(role_cmd_args, timeout=timeout)

class vcdr_create_vcenter_user(object):
    def _get_password(self, account=None, retype=False):
        '''
        Prompt for a password.
        '''
        prompt = 'Retype password' if retype else 'Password'
        if account is not None:
            prompt += ' for account %s' % account
        prompt += ': '
        while True:
            password = getpass(prompt=prompt)
            if password:
                return password

    def _get_username_and_domain(self, user):
        username = user
        domain = None
        if '@' in user:
            username, domain = user.split('@', 1)
        return username, domain

    def _get_user(self, govc_channel, user):
        username, domain = self._get_username_and_domain(user)
        users = govc_channel.ssouser_ls(json=True)
        if users:
            users = json.loads(users)
            for user in users:
                if user['Id']['Name'] == username and user['Id']['Domain'] == domain:
                    return user
        return None

    def _get_role(self, govc_channel, role_name):
        try:
            role = govc_channel.role_ls(role_name, json=True)
            return json.loads(role) if role else None
        except:
            # Unable to find role.
            pass
        return None

    def handle_vcdr_create_vcenter_user(self, vcenter, adminUsername, newUsername, adminPassword=None, newPassword=None, vcenterRole=None,
                                       vcenterEntity=None, snapshotPrivs=False, failbackPrivs=True, keepExistingPrivs=False, createRoleOnly=False):
        """
        @param vcenter: vCenter IP to create new user on
        @param adminUsername: Admin username at desired vCenter
        @param newUsername: New username to be created in the desired vCenter
        @param adminPassword: Admin password at desired vCenter
        @param newPassword: New password to be attached to the new user in the desired vCenter
        @param vcenterRole: Name of the vCenter role to attach to the new user
        @param vcenterEntity: VCenter entity path to give the new user privileges on, by default root
        @param snapshotPrivs: Associate privileges necessary for snapshotting and failover with the role
        @param failbackPrivs: Associate privileges necessary for snapshotting, failover, and failback with the role
        @param keepExistingPrivs: Keep existing privileges on provided role (i.e., do not prune unnecessary privileges from provided role)
        @param createRoleOnly: Create role with necessary privileges but do not create a user account or assign permissions
        """
        try:
            if adminPassword is None:
                adminPassword = self._get_password(account=adminUsername)
            if newPassword is None:
                while True:
                    newPassword = self._get_password(account=newUsername)
                    newPassword2 = self._get_password(account=newUsername, retype=True)
                    if newPassword == newPassword2:
                        break
                    print('Passwords do not match.')
            govc_channel = GOVC(vcenter, adminUsername, adminPassword, exit_on_cmd_failure=False)
        except:
            msg = u'Unable to establish connection with vCenter {} with username {}'.format(vcenter, adminUsername)
            logging.exception(msg)
            print(msg)
            return

        # Decide which privileges we care about. TODO: select based on CLI.
        if failbackPrivs:
            target_privs = VCDR_FAILBACK_PRIVS
        else:
            target_privs = VCDR_SNAPSHOT_PRIVS

        # Check the privileges of the Admin, and assign only the permissible privileges.
        admin_role = self._get_role(govc_channel, 'Admin')
        target_privs = list(set(admin_role['Privilege']) & set(target_privs + VCDR_LWD_PRIVS))

        # Create user if it doesn't already exist.
        if not createRoleOnly:
            existing_user = False
            try:
                existing_user = self._get_user(govc_channel, newUsername)
                newUsernameWithOutDomain, _ = self._get_username_and_domain(newUsername)
                if not existing_user:
                    govc_channel.ssouser_create(newUsernameWithOutDomain, newPassword)
                else:
                    govc_channel.ssouser_update(newUsernameWithOutDomain, newPassword)
            except:
                msg = u'Unable to {} user {} on vCenter {}'.format("update" if existing_user else "create", newUsername, vcenter)
                logging.exception(msg)
                print(msg)
                return

        # Create role if it doesn't already exist and add appropriate privs.
        try:
            existing_role = self._get_role(govc_channel, vcenterRole)
            if existing_role:
                govc_channel.role_update(vcenterRole, privileges_to_add=target_privs)
            else:
                govc_channel.role_create(vcenterRole, target_privs)
        except:
            msg = u'Unable to create/update role {} on vCenter {}'.format(vcenterRole, vcenter)
            logging.exception(msg)
            print(msg)
            return

        # Remove extraneous privs from role.
        if not keepExistingPrivs:
            try:
                role = self._get_role(govc_channel, vcenterRole)
                privs = role['Privilege']
                extra_privs = list(set(privs) - set(target_privs))
                if extra_privs:
                    govc_channel.role_update(vcenterRole, privileges_to_remove=extra_privs)
            except:
                msg = u'Unable to remove extraneous privs from role {} on vCenter {}'.format(vcenterRole, vcenter)
                logging.exception(msg)
                print(msg)
                return

        # Update the permissions.
        if not createRoleOnly:
            try:
                vcenterEntity = vcenterEntity or '/'
                govc_channel.set_sso_user_privileges(newUsername, vcenterRole, vcenterEntity)
            except:
                msg = u'Unable to create permission on {} for {}/{} on vCenter {}'.format(vcenterEntity, newUsername, vcenterRole, vcenter)
                logging.exception(msg)
                print(msg)
                return

        return


if __name__ == '__main__':
    import argparse

    logging.basicConfig(level=logging.DEBUG, filename=LOG_FILE, filemode='a')
    logging.info('Starting script')

    parser = argparse.ArgumentParser(description='Create vCenter user/role/permission with minimal privileges for use with VCDR.')
    parser.add_argument('--vcenter',
                        required=True,
                        help='vCenter IP on which to create/update user')
    parser.add_argument('--admin-username',
                        required=True,
                        metavar='admin-username',
                        help='Admin username at desired vCenter')
    parser.add_argument('--new-username',
                        required=True,
                        metavar='new-username',
                        help='New username to be created in the desired vCenter')
    parser.add_argument('--admin-password',
                        metavar='admin-password',
                        help='Admin password at desired vCenter (will be prompted if not provided)')
    parser.add_argument('--new-password',
                        metavar='new-password',
                        help='New password for the new user in the desired vCenter (will be prompted if not provided)')
    parser.add_argument('--vcenter-role',
                        required=True,
                        metavar='vcenter-role',
                        help='Name of new or existing vCenter role to associate to the new user')
    parser.add_argument('--keep-existing-privs',
                        action='store_true',
                        help='Keep existing privileges on provided role (i.e., do not prune unnecessary privileges from provided role)')
    parser.add_argument('--create-role-only',
                        action='store_true',
                        help='Create role with necessary privileges but do not create a user account or assign permissions')

    group = parser.add_mutually_exclusive_group()
    group.add_argument('--snapshot-privs',
                       action='store_true',
                       help='Create a user with privileges necessary to snapshot VMs and failover (the default)')
    group.add_argument('--failback-privs',
                        action='store_true',
                        help='Create a user with privileges necessary to snapshot VMs, failover, and failback')

    args = vars(parser.parse_args())

    vcenter = args.get('vcenter')
    admin_username = args.get('admin_username')
    new_username = args.get('new_username')
    admin_password = args.get('admin_password')
    new_password = args.get('new_password')
    vcenter_role = args.get('vcenter_role')
    vcenter_entity = '/'
    snapshot_privs = args.get('snapshot_privs')
    failback_privs = args.get('failback_privs')
    keep_existing_privs= args.get('keep_existing_privs', False)
    create_role_only = args.get('create_role_only', False)

    worker = vcdr_create_vcenter_user()
    worker.handle_vcdr_create_vcenter_user(vcenter, admin_username, new_username, adminPassword=admin_password, newPassword=new_password,
                                          vcenterRole=vcenter_role, vcenterEntity=vcenter_entity, snapshotPrivs=snapshot_privs,
                                          failbackPrivs=failback_privs, keepExistingPrivs=keep_existing_privs, createRoleOnly=create_role_only)

    logging.info('Ending script')

    exit(0)