Source code for saltext.vcf.clients.esxi_auth_proxy

"""ESXi vSphere Authentication Proxy (CAM) configuration via SOAP/pyVmomi.

Covers the 912-controls requirement ``ESXi.enable-auth-proxy``: enable the
vSphere Authentication Proxy so ESXi hosts can join Active Directory without
AD credentials being sent from the client that runs the join.

The join itself is done through the host's
``configManager.authenticationManager.JoinDomainWithCAM_Task`` — that is the
pyVmomi call the auth proxy is designed to serve.  Two advanced settings
control the ESXi-side plumbing:

* ``UserVars.ActiveDirectoryVerifyCAMCertificate`` — 1 to require a valid
  CAM (Camellia) certificate, 0 to skip verification.
* ``Config.HostAgent.plugins.vmauthenticationservice.authproxyaddress`` —
  hostname/IP of the CAM appliance.

The advanced-setting keys are stable across ESXi 7.0/8.0/9.x; the
``JoinDomainWithCAM_Task`` shape is also stable (``domainName``,
``camServer``).  If a lab surfaces a divergence, prefer the shape ESXi
actually accepts and log an issue.
"""

from pyVmomi import vim
from pyVmomi import vmodl  # pylint: disable=no-name-in-module

from saltext.vcf.utils import vim as soap

CAM_ADDRESS_KEY = "Config.HostAgent.plugins.vmauthenticationservice.authproxyaddress"
CAM_VERIFY_KEY = "UserVars.ActiveDirectoryVerifyCAMCertificate"


def _host(opts, host, profile=None):
    return soap.resolve_host_system(opts, host, profile=profile)


def _advanced_get(h, key):
    try:
        options = h.configManager.advancedOption.QueryOptions(name=key)
    except (vim.fault.VimFault, vmodl.MethodFault):
        return None
    if not options:
        return None
    return options[0].value


def _advanced_set(h, key, value):
    h.configManager.advancedOption.UpdateValues(
        changedValue=[vim.option.OptionValue(key=key, value=value)]
    )


def _ad_state(h):
    """Return ``{"joined": bool, "domain": str|None}`` from HostAuthenticationManager."""
    auth_mgr = h.configManager.authenticationManager
    info = getattr(auth_mgr, "info", None)
    stores = getattr(info, "authConfig", None) if info is not None else None
    if stores is None:
        # Some pyVmomi shapes surface authenticationManagerInfo on config.
        info = getattr(h.config, "authenticationManagerInfo", None) if h.config else None
        stores = getattr(info, "authConfig", None) if info is not None else None
    for store in stores or []:
        if isinstance(store, vim.host.ActiveDirectoryInfo):
            return {
                "joined": bool(store.enabled),
                "domain": store.joinedDomain or None,
            }
    return {"joined": False, "domain": None}


[docs] def get_config(opts, host, profile=None): """Return CAM configuration and current AD join state for *host*. :: { "cam_address": "cam.example.com" | None, "verify_cam_cert": True | False | None, "joined": bool, "domain": str | None, } """ h = _host(opts, host, profile=profile) verify_raw = _advanced_get(h, CAM_VERIFY_KEY) verify_bool = None if verify_raw is None else bool(int(verify_raw)) return { "cam_address": _advanced_get(h, CAM_ADDRESS_KEY) or None, "verify_cam_cert": verify_bool, **_ad_state(h), }
[docs] def set_config(opts, host, cam_address=None, verify_cam_cert=None, profile=None): """Set CAM advanced settings on *host*. Both fields are optional; passing ``None`` leaves that value alone. ``verify_cam_cert`` is coerced to the ``0``/``1`` int the setting stores. """ h = _host(opts, host, profile=profile) if cam_address is not None: _advanced_set(h, CAM_ADDRESS_KEY, str(cam_address)) if verify_cam_cert is not None: _advanced_set(h, CAM_VERIFY_KEY, 1 if verify_cam_cert else 0) return get_config(opts, host, profile=profile)
[docs] def join_domain_via_cam(opts, host, domain_name, cam_server, profile=None): """Join *host* to *domain_name* using the CAM (auth proxy) at *cam_server*. Calls ``HostActiveDirectoryAuthentication.JoinDomainWithCAM_Task``. Returns the task moId; callers that need to block should wrap in :func:`saltext.vcf.utils.vim.wait_for_task`. """ h = _host(opts, host, profile=profile) task = h.configManager.authenticationManager.JoinDomainWithCAM_Task( domainName=domain_name, camServer=cam_server ) return task._moId # noqa: SLF001
[docs] def leave_domain(opts, host, force=False, profile=None): """Leave the current AD domain via ``LeaveCurrentDomain_Task``.""" h = _host(opts, host, profile=profile) task = h.configManager.authenticationManager.LeaveCurrentDomain_Task(force=bool(force)) return task._moId # noqa: SLF001