All posts Security

Superuser Clearance for Snippets That Grant Firewall Admin Rights

SECURITY 22 MIN READ NETDEFENSE TEAM

NetDefense’s roles draw a line: a Read-Write member maintains the configuration your fleet runs on, and a Superuser decides who holds power over it. For an MSP, it’s the line between the technicians who look after a client’s firewalls and the few people trusted to decide who administers them. Some snippets sit right on that line. A USER or GROUP snippet creates or changes a local account or group on the firewall itself, and an account with the right privilege, or in the right group, is a firewall administrator. A Zabbix agent snippet can go further: a UserParameter’s command is something the firewall runs.

Until this release, a Read-Write member could use those snippets to make someone a firewall administrator. NetDefense’s Superuser check only recognized blanket grants such as page-all, plus shells, SSH keys, and the system scope. It missed other privileges that amount to administrator rights (user and group management, configuration restore, cron), group membership, values a variable fills in at sync time, and what an account already was on the device. So a Read-Write member, or anyone holding a leaked Read-Write token, could grant a privilege such as user management directly, put a user in the admins group, supply a privilege through a variable, or edit an account that was already an administrator on the box. The device’s local policy caught some of that where it was turned on, but it didn’t look at group membership at all, so our July post’s line that nothing could push a privilege escalation through sync to a device on the new default was broader than what the policy enforced. From NDAgent 1.20.1 the local policy also refuses membership in an administrator group. Older agents still don’t check it.

Signed commands couldn’t help, and they aren’t meant to: every one of those changes was an authentic, signed configuration sync. A signature proves an instruction came from NetDefense, not that the person behind it should have been allowed to ask.

Snippet content that grants firewall administrator rights, or gives the Zabbix agent a command to run, now needs Superuser clearance. Only a Superuser signed in interactively can save what the control plane recognizes as elevated, and the control plane refuses to build a device’s configuration that uses it without that clearance. Some of it only the device can recognize: a group someone created by hand on the firewall, or an account that’s already an administrator there. From NDAgent 1.20.1 the device checks those itself. It’s the only check that can, so on older agents nothing catches them.

TL;DR

  • Content that can make someone a firewall administrator, or give the Zabbix agent a command to run, is now elevated: USER and GROUP content that grants administrator-equivalent rights or changes an account or group that already has them, and Zabbix command fields, including the variables that fill them.
  • Only a Superuser signed in interactively gives content clearance. Read-Write members never do, and neither does any personal access token, a Superuser’s included. Elevated content without clearance is refused when it’s saved, when a device’s configuration is built, or, from NDAgent 1.20.1, on the device.
  • Variables are stamped too, and every stamp is bound to the exact content it was saved with. Change the content without a Superuser’s save and the clearance stops counting.
  • The control plane’s check runs for every device. Only NDAgent 1.20.1 and later also check the accounts and groups only the device can see, such as ones created by hand on the firewall. OPNsense 26.1.11 is the new minimum supported release.
  • Existing elevated USER and GROUP snippets, and Zabbix snippets whose command fields come from variables, fail their device builds until a Superuser saves them again: a one-time cost, and the price of not guessing whether a Superuser saved them.
  • This governs configuration sync. It doesn’t limit remote sessions or package installation, and it’s no defense against a compromised control plane.

What counts as elevated

The test isn’t whether a snippet says page-all. A privilege is administrator-equivalent when it gives full administrative control of the firewall, or a short path to it: the all-pages privilege, but also privileges such as user, group and privilege management, configuration restore, services that run as root, the authentication setup, firmware updates and high-availability sync, and the NetDefense settings page itself. NetDefense classifies privileges against a reviewed catalog, and one the catalog doesn’t know counts as administrator-equivalent too. Unknown fails closed.

Content is elevated when it does any of the following:

  • USER: grants an administrator-equivalent privilege; puts the account in admins or netdefense-readonly; gives it the system scope, an interactive shell, or SSH authorized keys.
  • GROUP: grants its members an administrator-equivalent privilege, or takes its members from a directory. The protected groups themselves, admins and netdefense-readonly, can’t be managed by a snippet at all.
  • ZABBIX_USERPARAMETER and ZABBIX_SETTINGS: a UserParameter’s command, remote commands, or sudo. When one of those fields takes its value from a variable, the variable needs a Superuser’s save too.
  • Combinations in one device’s configuration: a USER without clearance that joins, or is listed as a member of, a GROUP with an administrator-equivalent privilege delivered to the same device, or a USER or GROUP without clearance that shares its name with a Superuser-saved one.
  • What only the device can see: a USER that joins a group that is administrator-equivalent on that device, such as one created by hand there, or a USER or GROUP that changes an account or group that already is administrator-equivalent there. The control plane can’t see those accounts and groups, so it can’t refuse them when they’re saved. NDAgent 1.20.1 refuses them on the device instead.

