Compare commits

...

12 Commits

Author SHA1 Message Date
0f59f29ac4 fix(inventory_agent): Inventory agent now fallsback on error messages when it cant find values
Some checks failed
Run Check Script / check (pull_request) Failing after 38s
2025-08-22 11:52:51 -04:00
361f240762 feat: PXE setup now fully functional for inventory agent
The process will setup DHCP dnsmasq on opnsense to boot the correct ipxe file depending on the architecture
Then ipxe will chainload to either a mac-specific ipxe boot file or the fallback inventory boot file
Then a kickstart pre script will setup the cluster ssh key to allow ssh connections to the machine and also setup and start harmony_inventory_agent to allow being scraped

Note: there is a bug with the inventory agent currently, it cannot find lsmod on centos stream 9, will fix this soon
2025-08-22 10:48:43 -04:00
57c3b01e66 chore: refactor pxe templates to jinja templates rendered by askama
Some checks failed
Run Check Script / check (pull_request) Failing after 36s
2025-08-22 09:05:18 -04:00
94ddf027dd feat(pxe): chainloading works, kickstart for inventory still wip 2025-08-22 07:22:12 -04:00
06a2be4496 doc: Add README explaining how to build harmony_inventory_agent statically with musl target
Some checks failed
Run Check Script / check (pull_request) Failing after 35s
2025-08-21 21:58:35 -04:00
e2a09efdee Merge remote-tracking branch 'origin/master' into doc/pxe_test_setup 2025-08-21 21:56:09 -04:00
2618441de3 fix: Make sure directory exists before uploading file in opnsense http
Some checks failed
Run Check Script / check (pull_request) Failing after 31s
2025-08-21 17:31:43 -04:00
da6610c625 wip: PXE setup for ipxe and okd files in progress
Some checks failed
Run Check Script / check (pull_request) Failing after 36s
2025-08-21 17:28:17 -04:00
e956772593 feat: Add pxe example and new data files structure 2025-08-20 22:00:56 -04:00
27c51e0ec5 feat(wip): Support opnsense 25.7 which defaults to dnsmasq instead of isc dhcp 2025-08-20 21:54:46 -04:00
597dcbc848 doc: PXE test setup script and README file to explain what it does and how to use it
Some checks failed
Run Check Script / check (pull_request) Failing after 40s
2025-08-20 13:14:00 -04:00
a53e8552e9 wip: pxe test setup still has a few kinks with serial console 2025-08-20 12:14:17 -04:00
55 changed files with 6673 additions and 387 deletions

68
Cargo.lock generated
View File

@@ -362,6 +362,48 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "askama"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
dependencies = [
"askama_derive",
"itoa",
"percent-encoding",
"serde",
"serde_json",
]
[[package]]
name = "askama_derive"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
dependencies = [
"askama_parser",
"basic-toml",
"memchr",
"proc-macro2",
"quote",
"rustc-hash",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "askama_parser"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358"
dependencies = [
"memchr",
"serde",
"serde_derive",
"winnow",
]
[[package]]
name = "assert_cmd"
version = "2.0.17"
@@ -485,6 +527,15 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
[[package]]
name = "basic-toml"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
dependencies = [
"serde",
]
[[package]]
name = "bcrypt-pbkdf"
version = "0.10.0"
@@ -1655,6 +1706,22 @@ dependencies = [
"url",
]
[[package]]
name = "example-pxe"
version = "0.1.0"
dependencies = [
"askama",
"cidr",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_types",
"log",
"tokio",
"url",
]
[[package]]
name = "example-rust"
version = "0.1.0"
@@ -3580,6 +3647,7 @@ dependencies = [
"tokio",
"tokio-stream",
"tokio-util",
"uuid",
]
[[package]]

8
data/pxe/okd/README.md Normal file
View File

@@ -0,0 +1,8 @@
Here lies all the data files required for an OKD cluster PXE boot setup.
This inclues ISO files, binary boot files, ipxe, etc.
TODO as of august 2025 :
- `harmony_inventory_agent` should be downloaded from official releases, this embedded version is practical for now though
- The cluster ssh key should be generated and handled by harmony with the private key saved in a secret store

View File

@@ -0,0 +1,9 @@
harmony_inventory_agent filter=lfs diff=lfs merge=lfs -text
os filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9 filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9/images filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9/initrd.img filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9/vmlinuz filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9/images/efiboot.img filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9/images/install.img filter=lfs diff=lfs merge=lfs -text
os/centos-stream-9/images/pxeboot filter=lfs diff=lfs merge=lfs -text

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2

BIN
data/pxe/okd/http_files/harmony_inventory_agent (Stored with Git LFS) Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
data/pxe/okd/http_files/os/centos-stream-9/initrd.img (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/pxe/okd/http_files/os/centos-stream-9/vmlinuz (Stored with Git LFS) Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

108
docs/pxe_test/README.md Normal file
View File

@@ -0,0 +1,108 @@
# OPNsense PXE Lab Environment
This project contains a script to automatically set up a virtual lab environment for testing PXE boot services managed by an OPNsense firewall.
## Overview
The `pxe_vm_lab_setup.sh` script will create the following resources using libvirt/KVM:
1. **A Virtual Network**: An isolated network named `harmonylan` (`virbr1`) for the lab.
2. **Two Virtual Machines**:
* `opnsense-pxe`: A firewall VM that will act as the gateway and PXE server.
* `pxe-node-1`: A client VM configured to boot from the network.
## Prerequisites
Ensure you have the following software installed on your Arch Linux host:
* `libvirt`
* `qemu`
* `virt-install` (from the `virt-install` package)
* `curl`
* `bzip2`
## Usage
### 1. Create the Environment
Run the `up` command to download the necessary images and create the network and VMs.
```bash
sudo ./pxe_vm_lab_setup.sh up
```
### 2. Install and Configure OPNsense
The OPNsense VM is created but the OS needs to be installed manually via the console.
1. **Connect to the VM console**:
```bash
sudo virsh console opnsense-pxe
```
2. **Log in as the installer**:
* Username: `installer`
* Password: `opnsense`
3. **Follow the on-screen installation wizard**. When prompted to assign network interfaces (`WAN` and `LAN`):
* Find the MAC address for the `harmonylan` interface by running this command in another terminal:
```bash
virsh domiflist opnsense-pxe
# Example output:
# Interface Type Source Model MAC
# ---------------------------------------------------------
# vnet18 network default virtio 52:54:00:b5:c4:6d
# vnet19 network harmonylan virtio 52:54:00:21:f9:ba
```
* Assign the interface connected to `harmonylan` (e.g., `vtnet1` with MAC `52:54:00:21:f9:ba`) as your **LAN**.
* Assign the other interface as your **WAN**.
4. After the installation is complete, **shut down** the VM from the console menu.
5. **Detach the installation media** by editing the VM's configuration:
```bash
sudo virsh edit opnsense-pxe
```
Find and **delete** the entire `<disk>` block corresponding to the `.img` file (the one with `<target ... bus='usb'/>`).
6. **Start the VM** to boot into the newly installed system:
```bash
sudo virsh start opnsense-pxe
```
### 3. Connect to OPNsense from Your Host
To configure OPNsense, you need to connect your host to the `harmonylan` network.
1. By default, OPNsense configures its LAN interface with the IP `192.168.1.1`.
2. Assign a compatible IP address to your host's `virbr1` bridge interface:
```bash
sudo ip addr add 192.168.1.5/24 dev virbr1
```
3. You can now access the OPNsense VM from your host:
* **SSH**: `ssh root@192.168.1.1` (password: `opnsense`)
* **Web UI**: `https://192.168.1.1`
### 4. Configure PXE Services with Harmony
With connectivity established, you can now use Harmony to configure the OPNsense firewall for PXE booting. Point your Harmony OPNsense scores to the firewall using these details:
* **Hostname/IP**: `192.168.1.1`
* **Credentials**: `root` / `opnsense`
### 5. Boot the PXE Client
Once your Harmony configuration has been applied and OPNsense is serving DHCP/TFTP, start the client VM. It will automatically attempt to boot from the network.
```bash
sudo virsh start pxe-node-1
sudo virsh console pxe-node-1
```
## Cleanup
To destroy all VMs and networks created by the script, run the `clean` command:
```bash
sudo ./pxe_vm_lab_setup.sh clean
```

191
docs/pxe_test/pxe_vm_lab_setup.sh Executable file
View File

@@ -0,0 +1,191 @@
#!/usr/bin/env bash
set -euo pipefail
# --- Configuration ---
LAB_DIR="/var/lib/harmony_pxe_test"
IMG_DIR="${LAB_DIR}/images"
STATE_DIR="${LAB_DIR}/state"
VM_OPN="opnsense-pxe"
VM_PXE="pxe-node-1"
NET_HARMONYLAN="harmonylan"
# Network settings for the isolated LAN
VLAN_CIDR="192.168.150.0/24"
VLAN_GW="192.168.150.1"
VLAN_MASK="255.255.255.0"
# VM Specifications
RAM_OPN="2048"
VCPUS_OPN="2"
DISK_OPN_GB="10"
OS_VARIANT_OPN="freebsd14.0" # Updated to a more recent FreeBSD variant
RAM_PXE="4096"
VCPUS_PXE="2"
DISK_PXE_GB="40"
OS_VARIANT_LINUX="centos-stream9"
OPN_IMG_URL="https://mirror.ams1.nl.leaseweb.net/opnsense/releases/25.7/OPNsense-25.7-serial-amd64.img.bz2"
OPN_IMG_PATH="${IMG_DIR}/OPNsense-25.7-serial-amd64.img"
CENTOS_ISO_URL="https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/os/images/boot.iso"
CENTOS_ISO_PATH="${IMG_DIR}/CentOS-Stream-9-latest-boot.iso"
CONNECT_URI="qemu:///system"
download_if_missing() {
local url="$1"
local dest="$2"
if [[ ! -f "$dest" ]]; then
echo "Downloading $url to $dest"
mkdir -p "$(dirname "$dest")"
local tmp
tmp="$(mktemp)"
curl -L --progress-bar "$url" -o "$tmp"
case "$url" in
*.bz2) bunzip2 -c "$tmp" > "$dest" && rm -f "$tmp" ;;
*) mv "$tmp" "$dest" ;;
esac
else
echo "Already present: $dest"
fi
}
# Ensures a libvirt network is defined and active
ensure_network() {
local net_name="$1"
local net_xml_path="$2"
if virsh --connect "${CONNECT_URI}" net-info "${net_name}" >/dev/null 2>&1; then
echo "Network ${net_name} already exists."
else
echo "Defining network ${net_name} from ${net_xml_path}"
virsh --connect "${CONNECT_URI}" net-define "${net_xml_path}"
fi
if ! virsh --connect "${CONNECT_URI}" net-info "${net_name}" | grep "Active: *yes"; then
echo "Starting network ${net_name}..."
virsh --connect "${CONNECT_URI}" net-start "${net_name}"
virsh --connect "${CONNECT_URI}" net-autostart "${net_name}"
fi
}
# Destroys a VM completely
destroy_vm() {
local vm_name="$1"
if virsh --connect "${CONNECT_URI}" dominfo "$vm_name" >/dev/null 2>&1; then
echo "Destroying and undefining VM: ${vm_name}"
virsh --connect "${CONNECT_URI}" destroy "$vm_name" || true
virsh --connect "${CONNECT_URI}" undefine "$vm_name" --nvram
fi
}
# Destroys a libvirt network
destroy_network() {
local net_name="$1"
if virsh --connect "${CONNECT_URI}" net-info "$net_name" >/dev/null 2>&1; then
echo "Destroying and undefining network: ${net_name}"
virsh --connect "${CONNECT_URI}" net-destroy "$net_name" || true
virsh --connect "${CONNECT_URI}" net-undefine "$net_name"
fi
}
# --- Main Logic ---
create_lab_environment() {
# Create network definition files
cat > "${STATE_DIR}/default.xml" <<EOF
<network>
<name>default</name>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.100' end='192.168.122.200'/>
</dhcp>
</ip>
</network>
EOF
cat > "${STATE_DIR}/${NET_HARMONYLAN}.xml" <<EOF
<network>
<name>${NET_HARMONYLAN}</name>
<bridge name='virbr1' stp='on' delay='0'/>
</network>
EOF
# Ensure both networks exist and are active
ensure_network "default" "${STATE_DIR}/default.xml"
ensure_network "${NET_HARMONYLAN}" "${STATE_DIR}/${NET_HARMONYLAN}.xml"
# --- Create OPNsense VM (MODIFIED SECTION) ---
local disk_opn="${IMG_DIR}/${VM_OPN}.qcow2"
if [[ ! -f "$disk_opn" ]]; then
qemu-img create -f qcow2 "$disk_opn" "${DISK_OPN_GB}G"
fi
echo "Creating OPNsense VM using serial image..."
virt-install \
--connect "${CONNECT_URI}" \
--name "${VM_OPN}" \
--ram "${RAM_OPN}" \
--vcpus "${VCPUS_OPN}" \
--cpu host-passthrough \
--os-variant "${OS_VARIANT_OPN}" \
--graphics none \
--noautoconsole \
--disk path="${disk_opn}",device=disk,bus=virtio,boot.order=1 \
--disk path="${OPN_IMG_PATH}",device=disk,bus=usb,readonly=on,boot.order=2 \
--network network=default,model=virtio \
--network network="${NET_HARMONYLAN}",model=virtio \
--boot uefi,menu=on
echo "OPNsense VM created. Connect with: sudo virsh console ${VM_OPN}"
echo "The VM will boot from the serial installation image."
echo "Login with user 'installer' and password 'opnsense' to start the installation."
echo "Install onto the VirtIO disk (vtbd0)."
echo "After installation, shutdown the VM, then run 'sudo virsh edit ${VM_OPN}' and remove the USB disk block to boot from the installed system."
# --- Create PXE Client VM ---
local disk_pxe="${IMG_DIR}/${VM_PXE}.qcow2"
if [[ ! -f "$disk_pxe" ]]; then
qemu-img create -f qcow2 "$disk_pxe" "${DISK_PXE_GB}G"
fi
echo "Creating PXE client VM..."
virt-install \
--connect "${CONNECT_URI}" \
--name "${VM_PXE}" \
--ram "${RAM_PXE}" \
--vcpus "${VCPUS_PXE}" \
--cpu host-passthrough \
--os-variant "${OS_VARIANT_LINUX}" \
--graphics none \
--noautoconsole \
--disk path="${disk_pxe}",format=qcow2,bus=virtio \
--network network="${NET_HARMONYLAN}",model=virtio \
--pxe \
--boot uefi,menu=on
echo "PXE VM created. It will attempt to netboot on ${NET_HARMONYLAN}."
}
# --- Script Entrypoint ---
case "${1:-}" in
up)
mkdir -p "${IMG_DIR}" "${STATE_DIR}"
download_if_missing "$OPN_IMG_URL" "$OPN_IMG_PATH"
download_if_missing "$CENTOS_ISO_URL" "$CENTOS_ISO_PATH"
create_lab_environment
echo "Lab setup complete. Use 'sudo virsh list --all' to see VMs."
;;
clean)
destroy_vm "${VM_PXE}"
destroy_vm "${VM_OPN}"
destroy_network "${NET_HARMONYLAN}"
# Optionally destroy the default network if you want a full reset
# destroy_network "default"
echo "Cleanup complete."
;;
*)
echo "Usage: sudo $0 {up|clean}"
exit 1
;;
esac

View File

@@ -125,9 +125,10 @@ async fn main() {
harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology);
let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string()));
let http_score = StaticFilesHttpScore::new(Url::LocalFolder(
"./data/watchguard/pxe-http-files".to_string(),
));
let http_score = StaticFilesHttpScore {
folder_to_serve: Some(Url::LocalFolder("./data/watchguard/pxe-http-files".to_string())),
files: vec![],
};
let ipxe_score = IpxeScore::new();
harmony_tui::run(

View File

@@ -0,0 +1,19 @@
[package]
name = "example-pxe"
edition = "2024"
version.workspace = true
readme.workspace = true
license.workspace = true
publish = false
[dependencies]
harmony = { path = "../../harmony" }
harmony_cli = { path = "../../harmony_cli" }
harmony_types = { path = "../../harmony_types" }
cidr = { workspace = true }
tokio = { workspace = true }
harmony_macros = { path = "../../harmony_macros" }
log = { workspace = true }
env_logger = { workspace = true }
url = { workspace = true }
askama = "0.14.0"

View File

@@ -0,0 +1,92 @@
mod topology;
use std::net::IpAddr;
use askama::Template;
use harmony::{
data::{FileContent, FilePath},
modules::{dhcp::DhcpScore, http::StaticFilesHttpScore, tftp::TftpScore},
score::Score,
topology::{HAClusterTopology, Url},
};
use crate::topology::{get_inventory, get_topology};
#[tokio::main]
async fn main() {
let inventory = get_inventory();
let topology = get_topology().await;
let gateway_ip = &topology.router.get_gateway();
let kickstart_filename = "inventory.kickstart";
let cluster_pubkey_filename = "cluster_ssh_key.pub";
let harmony_inventory_agent = "harmony_inventory_agent";
// TODO this should be a single IPXEScore instead of having the user do this step by step
let scores: Vec<Box<dyn Score<HAClusterTopology>>> = vec![
Box::new(DhcpScore {
host_binding: vec![],
next_server: Some(topology.router.get_gateway()),
boot_filename: None,
filename: Some("undionly.kpxe".to_string()),
filename64: Some("ipxe.efi".to_string()),
filenameipxe: Some(format!("http://{gateway_ip}:8080/boot.ipxe").to_string()),
}),
Box::new(TftpScore {
files_to_serve: Url::LocalFolder("./data/pxe/okd/tftpboot/".to_string()),
}),
Box::new(StaticFilesHttpScore {
// TODO The current russh based copy is way too slow, check for a lib update or use scp
// when available
//
// For now just run :
// scp -r data/pxe/okd/http_files/* root@192.168.1.1:/usr/local/http/
//
folder_to_serve: None,
// folder_to_serve: Some(Url::LocalFolder("./data/pxe/okd/http_files/".to_string())),
files: vec![
FileContent {
path: FilePath::Relative("boot.ipxe".to_string()),
content: BootIpxeTpl { gateway_ip }.to_string(),
},
FileContent {
path: FilePath::Relative(kickstart_filename.to_string()),
content: InventoryKickstartTpl {
gateway_ip,
harmony_inventory_agent,
cluster_pubkey_filename,
}.to_string(),
},
FileContent {
path: FilePath::Relative("fallback.ipxe".to_string()),
content: FallbackIpxeTpl { gateway_ip, kickstart_filename}.to_string(),
},
],
}),
];
harmony_cli::run(inventory, topology, scores, None)
.await
.unwrap();
}
#[derive(Template)]
#[template(path = "boot.ipxe.j2")]
struct BootIpxeTpl<'a> {
gateway_ip: &'a IpAddr,
}
#[derive(Template)]
#[template(path = "fallback.ipxe.j2")]
struct FallbackIpxeTpl<'a> {
gateway_ip: &'a IpAddr,
kickstart_filename: &'a str,
}
#[derive(Template)]
#[template(path = "inventory.kickstart.j2")]
struct InventoryKickstartTpl<'a> {
gateway_ip: &'a IpAddr,
cluster_pubkey_filename: &'a str,
harmony_inventory_agent: &'a str,
}

View File

@@ -0,0 +1,65 @@
use std::{
net::{IpAddr, Ipv4Addr},
sync::Arc,
};
use cidr::Ipv4Cidr;
use harmony::{
hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup},
infra::opnsense::OPNSenseManagementInterface,
inventory::Inventory,
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
};
use harmony_macros::{ip, ipv4};
pub async fn get_topology() -> HAClusterTopology {
let firewall = harmony::topology::LogicalHost {
ip: ip!("192.168.1.1"),
name: String::from("opnsense-1"),
};
let opnsense = Arc::new(
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
);
let lan_subnet = ipv4!("192.168.1.0");
let gateway_ipv4 = ipv4!("192.168.1.1");
let gateway_ip = IpAddr::V4(gateway_ipv4);
harmony::topology::HAClusterTopology {
domain_name: "demo.harmony.mcd".to_string(),
router: Arc::new(UnmanagedRouter::new(
gateway_ip,
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
)),
load_balancer: opnsense.clone(),
firewall: opnsense.clone(),
tftp_server: opnsense.clone(),
http_server: opnsense.clone(),
dhcp_server: opnsense.clone(),
dns_server: opnsense.clone(),
control_plane: vec![LogicalHost {
ip: ip!("10.100.8.20"),
name: "cp0".to_string(),
}],
bootstrap_host: LogicalHost {
ip: ip!("10.100.8.20"),
name: "cp0".to_string(),
},
workers: vec![],
switch: vec![],
}
}
pub fn get_inventory() -> Inventory {
Inventory {
location: Location::new(
"Some virtual machine or maybe a physical machine if you're cool".to_string(),
"testopnsense".to_string(),
),
switch: SwitchGroup::from([]),
firewall: FirewallGroup::from([PhysicalHost::empty(HostCategory::Firewall)
.management(Arc::new(OPNSenseManagementInterface::new()))]),
storage_host: vec![],
worker_host: vec![],
control_plane_host: vec![],
}
}

View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHAAAAJikacCNpGnA
jQAAAAtzc2gtZWQyNTUxOQAAACAcemw8pbwuvHFaYynxBbS0Cf3ThYuj1Utr7CDqjwySHA
AAAECiiKk4V6Q5cVs6axDM4sjAzZn/QCZLQekmYQXS9XbEYxx6bDylvC68cVpjKfEFtLQJ
/dOFi6PVS2vsIOqPDJIcAAAAEGplYW5nYWJAbGlsaWFuZTIBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx6bDylvC68cVpjKfEFtLQJ/dOFi6PVS2vsIOqPDJIc jeangab@liliane2

View File

@@ -0,0 +1,6 @@
#!ipxe
set base-url http://{{ gateway_ip }}:8080
set hostfile ${base-url}/byMAC/01-${mac:hexhyp}.ipxe
chain ${hostfile} || chain ${base-url}/fallback.ipxe

View File

@@ -0,0 +1,40 @@
#!ipxe
# =================================================================
# Harmony Discovery Agent - Default Boot Script (default.ipxe)
# =================================================================
#
# This script boots the CentOS Stream live environment for the
# purpose of hardware inventory. It loads the kernel and initramfs
# directly and passes a Kickstart URL for full automation.
#
# --- Configuration
# Set the base URL for where the CentOS kernel/initrd are hosted.
set os_base_url http://{{gateway_ip}}:8080/os/centos-stream-9
# Set the URL for the Kickstart file.
set ks_url http://{{ gateway_ip }}:8080/{{ kickstart_filename }}
# --- Boot Process
echo "Harmony: Starting automated node discovery..."
echo "Fetching kernel from ${os_base_url}/vmlinuz..."
kernel ${os_base_url}/vmlinuz
echo "Fetching initramfs from ${os_base_url}/initrd.img..."
initrd ${os_base_url}/initrd.img
echo "Configuring kernel boot arguments..."
# Kernel Arguments Explained:
# - initrd=initrd.img: Specifies the initial ramdisk to use.
# - inst.stage2: Points to the OS source. For a live boot, the base URL is sufficient.
# - inst.ks: CRITICAL: Points to our Kickstart file for automation.
# - ip=dhcp: Ensures the live environment configures its network.
# - console=...: Provides boot output on both serial and graphical consoles for debugging.
imgargs vmlinuz initrd=initrd.img inst.sshd inst.stage2=${os_base_url} inst.ks=${ks_url} ip=dhcp console=ttyS0,115200 console=tty1
echo "Booting into CentOS Stream 9 live environment..."
boot || goto failed
:failed
echo "Boot failed. Dropping to iPXE shell."
shell

View File

