Deployment tags are the usual reason people reach for this feature: each connection should carry something like a site or a role, and the server should read it without inventing a second credential format. Community OpenVPN already has the mechanism, and it is small. The part that bites is expectations. Setting an ordinary variable in the client profile does nothing on the server, and a value that does arrive proves only that the client sent it.

This is a runbook for the working path, the verification step that shows what really arrived, and the rollback that leaves no half-configured hook behind. Nothing here needs a new certificate, a new plugin or a protocol extension.

Set the variable and enable the path

Two lines do the work in the client profile, and both are required. Without the first, there is nothing to send; without the second, the client never offers peer information at all. Order does not matter, which is worth knowing because the two lines often end up in different parts of a generated profile. What matters is that both survive whatever rewrites the profile before the client reads it.

setenv UV_DEPLOY field-support
push-peer-info

The UV_ prefix is not decoration. A plain DEPLOY variable stays local to the client process and is never forwarded, while the prefixed form is the namespace the protocol reserves for user-defined fields. Keep the rest of the approved profile intact and confirm that the client build you deploy supports both directives before rolling them out.

One practical check belongs here rather than at the end: make sure the directive is reaching the client binary and not being swallowed by a wrapper. A graphical client that generates its own command line, or a management interface that reloads a stored profile, can drop lines it does not recognise. If the server sees nothing, this is the first place to look.

Read the field from a server hook

On the server side, received peer information is validated and inserted into the environment of the connection workflow, which is what makes it readable from a hook without any protocol work of your own. The server does not need to parse the control channel or store anything for you; the value is simply present while the connection is being set up. A minimal client-connect script that records the label next to the authenticated name looks like this.

#!/bin/sh
# /etc/openvpn/server/hooks/label.sh
printf '%s %s\n' "$common_name" "${UV_DEPLOY:-unset}" \
  >> /var/log/openvpn/deploy-labels.log

Read the variable through the environment rather than parsing the log, and always give it a default. ${UV_DEPLOY:-unset} keeps the script working for clients that never send the field, which is the normal state during a staged rollout. Do not dump the whole environment into a log to find the name; it contains far more than the label, including values you did not intend to persist.

Run the script by hand and it will report unset for everything, because a manual shell does not carry the connection environment. That is not a bug in the script and it is not evidence that the field is missing. Test it through an actual connection, which is the only context where the variable exists.

Verify what actually arrived

Connect one client with the label and one without, then read the file. The pair is the test: one line should carry the value and the other should carry the default, and both should name the same authenticated identity you expect.

tail -n 5 /var/log/openvpn/deploy-labels.log
# field-support deployed-client-07
# unset         legacy-laptop-02

Those comment lines are the shape of the output, not a transcript from your server; read your own file. If the second client also shows a value, the variable is set somewhere else in the environment, and you are reading a stale setting rather than the peer field.

Check the server log for the connection as well. Peer information can appear there, which is convenient for debugging and a reason to keep the values boring: no names of people, no ticket numbers, nothing you would mind a log reader seeing.

Treat the label as a claim, not an identity

Anyone who can edit the client profile can change the tag, and the tunnel being authenticated does not make the tag authoritative. The transport protects the value in flight; it does not certify the assertion. Authorization still belongs to the authenticated identity and to an enrollment record the server owns, with the label compared against that record when it matters.

That distinction decides where the label may be used. Good uses are routing a session to the right log file, choosing a display group in a dashboard, or tagging a support session so a human can find it later. Bad uses are admitting a device to a subnet, selecting a firewall policy, or deciding which customer's data the session may read.

Validate the value before it reaches a path, a query or a shell command. An allowlist of expected strings is enough for a deployment tag, and it costs nothing. Never interpolate a peer-supplied string into a shell command line, and scope any stored observation to the session rather than to a tunnel address the next client will reuse.

case "${UV_DEPLOY:-}" in
  field-support|field-qa|lab) : ;;
  *) UV_DEPLOY=unknown ;;
esac

Roll it back cleanly

Removing the feature is two deletions and one verification. Drop the setenv and push-peer-info lines from the client profile, then remove or disable the hook and its log target on the server.

grep -rn 'UV_DEPLOY\|push-peer-info' /etc/openvpn/
tail -n 3 /var/log/openvpn/deploy-labels.log

Reconnect one client afterwards and confirm the log line stops appearing. A hook that keeps firing after the profile change is usually a second profile still deployed to a test device, and finding it now is cheaper than finding it during an audit.

What remains at the end is a small, bounded piece of session metadata whose origin is documented: the client asserts it, the server records it, and nothing about access depends on it. That is the useful version of the feature, and it needs no new credential to get there.

Tënë sô zo