Who can save it

  • A Superuser signed in interactively, with ndcli auth login or in the web app, can create, change, or pull elevated content.
  • A Read-Write member is refused with Superuser access required for organization '<org>'.
  • Every personal access token is refused, whatever the role of the account that created it. Tokens never carry Superuser clearance, so a leaked token can’t save elevated content, even one that belongs to a Superuser. NDCLI_TOKEN takes precedence over an interactive login, so save from a shell where it isn’t set.
  • Once a snippet holds elevated content, only a Superuser can change it. The same goes for any USER or GROUP snippet a Superuser saved last, elevated or not, so a Read-Write member can’t quietly edit an account a Superuser defined.
  • Pulling counts as saving. Saving pulled content that would need a Superuser to write, such as an administrator-equivalent account, a shell or SSH keys, or a Zabbix command, needs a Superuser, and so does overwriting a USER or GROUP snippet a Superuser saved. For anyone else the pull fails and nothing is stored; a preview-only pull is unaffected. Agents older than 1.20.1 can’t say whether a group is administrator-equivalent, so on those devices saving a pulled user who belongs to any group needs a Superuser. And a pulled USER no longer carries the account’s password hash.

Variables are stamped too

Every variable value now records whether a Superuser saved it, separately at each scope. When NetDefense builds a device’s configuration:

  • An elevated USER or GROUP snippet builds only if a Superuser saved the snippet and the value of every variable it uses: the value that wins for that device, so an OU, template, or device override counts, not just the organization default.
  • A Zabbix snippet needs a Superuser-saved value for each variable that can decide one of its command fields.
  • A value isn’t a way to add elevated content: a USER snippet with "scope": "${account_scope}" builds while the variable holds user, and fails with VARIABLE_ELEVATES_SNIPPET once someone sets it to system, whoever saved the value. Elevated content has to be written into the snippet by a Superuser.
  • Once a Superuser saves a secret variable, every snippet that uses it, of any type, needs Superuser clearance too, or its build fails with SECRET_VARIABLE_CLEARANCE. Keep that in mind before re-saving a secret that many snippets share.

Every stamp, on a snippet or a variable, is bound by a digest to the exact text it was saved with. Change the text without a Superuser’s save and the stamp stops counting: approval belongs to the content a Superuser saved, not to whatever it later becomes.

Values are also escaped to fit the snippet’s JSON now, so a value is always one string: it can’t add keys or list entries, and quotes, backslashes, and line breaks arrive as typed. The behavior change to check: an escape sequence typed into a value, like \n or \", now arrives as those two characters. See Values are inserted exactly as written.

Checked at build time and on the device

At build time, for every device. The control plane checks each device’s configuration before anything is sent. If an elevated snippet lacks clearance, the device gets no task: ndcli sync apply lists it under Errors, its drift status turns ERROR, and scheduled syncs skip it until the cause is fixed. The reason names snippets, types, and variables, never a value:

ndcli sync apply
Errors:
• fw-branch-01: Elevated snippets need Superuser clearance: svc-backup (USER: groups:protected; saved without Superuser clearance)

This check doesn’t depend on the agent version.

On the device, from NDAgent 1.20.1. The control plane can’t see a group someone created by hand on the firewall, or know that a local account is already an administrator there. The device can, and it’s the only place those are checked. Each USER and GROUP entry in the signed sync now carries a clearance mark when a Superuser saved it and every variable it uses, and the agent checks every account change against the live accounts, groups, and privileges on the box. Without the mark, it refuses an element that would add membership in an administrator-equivalent group, grant an administrator-equivalent privilege, set the system scope, or change anything about an account or group that already is administrator-equivalent; a snippet that reuses an existing administrator’s name counts. The refused element is reported with the code ADMIN_EQUIVALENT_REQUIRES_SUPERUSER, the rest of the sync still applies, and the task ends FAILED:

ndcli task describe
rejected (Superuser clearance required): user snippet "svc-backup" (index 4) for user "svc-backup" would add membership in administrator-equivalent group "noc-admins". It carries no Superuser clearance, so a Superuser must save the snippet (and any variable it uses) again; reject_dangerous_snippets=false does not lift this