@@ -0,0 +1,127 @@
# --- Pre-Boot Scripting (The Main Goal) ---
# This section runs after the live environment has booted into RAM.
# It sets up SSH and downloads/runs the harmony-inventory-agent.
%pre --log=/root/ks-pre.log
echo "Harmony Kickstart: Pre-boot script started."
# 1. Configure SSH Access for Root
# Create the .ssh directory and set correct permissions.
echo " - Setting up SSH authorized_keys for root..."
mkdir -p /root/.ssh
chmod 700 /root/.ssh
# Download the public key from the provisioning server.
# The -sS flags make curl silent but show errors. -L follows redirects.
curl -vSL "http://{{ gateway_ip }}:8080/{{ cluster_pubkey_filename }}" -o /root/.ssh/authorized_keys
if [ $? -ne 0 ]; then
echo " - ERROR: Failed to download SSH public key."
else
echo " - SSH key downloaded successfully."
chmod 600 /root/.ssh/authorized_keys
fi
# 2. Download the Harmony Inventory Agent
echo " - Downloading harmony-inventory-agent..."
curl -vSL "http://{{ gateway_ip }}:8080/{{ harmony_inventory_agent }}" -o /usr/bin/harmony-inventory-agent
if [ $? -ne 0 ]; then
echo " - ERROR: Failed to download harmony_inventory_agent."
else
echo " - Agent binary downloaded successfully."
chmod +x /usr/bin/harmony-inventory-agent
fi
# 3. Create a systemd service to run the agent persistently.
# This is the most robust method to ensure the agent stays running.
echo " - Creating systemd service for the agent..."
cat > /etc/systemd/system/harmony-agent.service << EOF
[Unit]
Description=Harmony Inventory Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/harmony-inventory-agent
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# 4. Enable and start the service
# The 'systemctl' commands will work correctly within the chroot environment of the %pre script.
echo " - Enabling and starting harmony-agent.service..."
systemctl daemon-reload
systemctl enable --now harmony-agent.service
# Check if the service started correctly
systemctl is-active --quiet harmony-agent.service
if [ $? -eq 0 ]; then
echo " - Harmony Inventory Agent service is now running."
else
echo " - ERROR: Harmony Inventory Agent service failed to start."
fi
echo "Harmony Kickstart: Pre-boot script finished. The machine is ready for inventory."
echo "Running cat - to pause system indefinitely"
cat -
%end
# =================================================================
# Harmony Discovery Agent - Kickstart File (NON-INSTALL, LIVE BOOT)
# =================================================================
#
# This file achieves a fully automated, non-interactive boot into a
# live CentOS environment. It does NOT install to disk.
#
# --- Automation and Interaction Control ---
# Perform the installation in command-line mode. This is critical for
# preventing Anaconda from starting a UI and halting for input.
cmdline
# Accept the End User License Agreement to prevent a prompt.
eula --agreed
# --- Core System Configuration (Required by Anaconda) ---
# Set keyboard and language. These are mandatory.
keyboard --vckeymap=us --xlayouts='us'
lang en_US.UTF-8
# Configure networking. This is essential for the %post script to work.
# The --activate flag ensures this device is brought up in the installer environment.
network --bootproto=dhcp --device=link --activate
# Set a locked root password. This is a mandatory command.
rootpw --lock
# Set the timezone. This is a mandatory command.
timezone UTC
# --- Disable Installation-Specific Features ---
# CRITICAL: Do not install a bootloader. The --disabled flag prevents
# this step and avoids errors about where to install it.
bootloader --disabled
# CRITICAL: Ignore all disks. This prevents Anaconda from stopping at the
# "Installation Destination" screen asking where to install.
# ignoredisk --drives /dev/sda
# Do not run the Initial Setup wizard on first boot.
firstboot --disable
# --- Package Selection ---
# We are not installing, so this section can be minimal.
# An empty %packages section is valid and ensures no time is wasted
# resolving dependencies for an installation that will not happen.
%packages
%end
# IMPORTANT: Do not include a final action command like 'reboot' or 'poweroff'.
# The default action is 'halt', which in cmdline mode will leave the system
# running in the live environment with the agent active, which is the desired state.

View File

@@ -80,9 +80,12 @@ async fn main() {
let load_balancer_score = OKDLoadBalancerScore::new(&topology);
let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string()));
let http_score = StaticFilesHttpScore::new(Url::LocalFolder(
"./data/watchguard/pxe-http-files".to_string(),
));
let http_score = StaticFilesHttpScore {
folder_to_serve: Some(Url::LocalFolder(
"./data/watchguard/pxe-http-files".to_string(),
)),
files: vec![],
};
harmony_tui::run(
inventory,

View File

@@ -0,0 +1,22 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileContent {
pub path: FilePath,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FilePath {
Relative(String),
Absolute(String),
}
impl std::fmt::Display for FilePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FilePath::Relative(path) => f.write_fmt(format_args!("./{path}")),
FilePath::Absolute(path) => f.write_fmt(format_args!("/{path}")),
}
}
}

View File

@@ -1,4 +1,6 @@
mod id;
mod version;
mod file;
pub use id::*;
pub use version::*;
pub use file::*;

View File

@@ -1,9 +1,12 @@
use async_trait::async_trait;
use harmony_macros::ip;
use harmony_types::net::MacAddress;
use log::debug;
use log::info;
use crate::data::FileContent;
use crate::executors::ExecutorError;
use crate::topology::PxeOptions;
use super::DHCPStaticEntry;
use super::DhcpServer;
@@ -49,9 +52,10 @@ impl Topology for HAClusterTopology {
"HAClusterTopology"
}
async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> {
todo!(
debug!(
"ensure_ready, not entirely sure what it should do here, probably something like verify that the hosts are reachable and all services are up and ready."
)
);
Ok(PreparationOutcome::Noop)
}
}
@@ -153,12 +157,10 @@ impl DhcpServer for HAClusterTopology {
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> {
self.dhcp_server.list_static_mappings().await
}
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> {
self.dhcp_server.set_next_server(ip).await
}
async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> {
self.dhcp_server.set_boot_filename(boot_filename).await
async fn set_pxe_options(&self, options: PxeOptions) -> Result<(), ExecutorError> {
self.dhcp_server.set_pxe_options(options).await
}
fn get_ip(&self) -> IpAddress {
self.dhcp_server.get_ip()
}
@@ -168,16 +170,6 @@ impl DhcpServer for HAClusterTopology {
async fn commit_config(&self) -> Result<(), ExecutorError> {
self.dhcp_server.commit_config().await
}
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> {
self.dhcp_server.set_filename(filename).await
}
async fn set_filename64(&self, filename64: &str) -> Result<(), ExecutorError> {
self.dhcp_server.set_filename64(filename64).await
}
async fn set_filenameipxe(&self, filenameipxe: &str) -> Result<(), ExecutorError> {
self.dhcp_server.set_filenameipxe(filenameipxe).await
}
}
#[async_trait]
@@ -221,17 +213,21 @@ impl HttpServer for HAClusterTopology {
self.http_server.serve_files(url).await
}
async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError> {
self.http_server.serve_file_content(file).await
}
fn get_ip(&self) -> IpAddress {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
self.http_server.get_ip()
}
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
self.http_server.ensure_initialized().await
}
async fn commit_config(&self) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
self.http_server.commit_config().await
}
async fn reload_restart(&self) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
self.http_server.reload_restart().await
}
}
@@ -299,19 +295,7 @@ impl DhcpServer for DummyInfra {
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
async fn set_next_server(&self, _ip: IpAddress) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
async fn set_boot_filename(&self, _boot_filename: &str) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
async fn set_filename64(&self, _filename: &str) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
async fn set_filenameipxe(&self, _filenameipxe: &str) -> Result<(), ExecutorError> {
async fn set_pxe_options(&self, _options: PxeOptions) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
fn get_ip(&self) -> IpAddress {
@@ -381,6 +365,9 @@ impl HttpServer for DummyInfra {
async fn serve_files(&self, _url: &Url) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
async fn serve_file_content(&self, _file: &FileContent) -> Result<(), ExecutorError> {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}
fn get_ip(&self) -> IpAddress {
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
}

View File

@@ -1,4 +1,4 @@
use crate::executors::ExecutorError;
use crate::{data::FileContent, executors::ExecutorError};
use async_trait::async_trait;
use super::{IpAddress, Url};
@@ -6,6 +6,7 @@ use super::{IpAddress, Url};
#[async_trait]
pub trait HttpServer: Send + Sync {
async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError>;
async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError>;
fn get_ip(&self) -> IpAddress;
// async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError>;

View File

@@ -46,16 +46,19 @@ pub trait K8sclient: Send + Sync {
async fn k8s_client(&self) -> Result<Arc<K8sClient>, String>;
}
pub struct PxeOptions {
pub ipxe_filename: String,
pub bios_filename: String,
pub efi_filename: String,
pub tftp_ip: Option<IpAddress>,
}
#[async_trait]
pub trait DhcpServer: Send + Sync + std::fmt::Debug {
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError>;
async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError>;
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>;
async fn set_filename64(&self, filename64: &str) -> Result<(), ExecutorError>;
async fn set_filenameipxe(&self, filenameipxe: &str) -> Result<(), ExecutorError>;
async fn set_pxe_options(&self, pxe_options: PxeOptions) -> Result<(), ExecutorError>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
async fn commit_config(&self) -> Result<(), ExecutorError>;

View File

@@ -1,10 +1,10 @@
use async_trait::async_trait;
use harmony_types::net::MacAddress;
use log::debug;
use log::{debug, info};
use crate::{
executors::ExecutorError,
topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost},
topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost, PxeOptions},
};
use super::OPNSenseFirewall;
@@ -26,7 +26,7 @@ impl DhcpServer for OPNSenseFirewall {
.unwrap();
}
debug!("Registered {:?}", entry);
info!("Registered {:?}", entry);
Ok(())
}
@@ -46,57 +46,25 @@ impl DhcpServer for OPNSenseFirewall {
self.host.clone()
}
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> {
let ipv4 = match ip {
std::net::IpAddr::V4(ipv4_addr) => ipv4_addr,
std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"),
};
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_next_server(ipv4);
debug!("OPNsense dhcp server set next server {ipv4}");
}
Ok(())
}
async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> {
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_boot_filename(boot_filename);
debug!("OPNsense dhcp server set boot filename {boot_filename}");
}
Ok(())
}
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> {
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_filename(filename);
debug!("OPNsense dhcp server set filename {filename}");
}
Ok(())
}
async fn set_filename64(&self, filename: &str) -> Result<(), ExecutorError> {
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_filename64(filename);
debug!("OPNsense dhcp server set filename {filename}");
}
Ok(())
}
async fn set_filenameipxe(&self, filenameipxe: &str) -> Result<(), ExecutorError> {
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_filenameipxe(filenameipxe);
debug!("OPNsense dhcp server set filenameipxe {filenameipxe}");
}
Ok(())
async fn set_pxe_options(&self, options: PxeOptions) -> Result<(), ExecutorError> {
let mut writable_opnsense = self.opnsense_config.write().await;
let PxeOptions {
ipxe_filename,
bios_filename,
efi_filename,
tftp_ip,
} = options;
writable_opnsense
.dhcp()
.set_pxe_options(
tftp_ip.map(|i| i.to_string()),
bios_filename,
efi_filename,
ipxe_filename,
)
.await
.map_err(|dhcp_error| {
ExecutorError::UnexpectedError(format!("Failed to set_pxe_options : {dhcp_error}"))
})
}
}

View File

