Files
OpenVixDiskLib/docs/nfc_auth.md
T

288 lines
13 KiB
Markdown

# VDDK NFC authentication
This document records how VMware VDDK authenticates for NBD/NFC disk
access, and how the Python replacement in `openvixdisklib/nfc_auth.py`
reproduces that path. Findings come from VDDK 8.0.2 libraries
(`libvixDiskLib`, `libvddkVimAccess`, `libvim-types`), live SOAP calls
against vCenter
8.0.1, and a TLS intercept of `VixDiskLib_ConnectEx` / `VixDiskLib_Open`.
The steps used to obtain those findings are in
`docs/reverse_engineering_procedure.md`.
The goal of this stage is authentication only: a logged-in VIM session
plus an authd TLS socket that has completed `200 Connect`. Opening a
VMDK and reading sectors is `docs/nfc_open.md`.
## Mapping from VDDK
The VDDK wrapper in `tests/integration/vixdisklib.py` calls
`VixDiskLib_ConnectEx` with UID credentials and `VixDiskLib_Open` on a
datastore path. VDDK does **not** send the vCenter username and
password to ESXi port 902. It:
1. Logs into vCenter over HTTPS 443 (SOAP / `urn:vim25`).
2. Asks vCenter for a one-time NFC ticket.
3. Connects to the ESXi **authd** daemon on TCP 902, upgrades to TLS,
and presents that ticket.
| VDDK call | What actually happens |
| ------------------------------------ | ---------------------------------------------------------- |
| `VixDiskLib_InitEx` | Load plugins, SSL, logging |
| `VixDiskLib_ConnectEx` | SOAP `SessionManager.Login` to vCenter |
| `VixDiskLib_Open` (read-only) | `NfcGetVmFiles` ticket, then authd handshake, then NFC I/O |
| `VixDiskLib_Open` (read-write) | `NfcRandomAccessOpenDisk` ticket (disk key + host) |
| `transport_modes="nbdssl"` (default) | NBDSSL (`vpxa-nfcssl://...@esxi:902`, second TLS wrap) |
| `transport_modes="nbd"` | NBD over NFC (`vpxa-nfc://...@esxi:902`) |
| `vmxSpec=moref=vm-13098` | VM managed object used as the ticket target |
| `snapshot_ref` | Not consumed by the ticket call itself |
| `VIXDISKLIB_CRED_UID` | Username/password for VIM only |
Lab topology used for capture:
- vCenter: `10.8.1.199` (VirtualCenter 8.0.1)
- VM: `vm-13098` on host `host-13001` (`10.8.1.250`)
- NFC service moref on vCenter: `nfcService`
- Authd: `10.8.1.250:902`
## Stage 1: VIM login
This is a public pyVmomi operation. Reuse `pyVim.connect.SmartConnect`
rather than crafting SOAP.
- Endpoint: `https://<vcenter>:443/sdk`
- Cookie: `vmware_soap_session`
- SOAPAction: `"urn:vim25/8.0.1.0"` (negotiated)
VDDK logs this as `Connected to VIM Server` / `Authenticating user` /
`Logged in!`. The Python replacement keeps that `ServiceInstance` and
its stub for the ticket call.
Direct ESXi login is the same SOAP login against hostd, but the NFC
moref and service name differ (`ha-nfc` instead of `nfcService` /
`vpxa-nfc`). The lab path is vCenter-mediated.
## Stage 2: NFC ticket
### Why this is not public pyVmomi
`vim.NfcService` is omitted from the public vim25 WSDL that pyVmomi
ships. vCenter still implements it:
- Version document: `GET /sdk/nfcServiceVersions.xml` → namespace
`urn:nfc`, version `7.0.3.2`
- Methods also accept `urn:vim25` (that is what VDDK uses)
- Well-known moref on this vCenter: `nfcService`
`ServiceManager.QueryServiceList` does **not** list NFC. The moref is
hardcoded in VDDK as `nfcService` (vCenter) or `ha-nfc` (ESXi).
`openvixdisklib/nfc_auth.py` registers the missing type with
`pyVmomi.VmomiSupport.CreateManagedType` and invokes it on the existing
SmartConnect stub, so serialization, cookies, and `HostServiceTicket`
stay in pyVmomi.
### Methods VDDK actually calls
Intercepted SOAP for a **read-only** `VixDiskLib_Open` of a datastore
path:
```xml
<NfcGetVmFiles xmlns="urn:vim25">
<_this type="NfcService">nfcService</_this>
<vm type="VirtualMachine">vm-13098</vm>
</NfcGetVmFiles>
```
No disk path, snapshot, or host is in this request. The path
(`[datastore0] ...-000007.vmdk`) is used later on the NFC channel.
A `GetVmFiles` ticket is **not** writable. Opening the same path with
NFC flags `0x1a` returns AIO error `0x0b` (`VIX_E_FILE_READ_ONLY`).
Writable `ConnectEx(readOnly=FALSE)` uses a disk-scoped ticket instead.
`libvim-types.so` maps vmodl `randomAccessOpen` to WSDL
`NfcRandomAccessOpenDisk` (same arguments as the read-only sibling):
```xml
<NfcRandomAccessOpenDisk xmlns="urn:vim25">
<_this type="NfcService">nfcService</_this>
<vm type="VirtualMachine">vm-13098</vm>
<diskDeviceKey>2000</diskDeviceKey>
<hostForAccess type="HostSystem">host-13001</hostForAccess>
</NfcRandomAccessOpenDisk>
```
A disk-scoped **read** ticket also works and returns the same
`HostServiceTicket` type:
```xml
<NfcRandomAccessOpenReadonly xmlns="urn:nfc">
<_this type="NfcService">nfcService</_this>
<vm type="VirtualMachine">vm-13098</vm>
<diskDeviceKey>2000</diskDeviceKey>
<hostForAccess type="HostSystem">host-13001</hostForAccess>
</NfcRandomAccessOpenReadonly>
```
`diskDeviceKey` is `VirtualDisk.key` from `vm.config.hardware.device`
(2000 for Hard disk 1). Read-only `VixDiskLib_Open` does **not** send
it: the drop-in uses `NfcGetVmFiles` and puts the VMDK path (leaf or
snapshot parent) only on NFC `OPEN_FILE`. Writable `open` resolves a
key from the datastore path, matching the current leaf or any parent
in `backing.parent` (for example `…-000007.vmdk` after the VM has
moved on to `…-000008.vmdk`).
### Return value: `vim.HostServiceTicket`
Public pyVmomi type. Example from this lab:
| Field | Example | Role |
| ---------------- | -------------------------------------- | ----------------------------------------- |
| `host` | `10.8.1.250` | ESXi management / NFC address |
| `port` | `902` | authd TCP port |
| `sslThumbprint` | `BE:22:58:...:76:29` | SHA-1 of the ESXi TLS cert |
| `service` | `vpxa-nfc` | authd `PROXY` argument |
| `serviceVersion` | `1.1` | NFC hosted by hostd (ESX 3.0+ convention) |
| `sessionId` | `52cdebc5-b7ee-359a-1dec-76f0bc105ac5` | One-time authd `SESSION` token |
Tickets are single-use. Calling `GetVmFiles` twice issues two tickets;
only the one presented to authd is consumed.
### Other NfcService methods seen in VDDK
WSDL names are prefixed with `Nfc`. The vmodl names (from
`libvim-types.so`) include:
| WSDL name | Parameters (observed / from C++) | Notes |
| ----------------------------- | -------------------------------------- | ------------------------------ |
| `NfcGetVmFiles` | `vm` | VDDK read-only Open path |
| `NfcRandomAccessOpenReadonly` | `vm`, `diskDeviceKey`, `hostForAccess` | Disk-scoped read ticket |
| `NfcRandomAccessOpenDisk` | `vm`, `diskDeviceKey`, `hostForAccess` | Disk-scoped read-write ticket |
| `NfcGetServerNfcLibVersion` | `hostForAccess` | Lab returned `11` |
| `NfcFileManagement` | requires `ds` (datastore) | File copy, not NBD |
| `NfcSystemManagement` | host moref | Not used for disk open |
`NfcGetServerNfcLibVersion` without `hostForAccess` fails with
`A specified parameter was not correct: hostForAccess`. Using moref
`ha-nfc` on vCenter fails with `ManagedObjectNotFound`; `nfcService`
is the correct vCenter object.
## Stage 3: authd handshake (TCP 902)
authd is the VMware Authentication Daemon. Plaintext banner from ESXi
8:
```
220 VMware Authentication Daemon Version 1.10: SSL Required, ServerDaemonProtocol:SOAP, MKSDisplayProtocol:VNC , VMXARGS supported, NFCSSL supported/t, SHA256 supported
```
SSL is required. Sending commands before `wrap_socket` closes the
connection. After TLS there is **no** `USER` / `PASS` when the client
holds a vCenter NFC ticket.
### Sequence captured from VDDK
VDDK log line immediately before the socket:
```
Using proxy/session authentication, sessionId=..., useSSL=0
Plain-text connection is deprecated; use SSL to connect to NFC server
```
`useSSL=0` does **not** mean skip TLS on 902. It means skip a second
NFCSSL wrap after authd TLS. The management channel is still TLS.
`useSSL=1` (nbdssl) is the same authd commands with `PROXY vpxa-nfcssl`
and a second TLS handshake after `200 Connect`.
Intercepted writes/reads after the TLS handshake:
```
C -> SESSION <sessionId>\r\n
C -> BANNER \r\n
S -> 220 VMware Authentication Daemon Version 1.10: ...\r\n
C -> THUMBPRINT_SHA2 PlainText\r\n
S -> 200 <SHA-256 thumbprint with colons>\r\n
C -> PROXY vpxa-nfc\r\n
S -> 200 Connect ha-nfc\r\n
```
NBDSSL uses the same `SESSION` / `BANNER` / `THUMBPRINT_SHA2 PlainText`
sequence. The ticket still has `service=vpxa-nfc`; the client rewrites
the PROXY argument:
```
C -> PROXY vpxa-nfcssl\r\n
S -> 200 Connect ha-nfcssl\r\n
```
After that reply, authd TLS is finished and `ha-nfcssl` expects a **new**
TLS ClientHello on the same TCP connection (`useSSL=1`). NFC frames then
travel as TLS application data of that second session. NBD (`useSSL=0`)
skips the second wrap and sends NFC as raw TCP instead.
Notes:
- `SESSION` does not get a reply of its own. Waiting for a line after
`SESSION` looks like a hang.
- `BANNER` is the 7-byte command `BANNER` plus a trailing space. That
space is part of the token; authd strips spaces when matching some
commands, so `THUMBPRINT_SHA2 <colon-thumbprint>` is parsed as one
token and returns `501 Invalid arguments`. `PlainText` has no extra
spaces/colons and is the argument VDDK sends.
- `PROXY` uses `ticket.service` (`vpxa-nfc` via vCenter) for NBD. NBDSSL
appends `ssl` (`vpxa-nfcssl`). The success line names the host-side
endpoint (`ha-nfc` or `ha-nfcssl`).
- After `200 Connect`, NBD speaks binary NFC on the raw fd. NBDSSL
starts a second TLS handshake, then the same NFC protocol.
### Commands that are not used for this ticket type
authd also implements FTP-style `USER` / `PASS` (and `XPAS`). Those
are for local ESXi credentials. With a vCenter ticket:
| Attempt | Result |
| -------------------------------------------- | --------------------------------------- |
| `USER` / `PASS` (vCenter account) | `530 Login incorrect` |
| `USER *` / `PASS <sessionId>` | `530 Login incorrect` |
| `USER <sessionId>` / `PASS <sessionId>` | `530 Login incorrect` |
| `SESSIONID <sessionId>` | `530 Please login with USER and PASS` |
| `CONNECT_VPXA <sessionId>` (after TLS) | `530 Please login with USER and PASS` |
| `SESSION <sessionId>` then wait for a reply | No line until `BANNER` / `PROXY` follow |
`THUMBPRINT` / `THUMBPRINT_SHA2` with the SHA-1 ticket thumbprint as
argument is not what VDDK sends. The SHA-1 value is for verifying the
TLS certificate, not for the `THUMBPRINT_SHA2` command.
## Python replacement
| Piece | Module | Reuses pyVmomi? |
| -------------------- | ---------------------------------------- | ---------------------------------- |
| VIM login | `openvixdisklib.nfc_auth.connect_vim` | Yes — `SmartConnect` |
| VM / host lookup | `vim.VirtualMachine` | Yes |
| `HostServiceTicket` | return type of ticket call | Yes — public data object |
| NFC ticket | `openvixdisklib.nfc_auth.get_nfc_ticket` | Same stub; type registered locally |
| authd TLS + commands | `openvixdisklib.nfc_auth.connect_authd` | No public API |
| End-to-end | `openvixdisklib.nfc_auth.authenticate` | `NfcAuthSession` |
Management SHA-1 thumbprints are read with
`openvixdisklib.nfc_auth.get_ssl_cert_thumbprint` (stdlib `ssl` and
`hashlib`; no pyOpenSSL). Integration tests call that instead of
hard-coding the lab certificate.
Run:
```bash
.venv/bin/pytest tests/integration/test_nfc_auth.py
```
The test completes VIM login and the authd handshake (`200 Connect`)
and asserts an established TLS socket on `ticket.host:ticket.port`.
## What comes after authentication
Authentication stops at `200 Connect ha-nfc` (NBD) or `200 Connect
ha-nfcssl` (NBDSSL). Opening the VMDK and reading or writing sectors is
documented in `docs/nfc_open.md` and implemented in
`openvixdisklib/nfc_open.py`. The datastore path is consumed there
(and, for writes, as `diskDeviceKey` on the ticket).