The device owner’s policy still applies on top. Clearance records whether a Superuser saved the content. The device’s local policy, reject_dangerous_snippets, says whether this firewall accepts privileged content at all. It has been on by default for new installations since NDAgent v1.14.0, and Allow All Snippet Content turns it off. NDAgent 1.20.1 makes it stricter: it now also refuses a user in admins, netdefense-readonly, or a group that is administrator-equivalent on the device, and every administrator-equivalent privilege, not only the blanket ones such as page-all. Neither lifts the other: Allow All Snippet Content doesn’t lift the clearance check, and a Superuser’s save doesn’t lift the local policy. A Superuser-saved snippet that puts a user in admins is still refused by a device whose owner hasn’t allowed it.

What the device check defends against, and what it doesn't

The clearance mark travels inside the signed instructions from the control plane. That makes the device check a defense, for configuration sync, against the people and credentials in your organization that don’t hold Superuser clearance: a Read-Write member, a leaked token, a script holding the wrong credential. Remote sessions are a separate path; see What this doesn’t cover. It is not a defense against a compromised control plane, or anyone who can write to its database, because that is where the mark is recorded and signed. Against that adversary, the device owner’s local policy, where it’s turned on, still refuses the privileged content it covers, such as administrator privileges, shells, SSH keys, and Zabbix commands, and from NDAgent 1.20.1 membership in administrator groups. NetDefense can’t change that setting for you. It doesn’t stop a sync from changing an existing administrator’s password, installing a package, or changing firewall rules. And on a device whose remote_access_policy is full, the default, that adversary can open a shell anyway, so set it to readonly or disabled where that matters.

What you’ll notice

Existing elevated snippets wait for a Superuser

The control plane records clearance from now on, whatever agent a device runs, with no backfill. NetDefense has no record of whether a Superuser saved older content, and guessing in your favor would defeat the point. So:

  • Every elevated USER or GROUP snippet fails its device builds until a Superuser saves it again, together with every variable it uses.
  • A USER without Superuser clearance that joins, or is listed as a member of, a GROUP with an administrator-equivalent privilege going to the same device, or that shares its name with a Superuser-saved USER or GROUP, also fails that device’s build until a Superuser saves it.
  • A Zabbix snippet whose command fields are written out exactly keeps building, whoever saved it. One waits when a variable fills a command field (the variable needs a Superuser’s save), when it spells a field another way, such as Command (the snippet and its variables need a Superuser’s save), or when its JSON repeats a key or holds NaN or Infinity (edit it or delete it).
  • A GROUP that takes its members from a directory, with nothing else elevated, keeps working without a re-save, as long as any variable it uses was saved by a Superuser.
  • On NDAgent 1.20.1 or later, a USER or GROUP snippet that joins a hand-made administrator group, or changes an existing administrator account or group, is refused on the device until a Superuser saves it.
  • On those devices, where Allow All Snippet Content is off (the default on installations since v1.14.0), the stricter local policy refuses a user in admins, netdefense-readonly, or a hand-made administrator group, and administrator-equivalent privileges beyond the blanket ones, whoever saved the snippet. Content like that which applied on an older agent stops applying after the upgrade, and a Superuser’s save doesn’t change that: the device owner decides, with Allow All Snippet Content.

Saving the same content again is enough for clearance, since the save is what records it. The exceptions are a USER snippet that holds a password hash, which has to lose the hash in the same save, and a Zabbix snippet whose JSON repeats a key or holds NaN or Infinity, which has to be edited. Clearance never lifts the device’s local policy, so the last case above is the device owner’s call, not a re-save. As a Superuser in an interactive session:

ndcli bash
# Devices whose configuration can't be built (-f json shows the reason)
$ ndcli sync status --drift-status ERROR
# Save each snippet again, from the content ndcli snippet describe shows
$ ndcli snippet update-content svc-backup ./svc-backup.json
# Save each variable value the error names again; a secret is re-entered
$ ndcli variable org set backup_role --value operators
$ ndcli variable org set svc_backup_password --value-stdin < ./svc-backup-password.txt

Use update-content: ndcli snippet edit sends nothing when the content hasn’t changed. A secret’s value can’t be read back, so re-enter it with --value-stdin. Re-saving a secret as a Superuser means every other snippet that uses it then needs a Superuser’s save too, so re-save those in the same pass. In the web app, make a whitespace-only edit to a snippet so Save Changes enables; for a variable or an override, open it with Edit and save it, retyping a secret’s value. Giving existing snippets Superuser clearance walks through the procedure.

