OmniCube Reference Manualactivate_zone_vnc.sh(1)

activate_zone_vnc.sh(1)

User Commands · OmniCube · August 29, 2026

NAME

activate_zone_vnc.sh - publish a bhyve or kvm zone VNC console over TCP

SYNOPSIS

/opt/omnicube/bin/activate_zone_vnc.sh [-s|-S] zone [port]

activate_zone_vnc.sh zone

DESCRIPTION

activate_zone_vnc.sh makes the graphical console of a running OmniCube virtual machine reachable over TCP. The VM console itself is exposed by the hypervisor on a zone-local UNIX domain socket; this command starts a socat(1) relay that accepts RFB (VNC) connections on a TCP port and forwards them to that socket. The relay is either plain TCP or, with -s or config/vnc_tls, a TLS listener presenting the node's server certificate and optionally requiring a client certificate. It is an interactive operator tool, run by hand when someone needs console access to a guest; it is not driven from cron or from an SMF method.

The command first calls abort_if_shutting_down from /opt/omnicube/lib/common/utils.sh (which it sources). If the host is at run level 0, 1, 5, 6, S or s, or /etc/nologin exists, the script logs one line and exits 0 without doing anything; OC_IGNORE_RUNLEVEL=1 bypasses that guard for deliberate single-user maintenance.

It then validates its operands and the VM, in order: zone must satisfy validate_name (pattern [a-zA-Z0-9][a-zA-Z0-9._-]*) and port, if given, must be entirely numeric. Both checks happen before either value is interpolated, because port is spliced into a zonecfg `select attr ... set value=amp;...' expression where a shell metacharacter would break out of the expression; zoneadm -z zone list must succeed, otherwise the VM is not configured on this node; no relay may already be running for this VM - the test is _zone_console_pids(3), the same match the cleanup paths use, so this guard and the kill there cannot drift apart; and zonecfg -z zone info brand must report bhyve or kvm, any other brand being rejected as unsupported.

Setting a static VNC port

When the optional port operand is supplied, the port is checked twice - with netstat(8), which is authoritative about what is actually bound and also catches a port taken by something that is not a relay at all, and against the process table for a socat (TCP|OPENSSL)-LISTEN:port, which catches a relay that has started but not yet bound. The ps(1) test alone is not enough: illumos truncates pr_psargs to 80 characters, and once the listen address carries certificate paths it can fall entirely past that cutoff. The zone configuration is then rewritten under pfexec:

For a bhyve zone the vnc attribute is set to

rfb=0.0.0.0:<port>,unix=/tmp/vm.vnc,w=1680,h=1050

so the hypervisor keeps the console on the zone-local socket /tmp/vm.vnc (as seen from inside the zone) at a fixed 1680x1050 geometry. For a kvm zone the vncport attribute is set to the numeric port.

Both forms end with verify and commit, so an invalid configuration is rejected before it is written.

Starting the relay

After the configuration step the VM must appear as running in the output of zoneadm -z zone list -v, otherwise the command aborts. The effective port is then read back out of the zone configuration rather than trusted from the command line: for bhyve it is extracted from the vnc attribute with a gawk(1) match on 0.0.0.0:<digits>, and for kvm it is the value of the vncport attribute. If no static port is configured the command aborts.

Transport security

Whether the relay speaks TLS is decided by config/vnc_tls on `${OC_SMF}', overridden for a single run by -s (TLS) or -S (plain). The certificate is resolved in this order, and the first layout that exists wins:

config/vnc_cert

Used when set, together with config/vnc_key when the private key is in its own file.

/etc/opt/omnicube/certs/server.pem

Certificate and private key concatenated in one file, passed as cert=.

/etc/opt/omnicube/certs/server.crt

plus server.key, passed as cert= and key=.

If TLS is requested and none of these is present the command exits 1 without starting anything. Every path is checked against ^/[a-zA-Z0-9._/-]+$ before use: socat address options are comma-separated and colon-delimited, so a path holding a comma or a colon would turn into a further option rather than a file name.

Client authentication is separate from encryption and comes from config/vnc_cafile:

unset

verify=0. The session is encrypted but not authenticated - anything that can open the port still reaches the console. A warning is logged and mailed.

a CA file

verify=1,cafile=path amp;. Only clients presenting a certificate signed by that CA are accepted, which is the only configuration that gives the relay real access control. No warning is emitted.

config/vnc_verify_client turns that choice into node policy. When it is true, TLS is mandatory: -S is rejected, config/vnc_tls is treated as true whatever its value, and config/vnc_cafile must name an existing file - if it does not, the command exits 1 having started nothing rather than falling back to verify=0.

Additional OPENSSL address options can be appended verbatim from config/vnc_tls_opts, constrained to ^[a-zA-Z0-9._,=:+!@-]+$. It is empty by default because the option names are release-dependent - openssl-min-proto-version= exists only from socat 1.7.4 and an unknown option is fatal.

