Chapters & navigation
VERSIONED REFERENCES
Deployment reference
The complete historical v4.04.3 deployment contract: packages, configuration, firewall service ownership and audit.
On this page
This tutorial describes the Linux-only v4.04.3 release contract. The version-specific v4.04.0 change set was first published in v4.04.2 and is carried forward in v4.04.3. Use a version only after its exact Release inventory is public and the protected qualification is green for this historical v4.04.3 contract. The new IVV/IVVQ strategy defines future assurance by release track; it does not alter this baseline's package inventory or recorded acceptance requirements.
For local testing of newer source, use the separate build and install from source procedure. Its version-specific candidate instructions do not change this stable release baseline.
Keep verified console or SSH recovery access before changing packages, firewall, SSH, HA or migration state.
1. Supported package matrix#
| Distribution family | Architecture | Expected package |
|---|---|---|
| Debian or Ubuntu | amd64 | syswarden_4.04.3_amd64.deb |
| Fedora or RHEL family | x86_64 | syswarden-4.04.3-1.x86_64.rpm |
| Alpine | x86_64 | syswarden_4.04.3_x86_64.apk |
The package workflow is configured to generate one DEB, one RPM and one APK
package plus SHA256SUMS.txt. No current package, updater or qualification
route exists outside this AMD64/x86_64 matrix. Native package lifecycle
evidence, not a filename or successful build, decides whether a target is
releasable.
2. Recovery prerequisites#
Before installation or upgrade:
- retain a verified local console or second SSH session;
- save
/etc/syswarden, required list files and HA trust material; - save the active firewall service state, nftables ruleset and installed package version;
- verify that TCP 62027 is blocked at the host boundary;
- verify that the applicable recovery artifact and recovery channel work on a representative host. For the version-specific migration to v4.03.3, that artifact is a complete pre-upgrade VM or volume snapshot; no in-place package downgrade is supported;
- record any unrelated process that already owns a path, service name or port that migration cleanup may inspect.
Do not rely on an HA withdrawal, package hook or remote dashboard as the only recovery path.
3. Exact public release inventory#
A qualified v4.04.3 Release contains exactly these ten assets:
syswarden_4.04.3_amd64.debsyswarden-4.04.3-1.x86_64.rpmsyswarden_4.04.3_x86_64.apkSHA256SUMS.txtRELEASE_SHA256SUMS.txtsyswarden-release.tar.gzsyswarden-sbom.spdx.jsonplumber-report.zipsyswarden-update-manifest-v1.jsonsyswarden-update-manifest-v1.json.sig
Reject a Release with a missing, duplicate or unexpected asset. The package checksum inventory, release checksum inventory and detached Ed25519 update signature must all verify before publication or installation.
4. Verify and install one package#
For packages built from the latest source, follow Build and Install from Source. The steps below use the qualified stable release.
Download exactly one package matching the host and SHA256SUMS.txt from the
same Release. Verify the selected package before invoking its package manager:
sha256sum --check --ignore-missing SHA256SUMS.txtStop unless the selected filename appears exactly once in the manifest and is reported as valid. On Debian, Ubuntu, Fedora or a RHEL-family host, run only the matching command:
sudo apt-get update
sudo apt-get install -y ./syswarden_4.04.3_amd64.deb
sudo dnf install -y ./syswarden-4.04.3-1.x86_64.rpmThe APT index refresh uses only the distribution repositories already configured on the host. Stop if those sources are unsigned, unexpected or do not expose every declared package dependency. Do not enable an unreviewed third-party repository to make the transaction proceed.
On an online Alpine host, prepare Cronie first when automatic activation is required:
set -eu
sudo apk add --no-cache cronie cronie-openrc
for service in crond cronie; do
if sudo rc-service "${service}" status >/dev/null 2>&1; then
sudo rc-service "${service}" stop
fi
for link in /etc/runlevels/*/"${service}"; do
[ -L "${link}" ] || continue
sudo rc-update del "${service}" "$(basename "$(dirname "${link}")")"
done
done
sudo rc-update add cronie default
sudo rc-service cronie start
sudo apk add --allow-untrusted ./syswarden_4.04.3_x86_64.apkThe automatic path requires committed cronie and cronie-openrc, an active
Cronie service assigned only to the default runlevel, and an inactive BusyBox
crond with no runlevel. apk-tools 3 can commit package payload after a failed
pre-install script, so v4.04.3 does not return a failing fresh hook for an
unprepared scheduler. A fresh APK transaction without prepared Cronie remains
payload-only and inactive: no SysWarden configuration, data, logs, runtime,
cron, service, process or firewall policy is created, and the package is not
marked broken. The immutable payload already contains the two package-owned
launchers, but neither performs activation.
For the deferred payload-only path, the package prints a bounded two-phase activation block. It validates the complete OpenRC inventory before its first mutation, executes fail-fast and reattests the exact provider and runlevel state before activation. Run that emitted block exactly, only after the initial APK transaction has completed and both Cronie packages are present. Do not replace it with shortened scheduler commands. If the exact emitted block and its package-manager log are unavailable, do not activate the payload manually; repeat the transaction on a disposable recovered host and preserve the output.
--allow-untrusted is acceptable only after the exact SHA-256 check has
succeeded. A prepared automatic installation or the exact deferred activation
path mutates dependencies, services, firewall policy, hardening and scheduled
jobs. Review package scripts and use a disposable lifecycle host before
production rollout.
During installation, the supported multi-origin OSINT path may encounter a
syntactically valid private, 6to4 or other non-public or special-use host entry.
The candidate discards that entry before consensus and emits a bounded warning
containing only the normalized origin and discarded-entry count. It does not
publish the entry. Malformed syntax, an insufficient source after filtering and
custom feed validation failures still stop installation. On DEB hosts, verify
that package configuration completed and that dpkg --audit reports no
half-configured package before continuing.
New Data-Shield content still requires matching canonical bytes from at least
two independent HTTPS origins. During package installation only, a validly
configured external fetch failure, rejected remote candidates or content
disagreement preserves an exact validated last-known-good file; if none exists,
the optional Data-Shield contribution is omitted and the independent OSINT path
continues. One mirror is never published alone. Invalid mirror configuration,
caller cancellation, unsafe local state and publication errors remain fatal.
The explicit or scheduled update-feeds path reports quorum loss as a failure
after reapplying validated policy, so monitoring remains truthful.
Version-specific GeoIP and blocklist sources in v4.04.0#
The historical v4.04.0 change set was first published in the historical v4.04.2 release and is carried forward in v4.04.3. It embeds one deterministic GeoIP allocation snapshot inside the SysWarden CLI. It is generated from the CC0-1.0 IPverse country-ip-blocks repository, whose country files represent RIR allocation or delegation data. The generator validates the immutable source archive, its complete CC0 legal text, the JSON and plaintext forms of every address family, canonical public prefixes and deterministic accounting before embedding. Runtime validation then revalidates the complete snapshot structure and content identity. If a source allocation only partly overlaps special-use space, the generator removes the reserved portion and retains the exact canonical public remainder; it does not discard the whole public allocation.
The reviewed source boundary is exact:
- commit:
7443b1e07cae182ac864a2a4247ea4b641970dfe; - commit timestamp:
2026-08-28T01:55:22Z; - archive size:
1760020bytes; - archive SHA-256:
ab2de300d06fcf63cdaac7f3cb093d17a1d509b2a32455c731e18331506b28a8; - CC0 license SHA-256:
a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499; - embedded snapshot ID:
ipverse-20260828T015522Z-07f1cb9cae15413e; - embedded snapshot gzip SHA-256:
d467540cef649539488dbeab76dbf808349d000a6fedfef7cdc66735e2339400.
The exact source URL, commit, timestamp, archive size, archive digest and license digest are part of the embedded snapshot metadata. Only an official SysWarden release whose signed manifest and package digest verify successfully authenticates the reviewed CLI and snapshot bytes. A local or otherwise unsigned build provides deterministic content binding and structural validation, not official release authenticity.
Both network.geo.blocked_countries and strict
network.geo.allowed_countries select prefixes directly from this embedded
snapshot.
Runtime operation performs no IPverse or other country-data request. It does
not read or require <country>.ipv4, <country>.ipv6,
allowed_<country>.ipv4 or allowed_<country>.ipv6 files in
/etc/syswarden/lists, and existing country files cannot override the embedded
selection.
All 249 canonical ISO 3166 alpha-2 codes are valid configuration values. The
pinned source has no allocation data for bv, cc, cx, eh, gs, hm,
pn, sh, sj, tf and um; these remain valid codes with empty IPv4 and
IPv6 contributions. It has IPv4 data but no IPv6 data for cf, er, fk,
kp, ms and yt; that missing family is a valid empty contribution. Empty
contributions add nothing to a deny selection.
Strict allow policy is intentionally more restrictive. SysWarden merges the configured GeoIP and operator-provisioned ASN allow populations before any nftables mutation. If both merged IPv4 and IPv6 populations are empty, policy application is rejected before mutation. If exactly one merged family is empty, the empty set remains fail-closed and all non-whitelisted traffic for that family is denied. Qualify both address families and preserve verified recovery access before enabling strict allow mode.
eu and zz are not supported country codes. Replace either legacy value with
the explicit canonical ISO country codes required by the policy before upgrade,
configuration validation or reload. SysWarden never expands eu into a mutable
regional grouping and never treats zz as a country.
syswarden update-feeds validates every configured country code against the
embedded snapshot and reports its immutable snapshot identifier and selection
counts. It performs no country-data network request. Outside LAN mode, the
command separately refreshes configured external Data-Shield, custom and OSINT
inputs, so one of those external paths can still fail independently.
IP country data describes address allocation or assignment and is not an
authoritative statement of where an address is currently used. Routing,
transfers, anycast, VPNs and proxies can make allocation-based policy differ
from physical location. The pinned source currently attributes
199.19.76.0/23 to both nl and us; either country selection therefore
contains that prefix. Treat strict country allow mode as a lockout-sensitive
allocation policy, not an identity control, and retain verified console or
independent SSH recovery access.
Source and license:
IPverse country-ip-blocks,
CC0-1.0. Linux packages install the intact upstream legal text as
/usr/share/doc/syswarden/GEOIP-DATA-LICENSE.txt, and the embedded snapshot
binds the same license digest.
Blocklist selection remains independent of GeoIP:
- choices
1and2select the standard and critical multi-origin Data-Shield feeds. They require quorum and do not require a custom SHA-256 value; leave all custom URL and hash fields empty; - choice
3is the custom mode. Configure at least one absolute HTTPS URL and pair every configured IPv4 or IPv6 URL with its exact SHA-256 digest; - choice
4disables the Data-Shield, custom and OSINT blocklist contributions.
The embedded GeoIP snapshot does not supply ASN policy. Configured ASN deny and
strict-allow selections remain operator-provisioned as described in the image
procedure below. Unsigned single-origin RADB and Spamhaus data is not accepted
as firewall authority; update-feeds reports that boundary and leaves any
existing operator-provisioned ASN files unchanged.
The historical public v4.02.8 binary predates the signed updater protocol. Its
historical first hop to v4.03.2 must use a separately downloaded and
checksum-verified Linux package.
Use the dedicated
historical v4.02.8 to v4.03.2 migration runbook
for the Debian 13 backup, provenance, acceptance and rollback sequence.
Use the separate
version-specific v4.03.2 to v4.03.3 migration runbook for the
four dynamic nftables set diagnosis, legacy interval removal, the recorded Ubuntu
26.04 OSINT installation gate and restricted rollback boundary.
After a qualified signed-protocol release is installed, syswarden update
verifies the canonical manifest, detached Ed25519 signature, platform identity,
package size and SHA-256 immediately before installation.
5. Optional RHEL-compatible image staging#
The RHEL 9+ image extensions page records the exact availability boundary. Only the offline RPM staging extension described below exists in v4.04.3.
The repository includes a separate, opt-in image-builder extension for an extracted, fresh and unmounted RHEL-family 9 or newer root. It is not called by the normal package installation, update or reload paths.
The image recipe must prepare all RPM dependencies first. The following RHEL 9 reference uses firewalld as the preserved frontend and prepares Cronie as the required scheduler. The image root must be disposable and unmounted:
set -euo pipefail
IMAGE_ROOT=/srv/image-root
CANDIDATE_RPM=/srv/image-input/syswarden-4.04.3-1.x86_64.rpm
# Copy this value from the independently authenticated package inventory.
EXPECTED_RPM_SHA256=REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS
[[ "${EXPECTED_RPM_SHA256}" =~ ^[0-9a-f]{64}$ ]]
printf '%s %s\n' "${EXPECTED_RPM_SHA256}" "${CANDIDATE_RPM}" | sha256sum --check --strict -
sudo extensions/rhel-image/stage-syswarden-rhel-image.sh \
--preflight-root \
--root "${IMAGE_ROOT}"
sudo dnf -y --installroot="${IMAGE_ROOT}" --releasever=9 \
--setopt=install_weak_deps=False install \
nftables ipset curl wget rsyslog cronie bash-completion \
wireguard-tools jq checkpolicy policycoreutils-python-utils \
dnf-automatic procps-ng e2fsprogs firewalld
sudo systemctl --root="${IMAGE_ROOT}" enable crond.service firewalld.service
sudo systemctl --root="${IMAGE_ROOT}" disable nftables.serviceqrencode is optional on RHEL-family images. Install it only from an
operator-approved repository when terminal WireGuard QR rendering is required;
the protected client configuration file remains available without it.
The read-only --preflight-root call must precede dnf and
systemctl --root. It rejects /, a non-canonical or symlinked root, unsafe
ancestor directories, mounts below the image root and live runtime markers.
Stop on any failure. It performs no image or host mutation.
The image recipe may use repositories and service-manager tooling while it
prepares the tree. The extension itself does neither. Do not install or enable
iptables-services for this profile. Unmount every temporary image-builder
mount before invoking the extension.
Verify the exact candidate RPM and invoke the extension from the source tree:
set -euo pipefail
IMAGE_ROOT=/srv/image-root
CANDIDATE_RPM=/srv/image-input/syswarden-4.04.3-1.x86_64.rpm
# Copy this value from the independently authenticated package inventory.
EXPECTED_RPM_SHA256=REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS
[[ "${EXPECTED_RPM_SHA256}" =~ ^[0-9a-f]{64}$ ]]
printf '%s %s\n' "${EXPECTED_RPM_SHA256}" "${CANDIDATE_RPM}" | sha256sum --check --strict -
sudo extensions/rhel-image/stage-syswarden-rhel-image.sh \
--root "${IMAGE_ROOT}" \
--rpm "${CANDIDATE_RPM}" \
--sha256 "${EXPECTED_RPM_SHA256}"Never derive the expected digest from the candidate RPM. Obtain it from the separately authenticated package inventory.
The extension installs that RPM with plugins, package scripts and triggers disabled. It does not enter the root or invoke product binaries, services, firewall tools, kernel-policy tools, cron, network endpoints or process signals during staging. A transaction journal permits bounded resume after an interruption; altered or unknown state fails closed.
After the extension succeeds, prepare three image-owner source files below
/srv/image-input/syswarden-config. This step must follow staging because the
extension accepts only a fresh image at entry. Save this first block as
config.toml with owner root:root and mode 0640:
schema_version = 1
[core]
config_dir = "/etc/syswarden/config/modules"
enterprise_mode = false
log_level = "INFO"Save this block as modules/00-core.toml with owner root:root and mode
0640:
[core]
firewall_backend = "keep"
hardening_enabled = false
cis_l2_hardening = false
secure_wipe_conf = false
ssh_port = ""Save this block as modules/10-network.toml with owner root:root and mode
0640:
[network]
whitelist_infra = true
lan_subnets = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
whitelist_ips = []
interfaces = ""
[network.geo]
enabled = true
blocked_countries = ["ru", "cn", "kp", "ir"]
allowed_countries = []
[network.asn]
enabled = true
# This intentionally invalid value blocks first boot until it is replaced.
blocked_asns = ["REPLACE_WITH_APPROVED_HIGH_RISK_ASN"]
allowed_asns = []
[network.saas]
allow_monitors = false
[network.blocklists]
list_choice = "1"
custom_url = ""
custom_url_ipv6 = ""
custom_hash = ""
custom_hash_ipv6 = ""
use_spamhaus = false
[network.wireguard]
enabled = false
port = "51820"
subnet = ""This profile selects standard blocklist choice 1, automatic infrastructure
protection, explicit country and ASN deny sets, no remote monitor allowlisting,
and no WireGuard. The country values are a reviewable example, not a universal
traffic policy. Remove a country if the deployed services require it. Replace
the intentionally invalid ASN marker with the exact high-risk ASN values from
the image owner's current approved risk register. Until it is replaced,
configuration validation fails and the first-boot marker is retained. The
repository does not assign risk labels to network operators.
After replacing the ASN marker in the protected source file, publish all three configuration files as one directory transaction from a trusted root shell:
set -euo pipefail
umask 077
if (( EUID != 0 )) || [[ "${GROUPS[0]}" != 0 ]]; then
printf '%s\n' 'Run this complete configuration publication block as root.' >&2
exit 1
fi
unset BASH_ENV ENV CDPATH GLOBIGNORE LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH
PATH=/usr/bin:/bin
LC_ALL=C
export PATH LC_ALL
IMAGE_ROOT=/srv/image-root
CONFIG_SOURCE=/srv/image-input/syswarden-config
CONFIG_DESTINATION="${IMAGE_ROOT}/etc/syswarden/config"
CONFIG_STAGE="${IMAGE_ROOT}/etc/syswarden/.config.pending-v1"
CONFIG_FILES=(config.toml modules/00-core.toml modules/10-network.toml)
extensions/rhel-image/stage-syswarden-rhel-image.sh \
--preflight-root \
--root "${IMAGE_ROOT}"
for SOURCE_DIRECTORY in \
/srv \
/srv/image-input \
"${CONFIG_SOURCE}" \
"${CONFIG_SOURCE}/modules"; do
[[ -d "${SOURCE_DIRECTORY}" && ! -L "${SOURCE_DIRECTORY}" ]]
[[ "$(/usr/bin/stat -c '%u:%g' -- "${SOURCE_DIRECTORY}")" == "0:0" ]]
SOURCE_MODE=$(/usr/bin/stat -c '%a' -- "${SOURCE_DIRECTORY}")
(( (8#${SOURCE_MODE} & 8#022) == 0 ))
done
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
SOURCE_FILE="${CONFIG_SOURCE}/${CONFIG_FILE}"
[[ -f "${SOURCE_FILE}" && ! -L "${SOURCE_FILE}" ]]
[[ "$(/usr/bin/stat -c '%u:%g:%a:%h' -- "${SOURCE_FILE}")" == "0:0:640:1" ]]
done
SYSWARDEN_PARENT="${IMAGE_ROOT}/etc/syswarden"
[[ ! -L "${SYSWARDEN_PARENT}" ]]
[[ ! -e "${SYSWARDEN_PARENT}" || -d "${SYSWARDEN_PARENT}" ]]
/usr/bin/install -d -o root -g root -m 0750 "${SYSWARDEN_PARENT}"
[[ ! -e "${CONFIG_DESTINATION}" && ! -L "${CONFIG_DESTINATION}" ]]
[[ ! -e "${CONFIG_STAGE}" && ! -L "${CONFIG_STAGE}" ]]
/usr/bin/install -d -o root -g root -m 0750 "${CONFIG_STAGE}/modules"
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
/usr/bin/install -o root -g root -m 0640 \
"${CONFIG_SOURCE}/${CONFIG_FILE}" "${CONFIG_STAGE}/${CONFIG_FILE}"
/usr/bin/cmp --silent -- \
"${CONFIG_SOURCE}/${CONFIG_FILE}" "${CONFIG_STAGE}/${CONFIG_FILE}"
done
/usr/bin/mv --no-clobber --no-target-directory \
"${CONFIG_STAGE}" "${CONFIG_DESTINATION}"
[[ ! -e "${CONFIG_STAGE}" && -d "${CONFIG_DESTINATION}" && ! -L "${CONFIG_DESTINATION}" ]]
[[ "$(/usr/bin/stat -c '%u:%g:%a' -- "${CONFIG_DESTINATION}")" == "0:0:750" ]]
[[ "$(/usr/bin/stat -c '%u:%g:%a' -- "${CONFIG_DESTINATION}/modules")" == "0:0:750" ]]
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
[[ "$(/usr/bin/stat -c '%u:%g:%a:%h' -- "${CONFIG_DESTINATION}/${CONFIG_FILE}")" == "0:0:640:1" ]]
/usr/bin/cmp --silent -- \
"${CONFIG_SOURCE}/${CONFIG_FILE}" "${CONFIG_DESTINATION}/${CONFIG_FILE}"
doneIf this block fails or leaves a pending directory, discard and rebuild the disposable image root.
The historical v4.04.0 change set was first published in the
historical v4.04.2 release and is carried forward in v4.04.3. It supplies the
configured country selections from its embedded release-bound snapshot,
including during image first boot.
When delivered through an official signed release, the signed manifest and
package digest authenticate the binary and its snapshot. A locally built or
otherwise unsigned binary provides content binding and structural validation,
not release authenticity. Do not stage country files in
/etc/syswarden/lists.
Every configured ASN still requires one non-empty canonical IPv4 file and one
non-empty canonical IPv6 file in /etc/syswarden/lists. For a blocked ASN, name
the pair <AS-number>.ipv4 and <AS-number>.ipv6. For a strict-allow ASN, use
allowed_<AS-number>.ipv4 and allowed_<AS-number>.ipv6. Replace the marker in
the example TOML before creating the blocked-ASN filenames. Generate the CIDRs
through an authenticated image-owner pipeline, verify an exact SHA256SUMS
manifest, and publish only regular non-symlink files as root with mode 0640.
Each file contains one canonical IP or CIDR per line for the exact ASN and
address family. Runtime validation rejects missing or empty files, mixed-family
entries, default routes, IPv4 prefixes broader than /24, and IPv6 prefixes
broader than /64 before nftables policy publication. The authenticated
image-owner pipeline must exclude private and other non-public ranges because
they do not establish ASN ownership.
Direct RADB or single-origin Spamhaus downloads are not authenticated authority for ASN policy. Existing verified operator-provisioned files remain unchanged.
Publish the exact policy set as one exclusive transaction from a trusted root
shell. The block refuses partial per-command elevation. Replace the invalid ASN
in both the TOML and APPROVED_ASNS before running it:
set -euo pipefail
umask 077
if (( EUID != 0 )) || [[ "${GROUPS[0]}" != 0 ]]; then
printf '%s\n' 'Run this complete policy publication block as root.' >&2
exit 1
fi
unset BASH_ENV ENV CDPATH GLOBIGNORE LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH
PATH=/usr/bin:/bin
LC_ALL=C
export PATH LC_ALL
IMAGE_ROOT=/srv/image-root
POLICY_SOURCE=/srv/image-input/policy-lists
extensions/rhel-image/stage-syswarden-rhel-image.sh \
--preflight-root \
--root "${IMAGE_ROOT}"
POLICY_DESTINATION="${IMAGE_ROOT}/etc/syswarden/lists"
POLICY_STAGE="${IMAGE_ROOT}/etc/syswarden/.lists.pending-v1"
# Copy this value from the authenticated image-owner policy inventory.
EXPECTED_POLICY_MANIFEST_SHA256=REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS
APPROVED_ASNS=(REPLACE_WITH_APPROVED_HIGH_RISK_ASN)
[[ "${EXPECTED_POLICY_MANIFEST_SHA256}" =~ ^[0-9a-f]{64}$ ]]
printf '%s %s\n' "${EXPECTED_POLICY_MANIFEST_SHA256}" \
"${POLICY_SOURCE}/SHA256SUMS" | /usr/bin/sha256sum --check --strict -
POLICY_FILES=()
for APPROVED_ASN in "${APPROVED_ASNS[@]}"; do
[[ "${APPROVED_ASN}" =~ ^AS[1-9][0-9]{0,9}$ ]]
ASN_NUMBER="${APPROVED_ASN#AS}"
(( 10#${ASN_NUMBER} <= 4294967295 ))
POLICY_FILES+=("${APPROVED_ASN}.ipv4" "${APPROVED_ASN}.ipv6")
done
mapfile -t MANIFEST_LINES < "${POLICY_SOURCE}/SHA256SUMS"
MANIFEST_FILES=()
declare -A EXPECTED_POLICY_SET=()
for POLICY_FILE in "${POLICY_FILES[@]}"; do
[[ -z "${EXPECTED_POLICY_SET[${POLICY_FILE}]+x}" ]]
EXPECTED_POLICY_SET["${POLICY_FILE}"]=1
done
declare -A MANIFEST_POLICY_SET=()
for MANIFEST_LINE in "${MANIFEST_LINES[@]}"; do
[[ "${MANIFEST_LINE}" =~ ^[0-9a-f]{64}\ \ ([A-Za-z0-9]+\.(ipv4|ipv6))$ ]]
MANIFEST_FILE="${BASH_REMATCH[1]}"
[[ -z "${MANIFEST_POLICY_SET[${MANIFEST_FILE}]+x}" ]]
MANIFEST_POLICY_SET["${MANIFEST_FILE}"]=1
MANIFEST_FILES+=("${MANIFEST_FILE}")
done
(( ${#EXPECTED_POLICY_SET[@]} == ${#MANIFEST_POLICY_SET[@]} ))
for POLICY_FILE in "${POLICY_FILES[@]}"; do
[[ -n "${MANIFEST_POLICY_SET[${POLICY_FILE}]+x}" ]]
done
(
cd "${POLICY_SOURCE}"
/usr/bin/sha256sum --check --strict SHA256SUMS
)
[[ ! -e "${POLICY_DESTINATION}" && ! -L "${POLICY_DESTINATION}" ]]
[[ ! -e "${POLICY_STAGE}" && ! -L "${POLICY_STAGE}" ]]
/usr/bin/install -d -o root -g root -m 0750 "${POLICY_STAGE}"
for POLICY_FILE in "${POLICY_FILES[@]}"; do
[[ -f "${POLICY_SOURCE}/${POLICY_FILE}" && ! -L "${POLICY_SOURCE}/${POLICY_FILE}" ]]
/usr/bin/install -o root -g root -m 0640 \
"${POLICY_SOURCE}/${POLICY_FILE}" "${POLICY_STAGE}/${POLICY_FILE}"
done
/usr/bin/install -o root -g root -m 0640 \
"${POLICY_SOURCE}/SHA256SUMS" "${POLICY_STAGE}/SHA256SUMS"
printf '%s %s\n' "${EXPECTED_POLICY_MANIFEST_SHA256}" \
"${POLICY_STAGE}/SHA256SUMS" | /usr/bin/sha256sum --check --strict -
(
cd "${POLICY_STAGE}"
/usr/bin/sha256sum --check --strict SHA256SUMS
)
/usr/bin/mv --no-clobber --no-target-directory \
"${POLICY_STAGE}" "${POLICY_DESTINATION}"
[[ ! -e "${POLICY_STAGE}" && -d "${POLICY_DESTINATION}" && ! -L "${POLICY_DESTINATION}" ]]
printf '%s %s\n' "${EXPECTED_POLICY_MANIFEST_SHA256}" \
"${POLICY_DESTINATION}/SHA256SUMS" | /usr/bin/sha256sum --check --strict -
(
cd "${POLICY_DESTINATION}"
/usr/bin/sha256sum --check --strict SHA256SUMS
)The block copies no glob and no unlisted input. If it fails or leaves a pending directory, discard and rebuild the disposable image root.
The extension enables a marker-guarded unit that requires crond.service. On
the real first boot, the normal install and reload workflows validate the
configuration, prove a live enabled Cronie provider, preserve the active
firewalld frontend through keep, and converge runtime state. nftables remains
the authoritative SysWarden policy engine.
Run the image builder's normal SELinux relabel, ownership, bootloader, package inventory and ISO assembly steps. Boot a disposable target and verify:
sudo systemctl is-active crond.service firewalld.service
sudo systemctl is-enabled crond.service firewalld.service
sudo systemctl status syswarden-image-firstboot.service
sudo journalctl -u syswarden-image-firstboot.service --no-pager
sudo syswarden config validate --path /etc/syswarden/config
sudo nft -j list table inet syswarden
sudo test ! -e /var/lib/syswarden/image/firstboot.pendingConfirm recovery access and expected business traffic before reusing the image recipe. A failed first-boot unit retains its marker and retries on the next boot; correct the cause rather than deleting the marker.
Offline staging is not runtime-readiness evidence. ISO assembly, signing, bootloader integration and target-host validation remain separate image-builder responsibilities. See the repository extension README for the complete boundary and recovery contract.
6. Safe upgrade offboarding#
The current product has no network terminal service. Upgrade qualification must prove that verified historical SysWarden-owned listener, credential, browser asset and service state is removed while unrelated configuration and unrelated processes are preserved.
After upgrade:
- confirm the package owns no TCP 62027 listener or firewall permission;
- confirm only the native local TUI entry point remains;
- confirm no remote PTY, browser asset or token-management route remains;
- confirm an unrelated process using the same numeric port was not stopped;
- confirm unrelated configuration was not deleted;
- confirm the core, authoritative nftables policy and selected firewall service state pass their normal health checks.
A downgrade is an emergency operation and can restore an older network surface. Keep TCP 62027 blocked at the host boundary and do not restore retired authentication material.
7. Configuration layout#
The modular root is /etc/syswarden/config. Later module filenames have higher
precedence. Back up the complete directory, validate the candidate and inspect
the effective diff before applying it.
schema_version = 1 is the current modular schema. When schema_version is
absent, the configuration is treated as historical input. An explicit future,
negative or non-integer schema version fails closed.
sudo syswarden config validate --path /etc/syswarden/config
sudo syswarden config
sudo syswarden reloadsyswarden config validate is read-only. All unknown and deprecated keys are
reported as diagnostics; a semantic or structural violation still fails
validation. The CLI validator and core loader enforce the same semantic matrix,
including SSH/HA port separation while WireGuard is enabled.
Firewall service selection#
core.firewall_backend accepts keep, nftables or iptables:
keepis the default and performs no firewall service transition, but refuses policy mutation while an iptables-services or netfilter-persistent service is active or enabled;nftablesvalidates thatnftables.serviceis already active and enabled and refuses any active or enabled firewalld, UFW, iptables-services or netfilter-persistent frontend;iptablesremains parseable for configuration compatibility, but operational firewall policy mutation paths reject it in v4.04.3 before changing persistent policy inputs or kernel firewall state.
Operational policy mutation requires an active, unambiguous supported service
manager. Package hooks running with no service-manager runtime defer install
and reload instead of invoking host firewall or kernel tools.
The packaged candidate always validates and commits the authoritative
SysWarden nftables policy. If exactly one supported firewalld or UFW frontend is
already active, SysWarden may reconcile only bounded owned compatibility rules.
Installed but inactive frontends are not activated. Automatic service migration
is outside the qualified v4.04.3 contract. Prepare and verify any required
service change through an independent recovery procedure before selecting
nftables, then run syswarden reload from a verified recovery session.
Native host and CIDR mutations in nftables interval sets use a start element and the exact exclusive end marker required by the kernel. The manager repairs a detected unterminated start before replacement and rejects ambiguous state or an interval containing internal boundaries. An exclusive end marker left alone after timed-start expiry is treated as functionally absent; a later re-ban must install and verify one complete pair. Unit tests are supporting evidence only. The protected release still requires add, timed renewal, permanent replacement, replay and removal for both address families in the isolated real-kernel nftables laboratory.
WireGuard requires the explicit nftables backend. The bounded firewalld and
UFW compatibility path does not open the WireGuard UDP port or forwarding
rules. Active firewalld and UFW target-host execution remains outside the
qualified evidence.
Recover exact historical WireGuard state#
Availability: The following is a version-specific v4.10.0 source procedure.
The public stable CLI does not provide recover-wireguard. Do not run these
commands on that release or infer availability from this page's stable
baseline. The candidate implementation remains subject to release validation
under the IVV/IVVQ strategy.
An upgrade can encounter an unmarked inet syswarden_wg table created by an
older SysWarden generation. Reload and uninstall remain fail-closed when the
table cannot be proven against the current ownership manifest. Do not delete
the table or shared forwarding rules manually.
Keep verified console access, stop and disable the historical WireGuard service, and confirm that its interface is absent. Then generate a read-only, secret-redacted recovery plan:
sudo syswarden recover-wireguardThe command accepts only the exact historical pre-v3.75.7 wg0 generation or
the exact historical v3.75.7 through historical v4.03.0 wg-syswarden
generation. It refuses unknown tables,
changed configuration, active services, present interfaces, duplicate rules
and ambiguous ownership. Review the JSON plan and its SHA-256 digest. Apply
only that exact plan with the digest printed by the dry run:
sudo syswarden recover-wireguard --apply --plan-sha256 <exact-sha256>The apply phase reattests the host state immediately before the bounded
nftables transaction and verifies that the historical state is absent. If any
evidence changes, generate and review a new plan. Run syswarden reload only
after recovery succeeds.
Use syswarden config migrate for the structured migration path. Preview it
with --dry-run before publishing any file. syswarden config migrate --dry-run
performs zero source or destination writes, including when a previous migration
transaction requires recovery. syswarden migrate-config remains a
compatibility alias with the same transactional and dry-run contract.
A historical SYSWARDEN_FIREWALL_BACKEND="firewalld" value migrates to
core.firewall_backend = "keep", preserving the existing service without an
automatic transition. A historical configuration that enables WireGuard with
any backend other than nftables is rejected for explicit operator remediation
rather than silently changing the backend choice.
The official SaaS monitor setting is disabled unless explicitly enabled:
[network.saas]
allow_monitors = falsenetwork.saas.allow_monitors takes precedence over the deprecated
integrations.saas.enabled alias; if neither is set, monitor allowlisting is
disabled. Enabled monitor feeds must use absolute HTTPS URLs without
credentials or fragments. TLS 1.3 is required, redirects are rejected and each
download is bounded to 10 seconds, 1 MiB, 1,024 bytes per line, 20,000 lines
and 10,000 entries. A required-feed, syntax or bound failure preserves the
previous lists. Valid canonical IPv4 and IPv6 results are published as one
lock-coordinated atomic pair with an SHA-256 pair manifest and rollback on
publication failure.
WAAP log settings use canonical single-space-separated absolute patterns. Configured globs are reduced to verified exact real regular-file matches. The core opens them descriptor-relative without following links and revalidates the file identity and type after every rotation; it never creates a missing target. Rsyslog inputs are emitted only for paths that pass the same validation at configuration time, but rsyslog later reopens those names itself, so operators must keep every parent directory protected against untrusted replacement. Rsyslog strings and WireGuard values are validated and encoded for their destination grammars before configuration publication.
Persistent list grammar#
Blocklist, whitelist and SSH-exception values use exact canonical IPv4 or IPv6 addresses and masked CIDRs. Hostnames, address zones, IPv4-mapped IPv6 values, invalid ports, control characters and ambiguous substring matches are rejected. The address family must match the destination list.
Use the explicit flag to limit a whitelist entry to one TCP service:
sudo syswarden whitelist 192.0.2.10 --port 443--port scopes a whitelist entry to one TCP service; omitting it creates an
address-wide whitelist entry. SSH exceptions are rendered only for the
effective SSH port. A port-qualified SSH entry must equal that effective port,
and a changed-port mismatch fails closed before candidate policy application.
Reconcile the SSH exception registry from verified console access before
applying an SSH port change.
Minimal authenticated HA configuration:
[integrations.ha]
enabled = true
peer_ips = ["192.0.2.10", "192.0.2.11"]
peer_port = 62026
token = "replace-with-a-secret-from-a-protected-channel"
[integrations.bunkerweb]
enabled = trueDo not commit a real token. Exact peer IPs may be dialed. Canonical CIDRs
authorize inbound peers only and are never outbound destinations. If
/etc/syswarden/ha-ca.pem exists, clients use it as the exclusive HA trust
pool; otherwise they use system trust roots.
8. User interfaces and ports#
Launch the dashboard from a trusted local console or SSH terminal:
sudo syswarden tuiThe TUI reads local telemetry and HA status in the invoking terminal. It opens no listening socket. SysWarden owns no listener or generated firewall permission on TCP 62027.
The authenticated HA API uses TLS 1.3 and configurable TCP port 62026 by default. Every HA route requires the bearer token. Configuration validation rejects an empty token or any token containing whitespace or control characters before reload mutation or service restart. The previously validated configuration and its running listener remain active until a valid candidate is successfully reloaded.
9. HA dialect and ownership#
For each peer and serialized synchronization cycle, use one authenticated
GET /ha/status to select the dialect. Enriched operations require both
sync_ttl and sync_provenance. Missing, partial, malformed or failed
capabilities stop the handoff for that peer and never relax TLS or bearer
authentication.
The ownership stores are intentionally separate:
DELETE {"bans": [...]}removes provenance ledger entries only;DELETE {"ips": [...]}explicitly removes historical static entries;- neither delete cascades to another peer;
- the integrator cleans only addresses present in its durable, peer-specific record of historical submissions;
- ownership is never inferred from
GET /ha/sync, provenance pagination or an effective union returned by a peer.
The enriched DELETE contract introduced by the historical v4.04.0 change set
was first published in the historical v4.04.2 release and is carried forward in
v4.04.3. After successful processing, it returns
{"status":"ok","deleted":N}. deleted is the number of exact provenance claims
removed for the canonical IP, source and authenticated peer scope. It is not
a count of distinct IP addresses and is not proof that
nftables unblocked the address, because another owner or a historical static
claim can keep it banned. A source or scope mismatch is a safe idempotent no-op
with deleted:0.
10. Cluster migration fence#
The operator, not the integrator, supplies the complete member and external legacy-writer inventory. One trusted control host creates one canonical activation manifest and distributes the same protected file to every member and to the integrator:
{
"schema_version": 1,
"membership_scope": "one_receiving_api_endpoint_per_syswarden_node",
"legacy_writer_ids": [
"bunkerweb-primary"
],
"members": [
{
"address": "192.0.2.10",
"port": 62026
}
]
}Replace the documentation address with each receiving member's canonical IP
literal. The inventory must be an absolute canonical path to a regular,
root-owned mode 0600 file no larger than 1 MiB under a protected directory
chain. JSON is strict: no byte-order mark, duplicate or unknown keys, or
trailing data. At least one unique address and port endpoint is required.
Addresses cannot be CIDRs, zoned, bracketed or IPv4-mapped IPv6 values, and
ports range from 1 through 65535. legacy_writer_ids is mandatory but may be
empty. Non-empty IDs match [a-z0-9][a-z0-9._-]{0,63} and are unique.
--assert-complete attests that the member and legacy-writer inventories are
complete.
sudo syswarden ha-fence manifest create \
--inventory /root/syswarden-ha-inventory.json \
--output /root/syswarden-ha-manifest.json \
--assert-complete
sudo syswarden ha-fence manifest verify \
--manifest /root/syswarden-ha-manifest.json
sudo syswarden ha-fence engage \
--manifest /root/syswarden-ha-manifest.json
sudo syswarden ha-fence status --jsonThe capability native_sync_fence_v1 means only that a node understands the
schema. The proof is the fresh, dynamic and challenge-bound
native_sync_fence object returned by the exact member over the verified TLS
connection.
The integrator accepts proof only when state is active_drained, challenge,
leaf identity, server instance, generation and condition are stable, and the
following operator-manifest values match exactly:
epoch;membership_sha256;legacy_writer_inventory_sha256.
These values are opaque, case-sensitive strings. The integrator compares exact
strings and does not recalculate digests or reimplement canonicalization. The
response header X-SysWarden-HA-Fence-Condition must equal the JSON condition
value from the same response.
Every historical static cleanup request sends the observed condition in
X-SysWarden-HA-Fence-Condition. A missing condition receives HTTP 428, a
malformed condition receives HTTP 400 and a stale condition receives HTTP 412.
All three reject without mutation. HTTP 412 means that the fence changed; stop,
refresh the complete all-member proof and require an operator decision.
An unreachable member, changed inventory, changed certificate, restarted server instance, changed generation, blind interval or reappearing address resets the observation. A one-hour continuous absence window across every declared peer is supporting evidence only. It is never proof of drain and never permits claim release from a partial view.
Fence release requires the unchanged manifest and durable terminal closure evidence for every declared external writer:
{
"schema_version": 1,
"epoch": "COPY_EXACT_EPOCH_FROM_MANIFEST",
"membership_sha256": "COPY_EXACT_MEMBERSHIP_SHA256_FROM_MANIFEST",
"legacy_writer_inventory_sha256": "COPY_EXACT_WRITER_SHA256_FROM_MANIFEST",
"legacy_retry_queue_drained": true,
"writers": [
{
"id": "bunkerweb-primary",
"disposition": "migrated_enriched_only",
"closure_generation": "REPLACE_WITH_DURABLE_GENERATION",
"closed_at": "REPLACE_WITH_CANONICAL_UTC_RFC3339",
"evidence_sha256": "REPLACE_WITH_LOWERCASE_EVIDENCE_SHA256"
}
]
}Replace every placeholder with values bound to the unchanged manifest and
durable closure evidence. The writer closure follows the same protected-file
rules and must use the canonical indented layout shown with one final newline.
Its writers exactly match the manifest writer IDs in canonical order; use an
empty array when the manifest contains no writers. The retry queue flag is
true. Terminal dispositions are migrated_enriched_only, disabled,
credential_revoked or network_quarantined. The generation is 1 through 256
non-space printable ASCII characters, the closure time is canonical whole-second
UTC RFC3339, and the evidence digest is lowercase SHA-256.
sudo syswarden ha-fence release \
--manifest /root/syswarden-ha-manifest.json \
--writer-closure /root/syswarden-ha-writer-closure.jsonBefore the historical v4.03.2 freeze, the BunkerWeb partner had to provide written confirmation against the exact frozen contract. No unexplained partner-attributable static residue may remain.
See BunkerWeb integration for the full integrator contract.
11. Command inventory#
The table lists product commands only. Cobra help and shell-completion utilities
remain available separately. Unmarked rows belong to the stable baseline.
Rows marked "source candidate only" belong to the version-specific v4.10.0
source at commit 8fc22a6bae4adfcf62d80e90da6387f2aa6db1c7; they are absent
from the stable release and are not a release qualification claim.
| Command | Purpose |
|---|---|
alerts | Stream kernel and WAAP alert events |
allow-ssh | Add an address to the SSH exception registry |
audit | Run a bounded local operational diagnostic |
block | Add addresses or CIDRs to the persistent blocklist |
check | Inspect recorded firewall state for an address |
config | Validate, inspect and migrate modular configuration |
config-get | Read one effective configuration key |
ha-fence | Administer a cluster migration fence |
ha-sync | Push missing local durable entries to exact peers |
install | Run the host-mutating installation pipeline |
list | Display local registries and active HA bans |
manual | Display the built-in operator reference |
migrate-config | Run the compatible configuration migration entry point |
reload | Reapply policy and normally restart the core |
recover-wireguard | Source candidate only: inspect and explicitly recover exact historical WireGuard nftables state |
revoke-ssh | Remove an address from the SSH exception registry |
runtime-unblock | Source candidate only: remove local runtime claims through the authenticated core; persistent blocklists and independent HA claims remain effective |
tui | Launch the native local terminal dashboard |
unblock | Remove addresses or CIDRs from the blocklist |
uninstall | Delete SysWarden services, rules, configuration, data and logs |
unwhitelist | Remove addresses or CIDRs from the whitelist |
update | Install an update verified by the signed manifest |
update-feeds | Validate embedded GeoIP, refresh external feeds and reapply policy |
whitelist | Add addresses or CIDRs to the persistent whitelist |
whitelist-infra | Detect and add local infrastructure addresses |
Use syswarden <command> --help before any host-mutating action.
12. Audit and removal#
syswarden audit is a bounded local diagnostic. It does not certify compliance.
syswarden uninstall deletes SysWarden configuration, data, logs, services and
firewall state. It is not a general host rollback. Back up required material,
retain console access and verify unrelated host state before removal.
Removal intentionally does not reverse successfully applied OS or CIS
hardening. Exact hardening policy files may therefore remain after the product
runtime is gone. On a Fedora or RHEL-family host where SysWarden installed and
configured dnf-automatic, dependency cleanup may also preserve the hardened
configuration as /etc/dnf/automatic.conf.rpmsave. Treat these files as host
security policy, not active SysWarden runtime. Review their contents and package
provenance from console access before deciding whether to keep or retire them;
do not delete them merely to make a residue scan empty.
Continue with the bounded use cases before production rollout.