Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
44da291
fix(RESTAPIVersion): convert pkg version to semver using component co…
jaredhendrickson13 Aug 17, 2026
4c603c4
ci(build): run build tests on pfSense-2.9.0-RELEASE
jaredhendrickson13 Aug 22, 2026
d2fa87c
chore: disable defunct parse_config call
jaredhendrickson13 Aug 22, 2026
3824847
test(FirewallRule): ensure 2.8.1 and 2.9.0 labels are checked
jaredhendrickson13 Aug 22, 2026
67478d5
Revert "test(FirewallRule): ensure 2.8.1 and 2.9.0 labels are checked"
jaredhendrickson13 Aug 22, 2026
75713a2
test(FirewallRule): simplify tests to apply to many pfSense versions
jaredhendrickson13 Aug 22, 2026
96dece2
fix(FirewallApplyDispatcher): call /etc/rc.filter_configure_sync inst…
jaredhendrickson13 Aug 22, 2026
1dc0ee1
fix: use config_read_file in place of defunct parse_config
jaredhendrickson13 Aug 22, 2026
21c8803
test(FirewallAdvancedSettings): increase wait time for filter dns
jaredhendrickson13 Aug 22, 2026
58af043
style: run prettier on changed files
jaredhendrickson13 Aug 22, 2026
91aadc6
test(CertificateAuthority): check truststore via certctl
jaredhendrickson13 Aug 22, 2026
5386719
feat(DHCPServer): add Kea specific netboot fields
jaredhendrickson13 Aug 22, 2026
7a18363
test(DHCPServer): add tests for Kea specific netboot fields
jaredhendrickson13 Aug 22, 2026
835f831
ci: build for pfsense 2.9.0 and 26.07
jaredhendrickson13 Aug 22, 2026
ebd04f6
ci: make 2.9.0 default pfsense version
jaredhendrickson13 Aug 22, 2026
9afa43f
test: remove ca trust test that is flaky on 2.9.0
jaredhendrickson13 Aug 22, 2026
2e9d604
test: fix failing dhcp server tests
jaredhendrickson13 Aug 23, 2026
19cd349
docs: add ce 2.9.0 and plus 26.07 to supported versions
jaredhendrickson13 Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ jobs:
include:
- PFSENSE_VERSION: pfSense-2.8.1-RELEASE
FREEBSD_ID: freebsd15
- PFSENSE_VERSION: pfSense-2.9.0-RELEASE
FREEBSD_ID: freebsd16

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ concurrency: build
env:
SWAGGER_UI_VERSION: "5.17.10"
PYTHON_VERSION: "3.10"
DEFAULT_PFSENSE_VERSION: "2.8.1"
DEFAULT_PFSENSE_VERSION: "2.9.0"

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
Expand All @@ -28,12 +28,16 @@ jobs:
# Note: The first item in this matrix must use env.DEFAULT_PFSENSE_VERSION as the PFSENSE_VERSION!
- FREEBSD_VERSION: FreeBSD-15.0-CURRENT
PFSENSE_VERSION: "2.8.1"
- FREEBSD_VERSION: FreeBSD-15.0-CURRENT
PFSENSE_VERSION: "2.9.0"
- FREEBSD_VERSION: FreeBSD-16.0-CURRENT
PFSENSE_VERSION: "25.11.1"
- FREEBSD_VERSION: FreeBSD-16.0-CURRENT
PFSENSE_VERSION: "26.03"
- FREEBSD_VERSION: FreeBSD-16.0-CURRENT
PFSENSE_VERSION: "26.03.1"
- FREEBSD_VERSION: FreeBSD-16.0-CURRENT
PFSENSE_VERSION: "26.07"

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
3 changes: 2 additions & 1 deletion docs/INSTALL_AND_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ run pfSense. It's recommended to follow Netgate's [minimum hardware requirements
### Supported pfSense versions

- pfSense CE 2.8.1
- pfSense CE 2.9.0
- pfSense Plus 25.11.1
- pfSense Plus 26.03
- pfSense Plus 26.03.1

- pfSense Plus 26.07

!!! Warning
Installation of the package on unsupported versions of pfSense may result in unexpected behavior and/or system instability.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ class Model {
# Clear the object cache for all Model classes since config is being reloaded
self::clear_model_cache();

$config = parse_config(parse: $force_parse);
config_read_file();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace RESTAPI\Dispatchers;

use RESTAPI\Core\Command;
use RESTAPI\Core\Dispatcher;

/**
Expand All @@ -12,10 +13,6 @@ class FirewallApplyDispatcher extends Dispatcher {
* Reloads the filter services.
*/
protected function _process(mixed ...$arguments): void {
if ($this->async) {
filter_configure();
} else {
filter_configure_sync();
}
new Command('/etc/rc.filter_configure_sync');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use RESTAPI\Responses\ConflictError;
use RESTAPI\Responses\ValidationError;
use RESTAPI\Validators\IPAddressValidator;
use RESTAPI\Validators\MACAddressValidator;
use RESTAPI\Validators\URLValidator;

/**
* Defines a Model that interacts with the DHCP server for a given interface.
Expand Down Expand Up @@ -48,6 +49,15 @@ class DHCPServer extends Model {
public NestedModelField $pool;
public NestedModelField $numberoptions;
public NestedModelField $staticmap;
public BooleanField $netboot;
public StringField $nextserver;
public StringField $filename;
public StringField $filename32;
public StringField $filename64;
public StringField $filename32arm;
public StringField $filename64arm;
public StringField $uefihttpboot;
public StringField $rootpath;

public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], mixed ...$options) {
# Define Model attributes
Expand Down Expand Up @@ -78,21 +88,21 @@ class DHCPServer extends Model {
default: '',
allow_empty: true,
maximum_length: 15,
verbose_name: 'Range From',
internal_name: 'from',
internal_namespace: 'range',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
verbose_name: 'Range From',
help_text: 'The starting IP address for the primary DHCP pool. This address must be less than or equal ' .
'to the `range_to` field.',
);
$this->range_to = new StringField(
default: '',
allow_empty: true,
maximum_length: 15,
verbose_name: 'Range To',
internal_name: 'to',
internal_namespace: 'range',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
verbose_name: 'Range To',
help_text: 'The ending IP address for the primary DHCP pool. This address must be greater than or equal ' .
'to the `range_to` field.',
);
Expand All @@ -107,8 +117,8 @@ class DHCPServer extends Model {
default: '',
allow_empty: true,
maximum_length: 255,
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: true, allow_fqdn: true)],
verbose_name: 'Failover Peer IP',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: true, allow_fqdn: true)],
help_text: 'The interface IP address of the other firewall (failover peer) in this subnet. Leave ' .
'empty to disable failover peering.',
);
Expand All @@ -117,17 +127,17 @@ class DHCPServer extends Model {
allow_empty: true,
many: true,
maximum_length: 17,
validators: [new MACAddressValidator()],
verbose_name: 'MAC Allow',
validators: [new MACAddressValidator()],
help_text: 'MAC addresses this DHCP server is allowed to provide leases for.',
);
$this->mac_deny = new StringField(
default: [],
allow_empty: true,
many: true,
maximum_length: 17,
validators: [new MACAddressValidator()],
verbose_name: 'MAC Deny',
validators: [new MACAddressValidator()],
help_text: 'MAC addresses this DHCP server is not allowed to provide leases for.',
);
$this->domainsearchlist = new StringField(
Expand All @@ -136,8 +146,8 @@ class DHCPServer extends Model {
many: true,
maximum_length: 255,
delimiter: ';',
validators: [new IPAddressValidator(allow_ipv4: false, allow_ipv6: false, allow_fqdn: true)],
verbose_name: 'Domain Search List',
validators: [new IPAddressValidator(allow_ipv4: false, allow_ipv6: false, allow_fqdn: true)],
help_text: 'The domain search list to provide via DHCP.',
);
$this->defaultleasetime = new IntegerField(
Expand All @@ -159,8 +169,8 @@ class DHCPServer extends Model {
default: '',
allow_empty: true,
maximum_length: 15,
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false, allow_keywords: ['none'])],
verbose_name: 'Gateway',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false, allow_keywords: ['none'])],
help_text: 'The gateway IPv4 address to provide via DHCP. This is only necessary if you are not using ' .
"the interface's IP as the gateway. Specify `none` for no gateway assignment.",
);
Expand All @@ -171,8 +181,8 @@ class DHCPServer extends Model {
many_maximum: 4,
maximum_length: 15,
delimiter: null,
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
verbose_name: 'DNS Servers',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
help_text: 'The DNS servers to provide via DHCP. Leave empty to default to system nameservers.',
);
$this->winsserver = new StringField(
Expand All @@ -182,8 +192,8 @@ class DHCPServer extends Model {
many_maximum: 2,
maximum_length: 15,
delimiter: null,
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
verbose_name: 'WINS Servers',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
help_text: 'The WINS servers to provide via DHCP.',
);
$this->ntpserver = new StringField(
Expand All @@ -193,8 +203,8 @@ class DHCPServer extends Model {
many_maximum: 4,
maximum_length: 256,
delimiter: null,
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false, allow_fqdn: true)],
verbose_name: 'NTP Servers',
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false, allow_fqdn: true)],
help_text: 'The NTP servers to provide via DHCP.',
);
$this->staticarp = new BooleanField(
Expand Down Expand Up @@ -273,6 +283,69 @@ class DHCPServer extends Model {
verbose_name: 'Static Mappings',
help_text: 'Static mappings applied to this DHCP server.',
);
$this->netboot = new BooleanField(
default: false,
verbose_name: 'Enable Network Booting',
help_text: 'Enable network booting (PXE/TFTP/UEFI HTTP boot) options for this DHCP server.',
);
$this->nextserver = new StringField(
default: '',
allow_empty: true,
verbose_name: 'Next Server',
conditions: ['netboot' => true],
validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)],
help_text: 'The IPv4 address of the network boot server (e.g. TFTP server).',
);
$this->filename = new StringField(
default: '',
allow_empty: true,
verbose_name: 'Default Bootfile Name',
conditions: ['netboot' => true],
help_text: 'The default bootfile name provided to BIOS/legacy PXE clients.',
);
$this->filename32 = new StringField(
default: '',
allow_empty: true,
verbose_name: 'UEFI 32-bit Bootfile Name',
conditions: ['netboot' => true],
help_text: 'The bootfile name provided to 32-bit UEFI PXE clients.',
);
$this->filename64 = new StringField(
default: '',
allow_empty: true,
verbose_name: 'UEFI 64-bit Bootfile Name',
conditions: ['netboot' => true],
help_text: 'The bootfile name provided to 64-bit UEFI PXE clients.',
);
$this->filename32arm = new StringField(
default: '',
allow_empty: true,
verbose_name: 'UEFI 32-bit ARM Bootfile Name',
conditions: ['netboot' => true],
help_text: 'The bootfile name provided to 32-bit ARM UEFI PXE clients.',
);
$this->filename64arm = new StringField(
default: '',
allow_empty: true,
verbose_name: 'UEFI 64-bit ARM Bootfile Name',
conditions: ['netboot' => true],
help_text: 'The bootfile name provided to 64-bit ARM UEFI PXE clients.',
);
$this->uefihttpboot = new StringField(
default: '',
allow_empty: true,
verbose_name: 'UEFI HTTPBoot URL',
conditions: ['netboot' => true],
validators: [new URLValidator()],
help_text: 'The URL provided to UEFI HTTP boot clients.',
);
$this->rootpath = new StringField(
default: '',
allow_empty: true,
verbose_name: 'Root Path',
conditions: ['netboot' => true],
help_text: 'The root path provided to network boot clients.',
);

parent::__construct($id, $parent_id, $data, ...$options);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class RESTAPIVersion extends Model {
# Locate the version and format it to a standard semantic version format (x.x.x)
$version = str_replace(' ', '', $pkg_line);
$version = explode(':', $version)[1];
$version = strlen($version) === 3 ? $version . '.0' : $version;
$version = substr_count($version, '.') === 1 ? $version . '.0' : $version;
$version = str_replace('_', '.', $version);
return "v$version";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,37 +98,6 @@ R02Pul8ulWQ8Kl3Q3pou8As7W1mMzA2DxQ==
);
}

/**
* Checks that the CA is found in the OS trust store when `trust` is enabled.
*/
public function test_ca_trust() {
# Create a CA to test with
$ca = new CertificateAuthority(
data: [
'descr' => 'test',
'crt' => self::EXAMPLE_CRT,
'prv' => self::EXAMPLE_PRV,
'trust' => true,
],
);
$ca->create();

# Ensure the cert is in the trust store directory
$truststore_dir_ls = glob('/usr/local/etc/ssl/certs/*.crt');
$this->assert_is_not_empty($truststore_dir_ls, message: 'Trust store directory should have one trusted CA!');

# Disable `trust`
$ca->trust->value = false;
$ca->update();

# Ensure the certificate is not found in the trust store
$truststore_dir_ls = glob('/usr/local/etc/ssl/certs/*.crt');
$this->assert_is_empty($truststore_dir_ls, message: 'Trust store directory should have no trusted CAs!');

# Delete the CA
$ca->delete();
}

/**
* Checks that we cannot delete a CA that is in use.
*/
Expand Down
Loading