@@ -2,23 +2,23 @@ use async_trait::async_trait;
use log::info;
use crate::{
data::FileContent,
executors::ExecutorError,
topology::{HttpServer, IpAddress, Url},
};
use super::OPNSenseFirewall;
const OPNSENSE_HTTP_ROOT_PATH: &str = "/usr/local/http";
#[async_trait]
impl HttpServer for OPNSenseFirewall {
async fn serve_files(&self, url: &Url) -> Result<(), ExecutorError> {
let http_root_path = "/usr/local/http";
let config = self.opnsense_config.read().await;
info!("Uploading files from url {url} to {http_root_path}");
info!("Uploading files from url {url} to {OPNSENSE_HTTP_ROOT_PATH}");
match url {
Url::LocalFolder(path) => {
config
.upload_files(path, http_root_path)
.upload_files(path, OPNSENSE_HTTP_ROOT_PATH)
.await
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;
}
@@ -27,8 +27,29 @@ impl HttpServer for OPNSenseFirewall {
Ok(())
}
async fn serve_file_content(&self, file: &FileContent) -> Result<(), ExecutorError> {
let path = match &file.path {
crate::data::FilePath::Relative(path) => {
format!("{OPNSENSE_HTTP_ROOT_PATH}/{}", path.to_string())
}
crate::data::FilePath::Absolute(path) => {
return Err(ExecutorError::ConfigurationError(format!(
"Cannot serve file from http server with absolute path : {path}"
)));
}
};
let config = self.opnsense_config.read().await;
info!("Uploading file content to {}", path);
config
.upload_file_content(&path, &file.content)
.await
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;
Ok(())
}
fn get_ip(&self) -> IpAddress {
todo!();
OPNSenseFirewall::get_ip(self)
}
async fn commit_config(&self) -> Result<(), ExecutorError> {

View File

@@ -28,7 +28,7 @@ impl TftpServer for OPNSenseFirewall {
}
fn get_ip(&self) -> IpAddress {
todo!()
OPNSenseFirewall::get_ip(self)
}
async fn set_ip(&self, ip: IpAddress) -> Result<(), ExecutorError> {

View File

@@ -7,7 +7,7 @@ use crate::{
domain::{data::Version, interpret::InterpretStatus},
interpret::{Interpret, InterpretError, InterpretName, Outcome},
inventory::Inventory,
topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, Topology},
topology::{DHCPStaticEntry, DhcpServer, HostBinding, IpAddress, PxeOptions, Topology},
};
use crate::domain::score::Score;
@@ -98,69 +98,14 @@ impl DhcpInterpret {
_inventory: &Inventory,
dhcp_server: &D,
) -> Result<Outcome, InterpretError> {
let next_server_outcome = match self.score.next_server {
Some(next_server) => {
dhcp_server.set_next_server(next_server).await?;
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set next boot to {next_server}"),
)
}
None => Outcome::noop(),
let pxe_options = PxeOptions {
ipxe_filename: self.score.filenameipxe.clone().unwrap_or_default(),
bios_filename: self.score.filename.clone().unwrap_or_default(),
efi_filename: self.score.filename64.clone().unwrap_or_default(),
tftp_ip: self.score.next_server,
};
let boot_filename_outcome = match &self.score.boot_filename {
Some(boot_filename) => {
dhcp_server.set_boot_filename(boot_filename).await?;
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set boot filename to {boot_filename}"),
)
}
None => Outcome::noop(),
};
let filename_outcome = match &self.score.filename {
Some(filename) => {
dhcp_server.set_filename(filename).await?;
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set filename to {filename}"),
)
}
None => Outcome::noop(),
};
let filename64_outcome = match &self.score.filename64 {
Some(filename64) => {
dhcp_server.set_filename64(filename64).await?;
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set filename64 to {filename64}"),
)
}
None => Outcome::noop(),
};
let filenameipxe_outcome = match &self.score.filenameipxe {
Some(filenameipxe) => {
dhcp_server.set_filenameipxe(filenameipxe).await?;
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set filenameipxe to {filenameipxe}"),
)
}
None => Outcome::noop(),
};
if next_server_outcome.status == InterpretStatus::NOOP
&& boot_filename_outcome.status == InterpretStatus::NOOP
&& filename_outcome.status == InterpretStatus::NOOP
&& filename64_outcome.status == InterpretStatus::NOOP
&& filenameipxe_outcome.status == InterpretStatus::NOOP
{
return Ok(Outcome::noop());
}
dhcp_server.set_pxe_options(pxe_options).await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,

View File

@@ -3,7 +3,7 @@ use derive_new::new;
use serde::Serialize;
use crate::{
data::{Id, Version},
data::{FileContent, Id, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
@@ -23,7 +23,8 @@ use crate::{
/// ```
#[derive(Debug, new, Clone, Serialize)]
pub struct StaticFilesHttpScore {
files_to_serve: Url,
pub folder_to_serve: Option<Url>,
pub files: Vec<FileContent>,
}
impl<T: Topology + HttpServer> Score<T> for StaticFilesHttpScore {
@@ -50,12 +51,20 @@ impl<T: Topology + HttpServer> Interpret<T> for StaticFilesHttpInterpret {
) -> Result<Outcome, InterpretError> {
http_server.ensure_initialized().await?;
// http_server.set_ip(topology.router.get_gateway()).await?;
http_server.serve_files(&self.score.files_to_serve).await?;
if let Some(folder) = self.score.folder_to_serve.as_ref() {
http_server.serve_files(folder).await?;
}
for f in self.score.files.iter() {
http_server.serve_file_content(&f).await?
}
http_server.commit_config().await?;
http_server.reload_restart().await?;
Ok(Outcome::success(format!(
"Http Server running and serving files from {}",
self.score.files_to_serve
"Http Server running and serving files from folder {:?} and content for {}",
self.score.folder_to_serve,
self.score.files.iter().map(|f| f.path.to_string()).collect::<Vec<String>>().join(",")
)))
}

View File

@@ -12,7 +12,7 @@ use crate::{
#[derive(Debug, new, Clone, Serialize)]
pub struct TftpScore {
files_to_serve: Url,
pub files_to_serve: Url,
}
impl<T: Topology + TftpServer + Router> Score<T> for TftpScore {

View File

@@ -0,0 +1,22 @@
## Compiling :
```bash
cargo build -p harmony_inventory_agent --release --target x86_64-unknown-linux-musl
```
This will create a statically linked binary that can run on pretty much any x86_64 system.
This requires installation of the target
```
rustup target add x86_64-unknown-linux-musl
```
And installation of the musl tools too.
On Archlinux, they can be installed with :
```
pacman -S musl
```

View File

@@ -1,4 +1,4 @@
use log::debug;
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs;
@@ -104,48 +104,58 @@ impl PhysicalHost {
fn all_tools_available() -> Result<(), String> {
let required_tools = [
("lsblk", "--version"),
("lspci", "--version"),
("lsmod", "--version"),
("dmidecode", "--version"),
("smartctl", "--version"),
("ip", "route"), // No version flag available
("lsblk", Some("--version")),
("lspci", Some("--version")),
("lsmod", None),
("dmidecode", Some("--version")),
("smartctl", Some("--version")),
("ip", Some("route")), // No version flag available
];
let mut missing_tools = Vec::new();
debug!("Looking for required_tools {required_tools:?}");
for (tool, tool_arg) in required_tools.iter() {
// First check if tool exists in PATH using which(1)
let exists = if let Ok(output) = Command::new("which").arg(tool).output() {
let mut exists = if let Ok(output) = Command::new("which").arg(tool).output() {
output.status.success()
} else {
// Fallback: manual PATH search if which(1) is unavailable
if let Ok(path_var) = std::env::var("PATH") {
path_var.split(':').any(|dir| {
let tool_path = std::path::Path::new(dir).join(tool);
tool_path.exists() && Self::is_executable(&tool_path)
})
} else {
false
}
false
};
if !exists {
// Fallback: manual PATH search if which(1) is unavailable
debug!("Looking for {tool} in path");
if let Ok(path_var) = std::env::var("PATH") {
debug!("PATH is {path_var}");
exists = path_var.split(':').any(|dir| {
let tool_path = std::path::Path::new(dir).join(tool);
tool_path.exists() && Self::is_executable(&tool_path)
})
}
}
if !exists {
warn!("Unable to find tool {tool} from PATH");
missing_tools.push(*tool);
continue;
}
// Verify tool is functional by checking version/help output
let mut cmd = Command::new(tool);
cmd.arg(tool_arg);
if let Some(tool_arg) = tool_arg {
cmd.arg(tool_arg);
}
cmd.stdout(std::process::Stdio::null());
cmd.stderr(std::process::Stdio::null());
if let Ok(status) = cmd.status() {
if !status.success() {
warn!("Unable to test {tool} status failed");
missing_tools.push(*tool);
}
} else {
warn!("Unable to test {tool}");
missing_tools.push(*tool);
}
}
@@ -167,6 +177,7 @@ impl PhysicalHost {
#[cfg(unix)]
fn is_executable(path: &std::path::Path) -> bool {
debug!("Checking if {} is executable", path.to_string_lossy());
use std::os::unix::fs::PermissionsExt;
match std::fs::metadata(path) {
@@ -285,11 +296,11 @@ impl PhysicalHost {
if device_path.exists() {
if drive.model.is_empty() {
drive.model = Self::read_sysfs_string(&device_path.join("device/model"))
.map_err(|e| format!("Failed to read model for {}: {}", name, e))?;
.unwrap_or(format!("Failed to read model for {}", name));
}
if drive.serial.is_empty() {
drive.serial = Self::read_sysfs_string(&device_path.join("device/serial"))
.map_err(|e| format!("Failed to read serial for {}: {}", name, e))?;
.unwrap_or(format!("Failed to read serial for {}", name));
}
}
@@ -655,6 +666,10 @@ impl PhysicalHost {
Ok("IDE".to_string())
} else if device_name.starts_with("vd") {
Ok("VirtIO".to_string())
} else if device_name.starts_with("sr") {
Ok("CDROM".to_string())
} else if device_name.starts_with("zram") {
Ok("Ramdisk".to_string())
} else {
// Try to determine from device path
let subsystem = Self::read_sysfs_string(&device_path.join("device/subsystem"))?;

View File

@@ -30,15 +30,15 @@ pub struct CaddyGeneral {
#[yaserde(rename = "TlsDnsApiKey")]
pub tls_dns_api_key: MaybeString,
#[yaserde(rename = "TlsDnsSecretApiKey")]
pub tls_dns_secret_api_key: MaybeString,
pub tls_dns_secret_api_key: Option<MaybeString>,
#[yaserde(rename = "TlsDnsOptionalField1")]
pub tls_dns_optional_field1: MaybeString,
pub tls_dns_optional_field1: Option<MaybeString>,
#[yaserde(rename = "TlsDnsOptionalField2")]
pub tls_dns_optional_field2: MaybeString,
pub tls_dns_optional_field2: Option<MaybeString>,
#[yaserde(rename = "TlsDnsOptionalField3")]
pub tls_dns_optional_field3: MaybeString,
pub tls_dns_optional_field3: Option<MaybeString>,
#[yaserde(rename = "TlsDnsOptionalField4")]
pub tls_dns_optional_field4: MaybeString,
pub tls_dns_optional_field4: Option<MaybeString>,
#[yaserde(rename = "TlsDnsPropagationTimeout")]
pub tls_dns_propagation_timeout: Option<MaybeString>,
#[yaserde(rename = "TlsDnsPropagationTimeoutPeriod")]
@@ -47,6 +47,8 @@ pub struct CaddyGeneral {
pub tls_dns_propagation_delay: Option<MaybeString>,
#[yaserde(rename = "TlsDnsPropagationResolvers")]
pub tls_dns_propagation_resolvers: MaybeString,
#[yaserde(rename = "TlsDnsEchDomain")]
pub tls_dns_ech_domain: MaybeString,
pub accesslist: MaybeString,
#[yaserde(rename = "DisableSuperuser")]
pub disable_superuser: Option<i32>,
@@ -56,6 +58,10 @@ pub struct CaddyGeneral {
pub http_version: Option<MaybeString>,
#[yaserde(rename = "HttpVersions")]
pub http_versions: Option<MaybeString>,
pub timeout_read_body: Option<MaybeString>,
pub timeout_read_header: Option<MaybeString>,
pub timeout_write: Option<MaybeString>,
pub timeout_idle: Option<MaybeString>,
#[yaserde(rename = "LogCredentials")]
pub log_credentials: MaybeString,
#[yaserde(rename = "LogAccessPlain")]

View File

@@ -0,0 +1,113 @@
use yaserde::{MaybeString, RawXml};
use yaserde_derive::{YaDeserialize, YaSerialize};
// This is the top-level struct that represents the entire <dnsmasq> element.
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct DnsMasq {
#[yaserde(attribute = true)]
pub version: String,
#[yaserde(attribute = true)]
pub persisted_at: Option<String>,
pub enable: u8,
pub regdhcp: u8,
pub regdhcpstatic: u8,
pub dhcpfirst: u8,
pub strict_order: u8,
pub domain_needed: u8,
pub no_private_reverse: u8,
pub no_resolv: Option<u8>,
pub log_queries: u8,
pub no_hosts: u8,
pub strictbind: u8,
pub dnssec: u8,
pub regdhcpdomain: MaybeString,
pub interface: Option<String>,
pub port: Option<u32>,
pub dns_forward_max: MaybeString,
pub cache_size: MaybeString,
pub local_ttl: MaybeString,
pub add_mac: Option<MaybeString>,
pub add_subnet: Option<u8>,
pub strip_subnet: Option<u8>,
pub no_ident: Option<u8>,
pub dhcp: Option<Dhcp>,
pub dhcp_ranges: Vec<DhcpRange>,
pub dhcp_options: Vec<DhcpOptions>,
pub dhcp_boot: Vec<DhcpBoot>,
pub dhcp_tags: Vec<RawXml>,
}
// Represents the <dhcp> element and its nested fields.
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "dhcp")]
pub struct Dhcp {
pub no_interface: MaybeString,
pub fqdn: u8,
pub domain: MaybeString,
pub local: Option<MaybeString>,
pub lease_max: MaybeString,
pub authoritative: u8,
pub default_fw_rules: u8,
pub reply_delay: MaybeString,
pub enable_ra: u8,
pub nosync: u8,
}
// Represents a single <dhcp_ranges> element.
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "dhcp_ranges")]
pub struct DhcpRange {
#[yaserde(attribute = true)]
pub uuid: String,
pub interface: String,
pub set_tag: MaybeString,
pub start_addr: String,
pub end_addr: String,
pub subnet_mask: MaybeString,
pub constructor: MaybeString,
pub mode: MaybeString,
pub prefix_len: MaybeString,
pub lease_time: MaybeString,
pub domain_type: String,
pub domain: MaybeString,
pub nosync: u8,
pub ra_mode: MaybeString,
pub ra_priority: MaybeString,
pub ra_mtu: MaybeString,
pub ra_interval: MaybeString,
pub ra_router_lifetime: MaybeString,
pub description: MaybeString,
}
// Represents a single <dhcp_boot> element.
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "dhcp_boot")]
pub struct DhcpBoot {
#[yaserde(attribute = true)]
pub uuid: String,
pub interface: MaybeString,
pub tag: MaybeString,
pub filename: Option<String>,
pub servername: String,
pub address: String,
pub description: Option<String>,
}
// Represents a single <dhcp_options> element.
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "dhcp_options")]
pub struct DhcpOptions {
#[yaserde(attribute = true)]
pub uuid: String,
#[yaserde(rename = "type")]
pub _type: String,
pub option: MaybeString,
pub option6: MaybeString,
pub interface: MaybeString,
pub tag: MaybeString,
pub set_tag: MaybeString,
pub value: String,
pub force: u8,
pub description: MaybeString,
}

View File

@@ -8,10 +8,12 @@ pub struct Interface {
#[yaserde(rename = "if")]
pub physical_interface_name: String,
pub descr: Option<MaybeString>,
pub mtu: Option<MaybeString>,
pub enable: MaybeString,
pub lock: Option<MaybeString>,
#[yaserde(rename = "spoofmac")]
pub spoof_mac: Option<MaybeString>,
pub mss: Option<MaybeString>,
pub ipaddr: Option<MaybeString>,
pub dhcphostname: Option<MaybeString>,
#[yaserde(rename = "alias-address")]

View File

@@ -3,6 +3,7 @@ mod dhcpd;
mod haproxy;
mod interfaces;
mod opnsense;
pub mod dnsmasq;
pub use caddy::*;
pub use dhcpd::*;
pub use haproxy::*;

View File

@@ -1,3 +1,4 @@
use crate::dnsmasq::DnsMasq;
use crate::HAProxy;
use crate::{data::dhcpd::DhcpInterface, xml_utils::to_xml_str};
use log::error;
@@ -22,7 +23,7 @@ pub struct OPNsense {
pub load_balancer: Option<LoadBalancer>,
pub rrd: Option<RawXml>,
pub ntpd: Ntpd,
pub widgets: Widgets,
pub widgets: Option<Widgets>,
pub revision: Revision,
#[yaserde(rename = "OPNsense")]
pub opnsense: OPNsenseXmlSection,
@@ -45,7 +46,7 @@ pub struct OPNsense {
#[yaserde(rename = "Pischem")]
pub pischem: Option<Pischem>,
pub ifgroups: Ifgroups,
pub dnsmasq: Option<RawXml>,
pub dnsmasq: Option<DnsMasq>,
}
impl From<String> for OPNsense {
@@ -165,9 +166,9 @@ pub struct Sysctl {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct SysctlItem {
pub descr: MaybeString,
pub tunable: String,
pub value: MaybeString,
pub descr: Option<MaybeString>,
pub tunable: Option<String>,
pub value: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@@ -182,8 +183,8 @@ pub struct System {
pub domain: String,
pub group: Vec<Group>,
pub user: Vec<User>,
pub nextuid: u32,
pub nextgid: u32,
pub nextuid: Option<u32>,
pub nextgid: Option<u32>,
pub timezone: String,
pub timeservers: String,
pub webgui: WebGui,
@@ -242,6 +243,7 @@ pub struct Ssh {
pub passwordauth: u8,
pub keysig: MaybeString,
pub permitrootlogin: u8,
pub rekeylimit: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@@ -271,6 +273,7 @@ pub struct Group {
pub member: Vec<u32>,
#[yaserde(rename = "priv")]
pub priv_field: String,
pub source_networks: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@@ -1506,7 +1509,7 @@ pub struct Vlans {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Bridges {
pub bridged: MaybeString,
pub bridged: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]

View File

@@ -20,6 +20,7 @@ russh-sftp = "2.0.6"
serde_json = "1.0.133"
tokio-util = { version = "0.7.13", features = ["codec"] }
tokio-stream = "0.1.17"
uuid.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true

View File

@@ -4,8 +4,7 @@ use crate::{
config::{SshConfigManager, SshCredentials, SshOPNSenseShell},
error::Error,
modules::{
caddy::CaddyConfig, dhcp::DhcpConfig, dns::DnsConfig, load_balancer::LoadBalancerConfig,
tftp::TftpConfig,
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::DnsConfig, dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig
},
};
use log::{debug, info, trace, warn};
@@ -43,8 +42,12 @@ impl Config {
})
}
pub fn dhcp(&mut self) -> DhcpConfig {
DhcpConfig::new(&mut self.opnsense, self.shell.clone())
pub fn dhcp_legacy_isc(&mut self) -> DhcpConfigLegacyISC {
DhcpConfigLegacyISC::new(&mut self.opnsense, self.shell.clone())
}
pub fn dhcp(&mut self) -> DhcpConfigDnsMasq {
DhcpConfigDnsMasq::new(&mut self.opnsense, self.shell.clone())
}
pub fn dns(&mut self) -> DnsConfig {
@@ -67,6 +70,10 @@ impl Config {
self.shell.upload_folder(source, destination).await
}
pub async fn upload_file_content(&self, path: &str, content: &str) -> Result<String, Error> {
self.shell.write_content_to_file(content, path).await
}
/// Checks in config file if system.firmware.plugins csv field contains the specified package
/// name.
///
@@ -200,7 +207,7 @@ impl Config {
#[cfg(test)]
mod tests {
use crate::config::{DummyOPNSenseShell, LocalFileConfigManager};
use crate::modules::dhcp::DhcpConfig;
use crate::modules::dhcp_legacy::DhcpConfigLegacyISC;
use std::fs;
use std::net::Ipv4Addr;
@@ -215,6 +222,9 @@ mod tests {
"src/tests/data/config-vm-test.xml",
"src/tests/data/config-structure.xml",
"src/tests/data/config-full-1.xml",
"src/tests/data/config-full-ncd0.xml",
"src/tests/data/config-full-25.7.xml",
"src/tests/data/config-full-25.7-dummy-dnsmasq-options.xml",
] {
let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_file_path.push(path);
@@ -257,7 +267,7 @@ mod tests {
println!("Config {:?}", config);
let mut dhcp_config = DhcpConfig::new(&mut config.opnsense, shell);
let mut dhcp_config = DhcpConfigLegacyISC::new(&mut config.opnsense, shell);
dhcp_config
.add_static_mapping(
"00:00:00:00:00:00",

View File

@@ -9,6 +9,7 @@ use crate::Error;
pub trait OPNsenseShell: std::fmt::Debug + Send + Sync {
async fn exec(&self, command: &str) -> Result<String, Error>;
async fn write_content_to_temp_file(&self, content: &str) -> Result<String, Error>;
async fn write_content_to_file(&self, content: &str, filename: &str) -> Result<String, Error>;
async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error>;
}
@@ -25,6 +26,14 @@ impl OPNsenseShell for DummyOPNSenseShell {
async fn write_content_to_temp_file(&self, _content: &str) -> Result<String, Error> {
unimplemented!("This is a dummy implementation");
}
async fn write_content_to_file(
&self,
_content: &str,
_filename: &str,
) -> Result<String, Error> {
unimplemented!("This is a dummy implementation");
}
async fn upload_folder(&self, _source: &str, _destination: &str) -> Result<String, Error> {
unimplemented!("This is a dummy implementation");
}

View File

@@ -1,5 +1,6 @@
use std::{
net::IpAddr,
path::Path,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
@@ -44,6 +45,10 @@ impl OPNsenseShell for SshOPNSenseShell {
.unwrap()
.as_millis()
);
self.write_content_to_file(content, &temp_filename).await
}
async fn write_content_to_file(&self, content: &str, filename: &str) -> Result<String, Error> {
let channel = self.get_ssh_channel().await?;
channel
.request_subsystem(true, "sftp")
@@ -53,10 +58,18 @@ impl OPNsenseShell for SshOPNSenseShell {
.await
.expect("Should acquire sftp subsystem");
let mut file = sftp.create(&temp_filename).await.unwrap();
if let Some(parent) = Path::new(filename).parent() {
if let Some(parent_str) = parent.to_str() {
if !parent_str.is_empty() {
self.ensure_remote_dir_exists(&sftp, parent_str).await?;
}
}
}
let mut file = sftp.create(filename).await.unwrap();
file.write_all(content.as_bytes()).await?;
Ok(temp_filename)
Ok(filename.to_string())
}
async fn upload_folder(&self, source: &str, destination: &str) -> Result<String, Error> {
@@ -69,10 +82,7 @@ impl OPNsenseShell for SshOPNSenseShell {
.await
.expect("Should acquire sftp subsystem");
if !sftp.try_exists(destination).await? {
info!("Creating remote directory {destination}");
sftp.create_dir(destination).await?;
}
self.ensure_remote_dir_exists(&sftp, destination).await?;
info!("Reading local directory {source}");
let mut entries = read_dir(source).await?;
@@ -149,6 +159,18 @@ impl SshOPNSenseShell {
wait_for_completion(&mut channel).await
}
async fn ensure_remote_dir_exists(
&self,
sftp: &SftpSession,
path: &str,
) -> Result<(), Error> {
if !sftp.try_exists(path).await? {
info!("Creating remote directory {path}");
sftp.create_dir(path).await?;
}
Ok(())
}
pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc<Config>) -> Self {
info!("Initializing SshOPNSenseShell on host {host:?}");
Self {

View File

@@ -1,19 +1,3 @@
use log::info;
use opnsense_config_xml::MaybeString;
use opnsense_config_xml::StaticMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use opnsense_config_xml::OPNsense;
use crate::config::OPNsenseShell;
use crate::Error;
pub struct DhcpConfig<'a> {
opnsense: &'a mut OPNsense,
opnsense_shell: Arc<dyn OPNsenseShell>,
}
#[derive(Debug)]
pub enum DhcpError {
InvalidMacAddress(String),
@@ -21,6 +5,7 @@ pub enum DhcpError {
IpAddressAlreadyMapped(String),
MacAddressAlreadyMapped(String),
IpAddressOutOfRange(String),
Configuration(String),
}
impl std::fmt::Display for DhcpError {
@@ -37,158 +22,9 @@ impl std::fmt::Display for DhcpError {
DhcpError::IpAddressOutOfRange(ip) => {
write!(f, "IP address {} is out of interface range", ip)
}
DhcpError::Configuration(msg) => f.write_str(&msg),
}
}
}
impl std::error::Error for DhcpError {}
impl<'a> DhcpConfig<'a> {
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
Self {
opnsense,
opnsense_shell,
}
}
pub fn remove_static_mapping(&mut self, mac: &str) {
let lan_dhcpd = self.get_lan_dhcpd();
lan_dhcpd
.staticmaps
.retain(|static_entry| static_entry.mac != mac);
}
fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface {
&mut self
.opnsense
.dhcpd
.elements
.iter_mut()
.find(|(name, _config)| name == "lan")
.expect("Interface lan should have dhcpd activated")
.1
}
pub fn add_static_mapping(
&mut self,
mac: &str,
ipaddr: Ipv4Addr,
hostname: &str,
) -> Result<(), DhcpError> {
let mac = mac.to_string();
let hostname = hostname.to_string();
let lan_dhcpd = self.get_lan_dhcpd();
let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps;
if !Self::is_valid_mac(&mac) {
return Err(DhcpError::InvalidMacAddress(mac));
}
// TODO validate that address is in subnet range
if existing_mappings.iter().any(|m| {
m.ipaddr
.parse::<Ipv4Addr>()
.expect("Mapping contains invalid ipv4")
== ipaddr
&& m.mac == mac
}) {
info!("Mapping already exists for {} [{}], skipping", ipaddr, mac);
return Ok(());
}
if existing_mappings.iter().any(|m| {
m.ipaddr
.parse::<Ipv4Addr>()
.expect("Mapping contains invalid ipv4")
== ipaddr
}) {
return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string()));
}
if existing_mappings.iter().any(|m| m.mac == mac) {
return Err(DhcpError::MacAddressAlreadyMapped(mac));
}
let static_map = StaticMap {
mac,
ipaddr: ipaddr.to_string(),
hostname,
descr: Default::default(),
winsserver: Default::default(),
dnsserver: Default::default(),
ntpserver: Default::default(),
};
existing_mappings.push(static_map);
Ok(())
}
fn is_valid_mac(mac: &str) -> bool {
let parts: Vec<&str> = mac.split(':').collect();
if parts.len() != 6 {
return false;
}
parts
.iter()
.all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit()))
}
pub async fn get_static_mappings(&self) -> Result<Vec<StaticMap>, Error> {
let list_static_output = self
.opnsense_shell
.exec("configctl dhcpd list static")
.await?;
let value: serde_json::Value = serde_json::from_str(&list_static_output)
.unwrap_or_else(|_| panic!("Got invalid json from configctl {list_static_output}"));
let static_maps = value["dhcpd"]
.as_array()
.ok_or(Error::Command(format!(
"Invalid DHCP data from configctl command, got {list_static_output}"
)))?
.iter()
.map(|entry| StaticMap {
mac: entry["mac"].as_str().unwrap_or_default().to_string(),
ipaddr: entry["ipaddr"].as_str().unwrap_or_default().to_string(),
hostname: entry["hostname"].as_str().unwrap_or_default().to_string(),
descr: entry["descr"].as_str().map(MaybeString::from),
winsserver: MaybeString::default(),
dnsserver: MaybeString::default(),
ntpserver: MaybeString::default(),
})
.collect();
Ok(static_maps)
}
pub fn enable_netboot(&mut self) {
self.get_lan_dhcpd().netboot = Some(1);
}
pub fn set_next_server(&mut self, ip: Ipv4Addr) {
self.enable_netboot();
self.get_lan_dhcpd().nextserver = Some(ip.to_string());
self.get_lan_dhcpd().tftp = Some(ip.to_string());
}
pub fn set_boot_filename(&mut self, boot_filename: &str) {
self.enable_netboot();
self.get_lan_dhcpd().bootfilename = Some(boot_filename.to_string());
}
pub fn set_filename(&mut self, filename: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filename = Some(filename.to_string());
}
pub fn set_filename64(&mut self, filename64: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filename64 = Some(filename64.to_string());
}
pub fn set_filenameipxe(&mut self, filenameipxe: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filenameipxe = Some(filenameipxe.to_string());
}
}

View File

@@ -0,0 +1,166 @@
use crate::modules::dhcp::DhcpError;
use log::info;
use opnsense_config_xml::MaybeString;
use opnsense_config_xml::StaticMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use opnsense_config_xml::OPNsense;
use crate::config::OPNsenseShell;
use crate::Error;
pub struct DhcpConfigLegacyISC<'a> {
opnsense: &'a mut OPNsense,
opnsense_shell: Arc<dyn OPNsenseShell>,
}
impl<'a> DhcpConfigLegacyISC<'a> {
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
Self {
opnsense,
opnsense_shell,
}
}
pub fn remove_static_mapping(&mut self, mac: &str) {
let lan_dhcpd = self.get_lan_dhcpd();
lan_dhcpd
.staticmaps
.retain(|static_entry| static_entry.mac != mac);
}
fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface {
&mut self
.opnsense
.dhcpd
.elements
.iter_mut()
.find(|(name, _config)| name == "lan")
.expect("Interface lan should have dhcpd activated")
.1
}
pub fn add_static_mapping(
&mut self,
mac: &str,
ipaddr: Ipv4Addr,
hostname: &str,
) -> Result<(), DhcpError> {
let mac = mac.to_string();
let hostname = hostname.to_string();
let lan_dhcpd = self.get_lan_dhcpd();
let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps;
if !Self::is_valid_mac(&mac) {
return Err(DhcpError::InvalidMacAddress(mac));
}
// TODO validate that address is in subnet range
if existing_mappings.iter().any(|m| {
m.ipaddr
.parse::<Ipv4Addr>()
.expect("Mapping contains invalid ipv4")
== ipaddr
&& m.mac == mac
}) {
info!("Mapping already exists for {} [{}], skipping", ipaddr, mac);
return Ok(());
}
if existing_mappings.iter().any(|m| {
m.ipaddr
.parse::<Ipv4Addr>()
.expect("Mapping contains invalid ipv4")
== ipaddr
}) {
return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string()));
}
if existing_mappings.iter().any(|m| m.mac == mac) {
return Err(DhcpError::MacAddressAlreadyMapped(mac));
}
let static_map = StaticMap {
mac,
ipaddr: ipaddr.to_string(),
hostname,
descr: Default::default(),
winsserver: Default::default(),
dnsserver: Default::default(),
ntpserver: Default::default(),
};
existing_mappings.push(static_map);
Ok(())
}
fn is_valid_mac(mac: &str) -> bool {
let parts: Vec<&str> = mac.split(':').collect();
if parts.len() != 6 {
return false;
}
parts
.iter()
.all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit()))
}
pub async fn get_static_mappings(&self) -> Result<Vec<StaticMap>, Error> {
let list_static_output = self
.opnsense_shell
.exec("configctl dhcpd list static")
.await?;
let value: serde_json::Value = serde_json::from_str(&list_static_output)
.unwrap_or_else(|_| panic!("Got invalid json from configctl {list_static_output}"));
let static_maps = value["dhcpd"]
.as_array()
.ok_or(Error::Command(format!(
"Invalid DHCP data from configctl command, got {list_static_output}"
)))?
.iter()
.map(|entry| StaticMap {
mac: entry["mac"].as_str().unwrap_or_default().to_string(),
ipaddr: entry["ipaddr"].as_str().unwrap_or_default().to_string(),
hostname: entry["hostname"].as_str().unwrap_or_default().to_string(),
descr: entry["descr"].as_str().map(MaybeString::from),
winsserver: MaybeString::default(),
dnsserver: MaybeString::default(),
ntpserver: MaybeString::default(),
})
.collect();
Ok(static_maps)
}
pub fn enable_netboot(&mut self) {
self.get_lan_dhcpd().netboot = Some(1);
}
pub fn set_next_server(&mut self, ip: Ipv4Addr) {
self.enable_netboot();
self.get_lan_dhcpd().nextserver = Some(ip.to_string());
self.get_lan_dhcpd().tftp = Some(ip.to_string());
}
pub fn set_boot_filename(&mut self, boot_filename: &str) {
self.enable_netboot();
self.get_lan_dhcpd().bootfilename = Some(boot_filename.to_string());
}
pub fn set_filename(&mut self, filename: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filename = Some(filename.to_string());
}
pub fn set_filename64(&mut self, filename64: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filename64 = Some(filename64.to_string());
}
pub fn set_filenameipxe(&mut self, filenameipxe: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filenameipxe = Some(filenameipxe.to_string());
}
}

View File

@@ -0,0 +1,198 @@
// dnsmasq.rs
use crate::modules::dhcp::DhcpError;
use log::{debug, info};
use opnsense_config_xml::dnsmasq::{DhcpBoot, DhcpOptions, DnsMasq};
use opnsense_config_xml::{MaybeString, StaticMap};
use std::net::Ipv4Addr;
use std::sync::Arc;
use uuid::Uuid;
use opnsense_config_xml::OPNsense;
use crate::config::OPNsenseShell;
use crate::Error;
pub struct DhcpConfigDnsMasq<'a> {
opnsense: &'a mut OPNsense,
opnsense_shell: Arc<dyn OPNsenseShell>,
}
const DNS_MASQ_PXE_CONFIG_FILE: &str = "/usr/local/etc/dnsmasq.conf.d/pxe.conf";
impl<'a> DhcpConfigDnsMasq<'a> {
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
Self {
opnsense,
opnsense_shell,
}
}
/// Removes a static mapping by its MAC address.
/// Static mappings are stored in the <dhcpd> section of the config, shared with the ISC module.
pub fn remove_static_mapping(&mut self, mac: &str) {
let lan_dhcpd = self.get_lan_dhcpd();
lan_dhcpd
.staticmaps
.retain(|static_entry| static_entry.mac != mac);
}
/// Retrieves a mutable reference to the LAN interface's DHCP configuration.
/// This is located in the shared <dhcpd> section of the config.
fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface {
&mut self
.opnsense
.dhcpd
.elements
.iter_mut()
.find(|(name, _config)| name == "lan")
.expect("Interface lan should have dhcpd activated")
.1
}
fn dnsmasq(&mut self) -> &mut DnsMasq {
self.opnsense
.dnsmasq
.as_mut()
.expect("Dnsmasq config should exist. Maybe it is not installed yet")
}
/// Adds a new static DHCP mapping.
/// Validates the MAC address and checks for existing mappings to prevent conflicts.
pub fn add_static_mapping(
&mut self,
mac: &str,
ipaddr: Ipv4Addr,
hostname: &str,
) -> Result<(), DhcpError> {
let mac = mac.to_string();
let hostname = hostname.to_string();
let lan_dhcpd = self.get_lan_dhcpd();
let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps;
if !Self::is_valid_mac(&mac) {
return Err(DhcpError::InvalidMacAddress(mac));
}
// TODO: Validate that the IP address is within a configured DHCP range.
if existing_mappings
.iter()
.any(|m| m.ipaddr == ipaddr.to_string() && m.mac == mac)
{
info!("Mapping already exists for {} [{}], skipping", ipaddr, mac);
return Ok(());
}
if existing_mappings
.iter()
.any(|m| m.ipaddr == ipaddr.to_string())
{
return Err(DhcpError::IpAddressAlreadyMapped(ipaddr.to_string()));
}
if existing_mappings.iter().any(|m| m.mac == mac) {
return Err(DhcpError::MacAddressAlreadyMapped(mac));
}
let static_map = StaticMap {
mac,
ipaddr: ipaddr.to_string(),
hostname: hostname,
..Default::default()
};
existing_mappings.push(static_map);
Ok(())
}
/// Helper function to validate a MAC address format.
fn is_valid_mac(mac: &str) -> bool {
let parts: Vec<&str> = mac.split(':').collect();
if parts.len() != 6 {
return false;
}
parts
.iter()
.all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit()))
}
/// Retrieves the list of current static mappings by shelling out to `configctl`.
/// This provides the real-time state from the running system.
pub async fn get_static_mappings(&self) -> Result<Vec<StaticMap>, Error> {
let list_static_output = self
.opnsense_shell
.exec("configctl dhcpd list static")
.await?;
let value: serde_json::Value = serde_json::from_str(&list_static_output)
.unwrap_or_else(|_| panic!("Got invalid json from configctl {list_static_output}"));
let static_maps = value["dhcpd"]
.as_array()
.ok_or(Error::Command(format!(
"Invalid DHCP data from configctl command, got {list_static_output}"
)))?
.iter()
.map(|entry| StaticMap {
mac: entry["mac"].as_str().unwrap_or_default().to_string(),
ipaddr: entry["ipaddr"].as_str().unwrap_or_default().to_string(),
hostname: entry["hostname"].as_str().unwrap_or_default().to_string(),
descr: entry["descr"].as_str().map(MaybeString::from),
..Default::default()
})
.collect();
Ok(static_maps)
}
pub async fn set_pxe_options(
&self,
tftp_ip: Option<String>,
bios_filename: String,
efi_filename: String,
ipxe_filename: String,
) -> Result<(), DhcpError> {
// As of writing this opnsense does not support negative tags, and the dnsmasq config is a
// bit complicated anyways. So we are writing directly a dnsmasq config file to
// /usr/local/etc/dnsmasq.conf.d
let tftp_str = tftp_ip.map_or(String::new(), |i| format!(",{i},{i}"));
let config = format!(
"
# Add tag ipxe to dhcp requests with user class (77) = iPXE
dhcp-match=set:ipxe,77,iPXE
# Add tag bios to dhcp requests with arch (93) = 0
dhcp-match=set:bios,93,0
# Add tag efi to dhcp requests with arch (93) = 7
dhcp-match=set:efi,93,7
# Provide ipxe efi file to uefi but NOT ipxe clients
dhcp-boot=tag:efi,tag:!ipxe,{efi_filename}{tftp_str}
# Provide ipxe boot script to ipxe clients
dhcp-boot=tag:ipxe,{ipxe_filename}{tftp_str}
# Provide undionly to legacy bios clients
dhcp-boot=tag:bios,{bios_filename}{tftp_str}
"
);
info!("Writing configuration file to {DNS_MASQ_PXE_CONFIG_FILE}");
debug!("Content:\n{config}");
self.opnsense_shell
.write_content_to_file(&config, DNS_MASQ_PXE_CONFIG_FILE)
.await
.map_err(|e| {
DhcpError::Configuration(format!(
"Could not configure pxe for dhcp because of : {e}"
))
})?;
info!("Restarting dnsmasq to apply changes");
self.opnsense_shell.exec("configctl dnsmasq restart").await
.map_err(|e| {
DhcpError::Configuration(format!(
"Restarting dnsmasq failed : {e}"
))
})?;
Ok(())
}
}

View File

@@ -1,5 +1,7 @@
pub mod caddy;
pub mod dhcp;
pub mod dhcp_legacy;
pub mod dns;
pub mod load_balancer;
pub mod tftp;
pub mod dhcp;
pub mod dnsmasq;

View File

@@ -0,0 +1,896 @@
<?xml version="1.0"?>
<opnsense>
<theme>opnsense</theme>
<sysctl>
<item/>
</sysctl>
<system>
<serialspeed>115200</serialspeed>
<primaryconsole>serial</primaryconsole>
<optimization>normal</optimization>
<hostname>OPNsense</hostname>
<domain>testpxe.harmony.mcd</domain>
<group>
<name>admins</name>
<description>System Administrators</description>
<scope>system</scope>
<gid>1999</gid>
<member>0</member>
<priv>page-all</priv>
<source_networks/>
</group>
<user>
<name>root</name>
<descr>System Administrator</descr>
<scope>system</scope>
<password>$2y$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS</password>
<pwd_changed_at/>
<uid>0</uid>
<disabled>0</disabled>
<landing_page/>
<comment/>
<email/>
<apikeys/>
<priv/>
<language/>
<expires/>
<authorizedkeys/>
<dashboard/>
<otp_seed/>
<shell/>
</user>
<timezone>Etc/UTC</timezone>
<timeservers>0.opnsense.pool.ntp.org 1.opnsense.pool.ntp.org 2.opnsense.pool.ntp.org 3.opnsense.pool.ntp.org</timeservers>
<webgui>
<protocol>https</protocol>
<ssl-certref>68a72b6f7f776</ssl-certref>
<port/>
<ssl-ciphers/>
<interfaces/>
<compression/>
</webgui>
<usevirtualterminal>1</usevirtualterminal>
<disablenatreflection>yes</disablenatreflection>
<disableconsolemenu>1</disableconsolemenu>
<disablevlanhwfilter>1</disablevlanhwfilter>
<disablechecksumoffloading>1</disablechecksumoffloading>
<disablesegmentationoffloading>1</disablesegmentationoffloading>
<disablelargereceiveoffloading>1</disablelargereceiveoffloading>
<ipv6allow>1</ipv6allow>
<powerd_ac_mode>hadp</powerd_ac_mode>
<powerd_battery_mode>hadp</powerd_battery_mode>
<powerd_normal_mode>hadp</powerd_normal_mode>
<bogons>
<interval>monthly</interval>
</bogons>
<pf_share_forward>1</pf_share_forward>
<lb_use_sticky>1</lb_use_sticky>
<ssh>
<group>admins</group>
<noauto>1</noauto>
<interfaces/>
<kex/>
<ciphers/>
<macs/>
<keys/>
<enabled>enabled</enabled>
<passwordauth>1</passwordauth>
<keysig/>
<permitrootlogin>1</permitrootlogin>
<rekeylimit/>
</ssh>
<rrdbackup>-1</rrdbackup>
<netflowbackup>-1</netflowbackup>
<firmware version="1.0.1">
<mirror/>
<flavour/>
<plugins>os-tftp</plugins>
<type/>
<subscription/>
<reboot>0</reboot>
</firmware>
<language>en_US</language>
<dnsserver/>
<dnsallowoverride>1</dnsallowoverride>
<dnsallowoverride_exclude/>
</system>
<interfaces>
<wan>
<if>vtnet0</if>
<mtu/>
<enable>1</enable>
<spoofmac/>
<mss/>
<ipaddr>dhcp</ipaddr>
<dhcphostname/>
<blockpriv>0</blockpriv>
<blockbogons>1</blockbogons>
<subnet/>
<ipaddrv6>dhcp6</ipaddrv6>
<dhcp6-ia-pd-len>0</dhcp6-ia-pd-len>
<gateway/>
<media/>
<mediaopt/>
</wan>
<lan>
<if>vtnet1</if>
<enable>1</enable>
<ipaddr>192.168.1.1</ipaddr>
<subnet>24</subnet>
<ipaddrv6>track6</ipaddrv6>
<subnetv6>64</subnetv6>
<media/>
<mediaopt/>
<track6-interface>wan</track6-interface>
<track6-prefix-id>0</track6-prefix-id>
</lan>
<lo0>
<internal_dynamic>1</internal_dynamic>
<if>lo0</if>
<descr>Loopback</descr>
<enable>1</enable>
<ipaddr>127.0.0.1</ipaddr>
<type>none</type>
<virtual>1</virtual>
<subnet>8</subnet>
<ipaddrv6>::1</ipaddrv6>
<subnetv6>128</subnetv6>
</lo0>
</interfaces>
<dhcpd/>
<snmpd>
<syslocation/>
<syscontact/>
<rocommunity>public</rocommunity>
</snmpd>
<syslog/>
<nat>
<outbound>
<mode>automatic</mode>
</outbound>
</nat>
<filter>
<rule>
<type>pass</type>
<interface>lan</interface>
<ipprotocol>inet</ipprotocol>
<descr>Default allow LAN to any rule</descr>
<source>
<network>lan</network>
</source>
<destination>
<any/>
</destination>
</rule>
<rule>
<type>pass</type>
<interface>lan</interface>
<ipprotocol>inet6</ipprotocol>
<descr>Default allow LAN IPv6 to any rule</descr>
<source>
<network>lan</network>
</source>
<destination>
<any/>
</destination>
</rule>
</filter>
<rrd>
<enable/>
</rrd>
<ntpd>
<prefer>0.opnsense.pool.ntp.org</prefer>
</ntpd>
<revision>
<username>root@192.168.1.5</username>
<description>/api/dnsmasq/settings/set made changes</description>
<time>1755800176.40</time>
</revision>
<OPNsense>
<captiveportal version="1.0.4">
<zones/>
<templates/>
</captiveportal>
<cron version="1.0.4">
<jobs/>
</cron>
<Netflow version="1.0.1">
<capture>
<interfaces/>
<egress_only/>
<version>v9</version>
<targets/>
</capture>
<collect>
<enable>0</enable>
</collect>
<activeTimeout>1800</activeTimeout>
<inactiveTimeout>15</inactiveTimeout>
</Netflow>
<Firewall>
<Lvtemplate version="0.0.1">
<templates/>
</Lvtemplate>
<Category version="1.0.0">
<categories/>
</Category>
<Filter version="1.0.4">
<rules/>
<snatrules/>
<npt/>
<onetoone/>
</Filter>
<Alias version="1.0.1">
<geoip>
<url/>
</geoip>
<aliases/>
</Alias>
</Firewall>
<IDS version="1.1.0">
<rules/>
<policies/>
<userDefinedRules/>
<files/>
<fileTags/>
<general>
<enabled>0</enabled>
<ips>0</ips>
<promisc>0</promisc>
<interfaces>wan</interfaces>
<homenet>192.168.0.0/16,10.0.0.0/8,172.16.0.0/12</homenet>
<defaultPacketSize/>
<UpdateCron/>
<AlertLogrotate>W0D23</AlertLogrotate>
<AlertSaveLogs>4</AlertSaveLogs>
<MPMAlgo/>
<detect>
<Profile/>
<toclient_groups/>
<toserver_groups/>
</detect>
<syslog>0</syslog>
<syslog_eve>0</syslog_eve>
<LogPayload>0</LogPayload>
<verbosity/>
<eveLog>
<http>
<enable>0</enable>
<extended>0</extended>
<dumpAllHeaders/>
</http>
<tls>
<enable>0</enable>
<extended>0</extended>
<sessionResumption>0</sessionResumption>
<custom/>
</tls>
</eveLog>
</general>
</IDS>
<IPsec version="1.0.4">
<general>
<enabled/>
<preferred_oldsa>0</preferred_oldsa>
<disablevpnrules>0</disablevpnrules>
<passthrough_networks/>
<user_source/>
<local_group/>
</general>
<keyPairs/>
<preSharedKeys/>
<charon>
<max_ikev1_exchanges/>
<threads>16</threads>
<ikesa_table_size>32</ikesa_table_size>
<ikesa_table_segments>4</ikesa_table_segments>
<init_limit_half_open>1000</init_limit_half_open>
<ignore_acquire_ts>1</ignore_acquire_ts>
<install_routes>0</install_routes>
<cisco_unity>0</cisco_unity>
<make_before_break>0</make_before_break>
<retransmit_tries/>
<retransmit_timeout/>
<retransmit_base/>
<retransmit_jitter/>
<retransmit_limit/>
<syslog>
<daemon>
<ike_name>1</ike_name>
<log_level>0</log_level>
<app>1</app>
<asn>1</asn>
<cfg>1</cfg>
<chd>1</chd>
<dmn>1</dmn>
<enc>1</enc>
<esp>1</esp>
<ike>1</ike>
<imc>1</imc>
<imv>1</imv>
<job>1</job>
<knl>1</knl>
<lib>1</lib>
<mgr>1</mgr>
<net>1</net>
<pts>1</pts>
<tls>1</tls>
<tnc>1</tnc>
</daemon>
</syslog>
<plugins>
<attr>
<subnet/>
<split-include/>
<x_28674/>
<x_28675/>
<x_28672/>
<x_28673>0</x_28673>
<x_28679/>
<dns/>
<nbns/>
</attr>
<eap-radius>
<servers/>
<accounting>0</accounting>
<class_group>0</class_group>
</eap-radius>
<xauth-pam>
<pam_service>ipsec</pam_service>
<session>0</session>
<trim_email>1</trim_email>
</xauth-pam>
</plugins>
</charon>
</IPsec>
<Interfaces>
<vxlans version="1.0.2"/>
<loopbacks version="1.0.0"/>
<neighbors version="1.0.0"/>
</Interfaces>
<Kea>
<dhcp4 version="1.0.4" persisted_at="1755786069.95">
<general>
<enabled>0</enabled>
<manual_config>0</manual_config>
<interfaces/>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
<dhcp_socket_type>raw</dhcp_socket_type>
</general>
<ha>
<enabled>0</enabled>
<this_server_name/>
<max_unacked_clients>2</max_unacked_clients>
</ha>
<subnets/>
<reservations/>
<ha_peers/>
</dhcp4>
<ctrl_agent version="0.0.1" persisted_at="1755786069.95">
<general>
<enabled>0</enabled>
<http_host>127.0.0.1</http_host>
<http_port>8000</http_port>
</general>
</ctrl_agent>
<dhcp6 version="1.0.0" persisted_at="1755786069.95">
<general>
<enabled>0</enabled>
<manual_config>0</manual_config>
<interfaces/>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
</general>
<ha>
<enabled>0</enabled>
<this_server_name/>
<max_unacked_clients>2</max_unacked_clients>
</ha>
<subnets/>
<reservations/>
<pd_pools/>
<ha_peers/>
</dhcp6>
</Kea>
<monit version="1.0.13">
<general>
<enabled>0</enabled>
<interval>120</interval>
<startdelay>120</startdelay>
<mailserver>127.0.0.1</mailserver>
<port>25</port>
<username/>
<password/>
<ssl>0</ssl>
<sslversion>auto</sslversion>
<sslverify>1</sslverify>
<logfile/>
<statefile/>
<eventqueuePath/>
<eventqueueSlots/>
<httpdEnabled>0</httpdEnabled>
<httpdUsername>root</httpdUsername>
<httpdPassword/>
<httpdPort>2812</httpdPort>
<httpdAllow/>
<mmonitUrl/>
<mmonitTimeout>5</mmonitTimeout>
<mmonitRegisterCredentials>1</mmonitRegisterCredentials>
</general>
<alert uuid="ce8ca7d9-66ab-41d5-acea-598f4803e8ba">
<enabled>0</enabled>
<recipient>root@localhost.local</recipient>
<noton>0</noton>
<events/>
<format/>
<reminder/>
<description/>
</alert>
<service uuid="dc3b9298-4a56-4c45-bd61-be2fdb103383">
<enabled>1</enabled>
<name>$HOST</name>
<description/>
<type>system</type>
<pidfile/>
<match/>
<path/>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>cfed35dc-f74b-417d-9ed9-682c5de96495,f961277a-07f1-49a4-90ee-bb15738d9ebb,30b2cce2-f650-4e44-a3e2-ee53886cda3f,3c86136f-35a4-4126-865b-82732c6542d9</tests>
<depends/>
<polltime/>
</service>
<service uuid="b4d5bdb4-206d-4afe-8d86-377ffbbdb2ec">
<enabled>1</enabled>
<name>RootFs</name>
<description/>
<type>filesystem</type>
<pidfile/>
<match/>
<path>/</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>fbb8dfe2-b9ad-4730-a0f3-41d7ecda6289</tests>
<depends/>
<polltime/>
</service>
<service uuid="f96e3cbb-6c98-4d20-8337-bab717d4ab54">
<enabled>0</enabled>
<name>carp_status_change</name>
<description/>
<type>custom</type>
<pidfile/>
<match/>
<path>/usr/local/opnsense/scripts/OPNsense/Monit/carp_status</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>11ceca8a-dff8-45e0-9dc5-ed80dc4b3947</tests>
<depends/>
<polltime/>
</service>
<service uuid="69bbd4d5-3a50-42a7-ab64-050450504038">
<enabled>0</enabled>
<name>gateway_alert</name>
<description/>
<type>custom</type>
<pidfile/>
<match/>
<path>/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>fad1f465-4a92-4b93-be66-59d7059b8779</tests>
<depends/>
<polltime/>
</service>
<test uuid="2bd5d8c0-6a4a-430b-b953-34214a107ccf">
<name>Ping</name>
<type>NetworkPing</type>
<condition>failed ping</condition>
<action>alert</action>
<path/>
</test>
<test uuid="0f06ffff-9bfa-463d-b75e-f7195cd8dcab">
<name>NetworkLink</name>
<type>NetworkInterface</type>
<condition>failed link</condition>
<action>alert</action>
<path/>
</test>
<test uuid="79b119ce-10e0-4a6a-bd1a-b0be371d0fd7">
<name>NetworkSaturation</name>
<type>NetworkInterface</type>
<condition>saturation is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="cfed35dc-f74b-417d-9ed9-682c5de96495">
<name>MemoryUsage</name>
<type>SystemResource</type>
<condition>memory usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="f961277a-07f1-49a4-90ee-bb15738d9ebb">
<name>CPUUsage</name>
<type>SystemResource</type>
<condition>cpu usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="30b2cce2-f650-4e44-a3e2-ee53886cda3f">
<name>LoadAvg1</name>
<type>SystemResource</type>
<condition>loadavg (1min) is greater than 4</condition>
<action>alert</action>
<path/>
</test>
<test uuid="3c86136f-35a4-4126-865b-82732c6542d9">
<name>LoadAvg5</name>
<type>SystemResource</type>
<condition>loadavg (5min) is greater than 3</condition>
<action>alert</action>
<path/>
</test>
<test uuid="27e91c6f-3e8e-4570-bb3a-27f46dd301a7">
<name>LoadAvg15</name>
<type>SystemResource</type>
<condition>loadavg (15min) is greater than 2</condition>
<action>alert</action>
<path/>
</test>
<test uuid="fbb8dfe2-b9ad-4730-a0f3-41d7ecda6289">
<name>SpaceUsage</name>
<type>SpaceUsage</type>
<condition>space usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="11ceca8a-dff8-45e0-9dc5-ed80dc4b3947">
<name>ChangedStatus</name>
<type>ProgramStatus</type>
<condition>changed status</condition>
<action>alert</action>
<path/>
</test>
<test uuid="fad1f465-4a92-4b93-be66-59d7059b8779">
<name>NonZeroStatus</name>
<type>ProgramStatus</type>
<condition>status != 0</condition>
<action>alert</action>
<path/>
</test>
</monit>
<OpenVPNExport version="0.0.1">
<servers/>
</OpenVPNExport>
<Syslog version="1.0.2">
<general>
<enabled>1</enabled>
<loglocal>1</loglocal>
<maxpreserve>31</maxpreserve>
<maxfilesize/>
</general>
<destinations/>
</Syslog>
<TrafficShaper version="1.0.3" persisted_at="1755786069.77">
<pipes/>
<queues/>
<rules/>
</TrafficShaper>
<unboundplus version="1.0.12">
<general>
<enabled>1</enabled>
<port>53</port>
<stats>0</stats>
<active_interface/>
<dnssec>0</dnssec>
<dns64>0</dns64>
<dns64prefix/>
<noarecords>0</noarecords>
<regdhcp>0</regdhcp>
<regdhcpdomain/>
<regdhcpstatic>0</regdhcpstatic>
<noreglladdr6>0</noreglladdr6>
<noregrecords>0</noregrecords>
<txtsupport>0</txtsupport>
<cacheflush>0</cacheflush>
<local_zone_type>transparent</local_zone_type>
<outgoing_interface/>
<enable_wpad>0</enable_wpad>
</general>
<advanced>
<hideidentity>0</hideidentity>
<hideversion>0</hideversion>
<prefetch>0</prefetch>
<prefetchkey>0</prefetchkey>
<dnssecstripped>0</dnssecstripped>
<aggressivensec>1</aggressivensec>
<serveexpired>0</serveexpired>
<serveexpiredreplyttl/>
<serveexpiredttl/>
<serveexpiredttlreset>0</serveexpiredttlreset>
<serveexpiredclienttimeout/>
<qnameminstrict>0</qnameminstrict>
<extendedstatistics>0</extendedstatistics>
<logqueries>0</logqueries>
<logreplies>0</logreplies>
<logtagqueryreply>0</logtagqueryreply>
<logservfail>0</logservfail>
<loglocalactions>0</loglocalactions>
<logverbosity>1</logverbosity>
<valloglevel>0</valloglevel>
<privatedomain/>
<privateaddress>0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,169.254.0.0/16,172.16.0.0/12,192.0.2.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,233.252.0.0/24,::1/128,2001:db8::/32,fc00::/8,fd00::/8,fe80::/10</privateaddress>
<insecuredomain/>
<msgcachesize/>
<rrsetcachesize/>
<outgoingnumtcp/>
<incomingnumtcp/>
<numqueriesperthread/>
<outgoingrange/>
<jostletimeout/>
<discardtimeout/>
<cachemaxttl/>
<cachemaxnegativettl/>
<cacheminttl/>
<infrahostttl/>
<infrakeepprobing>0</infrakeepprobing>
<infracachenumhosts/>
<unwantedreplythreshold/>
</advanced>
<acls>
<default_action>allow</default_action>
</acls>
<dnsbl>
<enabled>0</enabled>
<safesearch>0</safesearch>
<type/>
<lists/>
<whitelists/>
<blocklists/>
<wildcards/>
<address/>
<nxdomain>0</nxdomain>
</dnsbl>
<forwarding>
<enabled>0</enabled>
</forwarding>
<dots/>
<hosts/>
<aliases/>
</unboundplus>
<DHCRelay version="1.0.1" persisted_at="1755786069.97"/>
<trust>
<general version="1.0.1" persisted_at="1755786070.08">
<store_intermediate_certs>0</store_intermediate_certs>
<install_crls>0</install_crls>
<fetch_crls>0</fetch_crls>
<enable_legacy_sect>1</enable_legacy_sect>
<enable_config_constraints>0</enable_config_constraints>
<CipherString/>
<Ciphersuites/>
<SignatureAlgorithms/>
<groups/>
<MinProtocol/>
<MinProtocol_DTLS/>
</general>
</trust>
<tftp>
<general version="0.0.1">
<enabled>1</enabled>
<listen>192.168.1.1</listen>
</general>
</tftp>
<wireguard>
<general version="0.0.1">
<enabled>0</enabled>
</general>
<server version="1.0.0">
<servers/>
</server>
<client version="1.0.0">
<clients/>
</client>
</wireguard>
<Swanctl version="1.0.0">
<Connections/>
<locals/>
<remotes/>
<children/>
<Pools/>
<VTIs/>
<SPDs/>
</Swanctl>
<OpenVPN version="1.0.1">
<Overwrites/>
<Instances/>
<StaticKeys/>
</OpenVPN>
<Gateways version="1.0.0" persisted_at="1755786217.76"/>
</OPNsense>
<staticroutes version="1.0.0"/>
<ca/>
<cert>
<refid>68a72b6f7f776</refid>
<descr>Web GUI TLS certificate</descr>
<crt>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQlBxZ0F3SUJBZ0lVUkZqWUQ0Z1U0bzRNZGdiN2pIc29KNU9GVGFnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhHakFZQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVgpNQk1HQTFVRUNBd01XblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyCkJnTlZCQW9NSkU5UVRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWlRBZUZ3MHkKTlRBNE1qRXhOREl4TXpaYUZ3MHlOakE1TWpJeE5ESXhNelphTUlHR01Sb3dHQVlEVlFRRERCRlBVRTV6Wlc1egpaUzVwYm5SbGNtNWhiREVMTUFrR0ExVUVCaE1DVGt3eEZUQVRCZ05WQkFnTURGcDFhV1F0U0c5c2JHRnVaREVWCk1CTUdBMVVFQnd3TVRXbGtaR1ZzYUdGeWJtbHpNUzB3S3dZRFZRUUtEQ1JQVUU1elpXNXpaU0J6Wld4bUxYTnAKWjI1bFpDQjNaV0lnWTJWeWRHbG1hV05oZEdVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFDbENkeFJ3ZWJQQkxvYlVORnYvL2t3TEdKWExweDl6OFFHV2lyWTNpamVDeUxDQ0FwczBLaE1adTNRClhkczMranppbDRnSE96L0hvUEo5Z0xxMy9FYnR4cE9ENWkvQzZwbXc3SGM1M2tTQ3JCK2tlWUFnVWZ1aDU3MzAKZyt3cGc5RDQzaHFBNzF1L3F0ZC95eitnTVJnTWdZMndEK3ZWQWRrdGxVSWlmN2piTmR1RDRGMmdkL0gwbzljWApEUm5zMzNNQVptTkZwajN4QWFwQi9RWnhKV1JMZ1J5K1A5MWcyZEZFNzhNaWY4ZTRNSCtrU29ndzIwVG1JbmpzCitKdEVTc0xQZmx2eUZUa0lkTVdFbURWOG1HUk5hNXVoYXlEbVNEUU9xV0NUTlZHV3ZVWjZTRnJRZ1Q1MDBEdXgKWnRtYlhGdEVqRzlIaGd5SW5QT0dKbWYzTWVzS3dYclVNMW1BenVCRVBFR0lwOTc3UTY2SitpTDYzWTUvTTB3aAphMGVVNGppNTVRQnJOQjlaWjJsa080bGU2TXdmZm50c29JakMrVDh5RW5tbW5nQTlTdWNPRW9CcFFhd3cvRGhOCmtSNGk4TUptR1JNdmpLazlHVzZ3Z2VNVThJVDhKZDRjTmJOVzdFSGpzV08xN1luTVhIMEUxOVZqa2d1R3dIODAKZ3ZROGtzTmV4WVA3WWo0b0VycnRKbWVhWU8wbFVkV0tGektNdS8va0UvNG5HK0h4emlRUnA5QmdyTURNYks4ZgpkM29mY2tqZFZTTW9Vc1FJaWlmdTFMK1I4V1Y3K3hsTzdTWS80dGk3Y28zcjNXRTYyVlE4Vk9QMVphcStWRFpvClNIMVRCa0lTSU5paVJFRzhZSDQvRHJwNWZ2dHBPcERBRGN1TGdDNDJHcExmS1pwVEtRSURBUUFCbzRJQmREQ0MKQVhBd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0JrQXdOQVlKWUlaSUFZYjRRZ0VOQkNjVwpKVTlRVG5ObGJuTmxJRWRsYm1WeVlYUmxaQ0JUWlhKMlpYSWdRMlZ5ZEdsbWFXTmhkR1V3SFFZRFZSME9CQllFCkZIdUVQK05yYlorZWdMdWZVSUFKaUo2M1c4SDFNSUd3QmdOVkhTTUVnYWd3Z2FXaGdZeWtnWWt3Z1lZeEdqQVkKQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVk1CTUdBMVVFQ0F3TQpXblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyQmdOVkJBb01KRTlRClRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWllJVVJGallENGdVNG80TWRnYjcKakhzb0o1T0ZUYWd3SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01Bc0dBMVVkRHdRRQpBd0lGb0RBY0JnTlZIUkVFRlRBVGdoRlBVRTV6Wlc1elpTNXBiblJsY201aGJEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBZ0VBV2JzM2MwSXYwcEd3Y0wvUmRlbnBiZVJHQ3FsODY0V1ZITEtMZzJIR3BkKytJdmRFcHJEZkZ3SCsKdHdOd2VrZTlXUEtYa20vUkZDWE5DQmVLNjkxeURVWCtCNUJOMjMvSks5N1lzRVdtMURIV3FvSDE1WmdqelZ0QQp2d2JmbnRQdlhCWU1wV2ZQY0Zua0hjN3pxUjI3RzBEZHFUeGg2TjhFenV1S3JRWXFtaWhJUXFkNU9HRVhteW9ZCmdPVjdoZ0lWSUR6a1Z0QkRiS3dFV3VFN2pKYzViMXR4Mk1FUFRsVklEZGo0Zm5vdURWemdkczA2RER4aFM4eXAKbXJOSXhxb045ekEzYXVtTnRNZ2haSHVZRHdjbm5GSnBNZHlJSEdHZ1dlNnZZNHFtdEFSVDd3a0x6MTZnUG9LMAo5bFhVU0RmV3YwUDJGUXFHZTJjaXQ3VVE2ZGtsUWsrVGVtUEFwNnhEV09HR3oxRkdmUUoxN040b3AvOGtlOUo2Cm96RVp3QTh1aDVYTUl2N3loM2dobjV1d1R6RDUyZ1BBZFdaekEyaHVWV3p5cVM0WVc0N3ZkaGV6TTFTUndabVEKUmYzNDk0UVFydWd0bzdycWdMUlRTSXN4WEtkU21MaHZjT0hsSlhISW1XNTRzeFlXNm9NUStpRExFT29ZVVdpcgp1aUJvT1RsNEJaOG5Xcm9pV0JtWlFLaVRPYlFRczVWTkIwYnVybmRISTJVdmtIRTE3QTM0bFYySjY5Q0dNNzJ2CjQ5aE9TN3B2Tzg4cEVKZm90d01YYlRhdkR2WTBHazZxbERFMVBud1U2Wm8ySEprcFdUTUxOSzh1alZ1RkhlMGkKR2JvZi9va08vZW4rUi9PUXNyd1JYbzFwVTRiWnlyWGVQeUdqSSsrdFYzemhjd0IwWjNJPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</crt>
<prv>LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRQ2xDZHhSd2ViUEJMb2IKVU5Gdi8va3dMR0pYTHB4OXo4UUdXaXJZM2lqZUN5TENDQXBzMEtoTVp1M1FYZHMzK2p6aWw0Z0hPei9Ib1BKOQpnTHEzL0VidHhwT0Q1aS9DNnBtdzdIYzUza1NDckIra2VZQWdVZnVoNTczMGcrd3BnOUQ0M2hxQTcxdS9xdGQvCnl6K2dNUmdNZ1kyd0QrdlZBZGt0bFVJaWY3amJOZHVENEYyZ2QvSDBvOWNYRFJuczMzTUFabU5GcGozeEFhcEIKL1FaeEpXUkxnUnkrUDkxZzJkRkU3OE1pZjhlNE1IK2tTb2d3MjBUbUluanMrSnRFU3NMUGZsdnlGVGtJZE1XRQptRFY4bUdSTmE1dWhheURtU0RRT3FXQ1ROVkdXdlVaNlNGclFnVDUwMER1eFp0bWJYRnRFakc5SGhneUluUE9HCkptZjNNZXNLd1hyVU0xbUF6dUJFUEVHSXA5NzdRNjZKK2lMNjNZNS9NMHdoYTBlVTRqaTU1UUJyTkI5WloybGsKTzRsZTZNd2ZmbnRzb0lqQytUOHlFbm1tbmdBOVN1Y09Fb0JwUWF3dy9EaE5rUjRpOE1KbUdSTXZqS2s5R1c2dwpnZU1VOElUOEpkNGNOYk5XN0VIanNXTzE3WW5NWEgwRTE5VmprZ3VHd0g4MGd2UThrc05leFlQN1lqNG9FcnJ0CkptZWFZTzBsVWRXS0Z6S011Ly9rRS80bkcrSHh6aVFScDlCZ3JNRE1iSzhmZDNvZmNramRWU01vVXNRSWlpZnUKMUwrUjhXVjcreGxPN1NZLzR0aTdjbzNyM1dFNjJWUThWT1AxWmFxK1ZEWm9TSDFUQmtJU0lOaWlSRUc4WUg0LwpEcnA1ZnZ0cE9wREFEY3VMZ0M0MkdwTGZLWnBUS1FJREFRQUJBb0lDQUFTSHc4Tit4aDR5ckFVcDc4WGFTZlhYCmtnK0FtUTBmRWV0MnVDeGgxTTlia09Xd29OQ2gzYXpUT24zNHhaYkF5TUVUbGNsVkNBZ3IwOXc4RjJRTGljcm4KSTQrQVZ4bExwVkprKzFUY1ZCY2VNSFFzWGFjRmVSblZxYkkzbU5qKzVGS2dqaXV4NWx2WmpiYlZWbmJJUWplOQpxcTBGa3R5ekEwb3NDYmUydDlWVW9pVDVtTGhaOG90Ym9BRGkvQzR6YUEyL3djUGNyMkNaUWhvem51U21PUjJWCmVydUNOMHA4VURGTFA1a0gxdXlvY0NpTFh6ZXdIVEVRQ3krK0YwMEZuRmxqeDVSYW5za3JvMnhqWFR5QlZtZUYKcDYwRHF0Q0hkTjVlS2VlQWxDL0dIRlFvL2swdzd3ejMxbHVsVGgza3FDQzJsaXRwYzVpZ2JsTGxaUDgxSUpXTQp0bkhlczNsTXk1RGNDWUx3L3huZFdmVDZFMTB4WlhFNWI0QTdxYjF4Yjhsd1FoNHFJckhDZ2p1NDVPYXNCMERJClBYZ3E2eWkwL2FKWXV6SU5kcjRTeFRibExGUkp6MXlQaGZTZDVGbjdWQVBYU1JNTDlDbzJrL0M1SDlwdG1HMjYKZHBLQVNib1ZMcStrbXg3anVKYXc0a1JNNHZmYndHZGNMZEhqMXByZ08xNkd1ckpQOVRRQ0x5YzhaR0xOekcvaApIMzBpU2FlclJOUmtDRlhmeTEzWWJJZTZHTE12KzVmODlYSENGNmZrZ1JkZjVrbTA3cEc3SCtMZytmZFdtd2lZCm0waExNSFVZeHJ3WkFma2tvZjhlSllscEVQVmQ3ZytCVjd2eTZhYW0yQituUTdHYk84WUprSnlJME04amlSaDEKeGdjRmFZaGZlT21RZGtVbi9BcUJBb0lCQVFEU1JZbDl0SnJyQk5UOXlZN0twWTJiOGVURFJqdDByU1NQRUJvNgppeWoyVWR5S1ZSbHFFdzRma2IrejV3WWt2bnBpMW1uS3NjNFlLZmoyaDVSdXVDbzVzTUNLbmpDUXBpbll4bWRFCk45Z3l6SWRYMmlzRUh6dXNSZkZiajBVSWU1dHc0TE9pL3cyVzFYWGJUc3liOFdhTmhkbVQ4TGxDNjQ5WkNNUWQKeDZkeTdOWS9uYnVWVVQ0KzM3WmV0VlR1eDg1ekl5OTdnMWp4dFZhaXZrd2hQVWtLcWpXWDYzaUZidjFuY1FVdgpiQURrWkFoOXRWYWV2UGZ2NURDeDZITldiVFlObjVRZWd3OTRyVndoSjhYb1V5ZDRqWFB0VmdXU2VkN0tWd2U5CmNkNW9CZWFBOVhDdnJxdkNIRjI4QXg2OUI2YWQrQlk1S0dVcGU2LythQnlKdlQwUkFvSUJBUURJN2c3c0dMc3AKVWJ4dGhJQm9yRzF5MDRKWWlnaE5VMlF4YjdzSkxocnRTc2NtRkxSZU5DYy8zOTBCV3ZrbVFIazFnZkpxV3hDLwp2R0VMT0Iwd3U5VFBBRWFsS25IZ2RhNkFTMURuM3NTWTltcFBRRjYvbEY2cm00cDlNUU1TTFo1V3ZsL0ZNRklHCjUvaXVSVjJaOVdkeTV4QVFWNG5uZmdMOWJDNzhPa2k3VnFPTDJDZk0vcEJEMHdzRUZmOGZiejFSZXo4dEFRZ2QKVXY4cEpFTWdDTCtQeEdkdG5DYUcxYm5obXhEUUxiWmQ4TTFOQkpKOWZLZFgzVWtLcTlDdmFJVXBIZlduSFBWVAprVWNwMUVTYnEzOFVhTzFSS1NBNUtQd1ZiNkVPVGJBSGtlaEN4ZVhpN2F3YkZwYXlTelpIaWl4Y05QQjk1YUtSCkpJQ0J5ekFwQTVTWkFvSUJBRlZKYXlrWGxqWjVNVUwyKy9ucUNIUVdPeW1SVlJCUUlpSDg4QWFLNTBSeGs3aHcKSit6RWFkZ1lMOTl5ZHlWME5RUGQzKzhkQzNEMXBVdXBWbVZLUWFaQXNQZ0lqYjQrQjM4cmlqczdRMi9uVVlZcQpzWVBzZnpHeTlPQ2tUZVhRN1ExdHRxOElNS1RiVkFCdUI4UEF1RTN5Mm51TkNqZkFmOVluSGhUT0pIY1M1UnZNCmlJZForcHRaOWdpWUdDajUxaDBSU25NWXBYejBobjFnSGxUbEhMazhySnhBSUJSUEhtMVVoRHZsM0w3R2JFTkEKeUM5K2lqbzlIaHNySTQwTW92NEhtZlorUmtvMlZzWUQ4ZHYzem15eFF6SWkwQVBIZHJ3dmJLNUVmMmRGN1dhbApKdDI3UldOb1NnUzJaME5ZMVJZQnlGSEt0cTJLdzZtMjVNeGhlMkVDZ2dFQVhSNFdSRXhoMEpCVXB0eVZOZTFTCis3Z1IzRDU4QW5uM0lRSUt5QUpaOEVhTGJKYUQwSFNUREFNUFJTV0grYlkvZGhDMjY1c3djK3MxZmlHUFJacUcKMFRmcmhYZmFOby9UUXhta2NSRElRNnRQTVZNL2xjR0k3amF6UTdtSEZ0R1ZZOVh1UkZCVWMyYmwxTDNJMXlUbgp3RlJkR1hXNEwxUXl4b2R3YnV3RDhPNEI5VGxEbUxrUTJwM2ZxUkVZbnRUS3NneFFCdWRIZjIrTFdPRzVTZ3RECjI3akZ4Z0pyeUdrY0wvWFJJT2xPYnRLK0VrZGdMRStzcmdlYlpocWlKK2hrYmQyNGpxM1k4OVdNQ1ZLYVNScDkKVmxRYVIxYXIzRkdtSWJrT0JyYnlNVS9wTjZqSEZSZllmdVZGQ1hQWnYrWEZFU1pubmJEaVdpbDBkTEpacTJoQgpZUUtDQVFBOVlTcE1wS3dhVGwrTmhTZlovMXU0NjZiMkpZcmJPYlRJM2VCZUowMnFwNXdQTjZYUHJ5aVZaZ1FXClh5cG04a3M5MEJIblBPNUczNFJnKzhLRFlONU1Ed1hBclJubDdSeTEySG5oV3lSaHNKYmdZOEh1c2d4SEROMU8KMEcwSFBpVWtIbTYydlRNYll6bkhPeE5sS1hFdFhBcTlZM3dQYkFjb01rRXZ0MzEwdEdLSUNtdTdEWkpXRlVvTAp1Y3RVS3Boc0V5VWdHbHIwRjJKekVoQWdMRXplczB0S1JpRWdlaFdnbXdlMEhlTEhCdW5oRFBTMmFJY2lCME1pCjY2SGc3cVZyMDlneXpFeGxrY3RLRzhsSm9WbU8vdlhucWQrWDB5M21YTUVZbkFIWHpIeG1Pd2JCNnF3Y3VWTlEKZytqRXliUWF3d3A2OC9id0JncFREQUhORGxrRQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==</prv>
</cert>
<dhcpdv6/>
<virtualip version="1.0.1">
<vip/>
</virtualip>
<openvpn/>
<ppps>
<ppp/>
</ppps>
<vlans version="1.0.0">
<vlan/>
</vlans>
<bridges>
<bridged/>
</bridges>
<gifs version="1.0.0">
<gif/>
</gifs>
<gres version="1.0.0">
<gre/>
</gres>
<laggs version="1.0.0">
<lagg/>
</laggs>
<wireless>
<clone/>
</wireless>
<hasync version="1.0.2">
<pfsyncinterface/>
<synchronizetoip/>
<verifypeer>0</verifypeer>
<username/>
<password/>
<disablepreempt>0</disablepreempt>
<disconnectppps>0</disconnectppps>
<pfsyncpeerip/>
<pfsyncversion>1400</pfsyncversion>
<syncitems/>
</hasync>
<ifgroups version="1.0.0"/>
<dnsmasq version="1.0.7" persisted_at="1755800176.40">
<enable>1</enable>
<regdhcp>0</regdhcp>
<regdhcpstatic>0</regdhcpstatic>
<dhcpfirst>0</dhcpfirst>
<strict_order>0</strict_order>
<domain_needed>0</domain_needed>
<no_private_reverse>0</no_private_reverse>
<no_resolv>0</no_resolv>
<log_queries>0</log_queries>
<no_hosts>0</no_hosts>
<strictbind>0</strictbind>
<dnssec>0</dnssec>
<regdhcpdomain/>
<interface>lan</interface>
<port>0</port>
<dns_forward_max/>
<cache_size/>
<local_ttl/>
<add_mac/>
<add_subnet>0</add_subnet>
<strip_subnet>0</strip_subnet>
<dhcp>
<no_interface/>
<fqdn>1</fqdn>
<domain/>
<lease_max/>
<authoritative>0</authoritative>
<default_fw_rules>1</default_fw_rules>
<reply_delay/>
<enable_ra>0</enable_ra>
<nosync>0</nosync>
</dhcp>
<no_ident>1</no_ident>
<dhcp_tags uuid="8d190cf3-8d2d-47db-ab9b-fa21016b533e">
<tag>ipxe</tag>
</dhcp_tags>
<dhcp_tags uuid="0b2982da-198c-4ca4-9a3e-95813667047c">
<tag>pxeEfi</tag>
</dhcp_tags>
<dhcp_tags uuid="993e079f-09b9-4a0f-a70f-8898872b9983">
<tag>pxeBios</tag>
</dhcp_tags>
<dhcp_ranges uuid="843574fc-4c3f-4f81-9e86-56be45f4ba49">
<interface>lan</interface>
<set_tag/>
<start_addr>192.168.1.41</start_addr>
<end_addr>192.168.1.245</end_addr>
<subnet_mask/>
<constructor/>
<mode/>
<prefix_len/>
<lease_time/>
<domain_type>range</domain_type>
<domain/>
<nosync>0</nosync>
<ra_mode/>
<ra_priority/>
<ra_mtu/>
<ra_interval/>
<ra_router_lifetime/>
<description/>
</dhcp_ranges>
<dhcp_options uuid="1e8d6f0f-7c2c-4873-8960-95a5c9447318">
<type>match</type>
<option>77</option>
<option6/>
<interface/>
<tag/>
<set_tag>8d190cf3-8d2d-47db-ab9b-fa21016b533e</set_tag>
<value>iPXE</value>
<force/>
<description/>
</dhcp_options>
<dhcp_options uuid="9b54181b-aa68-4fd6-9d59-10a77c291fcb">
<type>match</type>
<option>93</option>
<option6/>
<interface/>
<tag/>
<set_tag>993e079f-09b9-4a0f-a70f-8898872b9983</set_tag>
<value>0</value>
<force/>
<description/>
</dhcp_options>
<dhcp_options uuid="26402b93-91bb-48a9-92da-b567a91ed4d8">
<type>match</type>
<option>93</option>
<option6/>
<interface/>
<tag/>
<set_tag>0b2982da-198c-4ca4-9a3e-95813667047c</set_tag>
<value>7</value>
<force/>
<description/>
</dhcp_options>
<dhcp_boot uuid="57436655-6f95-4590-bcdc-dfe542347560">
<interface/>
<tag>0b2982da-198c-4ca4-9a3e-95813667047c</tag>
<filename>ipxe.efi</filename>
<servername>192.168.1.1</servername>
<address>192.168.1.1</address>
<description/>
</dhcp_boot>
<dhcp_boot uuid="dcc00bf2-5148-40ef-9f79-1f17bf572f6c">
<interface/>
<tag>8d190cf3-8d2d-47db-ab9b-fa21016b533e</tag>
<filename>http://192.168.1.1:8080/boot.ipxe</filename>
<servername>192.168.1.1</servername>
<address>192.168.1.1</address>
<description/>
</dhcp_boot>
<dhcp_boot uuid="8b9263a4-d242-4c41-8ff2-6f1af5001c41">
<interface/>
<tag>993e079f-09b9-4a0f-a70f-8898872b9983</tag>
<filename>undionly.kpxe</filename>
<servername>192.168.1.1</servername>
<address>192.168.1.1</address>
<description/>
</dhcp_boot>
</dnsmasq>
</opnsense>

View File

@@ -0,0 +1,867 @@
<?xml version="1.0"?>
<opnsense>
<theme>opnsense</theme>
<sysctl version="1.0.1" persisted_at="1755708111.39">
<item/>
</sysctl>
<system>
<serialspeed>115200</serialspeed>
<primaryconsole>serial</primaryconsole>
<optimization>normal</optimization>
<hostname>OPNsense</hostname>
<domain>internal</domain>
<dnsallowoverride>1</dnsallowoverride>
<dnsallowoverride_exclude/>
<group uuid="67305f6f-7f7a-454d-8a4e-65cb8f072d81">
<gid>1999</gid>
<name>admins</name>
<scope>system</scope>
<description>System Administrators</description>
<priv>page-all</priv>
<member>0</member>
<source_networks/>
</group>
<user uuid="1d2ed537-5d1a-4772-9600-37b93f9f798b">
<uid>0</uid>
<name>root</name>
<disabled>0</disabled>
<scope>system</scope>
<expires/>
<authorizedkeys/>
<otp_seed/>
<shell/>
<password>$2y$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS</password>
<pwd_changed_at/>
<landing_page/>
<comment/>
<email/>
<apikeys/>
<priv/>
<language/>
<descr>System Administrator</descr>
<dashboard/>
</user>
<timezone>Etc/UTC</timezone>
<timeservers>0.opnsense.pool.ntp.org 1.opnsense.pool.ntp.org 2.opnsense.pool.ntp.org 3.opnsense.pool.ntp.org</timeservers>
<webgui>
<protocol>https</protocol>
<ssl-certref>68a5faf1685db</ssl-certref>
<port/>
<ssl-ciphers/>
<interfaces/>
<compression/>
</webgui>
<disablenatreflection>yes</disablenatreflection>
<usevirtualterminal>1</usevirtualterminal>
<disableconsolemenu>1</disableconsolemenu>
<disablevlanhwfilter>1</disablevlanhwfilter>
<disablechecksumoffloading>1</disablechecksumoffloading>
<disablesegmentationoffloading>1</disablesegmentationoffloading>
<disablelargereceiveoffloading>1</disablelargereceiveoffloading>
<ipv6allow>1</ipv6allow>
<powerd_ac_mode>hadp</powerd_ac_mode>
<powerd_battery_mode>hadp</powerd_battery_mode>
<powerd_normal_mode>hadp</powerd_normal_mode>
<bogons>
<interval>monthly</interval>
</bogons>
<pf_share_forward>1</pf_share_forward>
<lb_use_sticky>1</lb_use_sticky>
<ssh>
<group>admins</group>
<noauto>1</noauto>
<interfaces/>
<kex/>
<ciphers/>
<macs/>
<keys/>
<keysig/>
<rekeylimit/>
<enabled>enabled</enabled>
<passwordauth>1</passwordauth>
<permitrootlogin>1</permitrootlogin>
</ssh>
<rrdbackup>-1</rrdbackup>
<netflowbackup>-1</netflowbackup>
<firmware version="1.0.1" persisted_at="1755708111.32">
<mirror/>
<flavour/>
<plugins/>
<type/>
<subscription/>
<reboot>0</reboot>
</firmware>
<dnsserver/>
<language>en_US</language>
</system>
<interfaces>
<wan>
<enable>1</enable>
<if>vtnet0</if>
<mtu/>
<ipaddr>dhcp</ipaddr>
<ipaddrv6>dhcp6</ipaddrv6>
<subnet/>
<gateway/>
<blockpriv>0</blockpriv>
<blockbogons>1</blockbogons>
<media/>
<mediaopt/>
<dhcp6-ia-pd-len>0</dhcp6-ia-pd-len>
<dhcphostname/>
<spoofmac/>
<mss/>
</wan>
<lan>
<enable>1</enable>
<if>vtnet1</if>
<ipaddr>192.168.1.1</ipaddr>
<subnet>24</subnet>
<ipaddrv6>track6</ipaddrv6>
<subnetv6>64</subnetv6>
<media/>
<mediaopt/>
<track6-interface>wan</track6-interface>
<track6-prefix-id>0</track6-prefix-id>
</lan>
<lo0>
<internal_dynamic>1</internal_dynamic>
<descr>Loopback</descr>
<enable>1</enable>
<if>lo0</if>
<ipaddr>127.0.0.1</ipaddr>
<ipaddrv6>::1</ipaddrv6>
<subnet>8</subnet>
<subnetv6>128</subnetv6>
<type>none</type>
<virtual>1</virtual>
</lo0>
</interfaces>
<dnsmasq version="1.0.7" persisted_at="1755723263.06">
<enable>1</enable>
<regdhcp>0</regdhcp>
<regdhcpstatic>0</regdhcpstatic>
<dhcpfirst>0</dhcpfirst>
<strict_order>0</strict_order>
<domain_needed>0</domain_needed>
<no_private_reverse>0</no_private_reverse>
<no_resolv>0</no_resolv>
<log_queries>0</log_queries>
<no_hosts>0</no_hosts>
<strictbind>0</strictbind>
<dnssec>0</dnssec>
<regdhcpdomain/>
<interface>lan</interface>
<port>0</port>
<dns_forward_max/>
<cache_size/>
<local_ttl/>
<add_mac/>
<add_subnet>0</add_subnet>
<strip_subnet>0</strip_subnet>
<dhcp>
<no_interface/>
<fqdn>1</fqdn>
<domain/>
<lease_max/>
<authoritative>0</authoritative>
<default_fw_rules>1</default_fw_rules>
<reply_delay/>
<enable_ra>0</enable_ra>
<nosync>0</nosync>
</dhcp>
<no_ident>1</no_ident>
<dhcp_ranges uuid="78b5c4a4-565d-4cd7-af10-29050f29e494">
<interface>lan</interface>
<set_tag/>
<start_addr>192.168.1.41</start_addr>
<end_addr>192.168.1.245</end_addr>
<subnet_mask/>
<constructor/>
<mode/>
<prefix_len/>
<lease_time/>
<domain_type>range</domain_type>
<domain/>
<nosync>0</nosync>
<ra_mode/>
<ra_priority/>
<ra_mtu/>
<ra_interval/>
<ra_router_lifetime/>
<description/>
</dhcp_ranges>
<dhcp_options uuid="b05f2b57-bad8-4072-8f51-d051671832fe">
<type>set</type>
<option>67</option>
<option6/>
<interface>lan</interface>
<tag/>
<set_tag/>
<value>test/boot/filename</value>
<force>0</force>
<description/>
</dhcp_options>
<dhcp_options uuid="5a5a7854-ec44-4631-95a9-85b4897588d5">
<type>set</type>
<option>128</option>
<option6/>
<interface>lan</interface>
<tag/>
<set_tag/>
<value>test some pxe setting vendor specific 128</value>
<force>0</force>
<description/>
</dhcp_options>
<dhcp_options uuid="7706861d-1851-420f-9b50-891ad413663e">
<type>set</type>
<option>208</option>
<option6/>
<interface/>
<tag/>
<set_tag/>
<value>pxelinux magic what is this (on any interface)</value>
<force>0</force>
<description/>
</dhcp_options>
<dhcp_boot uuid="748c0bd2-ef58-4cba-8486-bbf142f53859">
<interface/>
<tag/>
<filename>boot options filename</filename>
<servername>boot servername</servername>
<address>boot server address</address>
<description>boot description</description>
</dhcp_boot>
</dnsmasq>
<snmpd>
<syslocation/>
<syscontact/>
<rocommunity>public</rocommunity>
</snmpd>
<nat>
<outbound>
<mode>automatic</mode>
</outbound>
</nat>
<filter>
<rule>
<type>pass</type>
<ipprotocol>inet</ipprotocol>
<descr>Default allow LAN to any rule</descr>
<interface>lan</interface>
<source>
<network>lan</network>
</source>
<destination>
<any/>
</destination>
</rule>
<rule>
<type>pass</type>
<ipprotocol>inet6</ipprotocol>
<descr>Default allow LAN IPv6 to any rule</descr>
<interface>lan</interface>
<source>
<network>lan</network>
</source>
<destination>
<any/>
</destination>
</rule>
</filter>
<rrd>
<enable/>
</rrd>
<ntpd>
<prefer>0.opnsense.pool.ntp.org</prefer>
</ntpd>
<revision>
<username>root@192.168.1.5</username>
<description>/api/dnsmasq/settings/set made changes</description>
<time>1755723263.06</time>
</revision>
<OPNsense>
<wireguard>
<client version="1.0.0" persisted_at="1755708111.04">
<clients/>
</client>
<general version="0.0.1" persisted_at="1755708111.05">
<enabled>0</enabled>
</general>
<server version="1.0.0" persisted_at="1755708111.05">
<servers/>
</server>
</wireguard>
<IPsec version="1.0.4" persisted_at="1755708111.06">
<general>
<enabled/>
<preferred_oldsa>0</preferred_oldsa>
<disablevpnrules>0</disablevpnrules>
<passthrough_networks/>
<user_source/>
<local_group/>
</general>
<charon>
<max_ikev1_exchanges/>
<threads>16</threads>
<ikesa_table_size>32</ikesa_table_size>
<ikesa_table_segments>4</ikesa_table_segments>
<init_limit_half_open>1000</init_limit_half_open>
<ignore_acquire_ts>1</ignore_acquire_ts>
<install_routes>0</install_routes>
<cisco_unity>0</cisco_unity>
<make_before_break>0</make_before_break>
<retransmit_tries/>
<retransmit_timeout/>
<retransmit_base/>
<retransmit_jitter/>
<retransmit_limit/>
<syslog>
<daemon>
<ike_name>1</ike_name>
<log_level>0</log_level>
<app>1</app>
<asn>1</asn>
<cfg>1</cfg>
<chd>1</chd>
<dmn>1</dmn>
<enc>1</enc>
<esp>1</esp>
<ike>1</ike>
<imc>1</imc>
<imv>1</imv>
<job>1</job>
<knl>1</knl>
<lib>1</lib>
<mgr>1</mgr>
<net>1</net>
<pts>1</pts>
<tls>1</tls>
<tnc>1</tnc>
</daemon>
</syslog>
<plugins>
<attr>
<subnet/>
<split-include/>
<x_28674/>
<x_28675/>
<x_28672/>
<x_28673>0</x_28673>
<x_28679/>
<dns/>
<nbns/>
</attr>
<eap-radius>
<servers/>
<accounting>0</accounting>
<class_group>0</class_group>
</eap-radius>
<xauth-pam>
<pam_service>ipsec</pam_service>
<session>0</session>
<trim_email>1</trim_email>
</xauth-pam>
</plugins>
</charon>
<keyPairs/>
<preSharedKeys/>
</IPsec>
<Swanctl version="1.0.0" persisted_at="1755708111.08">
<Connections/>
<locals/>
<remotes/>
<children/>
<Pools/>
<VTIs/>
<SPDs/>
</Swanctl>
<OpenVPNExport version="0.0.1" persisted_at="1755708111.40">
<servers/>
</OpenVPNExport>
<OpenVPN version="1.0.1" persisted_at="1755708111.40">
<Overwrites/>
<Instances/>
<StaticKeys/>
</OpenVPN>
<captiveportal version="1.0.4" persisted_at="1755708111.41">
<zones/>
<templates/>
</captiveportal>
<cron version="1.0.4" persisted_at="1755708111.43">
<jobs/>
</cron>
<DHCRelay version="1.0.1" persisted_at="1755708111.43"/>
<Firewall>
<Lvtemplate version="0.0.1" persisted_at="1755708111.45">
<templates/>
</Lvtemplate>
<Alias version="1.0.1" persisted_at="1755708111.65">
<geoip>
<url/>
</geoip>
<aliases/>
</Alias>
<Category version="1.0.0" persisted_at="1755708111.65">
<categories/>
</Category>
<Filter version="1.0.4" persisted_at="1755708111.70">
<rules/>
<snatrules/>
<npt/>
<onetoone/>
</Filter>
</Firewall>
<Netflow version="1.0.1" persisted_at="1755708111.45">
<capture>
<interfaces/>
<egress_only/>
<version>v9</version>
<targets/>
</capture>
<collect>
<enable>0</enable>
</collect>
<activeTimeout>1800</activeTimeout>
<inactiveTimeout>15</inactiveTimeout>
</Netflow>
<IDS version="1.1.0" persisted_at="1755708111.90">
<rules/>
<policies/>
<userDefinedRules/>
<files/>
<fileTags/>
<general>
<enabled>0</enabled>
<ips>0</ips>
<promisc>0</promisc>
<interfaces>wan</interfaces>
<homenet>192.168.0.0/16,10.0.0.0/8,172.16.0.0/12</homenet>
<defaultPacketSize/>
<UpdateCron/>
<AlertLogrotate>W0D23</AlertLogrotate>
<AlertSaveLogs>4</AlertSaveLogs>
<MPMAlgo/>
<detect>
<Profile/>
<toclient_groups/>
<toserver_groups/>
</detect>
<syslog>0</syslog>
<syslog_eve>0</syslog_eve>
<LogPayload>0</LogPayload>
<verbosity/>
<eveLog>
<http>
<enable>0</enable>
<extended>0</extended>
<dumpAllHeaders/>
</http>
<tls>
<enable>0</enable>
<extended>0</extended>
<sessionResumption>0</sessionResumption>
<custom/>
</tls>
</eveLog>
</general>
</IDS>
<Interfaces>
<loopbacks version="1.0.0" persisted_at="1755708111.95"/>
<neighbors version="1.0.0" persisted_at="1755708111.96"/>
<vxlans version="1.0.2" persisted_at="1755708111.99"/>
</Interfaces>
<Kea>
<ctrl_agent version="0.0.1" persisted_at="1755708111.99">
<general>
<enabled>0</enabled>
<http_host>127.0.0.1</http_host>
<http_port>8000</http_port>
</general>
</ctrl_agent>
<dhcp4 version="1.0.4" persisted_at="1755708112.00">
<general>
<enabled>0</enabled>
<manual_config>0</manual_config>
<interfaces/>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
<dhcp_socket_type>raw</dhcp_socket_type>
</general>
<ha>
<enabled>0</enabled>
<this_server_name/>
<max_unacked_clients>2</max_unacked_clients>
</ha>
<subnets/>
<reservations/>
<ha_peers/>
</dhcp4>
<dhcp6 version="1.0.0" persisted_at="1755708112.00">
<general>
<enabled>0</enabled>
<manual_config>0</manual_config>
<interfaces/>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
</general>
<ha>
<enabled>0</enabled>
<this_server_name/>
<max_unacked_clients>2</max_unacked_clients>
</ha>
<subnets/>
<reservations/>
<pd_pools/>
<ha_peers/>
</dhcp6>
</Kea>
<monit version="1.0.13" persisted_at="1755708112.02">
<general>
<enabled>0</enabled>
<interval>120</interval>
<startdelay>120</startdelay>
<mailserver>127.0.0.1</mailserver>
<port>25</port>
<username/>
<password/>
<ssl>0</ssl>
<sslversion>auto</sslversion>
<sslverify>1</sslverify>
<logfile/>
<statefile/>
<eventqueuePath/>
<eventqueueSlots/>
<httpdEnabled>0</httpdEnabled>
<httpdUsername>root</httpdUsername>
<httpdPassword/>
<httpdPort>2812</httpdPort>
<httpdAllow/>
<mmonitUrl/>
<mmonitTimeout>5</mmonitTimeout>
<mmonitRegisterCredentials>1</mmonitRegisterCredentials>
</general>
<alert uuid="76cd5195-a487-4f4f-8ef5-4f9815bf19e5">
<enabled>0</enabled>
<recipient>root@localhost.local</recipient>
<noton>0</noton>
<events/>
<format/>
<reminder/>
<description/>
</alert>
<service uuid="11610907-f700-4f0a-9926-c25a47709493">
<enabled>1</enabled>
<name>$HOST</name>
<description/>
<type>system</type>
<pidfile/>
<match/>
<path/>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>02014be3-fc31-4af3-a0d5-061eaa67d28a,ebfd0d97-ae21-45d5-8b42-5220c75ce46f,d37f25f0-89e3-44b6-8ad2-280ac83a8904,37afd0d9-990c-4f03-a817-45691461e3d0</tests>
<depends/>
<polltime/>
</service>
<service uuid="dcc7e42f-a878-49a3-8b90-67faf21d67bd">
<enabled>1</enabled>
<name>RootFs</name>
<description/>
<type>filesystem</type>
<pidfile/>
<match/>
<path>/</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>b44b859c-bc72-4c2e-82c9-4f56d84a5497</tests>
<depends/>
<polltime/>
</service>
<service uuid="5ca544d4-846a-4f61-9d6f-c191eafcd9fb">
<enabled>0</enabled>
<name>carp_status_change</name>
<description/>
<type>custom</type>
<pidfile/>
<match/>
<path>/usr/local/opnsense/scripts/OPNsense/Monit/carp_status</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>0909801b-cd11-41c8-afeb-369396247308</tests>
<depends/>
<polltime/>
</service>
<service uuid="6e16e26e-e732-4b9b-ac92-2086b84f3158">
<enabled>0</enabled>
<name>gateway_alert</name>
<description/>
<type>custom</type>
<pidfile/>
<match/>
<path>/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>56e67d76-cef6-4167-a51e-2c69a921ebc9</tests>
<depends/>
<polltime/>
</service>
<test uuid="b7671673-5cf1-4123-a58c-37f57a8e9d59">
<name>Ping</name>
<type>NetworkPing</type>
<condition>failed ping</condition>
<action>alert</action>
<path/>
</test>
<test uuid="30cf3664-5c6f-4ac5-a52a-36023270c6fb">
<name>NetworkLink</name>
<type>NetworkInterface</type>
<condition>failed link</condition>
<action>alert</action>
<path/>
</test>
<test uuid="a7c0836a-b102-4f37-a73b-2a50903ffcc8">
<name>NetworkSaturation</name>
<type>NetworkInterface</type>
<condition>saturation is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="02014be3-fc31-4af3-a0d5-061eaa67d28a">
<name>MemoryUsage</name>
<type>SystemResource</type>
<condition>memory usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="ebfd0d97-ae21-45d5-8b42-5220c75ce46f">
<name>CPUUsage</name>
<type>SystemResource</type>
<condition>cpu usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="d37f25f0-89e3-44b6-8ad2-280ac83a8904">
<name>LoadAvg1</name>
<type>SystemResource</type>
<condition>loadavg (1min) is greater than 4</condition>
<action>alert</action>
<path/>
</test>
<test uuid="37afd0d9-990c-4f03-a817-45691461e3d0">
<name>LoadAvg5</name>
<type>SystemResource</type>
<condition>loadavg (5min) is greater than 3</condition>
<action>alert</action>
<path/>
</test>
<test uuid="608d2888-9df5-486d-bbfb-bf17fad75a7e">
<name>LoadAvg15</name>
<type>SystemResource</type>
<condition>loadavg (15min) is greater than 2</condition>
<action>alert</action>
<path/>
</test>
<test uuid="b44b859c-bc72-4c2e-82c9-4f56d84a5497">
<name>SpaceUsage</name>
<type>SpaceUsage</type>
<condition>space usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="0909801b-cd11-41c8-afeb-369396247308">
<name>ChangedStatus</name>
<type>ProgramStatus</type>
<condition>changed status</condition>
<action>alert</action>
<path/>
</test>
<test uuid="56e67d76-cef6-4167-a51e-2c69a921ebc9">
<name>NonZeroStatus</name>
<type>ProgramStatus</type>
<condition>status != 0</condition>
<action>alert</action>
<path/>
</test>
</monit>
<Gateways version="1.0.0" persisted_at="1755721633.53"/>
<Syslog version="1.0.2" persisted_at="1755708112.05">
<general>
<enabled>1</enabled>
<loglocal>1</loglocal>
<maxpreserve>31</maxpreserve>
<maxfilesize/>
</general>
<destinations/>
</Syslog>
<TrafficShaper version="1.0.3" persisted_at="1755708112.06">
<pipes/>
<queues/>
<rules/>
</TrafficShaper>
<trust>
<general version="1.0.1" persisted_at="1755708112.22">
<store_intermediate_certs>0</store_intermediate_certs>
<install_crls>0</install_crls>
<fetch_crls>0</fetch_crls>
<enable_legacy_sect>1</enable_legacy_sect>
<enable_config_constraints>0</enable_config_constraints>
<CipherString/>
<Ciphersuites/>
<SignatureAlgorithms/>
<groups/>
<MinProtocol/>
<MinProtocol_DTLS/>
</general>
</trust>
<unboundplus version="1.0.12" persisted_at="1755708112.29">
<general>
<enabled>1</enabled>
<port>53</port>
<stats>0</stats>
<active_interface/>
<dnssec>0</dnssec>
<dns64>0</dns64>
<dns64prefix/>
<noarecords>0</noarecords>
<regdhcp>0</regdhcp>
<regdhcpdomain/>
<regdhcpstatic>0</regdhcpstatic>
<noreglladdr6>0</noreglladdr6>
<noregrecords>0</noregrecords>
<txtsupport>0</txtsupport>
<cacheflush>0</cacheflush>
<local_zone_type>transparent</local_zone_type>
<outgoing_interface/>
<enable_wpad>0</enable_wpad>
</general>
<advanced>
<hideidentity>0</hideidentity>
<hideversion>0</hideversion>
<prefetch>0</prefetch>
<prefetchkey>0</prefetchkey>
<dnssecstripped>0</dnssecstripped>
<aggressivensec>1</aggressivensec>
<serveexpired>0</serveexpired>
<serveexpiredreplyttl/>
<serveexpiredttl/>
<serveexpiredttlreset>0</serveexpiredttlreset>
<serveexpiredclienttimeout/>
<qnameminstrict>0</qnameminstrict>
<extendedstatistics>0</extendedstatistics>
<logqueries>0</logqueries>
<logreplies>0</logreplies>
<logtagqueryreply>0</logtagqueryreply>
<logservfail>0</logservfail>
<loglocalactions>0</loglocalactions>
<logverbosity>1</logverbosity>
<valloglevel>0</valloglevel>
<privatedomain/>
<privateaddress>0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,169.254.0.0/16,172.16.0.0/12,192.0.2.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,233.252.0.0/24,::1/128,2001:db8::/32,fc00::/8,fd00::/8,fe80::/10</privateaddress>
<insecuredomain/>
<msgcachesize/>
<rrsetcachesize/>
<outgoingnumtcp/>
<incomingnumtcp/>
<numqueriesperthread/>
<outgoingrange/>
<jostletimeout/>
<discardtimeout/>
<cachemaxttl/>
<cachemaxnegativettl/>
<cacheminttl/>
<infrahostttl/>
<infrakeepprobing>0</infrakeepprobing>
<infracachenumhosts/>
<unwantedreplythreshold/>
</advanced>
<acls>
<default_action>allow</default_action>
</acls>
<dnsbl>
<enabled>0</enabled>
<safesearch>0</safesearch>
<type/>
<lists/>
<whitelists/>
<blocklists/>
<wildcards/>
<address/>
<nxdomain>0</nxdomain>
</dnsbl>
<forwarding>
<enabled>0</enabled>
</forwarding>
<dots/>
<hosts/>
<aliases/>
</unboundplus>
</OPNsense>
<hasync version="1.0.2" persisted_at="1755708111.35">
<disablepreempt>0</disablepreempt>
<disconnectppps>0</disconnectppps>
<pfsyncinterface/>
<pfsyncpeerip/>
<pfsyncversion>1400</pfsyncversion>
<synchronizetoip/>
<verifypeer>0</verifypeer>
<username/>
<password/>
<syncitems/>
</hasync>
<openvpn/>
<ifgroups version="1.0.0" persisted_at="1755708111.71"/>
<bridges version="1.0.0" persisted_at="1755708111.91">
<bridged/>
</bridges>
<gifs version="1.0.0" persisted_at="1755708111.92">
<gif/>
</gifs>
<gres version="1.0.0" persisted_at="1755708111.93">
<gre/>
</gres>
<laggs version="1.0.0" persisted_at="1755708111.95">
<lagg/>
</laggs>
<virtualip version="1.0.1" persisted_at="1755708111.96">
<vip/>
</virtualip>
<vlans version="1.0.0" persisted_at="1755708111.98">
<vlan/>
</vlans>
<staticroutes version="1.0.0" persisted_at="1755708112.03"/>
<ppps>
<ppp/>
</ppps>
<wireless>
<clone/>
</wireless>
<ca/>
<dhcpd/>
<dhcpdv6/>
<cert uuid="d60972af-642c-411f-abb6-43e0d680fefd">
<refid>68a5faf1685db</refid>
<descr>Web GUI TLS certificate</descr>
<caref/>
<crt>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQlBxZ0F3SUJBZ0lVQlpQYjUwMXNaM3hhTTZzSDVUM1ZNRG9mcnlNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhHakFZQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVgpNQk1HQTFVRUNBd01XblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyCkJnTlZCQW9NSkU5UVRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWlRBZUZ3MHkKTlRBNE1qQXhOalF5TWpaYUZ3MHlOakE1TWpFeE5qUXlNalphTUlHR01Sb3dHQVlEVlFRRERCRlBVRTV6Wlc1egpaUzVwYm5SbGNtNWhiREVMTUFrR0ExVUVCaE1DVGt3eEZUQVRCZ05WQkFnTURGcDFhV1F0U0c5c2JHRnVaREVWCk1CTUdBMVVFQnd3TVRXbGtaR1ZzYUdGeWJtbHpNUzB3S3dZRFZRUUtEQ1JQVUU1elpXNXpaU0J6Wld4bUxYTnAKWjI1bFpDQjNaV0lnWTJWeWRHbG1hV05oZEdVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFEQVozZk1lakdOckZYVFhZd24vTUp5YXBYMDdVOTVWZ0JERzZmWU1wZkZEU1NXeFRValRLRlNvR3JRCkpDb0ZyQ0NRQ3BsWnlua0ZjVFZvWVAraGszcndWWmxHZVFyL0RyR2ZiM1lPc2RGbEtublo5YzRTOGVrSkc2WTIKYWI0eFJTcnBja0hoTWQ4MHNENUFRdks1Skc2MS9UMEhNRXVMcGlraUF3MFpCempWbjlVUUpSRTJiS29kOW9IdgpEbG5DVGNTV1FUNWYrV3A0Sll0anVBVHBZMUlRVW4wbkxuWDBHUC9JRGtsWjFrdEZOczRPOGcrQmRVWFU0MUdvCjNGTFpCc1hQVm90WFVrRVl2R0ZldjJRMlBwSnNib25aWEpoR255amREUlZkZFZGOGhacGJua0wwU2xJTVFNeFQKSTRXK051ZmUvUTZGRDdmZnFRa0toemZ3SlJ2N0dGM2RTRlEwWldPUGNTZVZXY3lWTlVVMTMvcnBteUpvNXZhWQpYR000THcxb1d2c1FjUWxaUllnSkxhSWMzM0dnMGQySHhvZTIvdytIeFRXOEw4ZldqbzgxK250YWJXTlZhV0IwCnd6TXNFNGRBOWtxR2dmcWZjbm96ckovamJtNEFBTTB6QTlVZFh0SUJtRGdpeFNkVzB4eHNQNlZWRTdMdnlURTgKWnBJTjRoL1FCYURyc2hhRXJ6TXhUd01IZXJ1RlV4bFpxZEdSa3ErQXRJU1VwRE05VXB0YWZZMk5ydzgxVDFhSwoycFduVFlFQktCUnVwdk00TzFHNXN5NU5GZm13NFRTc0pqRUhtMFEvVTZ4ZU45bVg0OFhIYUZFNnhvQTJEZEMrCjJHS2lKWFlrYi9nZm13cmp1Y3NGWGpBS2tDNWF6ZXFqaERXeGxDbmJNS1YwSTQxOWp3SURBUUFCbzRJQmREQ0MKQVhBd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0JrQXdOQVlKWUlaSUFZYjRRZ0VOQkNjVwpKVTlRVG5ObGJuTmxJRWRsYm1WeVlYUmxaQ0JUWlhKMlpYSWdRMlZ5ZEdsbWFXTmhkR1V3SFFZRFZSME9CQllFCkZMK2YzU0tCM0tMSi9nWStBUTJIZDBhTzVPRU1NSUd3QmdOVkhTTUVnYWd3Z2FXaGdZeWtnWWt3Z1lZeEdqQVkKQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVk1CTUdBMVVFQ0F3TQpXblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyQmdOVkJBb01KRTlRClRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWllJVUJaUGI1MDFzWjN4YU02c0gKNVQzVk1Eb2ZyeU13SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01Bc0dBMVVkRHdRRQpBd0lGb0RBY0JnTlZIUkVFRlRBVGdoRlBVRTV6Wlc1elpTNXBiblJsY201aGJEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBZ0VBY3F3VEN0RmVwWDlFOEFMYXRaWEE4dkN2b1YwZkxuaElPM0lWT2tZNEhYMTRRRU9NUGo4KzVXMXoKc3hyUGNscEJQVU5tbXJRb0hWUHBuWTRFM2hFanlYY1JWSzZtTEFrSkpRUDJPOEE2NFFpL3FNSkhCYUlEU0MzKwpqMHdkMGYyODRvcEppQ0F2UnF0SGh1bDd3akd4QzNZUXRxWTFMODNWUHBOWGRjOVVsZExTUGZ6WWluMlBPekMrClFDWU9qN3VQUDlCVGExTURudkdPdkdrOHdQeUJGaFZQWVFBWjYwb1ZaS2psOUI2R1piRzF1SG1ON3p3a3k0eEIKNk1RSlF3cHFscDdXQ09QSHJlZTFiVGZaMkJMZFQrZzJHakVMT0xNRGJMTHAzNUw0blBDUmNGRkJZNEszMHBncQpVWjdncEtNTmhuR3huQ29lR3dHUk54ZHFoZnNlT3FqaURVM0hBVDEya3FReU1vOEZNT1g1bG9EaVpXSGZTS0JrClVRaG5Tc1BTMG0vZ2dSRVgwNVQzYWM2NUxNOVVXMEs2MXppZUs5WFhTcjNXWlQ1TkhNV2JmU0VScUR4SGRtZ0YKeGt3YXkxZWpCZTFoZzdGMGpicTVDMGo1ZXB5ZDNOc1BTVUtDY2FDNi9aWTNkUHVYWVZZL0J2dnFsZHZJSzRBeAo0R1BLQ0xzaStGRjliSXZRWG4zbTV2KzJoQWF2WlpxcmJOTFUzVFQ0aDd2QllBOVRNcXpwd3lEaW5BR3RhaEE3CnhDSW5IU01kZXpQcnNkZDJrTW5TckhPdWtGeGdKb2lNZ3krVlJmdTdvZk9NMjhYQ1FySWF6djBFYmFhTU1ZMTcKRzlXOFd3SXZUb2lYY1ZWNTk3K1NUNHRsSTVIM3lDZFBtSk1kRm5GcDg1K2JzbW42MFQwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</crt>
<csr/>
<prv>LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRREFaM2ZNZWpHTnJGWFQKWFl3bi9NSnlhcFgwN1U5NVZnQkRHNmZZTXBmRkRTU1d4VFVqVEtGU29HclFKQ29GckNDUUNwbFp5bmtGY1RWbwpZUCtoazNyd1ZabEdlUXIvRHJHZmIzWU9zZEZsS25uWjljNFM4ZWtKRzZZMmFiNHhSU3JwY2tIaE1kODBzRDVBClF2SzVKRzYxL1QwSE1FdUxwaWtpQXcwWkJ6alZuOVVRSlJFMmJLb2Q5b0h2RGxuQ1RjU1dRVDVmK1dwNEpZdGoKdUFUcFkxSVFVbjBuTG5YMEdQL0lEa2xaMWt0Rk5zNE84ZytCZFVYVTQxR28zRkxaQnNYUFZvdFhVa0VZdkdGZQp2MlEyUHBKc2JvblpYSmhHbnlqZERSVmRkVkY4aFpwYm5rTDBTbElNUU14VEk0VytOdWZlL1E2RkQ3ZmZxUWtLCmh6ZndKUnY3R0YzZFNGUTBaV09QY1NlVldjeVZOVVUxMy9ycG15Sm81dmFZWEdNNEx3MW9XdnNRY1FsWlJZZ0oKTGFJYzMzR2cwZDJIeG9lMi93K0h4VFc4TDhmV2pvODErbnRhYldOVmFXQjB3ek1zRTRkQTlrcUdnZnFmY25vegpySi9qYm00QUFNMHpBOVVkWHRJQm1EZ2l4U2RXMHh4c1A2VlZFN0x2eVRFOFpwSU40aC9RQmFEcnNoYUVyek14ClR3TUhlcnVGVXhsWnFkR1JrcStBdElTVXBETTlVcHRhZlkyTnJ3ODFUMWFLMnBXblRZRUJLQlJ1cHZNNE8xRzUKc3k1TkZmbXc0VFNzSmpFSG0wUS9VNnhlTjltWDQ4WEhhRkU2eG9BMkRkQysyR0tpSlhZa2IvZ2Ztd3JqdWNzRgpYakFLa0M1YXplcWpoRFd4bENuYk1LVjBJNDE5andJREFRQUJBb0lDQUNVVkRBTE4zalVXN09leTFPdDBES251Cm52NDRxcU9SRHJYZ1k2WUlnalhKUmE4RlRTdURmbWdsWU5EQzE1S0dUVFJWeHA2R3BuS0ZFaTBPM05Yd1RiWjYKV1BNN0t3SmplNXBsNmhRRTgzMlRCUzhiNzk2NDN4Z1JTeVNibHJ0NlFENEQ5bXlIcHlSSmY0WDFJVURMbzhiUgppdXlTdzB5ajlyT0djUVRNM29oVnFNUFcwUTF6UGdwT1UxYVdwbmdMY3dNZWlmNEhYUnpRNTUrTmZPemFacHVjCnVtQk4xUS81clhxS1BscmhNVnFpcUc0Nit3QVJjU2NKdE5oZHRsMzdyeTQ1Mk5zNGtERkxSVnowZUVUNEpGSmYKcjVQRUE5bEFuYWlVOS9RdVEwbERtcTlqdmpYRkNURXhYKy82SGJHK2RVd0Y2OEY3ZVEzVFQxbkhHK0hkMVJsbgpOWm1JM0p2d0Z1cG9JeU9VdlpJb3VGVmo2ak8ra0JLejkza1BHWmdMbnNmUUw5WDhRbTU3cjh4K3Z1eFNudGI1CjV4WVBxRkdrOWQrbDUwbTlQakdkekxGT3UwYnJ5TmQ4MFVMS2tuUlFtUVpvTngxck5GTUxpSjNlZENWUS9lclUKT1BDQ0Z0WEJMemJGTjR2ZzVWRjZMUkhvZGxqcEgxRzJOSXNoSzJhc1FuWS9RWDFpUUNLSk1tWERSUndMTWVsNQp3MUF4T2FqYVkzbWx2ZlRVd2xqdkE3a0tFUDBvZzRPeXZldDA2WTVRWk1EQXc1V00yT0pZVDVxcmFlYjZDbTdMCjlNckk4bG50TGp3WFVSZG4yU3U2RCtCWXNpcC9KK3BvOFNqYlJBaGJIc0lJbkJ1QWJnbGxqdTB2QXRXZmFkQlQKOTg4YnUwK3VUb1Q2T1Jkbk84Y1JBb0lCQVFEcStWYkVUQWVpSHN6K29jZnFWV3VwVHJrc2FNNm1wUUMwb0tqZApwb1FzWGVuTmNiNThHQ3FhWHkvdTJHWmNKUnR1QXRHamEyUVpEUUFUSjQyVTFmaTFhWm90Y053eXhPdmlud1NjCmVLZyt0ZGcwdW9LeGs2aXJKRFptaDBIK3Ewblg2RFJYK25RNDVmWVNmRkRFK0ZLd1lac0dQMkhYV3dKaVZ6OE0KU2NkL2pETTFRTWV2OXIzZWx1dS9DWFlvZ1N0N00wMklyczVoNjRuNjFmdVZjNHI4YmUwdFkrUTVsUnlwWk9NVwpkQ2VkWGFOV3RaNjF2bEFxamNiWkpkdXFBUjJjNzAyR3NML201TXA4Zmd3YmY2aG51TXJLaVlpQjlZalZxalc2CmYyUW1PclZtMUk0MFJBMC9OaFBTR2NXejBkNXZrdXY0VHUra2JFbERZTCsxaHY1M0FvSUJBUURSbnZaTmJaa1UKTXpmUTRLWEdML3dLUXJEbjNvL0RENWVBR1ZDTGcwTUkyYlAxYWpubHNVTjE4NCs1UWF6cVVOaWlZT3laODczeQpQYkw0cTBOZWFDYXdxby9WbjJMSkVIUFVTTVhUWjB4ckxTa1hPUjFuMDUwT2tDWXhVbFpOUXFvZU1xcHJGNXZLCm1NNlJxalN4NS8ydU9IUlR1SDRVV2RETEpwTDVUN2RpUCtXcFUwSDlSUWhrNDdkQUJaUjZEZjNxaDJEYmVxUWoKdWcxY0hWUVNqaldhUGpVZGlLR2dHemdvdlE2UkdNZDA1UVUzdkRMdzBCSkNPQ25XV2x0VXkvMW1jMUpPUHR2ZQp4UGltV2tRNmlkRHZ4RGZFRGg5U05zY1FPMnBTVjZxNnhCTWlqVGgvTldGN2NsOU1LYUhJWGxzTmt0RFVXWHZyCmNKRlM4eE1TcDhlcEFvSUJBUUNtWktVTjRxMHhIOUNZckdYT1Nta3Yvc0JnYzJPTFhLTXdSZWp1OVFENkRoTUgKMmZsREZUWHVGV1B6SmlqdUxaVE1CWkVBd1lhanVySUgzbVdETlRhbStMNG1XWnFGRlMvWlRqUk12YUNlcjlVSQpHZDk4OG94cGpQNDlBcUU0UDRIT00vQUZNU1ZtT1dwVTB0VzdkZ0hRUjM0cElXOGV1cUxva3RIaDJNaytTRURuCkFCV29SUGxWaTlncmN2N0tWaFk5YXlvSGxZb3VpMFl0YTZSNXc5VnpSa0REZU01Zi9Iak1kOVhieTZ0VjQ3NU0KSTliYzZvVUliVmVYNUJnMnZnMkRXVzZ6NTZ3dFRHMGJWWU1yWWU0V2JTU2w0bGpaZHM5TVJ2ay9OUUR0bFh0cAo4ekUwVDlCMXA4ekhabHE3S08zMFlyMVpIRVRWVVoxYjZrSTN3UDJuQW9JQkFFZ1VGKzlCMjF4RnpGQ0hucGtLClVPa2FTNGcvVUVHcmI5VzlYcVBLUzllVVBEd0wvY0tNZEh6dmRpRW1neFhESE9xZzExcU1wR2pTYkdMelNPUUMKZmlOTFV0QUswVVgvNFVSQ2pidUdqcEZmNHZ3NFNITTJJWkFyWXVhY3dFNHF1U0pQRzZoZFl0V0VPNnQ4MGtmRwpWTVYrWmdtUHE5TEZtM1R2VzZSY2s5czF5M3V3eEVVWllxeUdYTEduK1lrS25KL3pVd3ZGSFFHbjdRWWFrNWtaCnl6YXhZMFEzZ2hQeXFCbmlBRXRHTVBkeDlKeFltMCtRekdaMnQzUWNkOEV0cjRGMTcvdzF3eGJUdGdoRmk2WngKVXlYTzI3b1BmUmVnL0V3SmtpS2tRSEdlRUZKV0t2SWE0ZDAzMDZyMXVjcVRIMDRJaU1RcnpOK0ZRb002VC9tZgpOWmtDZ2dFQUsrRVJNVVdJZTE3V1k3VDIycy9lOEplN0xxSXlUcU9mSGovaWFUUjhHbXhqcU1HNEdod1RpVXJsCkh0Skhud3BMVGFjVmdYUjV3UmtYcEhRT2JqSUFzeVNBUGxwSzBvZUkyK2kvS0cyQjZ2U0cza0V2b1VZY0RlRk4KdzhHd0oxNDNTd21LQXM4eUtWMmd1RjhmRXNNVitEQzNzVHFlZXJmMy82bFprMUVCVFF0QTZqVHdqK0picXgwVgpaalZJUXBwUE8vc1VHdi9LZVE3MW5ockJpT0lXclJ0dDRTUDJ2aWx2em9DUTQxVjFqZ09wS3VwU3E1Y2J3VDRxCmp1bkJIMkx5VnNQaUc4M0Vha1JSUEhDK0craTk1MFJxckxVRUJOeVdHNGlMNTdUdU9xYVJuSmRnN2ZFb2lVLzMKNld4TjlvR2VRWjV0NjZkdTJEL01WSUZ4ZzJ1cXRBPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=</prv>
</cert>
<syslog/>
</opnsense>

View File

@@ -0,0 +1,826 @@
<?xml version="1.0"?>
<opnsense>
<theme>opnsense</theme>
<sysctl version="1.0.1" persisted_at="1755708111.39">
<item/>
</sysctl>
<system>
<serialspeed>115200</serialspeed>
<primaryconsole>serial</primaryconsole>
<optimization>normal</optimization>
<hostname>OPNsense</hostname>
<domain>internal</domain>
<dnsallowoverride>1</dnsallowoverride>
<dnsallowoverride_exclude/>
<group uuid="67305f6f-7f7a-454d-8a4e-65cb8f072d81">
<gid>1999</gid>
<name>admins</name>
<scope>system</scope>
<description>System Administrators</description>
<priv>page-all</priv>
<member>0</member>
<source_networks/>
</group>
<user uuid="1d2ed537-5d1a-4772-9600-37b93f9f798b">
<uid>0</uid>
<name>root</name>
<disabled>0</disabled>
<scope>system</scope>
<expires/>
<authorizedkeys/>
<otp_seed/>
<shell/>
<password>$2y$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS</password>
<pwd_changed_at/>
<landing_page/>
<comment/>
<email/>
<apikeys/>
<priv/>
<language/>
<descr>System Administrator</descr>
<dashboard/>
</user>
<timezone>Etc/UTC</timezone>
<timeservers>0.opnsense.pool.ntp.org 1.opnsense.pool.ntp.org 2.opnsense.pool.ntp.org 3.opnsense.pool.ntp.org</timeservers>
<webgui>
<protocol>https</protocol>
<ssl-certref>68a5faf1685db</ssl-certref>
<port/>
<ssl-ciphers/>
<interfaces/>
<compression/>
</webgui>
<disablenatreflection>yes</disablenatreflection>
<usevirtualterminal>1</usevirtualterminal>
<disableconsolemenu>1</disableconsolemenu>
<disablevlanhwfilter>1</disablevlanhwfilter>
<disablechecksumoffloading>1</disablechecksumoffloading>
<disablesegmentationoffloading>1</disablesegmentationoffloading>
<disablelargereceiveoffloading>1</disablelargereceiveoffloading>
<ipv6allow>1</ipv6allow>
<powerd_ac_mode>hadp</powerd_ac_mode>
<powerd_battery_mode>hadp</powerd_battery_mode>
<powerd_normal_mode>hadp</powerd_normal_mode>
<bogons>
<interval>monthly</interval>
</bogons>
<pf_share_forward>1</pf_share_forward>
<lb_use_sticky>1</lb_use_sticky>
<ssh>
<group>admins</group>
<noauto>1</noauto>
<interfaces/>
<kex/>
<ciphers/>
<macs/>
<keys/>
<keysig/>
<rekeylimit/>
<enabled>enabled</enabled>
<passwordauth>1</passwordauth>
<permitrootlogin>1</permitrootlogin>
</ssh>
<rrdbackup>-1</rrdbackup>
<netflowbackup>-1</netflowbackup>
<firmware version="1.0.1" persisted_at="1755708111.32">
<mirror/>
<flavour/>
<plugins/>
<type/>
<subscription/>
<reboot>0</reboot>
</firmware>
<dnsserver/>
<language>en_US</language>
</system>
<interfaces>
<wan>
<enable>1</enable>
<if>vtnet0</if>
<mtu/>
<ipaddr>dhcp</ipaddr>
<ipaddrv6>dhcp6</ipaddrv6>
<subnet/>
<gateway/>
<blockpriv>0</blockpriv>
<blockbogons>1</blockbogons>
<media/>
<mediaopt/>
<dhcp6-ia-pd-len>0</dhcp6-ia-pd-len>
<dhcphostname/>
<spoofmac/>
<mss/>
</wan>
<lan>
<enable>1</enable>
<if>vtnet1</if>
<ipaddr>192.168.1.1</ipaddr>
<subnet>24</subnet>
<ipaddrv6>track6</ipaddrv6>
<subnetv6>64</subnetv6>
<media/>
<mediaopt/>
<track6-interface>wan</track6-interface>
<track6-prefix-id>0</track6-prefix-id>
</lan>
<lo0>
<internal_dynamic>1</internal_dynamic>
<descr>Loopback</descr>
<enable>1</enable>
<if>lo0</if>
<ipaddr>127.0.0.1</ipaddr>
<ipaddrv6>::1</ipaddrv6>
<subnet>8</subnet>
<subnetv6>128</subnetv6>
<type>none</type>
<virtual>1</virtual>
</lo0>
</interfaces>
<dnsmasq version="1.0.7" persisted_at="1755721633.54">
<enable>1</enable>
<regdhcp>0</regdhcp>
<regdhcpstatic>0</regdhcpstatic>
<dhcpfirst>0</dhcpfirst>
<strict_order>0</strict_order>
<domain_needed>0</domain_needed>
<no_private_reverse>0</no_private_reverse>
<no_resolv>0</no_resolv>
<log_queries>0</log_queries>
<no_hosts>0</no_hosts>
<strictbind>0</strictbind>
<dnssec>0</dnssec>
<regdhcpdomain/>
<interface>lan</interface>
<port>0</port>
<dns_forward_max/>
<cache_size/>
<local_ttl/>
<add_mac/>
<add_subnet>0</add_subnet>
<strip_subnet>0</strip_subnet>
<dhcp>
<no_interface/>
<fqdn>1</fqdn>
<domain/>
<lease_max/>
<authoritative>0</authoritative>
<default_fw_rules>1</default_fw_rules>
<reply_delay/>
<enable_ra>0</enable_ra>
<nosync>0</nosync>
</dhcp>
<no_ident>1</no_ident>
<dhcp_ranges uuid="78b5c4a4-565d-4cd7-af10-29050f29e494">
<interface>lan</interface>
<set_tag/>
<start_addr>192.168.1.41</start_addr>
<end_addr>192.168.1.245</end_addr>
<subnet_mask/>
<constructor/>
<mode/>
<prefix_len/>
<lease_time/>
<domain_type>range</domain_type>
<domain/>
<nosync>0</nosync>
<ra_mode/>
<ra_priority/>
<ra_mtu/>
<ra_interval/>
<ra_router_lifetime/>
<description/>
</dhcp_ranges>
</dnsmasq>
<snmpd>
<syslocation/>
<syscontact/>
<rocommunity>public</rocommunity>
</snmpd>
<nat>
<outbound>
<mode>automatic</mode>
</outbound>
</nat>
<filter>
<rule>
<type>pass</type>
<ipprotocol>inet</ipprotocol>
<descr>Default allow LAN to any rule</descr>
<interface>lan</interface>
<source>
<network>lan</network>
</source>
<destination>
<any/>
</destination>
</rule>
<rule>
<type>pass</type>
<ipprotocol>inet6</ipprotocol>
<descr>Default allow LAN IPv6 to any rule</descr>
<interface>lan</interface>
<source>
<network>lan</network>
</source>
<destination>
<any/>
</destination>
</rule>
</filter>
<rrd>
<enable/>
</rrd>
<ntpd>
<prefer>0.opnsense.pool.ntp.org</prefer>
</ntpd>
<revision>
<username>root@192.168.1.5</username>
<description>/system_advanced_admin.php made changes</description>
<time>1755721653.06</time>
</revision>
<OPNsense>
<wireguard>
<client version="1.0.0" persisted_at="1755708111.04">
<clients/>
</client>
<general version="0.0.1" persisted_at="1755708111.05">
<enabled>0</enabled>
</general>
<server version="1.0.0" persisted_at="1755708111.05">
<servers/>
</server>
</wireguard>
<IPsec version="1.0.4" persisted_at="1755708111.06">
<general>
<enabled/>
<preferred_oldsa>0</preferred_oldsa>
<disablevpnrules>0</disablevpnrules>
<passthrough_networks/>
<user_source/>
<local_group/>
</general>
<charon>
<max_ikev1_exchanges/>
<threads>16</threads>
<ikesa_table_size>32</ikesa_table_size>
<ikesa_table_segments>4</ikesa_table_segments>
<init_limit_half_open>1000</init_limit_half_open>
<ignore_acquire_ts>1</ignore_acquire_ts>
<install_routes>0</install_routes>
<cisco_unity>0</cisco_unity>
<make_before_break>0</make_before_break>
<retransmit_tries/>
<retransmit_timeout/>
<retransmit_base/>
<retransmit_jitter/>
<retransmit_limit/>
<syslog>
<daemon>
<ike_name>1</ike_name>
<log_level>0</log_level>
<app>1</app>
<asn>1</asn>
<cfg>1</cfg>
<chd>1</chd>
<dmn>1</dmn>
<enc>1</enc>
<esp>1</esp>
<ike>1</ike>
<imc>1</imc>
<imv>1</imv>
<job>1</job>
<knl>1</knl>
<lib>1</lib>
<mgr>1</mgr>
<net>1</net>
<pts>1</pts>
<tls>1</tls>
<tnc>1</tnc>
</daemon>
</syslog>
<plugins>
<attr>
<subnet/>
<split-include/>
<x_28674/>
<x_28675/>
<x_28672/>
<x_28673>0</x_28673>
<x_28679/>
<dns/>
<nbns/>
</attr>
<eap-radius>
<servers/>
<accounting>0</accounting>
<class_group>0</class_group>
</eap-radius>
<xauth-pam>
<pam_service>ipsec</pam_service>
<session>0</session>
<trim_email>1</trim_email>
</xauth-pam>
</plugins>
</charon>
<keyPairs/>
<preSharedKeys/>
</IPsec>
<Swanctl version="1.0.0" persisted_at="1755708111.08">
<Connections/>
<locals/>
<remotes/>
<children/>
<Pools/>
<VTIs/>
<SPDs/>
</Swanctl>
<OpenVPNExport version="0.0.1" persisted_at="1755708111.40">
<servers/>
</OpenVPNExport>
<OpenVPN version="1.0.1" persisted_at="1755708111.40">
<Overwrites/>
<Instances/>
<StaticKeys/>
</OpenVPN>
<captiveportal version="1.0.4" persisted_at="1755708111.41">
<zones/>
<templates/>
</captiveportal>
<cron version="1.0.4" persisted_at="1755708111.43">
<jobs/>
</cron>
<DHCRelay version="1.0.1" persisted_at="1755708111.43"/>
<Firewall>
<Lvtemplate version="0.0.1" persisted_at="1755708111.45">
<templates/>
</Lvtemplate>
<Alias version="1.0.1" persisted_at="1755708111.65">
<geoip>
<url/>
</geoip>
<aliases/>
</Alias>
<Category version="1.0.0" persisted_at="1755708111.65">
<categories/>
</Category>
<Filter version="1.0.4" persisted_at="1755708111.70">
<rules/>
<snatrules/>
<npt/>
<onetoone/>
</Filter>
</Firewall>
<Netflow version="1.0.1" persisted_at="1755708111.45">
<capture>
<interfaces/>
<egress_only/>
<version>v9</version>
<targets/>
</capture>
<collect>
<enable>0</enable>
</collect>
<activeTimeout>1800</activeTimeout>
<inactiveTimeout>15</inactiveTimeout>
</Netflow>
<IDS version="1.1.0" persisted_at="1755708111.90">
<rules/>
<policies/>
<userDefinedRules/>
<files/>
<fileTags/>
<general>
<enabled>0</enabled>
<ips>0</ips>
<promisc>0</promisc>
<interfaces>wan</interfaces>
<homenet>192.168.0.0/16,10.0.0.0/8,172.16.0.0/12</homenet>
<defaultPacketSize/>
<UpdateCron/>
<AlertLogrotate>W0D23</AlertLogrotate>
<AlertSaveLogs>4</AlertSaveLogs>
<MPMAlgo/>
<detect>
<Profile/>
<toclient_groups/>
<toserver_groups/>
</detect>
<syslog>0</syslog>
<syslog_eve>0</syslog_eve>
<LogPayload>0</LogPayload>
<verbosity/>
<eveLog>
<http>
<enable>0</enable>
<extended>0</extended>
<dumpAllHeaders/>
</http>
<tls>
<enable>0</enable>
<extended>0</extended>
<sessionResumption>0</sessionResumption>
<custom/>
</tls>
</eveLog>
</general>
</IDS>
<Interfaces>
<loopbacks version="1.0.0" persisted_at="1755708111.95"/>
<neighbors version="1.0.0" persisted_at="1755708111.96"/>
<vxlans version="1.0.2" persisted_at="1755708111.99"/>
</Interfaces>
<Kea>
<ctrl_agent version="0.0.1" persisted_at="1755708111.99">
<general>
<enabled>0</enabled>
<http_host>127.0.0.1</http_host>
<http_port>8000</http_port>
</general>
</ctrl_agent>
<dhcp4 version="1.0.4" persisted_at="1755708112.00">
<general>
<enabled>0</enabled>
<manual_config>0</manual_config>
<interfaces/>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
<dhcp_socket_type>raw</dhcp_socket_type>
</general>
<ha>
<enabled>0</enabled>
<this_server_name/>
<max_unacked_clients>2</max_unacked_clients>
</ha>
<subnets/>
<reservations/>
<ha_peers/>
</dhcp4>
<dhcp6 version="1.0.0" persisted_at="1755708112.00">
<general>
<enabled>0</enabled>
<manual_config>0</manual_config>
<interfaces/>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
</general>
<ha>
<enabled>0</enabled>
<this_server_name/>
<max_unacked_clients>2</max_unacked_clients>
</ha>
<subnets/>
<reservations/>
<pd_pools/>
<ha_peers/>
</dhcp6>
</Kea>
<monit version="1.0.13" persisted_at="1755708112.02">
<general>
<enabled>0</enabled>
<interval>120</interval>
<startdelay>120</startdelay>
<mailserver>127.0.0.1</mailserver>
<port>25</port>
<username/>
<password/>
<ssl>0</ssl>
<sslversion>auto</sslversion>
<sslverify>1</sslverify>
<logfile/>
<statefile/>
<eventqueuePath/>
<eventqueueSlots/>
<httpdEnabled>0</httpdEnabled>
<httpdUsername>root</httpdUsername>
<httpdPassword/>
<httpdPort>2812</httpdPort>
<httpdAllow/>
<mmonitUrl/>
<mmonitTimeout>5</mmonitTimeout>
<mmonitRegisterCredentials>1</mmonitRegisterCredentials>
</general>
<alert uuid="76cd5195-a487-4f4f-8ef5-4f9815bf19e5">
<enabled>0</enabled>
<recipient>root@localhost.local</recipient>
<noton>0</noton>
<events/>
<format/>
<reminder/>
<description/>
</alert>
<service uuid="11610907-f700-4f0a-9926-c25a47709493">
<enabled>1</enabled>
<name>$HOST</name>
<description/>
<type>system</type>
<pidfile/>
<match/>
<path/>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>02014be3-fc31-4af3-a0d5-061eaa67d28a,ebfd0d97-ae21-45d5-8b42-5220c75ce46f,d37f25f0-89e3-44b6-8ad2-280ac83a8904,37afd0d9-990c-4f03-a817-45691461e3d0</tests>
<depends/>
<polltime/>
</service>
<service uuid="dcc7e42f-a878-49a3-8b90-67faf21d67bd">
<enabled>1</enabled>
<name>RootFs</name>
<description/>
<type>filesystem</type>
<pidfile/>
<match/>
<path>/</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>b44b859c-bc72-4c2e-82c9-4f56d84a5497</tests>
<depends/>
<polltime/>
</service>
<service uuid="5ca544d4-846a-4f61-9d6f-c191eafcd9fb">
<enabled>0</enabled>
<name>carp_status_change</name>
<description/>
<type>custom</type>
<pidfile/>
<match/>
<path>/usr/local/opnsense/scripts/OPNsense/Monit/carp_status</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>0909801b-cd11-41c8-afeb-369396247308</tests>
<depends/>
<polltime/>
</service>
<service uuid="6e16e26e-e732-4b9b-ac92-2086b84f3158">
<enabled>0</enabled>
<name>gateway_alert</name>
<description/>
<type>custom</type>
<pidfile/>
<match/>
<path>/usr/local/opnsense/scripts/OPNsense/Monit/gateway_alert</path>
<timeout>300</timeout>
<starttimeout>30</starttimeout>
<address/>
<interface/>
<start/>
<stop/>
<tests>56e67d76-cef6-4167-a51e-2c69a921ebc9</tests>
<depends/>
<polltime/>
</service>
<test uuid="b7671673-5cf1-4123-a58c-37f57a8e9d59">
<name>Ping</name>
<type>NetworkPing</type>
<condition>failed ping</condition>
<action>alert</action>
<path/>
</test>
<test uuid="30cf3664-5c6f-4ac5-a52a-36023270c6fb">
<name>NetworkLink</name>
<type>NetworkInterface</type>
<condition>failed link</condition>
<action>alert</action>
<path/>
</test>
<test uuid="a7c0836a-b102-4f37-a73b-2a50903ffcc8">
<name>NetworkSaturation</name>
<type>NetworkInterface</type>
<condition>saturation is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="02014be3-fc31-4af3-a0d5-061eaa67d28a">
<name>MemoryUsage</name>
<type>SystemResource</type>
<condition>memory usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="ebfd0d97-ae21-45d5-8b42-5220c75ce46f">
<name>CPUUsage</name>
<type>SystemResource</type>
<condition>cpu usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="d37f25f0-89e3-44b6-8ad2-280ac83a8904">
<name>LoadAvg1</name>
<type>SystemResource</type>
<condition>loadavg (1min) is greater than 4</condition>
<action>alert</action>
<path/>
</test>
<test uuid="37afd0d9-990c-4f03-a817-45691461e3d0">
<name>LoadAvg5</name>
<type>SystemResource</type>
<condition>loadavg (5min) is greater than 3</condition>
<action>alert</action>
<path/>
</test>
<test uuid="608d2888-9df5-486d-bbfb-bf17fad75a7e">
<name>LoadAvg15</name>
<type>SystemResource</type>
<condition>loadavg (15min) is greater than 2</condition>
<action>alert</action>
<path/>
</test>
<test uuid="b44b859c-bc72-4c2e-82c9-4f56d84a5497">
<name>SpaceUsage</name>
<type>SpaceUsage</type>
<condition>space usage is greater than 75%</condition>
<action>alert</action>
<path/>
</test>
<test uuid="0909801b-cd11-41c8-afeb-369396247308">
<name>ChangedStatus</name>
<type>ProgramStatus</type>
<condition>changed status</condition>
<action>alert</action>
<path/>
</test>
<test uuid="56e67d76-cef6-4167-a51e-2c69a921ebc9">
<name>NonZeroStatus</name>
<type>ProgramStatus</type>
<condition>status != 0</condition>
<action>alert</action>
<path/>
</test>
</monit>
<Gateways version="1.0.0" persisted_at="1755721633.53"/>
<Syslog version="1.0.2" persisted_at="1755708112.05">
<general>
<enabled>1</enabled>
<loglocal>1</loglocal>
<maxpreserve>31</maxpreserve>
<maxfilesize/>
</general>
<destinations/>
</Syslog>
<TrafficShaper version="1.0.3" persisted_at="1755708112.06">
<pipes/>
<queues/>
<rules/>
</TrafficShaper>
<trust>
<general version="1.0.1" persisted_at="1755708112.22">
<store_intermediate_certs>0</store_intermediate_certs>
<install_crls>0</install_crls>
<fetch_crls>0</fetch_crls>
<enable_legacy_sect>1</enable_legacy_sect>
<enable_config_constraints>0</enable_config_constraints>
<CipherString/>
<Ciphersuites/>
<SignatureAlgorithms/>
<groups/>
<MinProtocol/>
<MinProtocol_DTLS/>
</general>
</trust>
<unboundplus version="1.0.12" persisted_at="1755708112.29">
<general>
<enabled>1</enabled>
<port>53</port>
<stats>0</stats>
<active_interface/>
<dnssec>0</dnssec>
<dns64>0</dns64>
<dns64prefix/>
<noarecords>0</noarecords>
<regdhcp>0</regdhcp>
<regdhcpdomain/>
<regdhcpstatic>0</regdhcpstatic>
<noreglladdr6>0</noreglladdr6>
<noregrecords>0</noregrecords>
<txtsupport>0</txtsupport>
<cacheflush>0</cacheflush>
<local_zone_type>transparent</local_zone_type>
<outgoing_interface/>
<enable_wpad>0</enable_wpad>
</general>
<advanced>
<hideidentity>0</hideidentity>
<hideversion>0</hideversion>
<prefetch>0</prefetch>
<prefetchkey>0</prefetchkey>
<dnssecstripped>0</dnssecstripped>
<aggressivensec>1</aggressivensec>
<serveexpired>0</serveexpired>
<serveexpiredreplyttl/>
<serveexpiredttl/>
<serveexpiredttlreset>0</serveexpiredttlreset>
<serveexpiredclienttimeout/>
<qnameminstrict>0</qnameminstrict>
<extendedstatistics>0</extendedstatistics>
<logqueries>0</logqueries>
<logreplies>0</logreplies>
<logtagqueryreply>0</logtagqueryreply>
<logservfail>0</logservfail>
<loglocalactions>0</loglocalactions>
<logverbosity>1</logverbosity>
<valloglevel>0</valloglevel>
<privatedomain/>
<privateaddress>0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,169.254.0.0/16,172.16.0.0/12,192.0.2.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,233.252.0.0/24,::1/128,2001:db8::/32,fc00::/8,fd00::/8,fe80::/10</privateaddress>
<insecuredomain/>
<msgcachesize/>
<rrsetcachesize/>
<outgoingnumtcp/>
<incomingnumtcp/>
<numqueriesperthread/>
<outgoingrange/>
<jostletimeout/>
<discardtimeout/>
<cachemaxttl/>
<cachemaxnegativettl/>
<cacheminttl/>
<infrahostttl/>
<infrakeepprobing>0</infrakeepprobing>
<infracachenumhosts/>
<unwantedreplythreshold/>
</advanced>
<acls>
<default_action>allow</default_action>
</acls>
<dnsbl>
<enabled>0</enabled>
<safesearch>0</safesearch>
<type/>
<lists/>
<whitelists/>
<blocklists/>
<wildcards/>
<address/>
<nxdomain>0</nxdomain>
</dnsbl>
<forwarding>
<enabled>0</enabled>
</forwarding>
<dots/>
<hosts/>
<aliases/>
</unboundplus>
</OPNsense>
<hasync version="1.0.2" persisted_at="1755708111.35">
<disablepreempt>0</disablepreempt>
<disconnectppps>0</disconnectppps>
<pfsyncinterface/>
<pfsyncpeerip/>
<pfsyncversion>1400</pfsyncversion>
<synchronizetoip/>
<verifypeer>0</verifypeer>
<username/>
<password/>
<syncitems/>
</hasync>
<openvpn/>
<ifgroups version="1.0.0" persisted_at="1755708111.71"/>
<bridges version="1.0.0" persisted_at="1755708111.91">
<bridged/>
</bridges>
<gifs version="1.0.0" persisted_at="1755708111.92">
<gif/>
</gifs>
<gres version="1.0.0" persisted_at="1755708111.93">
<gre/>
</gres>
<laggs version="1.0.0" persisted_at="1755708111.95">
<lagg/>
</laggs>
<virtualip version="1.0.1" persisted_at="1755708111.96">
<vip/>
</virtualip>
<vlans version="1.0.0" persisted_at="1755708111.98">
<vlan/>
</vlans>
<staticroutes version="1.0.0" persisted_at="1755708112.03"/>
<ppps>
<ppp/>
</ppps>
<wireless>
<clone/>
</wireless>
<ca/>
<dhcpd/>
<dhcpdv6/>
<cert uuid="d60972af-642c-411f-abb6-43e0d680fefd">
<refid>68a5faf1685db</refid>
<descr>Web GUI TLS certificate</descr>
<caref/>
<crt>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQlBxZ0F3SUJBZ0lVQlpQYjUwMXNaM3hhTTZzSDVUM1ZNRG9mcnlNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhHakFZQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVgpNQk1HQTFVRUNBd01XblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyCkJnTlZCQW9NSkU5UVRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWlRBZUZ3MHkKTlRBNE1qQXhOalF5TWpaYUZ3MHlOakE1TWpFeE5qUXlNalphTUlHR01Sb3dHQVlEVlFRRERCRlBVRTV6Wlc1egpaUzVwYm5SbGNtNWhiREVMTUFrR0ExVUVCaE1DVGt3eEZUQVRCZ05WQkFnTURGcDFhV1F0U0c5c2JHRnVaREVWCk1CTUdBMVVFQnd3TVRXbGtaR1ZzYUdGeWJtbHpNUzB3S3dZRFZRUUtEQ1JQVUU1elpXNXpaU0J6Wld4bUxYTnAKWjI1bFpDQjNaV0lnWTJWeWRHbG1hV05oZEdVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFEQVozZk1lakdOckZYVFhZd24vTUp5YXBYMDdVOTVWZ0JERzZmWU1wZkZEU1NXeFRValRLRlNvR3JRCkpDb0ZyQ0NRQ3BsWnlua0ZjVFZvWVAraGszcndWWmxHZVFyL0RyR2ZiM1lPc2RGbEtublo5YzRTOGVrSkc2WTIKYWI0eFJTcnBja0hoTWQ4MHNENUFRdks1Skc2MS9UMEhNRXVMcGlraUF3MFpCempWbjlVUUpSRTJiS29kOW9IdgpEbG5DVGNTV1FUNWYrV3A0Sll0anVBVHBZMUlRVW4wbkxuWDBHUC9JRGtsWjFrdEZOczRPOGcrQmRVWFU0MUdvCjNGTFpCc1hQVm90WFVrRVl2R0ZldjJRMlBwSnNib25aWEpoR255amREUlZkZFZGOGhacGJua0wwU2xJTVFNeFQKSTRXK051ZmUvUTZGRDdmZnFRa0toemZ3SlJ2N0dGM2RTRlEwWldPUGNTZVZXY3lWTlVVMTMvcnBteUpvNXZhWQpYR000THcxb1d2c1FjUWxaUllnSkxhSWMzM0dnMGQySHhvZTIvdytIeFRXOEw4ZldqbzgxK250YWJXTlZhV0IwCnd6TXNFNGRBOWtxR2dmcWZjbm96ckovamJtNEFBTTB6QTlVZFh0SUJtRGdpeFNkVzB4eHNQNlZWRTdMdnlURTgKWnBJTjRoL1FCYURyc2hhRXJ6TXhUd01IZXJ1RlV4bFpxZEdSa3ErQXRJU1VwRE05VXB0YWZZMk5ydzgxVDFhSwoycFduVFlFQktCUnVwdk00TzFHNXN5NU5GZm13NFRTc0pqRUhtMFEvVTZ4ZU45bVg0OFhIYUZFNnhvQTJEZEMrCjJHS2lKWFlrYi9nZm13cmp1Y3NGWGpBS2tDNWF6ZXFqaERXeGxDbmJNS1YwSTQxOWp3SURBUUFCbzRJQmREQ0MKQVhBd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0JrQXdOQVlKWUlaSUFZYjRRZ0VOQkNjVwpKVTlRVG5ObGJuTmxJRWRsYm1WeVlYUmxaQ0JUWlhKMlpYSWdRMlZ5ZEdsbWFXTmhkR1V3SFFZRFZSME9CQllFCkZMK2YzU0tCM0tMSi9nWStBUTJIZDBhTzVPRU1NSUd3QmdOVkhTTUVnYWd3Z2FXaGdZeWtnWWt3Z1lZeEdqQVkKQmdOVkJBTU1FVTlRVG5ObGJuTmxMbWx1ZEdWeWJtRnNNUXN3Q1FZRFZRUUdFd0pPVERFVk1CTUdBMVVFQ0F3TQpXblZwWkMxSWIyeHNZVzVrTVJVd0V3WURWUVFIREF4TmFXUmtaV3hvWVhKdWFYTXhMVEFyQmdOVkJBb01KRTlRClRuTmxibk5sSUhObGJHWXRjMmxuYm1Wa0lIZGxZaUJqWlhKMGFXWnBZMkYwWllJVUJaUGI1MDFzWjN4YU02c0gKNVQzVk1Eb2ZyeU13SFFZRFZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQ0FJQ01Bc0dBMVVkRHdRRQpBd0lGb0RBY0JnTlZIUkVFRlRBVGdoRlBVRTV6Wlc1elpTNXBiblJsY201aGJEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBZ0VBY3F3VEN0RmVwWDlFOEFMYXRaWEE4dkN2b1YwZkxuaElPM0lWT2tZNEhYMTRRRU9NUGo4KzVXMXoKc3hyUGNscEJQVU5tbXJRb0hWUHBuWTRFM2hFanlYY1JWSzZtTEFrSkpRUDJPOEE2NFFpL3FNSkhCYUlEU0MzKwpqMHdkMGYyODRvcEppQ0F2UnF0SGh1bDd3akd4QzNZUXRxWTFMODNWUHBOWGRjOVVsZExTUGZ6WWluMlBPekMrClFDWU9qN3VQUDlCVGExTURudkdPdkdrOHdQeUJGaFZQWVFBWjYwb1ZaS2psOUI2R1piRzF1SG1ON3p3a3k0eEIKNk1RSlF3cHFscDdXQ09QSHJlZTFiVGZaMkJMZFQrZzJHakVMT0xNRGJMTHAzNUw0blBDUmNGRkJZNEszMHBncQpVWjdncEtNTmhuR3huQ29lR3dHUk54ZHFoZnNlT3FqaURVM0hBVDEya3FReU1vOEZNT1g1bG9EaVpXSGZTS0JrClVRaG5Tc1BTMG0vZ2dSRVgwNVQzYWM2NUxNOVVXMEs2MXppZUs5WFhTcjNXWlQ1TkhNV2JmU0VScUR4SGRtZ0YKeGt3YXkxZWpCZTFoZzdGMGpicTVDMGo1ZXB5ZDNOc1BTVUtDY2FDNi9aWTNkUHVYWVZZL0J2dnFsZHZJSzRBeAo0R1BLQ0xzaStGRjliSXZRWG4zbTV2KzJoQWF2WlpxcmJOTFUzVFQ0aDd2QllBOVRNcXpwd3lEaW5BR3RhaEE3CnhDSW5IU01kZXpQcnNkZDJrTW5TckhPdWtGeGdKb2lNZ3krVlJmdTdvZk9NMjhYQ1FySWF6djBFYmFhTU1ZMTcKRzlXOFd3SXZUb2lYY1ZWNTk3K1NUNHRsSTVIM3lDZFBtSk1kRm5GcDg1K2JzbW42MFQwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</crt>
<csr/>
<prv>LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRREFaM2ZNZWpHTnJGWFQKWFl3bi9NSnlhcFgwN1U5NVZnQkRHNmZZTXBmRkRTU1d4VFVqVEtGU29HclFKQ29GckNDUUNwbFp5bmtGY1RWbwpZUCtoazNyd1ZabEdlUXIvRHJHZmIzWU9zZEZsS25uWjljNFM4ZWtKRzZZMmFiNHhSU3JwY2tIaE1kODBzRDVBClF2SzVKRzYxL1QwSE1FdUxwaWtpQXcwWkJ6alZuOVVRSlJFMmJLb2Q5b0h2RGxuQ1RjU1dRVDVmK1dwNEpZdGoKdUFUcFkxSVFVbjBuTG5YMEdQL0lEa2xaMWt0Rk5zNE84ZytCZFVYVTQxR28zRkxaQnNYUFZvdFhVa0VZdkdGZQp2MlEyUHBKc2JvblpYSmhHbnlqZERSVmRkVkY4aFpwYm5rTDBTbElNUU14VEk0VytOdWZlL1E2RkQ3ZmZxUWtLCmh6ZndKUnY3R0YzZFNGUTBaV09QY1NlVldjeVZOVVUxMy9ycG15Sm81dmFZWEdNNEx3MW9XdnNRY1FsWlJZZ0oKTGFJYzMzR2cwZDJIeG9lMi93K0h4VFc4TDhmV2pvODErbnRhYldOVmFXQjB3ek1zRTRkQTlrcUdnZnFmY25vegpySi9qYm00QUFNMHpBOVVkWHRJQm1EZ2l4U2RXMHh4c1A2VlZFN0x2eVRFOFpwSU40aC9RQmFEcnNoYUVyek14ClR3TUhlcnVGVXhsWnFkR1JrcStBdElTVXBETTlVcHRhZlkyTnJ3ODFUMWFLMnBXblRZRUJLQlJ1cHZNNE8xRzUKc3k1TkZmbXc0VFNzSmpFSG0wUS9VNnhlTjltWDQ4WEhhRkU2eG9BMkRkQysyR0tpSlhZa2IvZ2Ztd3JqdWNzRgpYakFLa0M1YXplcWpoRFd4bENuYk1LVjBJNDE5andJREFRQUJBb0lDQUNVVkRBTE4zalVXN09leTFPdDBES251Cm52NDRxcU9SRHJYZ1k2WUlnalhKUmE4RlRTdURmbWdsWU5EQzE1S0dUVFJWeHA2R3BuS0ZFaTBPM05Yd1RiWjYKV1BNN0t3SmplNXBsNmhRRTgzMlRCUzhiNzk2NDN4Z1JTeVNibHJ0NlFENEQ5bXlIcHlSSmY0WDFJVURMbzhiUgppdXlTdzB5ajlyT0djUVRNM29oVnFNUFcwUTF6UGdwT1UxYVdwbmdMY3dNZWlmNEhYUnpRNTUrTmZPemFacHVjCnVtQk4xUS81clhxS1BscmhNVnFpcUc0Nit3QVJjU2NKdE5oZHRsMzdyeTQ1Mk5zNGtERkxSVnowZUVUNEpGSmYKcjVQRUE5bEFuYWlVOS9RdVEwbERtcTlqdmpYRkNURXhYKy82SGJHK2RVd0Y2OEY3ZVEzVFQxbkhHK0hkMVJsbgpOWm1JM0p2d0Z1cG9JeU9VdlpJb3VGVmo2ak8ra0JLejkza1BHWmdMbnNmUUw5WDhRbTU3cjh4K3Z1eFNudGI1CjV4WVBxRkdrOWQrbDUwbTlQakdkekxGT3UwYnJ5TmQ4MFVMS2tuUlFtUVpvTngxck5GTUxpSjNlZENWUS9lclUKT1BDQ0Z0WEJMemJGTjR2ZzVWRjZMUkhvZGxqcEgxRzJOSXNoSzJhc1FuWS9RWDFpUUNLSk1tWERSUndMTWVsNQp3MUF4T2FqYVkzbWx2ZlRVd2xqdkE3a0tFUDBvZzRPeXZldDA2WTVRWk1EQXc1V00yT0pZVDVxcmFlYjZDbTdMCjlNckk4bG50TGp3WFVSZG4yU3U2RCtCWXNpcC9KK3BvOFNqYlJBaGJIc0lJbkJ1QWJnbGxqdTB2QXRXZmFkQlQKOTg4YnUwK3VUb1Q2T1Jkbk84Y1JBb0lCQVFEcStWYkVUQWVpSHN6K29jZnFWV3VwVHJrc2FNNm1wUUMwb0tqZApwb1FzWGVuTmNiNThHQ3FhWHkvdTJHWmNKUnR1QXRHamEyUVpEUUFUSjQyVTFmaTFhWm90Y053eXhPdmlud1NjCmVLZyt0ZGcwdW9LeGs2aXJKRFptaDBIK3Ewblg2RFJYK25RNDVmWVNmRkRFK0ZLd1lac0dQMkhYV3dKaVZ6OE0KU2NkL2pETTFRTWV2OXIzZWx1dS9DWFlvZ1N0N00wMklyczVoNjRuNjFmdVZjNHI4YmUwdFkrUTVsUnlwWk9NVwpkQ2VkWGFOV3RaNjF2bEFxamNiWkpkdXFBUjJjNzAyR3NML201TXA4Zmd3YmY2aG51TXJLaVlpQjlZalZxalc2CmYyUW1PclZtMUk0MFJBMC9OaFBTR2NXejBkNXZrdXY0VHUra2JFbERZTCsxaHY1M0FvSUJBUURSbnZaTmJaa1UKTXpmUTRLWEdML3dLUXJEbjNvL0RENWVBR1ZDTGcwTUkyYlAxYWpubHNVTjE4NCs1UWF6cVVOaWlZT3laODczeQpQYkw0cTBOZWFDYXdxby9WbjJMSkVIUFVTTVhUWjB4ckxTa1hPUjFuMDUwT2tDWXhVbFpOUXFvZU1xcHJGNXZLCm1NNlJxalN4NS8ydU9IUlR1SDRVV2RETEpwTDVUN2RpUCtXcFUwSDlSUWhrNDdkQUJaUjZEZjNxaDJEYmVxUWoKdWcxY0hWUVNqaldhUGpVZGlLR2dHemdvdlE2UkdNZDA1UVUzdkRMdzBCSkNPQ25XV2x0VXkvMW1jMUpPUHR2ZQp4UGltV2tRNmlkRHZ4RGZFRGg5U05zY1FPMnBTVjZxNnhCTWlqVGgvTldGN2NsOU1LYUhJWGxzTmt0RFVXWHZyCmNKRlM4eE1TcDhlcEFvSUJBUUNtWktVTjRxMHhIOUNZckdYT1Nta3Yvc0JnYzJPTFhLTXdSZWp1OVFENkRoTUgKMmZsREZUWHVGV1B6SmlqdUxaVE1CWkVBd1lhanVySUgzbVdETlRhbStMNG1XWnFGRlMvWlRqUk12YUNlcjlVSQpHZDk4OG94cGpQNDlBcUU0UDRIT00vQUZNU1ZtT1dwVTB0VzdkZ0hRUjM0cElXOGV1cUxva3RIaDJNaytTRURuCkFCV29SUGxWaTlncmN2N0tWaFk5YXlvSGxZb3VpMFl0YTZSNXc5VnpSa0REZU01Zi9Iak1kOVhieTZ0VjQ3NU0KSTliYzZvVUliVmVYNUJnMnZnMkRXVzZ6NTZ3dFRHMGJWWU1yWWU0V2JTU2w0bGpaZHM5TVJ2ay9OUUR0bFh0cAo4ekUwVDlCMXA4ekhabHE3S08zMFlyMVpIRVRWVVoxYjZrSTN3UDJuQW9JQkFFZ1VGKzlCMjF4RnpGQ0hucGtLClVPa2FTNGcvVUVHcmI5VzlYcVBLUzllVVBEd0wvY0tNZEh6dmRpRW1neFhESE9xZzExcU1wR2pTYkdMelNPUUMKZmlOTFV0QUswVVgvNFVSQ2pidUdqcEZmNHZ3NFNITTJJWkFyWXVhY3dFNHF1U0pQRzZoZFl0V0VPNnQ4MGtmRwpWTVYrWmdtUHE5TEZtM1R2VzZSY2s5czF5M3V3eEVVWllxeUdYTEduK1lrS25KL3pVd3ZGSFFHbjdRWWFrNWtaCnl6YXhZMFEzZ2hQeXFCbmlBRXRHTVBkeDlKeFltMCtRekdaMnQzUWNkOEV0cjRGMTcvdzF3eGJUdGdoRmk2WngKVXlYTzI3b1BmUmVnL0V3SmtpS2tRSEdlRUZKV0t2SWE0ZDAzMDZyMXVjcVRIMDRJaU1RcnpOK0ZRb002VC9tZgpOWmtDZ2dFQUsrRVJNVVdJZTE3V1k3VDIycy9lOEplN0xxSXlUcU9mSGovaWFUUjhHbXhqcU1HNEdod1RpVXJsCkh0Skhud3BMVGFjVmdYUjV3UmtYcEhRT2JqSUFzeVNBUGxwSzBvZUkyK2kvS0cyQjZ2U0cza0V2b1VZY0RlRk4KdzhHd0oxNDNTd21LQXM4eUtWMmd1RjhmRXNNVitEQzNzVHFlZXJmMy82bFprMUVCVFF0QTZqVHdqK0picXgwVgpaalZJUXBwUE8vc1VHdi9LZVE3MW5ockJpT0lXclJ0dDRTUDJ2aWx2em9DUTQxVjFqZ09wS3VwU3E1Y2J3VDRxCmp1bkJIMkx5VnNQaUc4M0Vha1JSUEhDK0craTk1MFJxckxVRUJOeVdHNGlMNTdUdU9xYVJuSmRnN2ZFb2lVLzMKNld4TjlvR2VRWjV0NjZkdTJEL01WSUZ4ZzJ1cXRBPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=</prv>
</cert>
<syslog/>
</opnsense>

File diff suppressed because it is too large Load Diff