Read-Write members and tokens are refused

A Read-Write member saving elevated content gets Superuser access required for organization '<org>', and changing a snippet that holds Superuser-level content returns INSUFFICIENT_ROLE. If a pipeline creates account snippets with a personal access token, the elevated ones now need a person: a Superuser, signed in interactively. Snippets with nothing elevated in them still work with a token, with two exceptions: a token can’t change a USER or GROUP snippet a Superuser saved last, and a snippet a token saves stops building if it uses a secret a Superuser saved (SECRET_VARIABLE_CLEARANCE). Variable values a token saves never count as a Superuser’s either, so a pipeline that rotates a value an elevated snippet uses breaks that snippet’s build until a Superuser saves the value again.

USER passwords are plaintext only

A USER snippet’s password is the password to set, never a hash: OPNsense hashes whatever it receives, so a pasted hash would become the password itself. Saving a hash-shaped password is now refused, and from NDAgent 1.20.1 a device refuses one an older snippet still carries (USER_PASSWORD_IS_HASH), unless it’s the very hash the device already stores. The web app’s USER editor says so too, and warns when a password looks like a hash. And a password the device already has, in OPNsense’s default bcrypt or Argon2id format, is no longer re-sent on every sync, so its password-changed date stops moving. Keep passwords in a secret variable; see USER snippets and passwords.

OPNsense 26.1.11 is the new minimum

OPNsense 26.1.11 fixed privilege escalation through a set of privileges that are ordinary from that release on (release notes). On a device running anything older, or one whose release the agent can’t read, NDAgent 1.20.1 counts those privileges as administrator-equivalent: a snippet that grants one, or changes an account or group that holds one, needs Superuser clearance, and the device’s local policy refuses it while Allow All Snippet Content is off. It’s the stricter reading, never the looser one. The agent keeps running and syncing, and logs a warning naming what’s affected on that release. Upgrade OPNsense to 26.1.11 or later and those privileges go back to being ordinary. See Prerequisites.

Read-only sessions stop showing stored secrets

Read-only remote access is enforced on the device, not in the browser. NDAgent 1.20.1 extends what it holds back, on the pages a read-only session can open and the OPNsense releases NetDefense supports:

  • Stored secrets are blanked. Where a page or an API response would show one (local users’ password hashes, API keys and one-time-password seeds, private keys of certificates and authorities, WireGuard and IPsec keys, VPN, high-availability, and service passwords), the session gets an empty field. Everything else stays visible. A response that may carry a secret and can’t be cleaned reliably is withheld.
  • Exports that hand over secrets are refused: a certificate’s or authority’s private key, a PKCS#12 bundle, and the generated IPsec configuration, which lists every pre-shared key. The certificate itself and a signing request still download.
  • Saves and actions that slipped past the read-only account are refused. Saving a dashboard layout or a menu favorite cut a configuration revision each time and pushed older backups out of the device’s history. Requests we found that change the device without saving its configuration, such as clearing a log, restarting a service, or applying pending interface changes, are refused too. Earlier agents let these through: our read-only post and site pages, the Security page among them, described the read-only account as blocking every write, which was broader than what it enforced. Crash reports and packet captures, which can’t be cleaned of secrets, are no longer available either.
  • A refusal says so, with an explicit reply such as 405 Method Not Allowed (read-only session).

Some gaps remain, and we’d rather you hear it from us:

  • Logs and free-text settings stay readable. A credential a component wrote into a log, or that someone typed into a free-text setting such as an alias URL, a cron command, or a custom-options field, shows as written. Reading logs is much of what a read-only operator is there for.
  • A search can recover a hidden value. Lists whose rows hold secrets can still be searched, and OPNsense matches the search phrase against the stored secret too. The rows come back blanked, but which rows match shows whether a phrase occurs in a hidden field, and a phrase extended one character at a time can recover the whole value, such as a password hash, a one-time-password seed, a pre-shared key, or a private key, from the Users, Certificates, WireGuard, IPsec, and similar lists. Treat read-only access to a device as access to those secrets.
  • Coverage follows our review. These rules come from reviewing the pages and actions of the OPNsense releases NetDefense supports. A page, field, or action that a later OPNsense release or a plugin we haven’t reviewed adds may not be covered until we’ve reviewed it.