The listen address comes from the SMF property config/vnc_bind on `svc:/omnicube/sysadm:${OC_SMF_INSTANCE:-default}' (referred to below as ${OC_SMF}):

unset or *

Listen on all interfaces; the listen spec is TCP-LISTEN:<port>,reuseaddr,fork. This is the historical default, kept so existing deployments do not change behaviour. A warning is logged and mailed on every activation, because RFB here is unauthenticated.

an address

Matching ^[a-zA-Z0-9.:_-]+$, appended as bind=address so the relay binds only that address. 127.0.0.1 is the recommended value, reached with ssh -L, so ssh supplies the authentication RFB lacks.

anything else

Rejected: error is logged and the command exits 1.

Finally the relay is started in the background as

/usr/bin/nohup pfexec /usr/bin/socat <debug> <log> <listen_spec> \\
    UNIX-CONNECT:<zonepath>/root/tmp/vm.vnc >/dev/null 2>&1 &

where <listen_spec> is

TCP-LISTEN:<port>,reuseaddr,fork[,bind=<addr>]

for a plain relay and

OPENSSL-LISTEN:<port>,cert=<cert>[,key=<key>],<verify>\\
    [,<tls_opts>],reuseaddr,fork[,bind=<addr>]

for a TLS one.

Logging

socat writes its own messages to standard error, and the relay is a background child of the invoking shell, so those lines would land on a terminal that is about to go away: a relay that dies on an unreadable certificate, or on a port taken between the check and the bind, would leave no trace anywhere. They are therefore redirected to the zone's own log directory, beside the zone.log that zoneadmd keeps there:

-lf <zonepath>/log/vnc.log

The zonepath comes from zonecfg -z zone info zonepath, the same lookup that fixes the socket path, so the log always lands beside the zone.log of whichever layout the zone actually uses; it falls back to /zones/zone/root when zonecfg reports nothing, which reproduces the path this command used to hardcode. It is rejected if it is not an absolute path over [a-zA-Z0-9._/-], because it is interpolated into a socat UNIX-CONNECT: address, where a , or a : would parse as a further option. The directory is created with pfexec mkdir -p rather than tested with [ -d ]: a zonepath is typically mode 0700 root, so an operator cannot stat the log directory even when it exists, and a -lf target that cannot be opened makes socat exit instead of relaying. For a running zone the directory is already there.

How much is written is set by config/vnc_log_level, which maps to socat -d:

error

No -d. Fatal and error messages only.

warning

-d. This is the value the manifest ships. warn is accepted as a synonym.

notice

-d -d. Adds one block per accepted connection: the source address, whether a peer certificate was presented and checked, and the negotiated TLS version. This is the closest thing the relay has to an access log and is the level to run when config/vnc_verify_client is on.

info

-d -d -d.

debug

-d -d -d -d. Handshake-level detail, for diagnosing a TLS failure.

Any other value is a fatal error; the property is declared in the service manifest, so setting it needs no type: svccfg -s ${OC_SMF} setprop config/vnc_log_level = notice.Thecommandlinealsoendswith >/dev/null 2>&1, which is belt and braces: -lf takes over once socat has parsed its options, but a complaint about an option an older build does not know would still reach the terminal.

Rotation is handled by the omnicube_vnc and omnicube_vnc_root entries in /etc/logadm.d/omnicube.conf, one per zonepath layout - a logadm pattern is csh-style filename substitution, in which * does not cross a /, so one pattern cannot cover both. Both use -c (copy and truncate), because socat holds the file open for the life of the relay and never reopens it. See logadm(8).

/usr/bin/socat is spelled with its absolute path on purpose: pfexec matches the exec_attr(5) entry on the resolved binary path, and a bare socat found elsewhere in PATH would be denied with EPERM. /usr/bin/nohup is unprivileged and needs no exec_attr(5) entry, but is spelled absolutely as well so that the launch line does not depend on the caller's PATH at all.

nohup(1) is what lets the console outlive the session that published it. Without it the relay is an ordinary background child of the operator's login shell: the kernel sends SIGHUP to an orphaned session's processes, so the console would die with the ssh session that started it - surprising for anyone who activates a console and then passes the address to someone else. nohup execs pfexec in place, so no extra process appears in the process table and the duplicate-relay and port-in-use tests are unaffected, and it writes no nohup.out because the redirection leaves its standard output pointing at /dev/null rather than a terminal. The fork option means every accepted connection is served by a new child; those children run as uid 0 and cannot drop privileges, because the UNIX socket under the zonepath is root-owned.

OPTIONS

-s

Wrap the relay in TLS, i.e. use OPENSSL-LISTEN instead of TCP-LISTEN, regardless of config/vnc_tls.

-S

Force a plain TCP relay even when config/vnc_tls is true. Rejected with exit status 1 when config/vnc_verify_client is true, since that property forbids a relay without client certificates.

If both are given the last one wins. Options must precede the operands.

OPERANDS

The following positional operands are accepted.

zone

Required. Name of the zone (VM) whose console is to be published. Must pass validate_name and must be a zone configured on this node.

port

Optional. TCP port for the relay, digits only. When given, the zone configuration is updated to use this port as the VM's static VNC port before the relay is started. When omitted, no configuration change is made and the port already recorded in the zone configuration is used; if none is recorded the command aborts.

EXAMPLES

Example 1: Publish the console of an already configured VM

$ /opt/omnicube/bin/activate_zone_vnc.sh vm-win2019

The VM must already carry a vnc (bhyve) or vncport (kvm) attribute; the relay listens on that port.

Example 2: Assign a static port and publish it

$ /opt/omnicube/bin/activate_zone_vnc.sh vm-win2019 5905

Example 3: Publish a console over TLS

$ /opt/omnicube/bin/activate_zone_vnc.sh -s zn01beio-wincluster19 6000

which runs

/usr/bin/nohup pfexec /usr/bin/socat \\
    -lf /zones/zn01beio-wincluster19/root/log/vnc.log \\
    OPENSSL-LISTEN:6000,cert=/etc/opt/omnicube/certs/server.pem,\\
verify=0,reuseaddr,fork \\
    UNIX-CONNECT:/zones/zn01beio-wincluster19/root/root/tmp/vm.vnc \\
    >/dev/null 2>&1 &

with the two /zones/zn01beio-wincluster19/root paths taken from the zone's zonepath, here the OmniCube standard layout.

Viewers speak plain RFB, so the client side needs its own stub - for example, on the workstation:

$ socat TCP-LISTEN:5900,reuseaddr,fork \\
    OPENSSL-CONNECT:node1:6000,verify=0
$ vncviewer 127.0.0.1:5900

Example 4: Require a client certificate, node-wide

# svccfg -s svc:/omnicube/sysadm:default setprop \\
      config/vnc_tls = boolean: true
# svccfg -s svc:/omnicube/sysadm:default setprop \\
      config/vnc_cafile = astring: /etc/opt/omnicube/certs/clients-ca.crt
# svccfg -s svc:/omnicube/sysadm:default setprop \\
      config/vnc_verify_client = boolean: true
# svcadm refresh svc:/omnicube/sysadm:default
$ activate_zone_vnc.sh zn01beio-wincluster19

From then on every activation on this node is mutually authenticated, and an attempt to start a plain relay fails:

$ activate_zone_vnc.sh -S zn01beio-wincluster19
[E] config/vnc_verify_client is true on svc:/omnicube/sysadm:default: a plain
TCP relay (-S) is not permitted, aborting

The client then presents its certificate:

$ socat TCP-LISTEN:5900,reuseaddr,fork \\
    OPENSSL-CONNECT:node1:6000,cert=client.pem,cafile=server-ca.crt

Example 5: Restrict the relay to loopback and tunnel over ssh

# svccfg -s svc:/omnicube/sysadm:default \\
      setprop config/vnc_bind = astring: 127.0.0.1
# svcadm refresh svc:/omnicube/sysadm:default
$ activate_zone_vnc.sh vm-win2019 5905

Then, from the workstation:

$ ssh -L 5905:127.0.0.1:5905 node1
$ vncviewer 127.0.0.1:5905

EXIT STATUS

0

The relay was started, or the run-level guard aborted the run because the host is shutting down or in single-user state.

1

No VM name given; invalid zone name; non-numeric port; an unknown option; zone not configured on this node; a zonepath outside the permitted charset; a VNC relay for this zone already exists; the requested port is already used by another socat listener; the zone is not running; no static VNC port is defined in the zone configuration; unsupported zone brand; an invalid config/vnc_bind, config/vnc_tls, config/vnc_verify_client, config/vnc_tls_opts or config/vnc_log_level value; TLS requested with no usable certificate; a certificate, key or CA path that is missing or outside the permitted charset; -S while config/vnc_verify_client is true; or config/vnc_verify_client true with config/vnc_cafile unset.

78

Raised while sourcing utils.sh: the SMF property config/site is not set on ${OC_SMF}, so PROPPREFIX could not be initialized.

FILES

/opt/omnicube/bin/activate_zone_vnc.sh

This command.

/opt/omnicube/lib/common/utils.sh

Shared shell library, sourced for the run-level guard, logging and validation.

<zonepath>/root/tmp/vm.vnc

UNIX domain socket carrying the guest console; the relay's downstream endpoint. A zone's root filesystem is always <zonepath>/root, so with the OmniCube standard zonepath of /zones/zone/root this resolves to /zones/zone/root/root/tmp/vm.vnc, and with a flat /zones/zone zonepath to /zones/zone/root/tmp/vm.vnc. Both layouts are valid; the path is derived, not assumed.

/etc/opt/omnicube/certs

Default location of the TLS material: server.pem, or server.crt plus server.key. The private key stays mode 0600 root, but this directory must remain traversable (0755) by the operators who run this command: the existence checks run as the invoking user, only socat itself runs as root.

<zonepath>/log/vnc.log

socat's own log for this relay, mode 0600 root, beside the zone's zone.log. Rotated by the omnicube_vnc and omnicube_vnc_root entries in /etc/logadm.d/omnicube.conf - one per zonepath layout, because logadm patterns are csh-style filename substitution where * does not cross a /.

/var/run/omnicube

Runtime directory created by utils.sh (lock and mail-throttle state).

/etc/nologin

Presence aborts the run through the run-level guard.

/etc/security/exec_attr.d/omnicube

RBAC entries granting /usr/bin/socat and /usr/sbin/zonecfg uid 0 under the OmniCube Administration profile.

ENVIRONMENT

OC_IGNORE_RUNLEVEL

When set to a non-empty value, skips the run-level guard so the command can be used during deliberate single-user maintenance.

OC_SMF_INSTANCE

Selects a non-default svc:/omnicube/sysadm instance; must be set before utils.sh is sourced.

PATH

Must resolve /usr/bin/ggrep, /usr/bin/netstat, gawk, sed, zoneadm, zonecfg and svcprop. The nohup, mkdir and socat on the launch line are spelled absolutely and do not depend on it.

SECURITY

The relay carries RFB with no authentication whatsoever: anything that can open the TCP port gets keyboard, mouse and screen access to the guest, including its console login prompt and, on a booting guest, its boot loader. The default config/vnc_bind of "all interfaces" is only acceptable behind a host firewall or on a trusted management VLAN; prefer 127.0.0.1 plus ssh -L.

-s alone does not fix that. TLS with verify=0 buys confidentiality - the console is no longer readable on the wire - but the port is still open to whoever can reach it, and the RFB stream behind it is still unauthenticated. Encryption is not access control. Only config/vnc_cafile, which turns the listener into verify=1 and demands a client certificate signed by that CA, moves the access decision onto something the connecting party must possess; config/vnc_verify_client makes that mandatory for the node, so an operator cannot start a weaker relay by hand. On a node where the VNC ports are reachable from anything but a trusted management network, that pair is the configuration to deploy.

The relay authenticates the client, never the guest session behind it: a verified client still lands on the console as whoever the guest last left logged in. Both the zonecfg edit and the socat process run under pfexec, which requires the root-equivalent OmniCube Administration profile; socat in particular can execute arbitrary commands as root through its EXEC: address type.

SEE ALSO

generate_mac_address_vm(1), manage_zone.sh(8), create_zone_config.sh(8), qemu-monitor-command(8), logadm(8), oc_console(3), oc_runlevel(3), oc_validate(3), omnicube_utils(3), vm_tpl.cfg(5), omnicube(7).

NOTES

The zone configuration is rewritten before the "is the VM running" check, so passing a port for a stopped VM still updates vnc or vncport and then aborts. For a bhyve zone the new vnc value only takes effect when the guest next boots; the relay uses the port recorded in the configuration either way.

The duplicate-relay test matches any socat command line containing the substring /zone/ , so a zone whose name is a substring of another zone's path can produce a false positive.

vnc.log sits in a directory that the zone-mgt SMF method inspects: check_zonepath refuses to clear a stale, unmounted zonepath that holds anything other than regenerable logs, so vnc.log is whitelisted there next to zone.log. In practice the file is written while the zone's dataset is mounted and disappears with it, but the whitelist keeps a half-mounted node self-healing rather than fatal.

A TLS relay is still one socat process matching the same /zone/ substring, so the duplicate-relay test above and the leftover-console cleanup in manage_zone.sh(8) work unchanged; the port-in-use test matches both TCP-LISTEN: and OPENSSL-LISTEN:. Standard VNC viewers do not speak TLS, so the client side needs its own socat (or stunnel) stub as shown under EXAMPLES.

The relay is started with nohup(1), so it survives the operator logging out, but it is not managed by SMF: it does not survive a reboot and is not restarted automatically; the relay is stopped by kill_zone_console (see oc_console(3)) from manage_zone.sh(8) and from the zone-mgt SMF method, which is why a console does not survive a stop, a relocate or a reboot of its zone; manage_zone.sh is granted /usr/bin/kill for that purpose. A relocate carries the console across: if one was published on the source node, manage_zone.sh(8) runs this command on the target once the zone is running there. Whether that relay ends up using TLS is decided by the target's own SMF properties, not by what the source was doing.

man1/activate_zone_vnc.sh.1generated 2026-09-02 05:17 CEST