Device-Side Protection lists what’s hidden and what isn’t.

What this doesn’t cover

Superuser clearance moves the decision to make someone a firewall administrator through a snippet to a Superuser. It isn’t a general lockdown, and these are its edges:

  • Remote sessions. Clearance governs configuration sync. A Read-Write member can still open a full remote session on a device whose remote access policy allows it, and full is the default; a full session is a shell. Where that matters, set the device’s remote_access_policy to readonly or disabled, a ceiling that lives on the device and that the control plane can’t raise.
  • Package installation. Software policies are still a Read-Write setting, and installing a package runs its post-install scripts as root. Neither Superuser clearance nor the device’s local policy covers that path.
  • A compromised control plane. The clearance mark is recorded and signed there, so it can’t protect you from the control plane itself. What still holds against it is on the device: the local policy, for the content it covers, and remote_access_policy.
  • Attaching, detaching, and deleting. A Read-Write member can still attach a Superuser-saved snippet to a template, or that template to an OU, spreading approved administrator content to more devices without changing it. They can also detach or delete one, and the account it manages is removed at the next sync. Treat templates that carry elevated snippets as high-impact when you decide who gets Read-Write.
  • Which Zabbix server asks. A UserParameter’s command needs a Superuser, but which Zabbix server may ask the agent to run it is still a Read-Write setting.
  • Who is at the keyboard. Clearance follows the account and how it signed in. An AI assistant driving the NetDefense MCP server on a Superuser’s interactive session saves with that Superuser’s clearance. Give automation a personal access token instead; a token can’t save elevated content, whatever its scope.
  • Older agents. Below NDAgent 1.20.1 there’s no device check, and the local policy doesn’t look at group membership. A Read-Write snippet that joins a hand-made administrator group, or takes over an existing administrator account, isn’t caught on those devices. Upgrade them.

Also in this release

Firmware upgrades finish when the device says so

A firmware upgrade task used to complete when the agent reconnected, which could be before OPNsense had finished installing, or even rebooted. From NDAgent 1.20.1 the device decides: the task stays IN_PROGRESS through the update and any reboot, and ends FAILED with the reason if OPNsense reports a failure or the result can’t be confirmed in time. A device takes one firmware task at a time, and one queued behind a run that reboots the device fails as not started, with nothing applied. So stagger your firmware schedules: a daily and a weekly schedule set to the same time of day collide once a week. See One firmware run per device at a time.

Agent restarts wait for the old agent to exit

Restarting the agent, from the Settings page, a restart task, a package upgrade, or the command line, now waits until the previous agent has fully exited. Before, a quick restart could occasionally find the old process still shutting down and leave the device without a running agent.

NDCLI 1.36.0

  • ndcli run firmware-upgrade --no-reboot now works as documented. The old --reboot flag is gone, so a script that passed --reboot=false needs --no-reboot instead; running with neither still reboots.
  • ndcli task list accepts several --type and --status values, repeated or comma-separated, and refuses a value it doesn’t recognize instead of quietly returning the whole list.
ndcli bash
$ ndcli run firmware-upgrade --mode minor --no-reboot --device fw-prod-01
$ ndcli task list --type FIRMWARE_UPGRADE --status FAILED,EXPIRED

Getting started

The control plane’s checks already run for every device. Devices on older agents stay exposed to hand-made administrator groups and existing administrator accounts until you upgrade. For the rest:

  1. Upgrade your agents to NDAgent 1.20.1 or later. See Updating. Where Allow All Snippet Content is off, the stricter local policy refuses a user in admins or a hand-made administrator group, and administrator-equivalent privileges beyond the blanket ones, after the upgrade, whoever saved the snippet.
  2. Upgrade OPNsense to 26.1.11 or later.
  3. Have a Superuser, signed in interactively, save again the elevated snippets and variables your build errors name, along with every snippet that uses a secret they re-save.
  4. Upgrade ndcli to 1.36.0 or later (brew upgrade ndcli, or see the NDCLI getting started guide for other installation methods).

The full reference is in Snippets that grant administrator rights, Variables that need a Superuser, and Device-Side Protection.

Join the discussion in our Community

Related reading

Feature Firmware Tracks: Stage OPNsense Updates from Fast to Stable
Feature RULE and ALIAS Snippets Now Set Every OPNsense Field, and Place Rules Where You Said
Feature Certificate Sync for OPNsense: Renew Once, Every Firewall Follows

Enjoyed this post? Explore more in the documentation.