From d75a4c7ef5701202b4a00d89a58ff4cc163ad09a Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 4 Jan 2025 13:55:36 -0700 Subject: [PATCH 01/34] fix: prevent clobbering session during model deletions in forms #630 --- .../files/usr/local/pkg/RESTAPI/Core/Form.inc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc index fe691e89c..c80ed75ba 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc @@ -132,7 +132,7 @@ class Form { # Gather information about the authenticated user $client = new Auth(); - $client->username = $_SESSION['Username'] ?: DEFAULT_CLIENT_USERNAME; + $client->username = $_SESSION['Username']; # Obtain the `id` from URL parameters $this->id = is_numeric($_GET['id']) ? intval($_GET['id']) : null; @@ -403,7 +403,9 @@ class Form { } try { - (new $this->model(id: $id))->delete(); + $model_to_delete = new $this->model(id: $id); + $model_to_delete->client = $this->model->client; + $model_to_delete->delete(); $this->print_success_banner("Deleted {$this->model->verbose_name} with ID $id."); } catch (Response $resp_error) { $error_message = $resp_error->getMessage(); From 96eb2eef330c3348edfa3fe49a2764f61b7255d0 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 11 Jan 2025 13:26:51 -0700 Subject: [PATCH 02/34] fix: make HAProxyBackend:: a Base64Field #640 --- .../files/usr/local/pkg/RESTAPI/Models/HAProxyBackend.inc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackend.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackend.inc index cbfda047d..47c460002 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackend.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackend.inc @@ -4,6 +4,7 @@ namespace RESTAPI\Models; use RESTAPI\Core\Model; use RESTAPI\Dispatchers\HAProxyApplyDispatcher; +use RESTAPI\Fields\Base64Field; use RESTAPI\Fields\BooleanField; use RESTAPI\Fields\IntegerField; use RESTAPI\Fields\InterfaceField; @@ -72,7 +73,7 @@ class HAProxyBackend extends Model { public NestedModelField $errorfiles; public BooleanField $cookie_attribute_secure; public StringField $advanced; - public StringField $advanced_backend; + public Base64Field $advanced_backend; public BooleanField $transparent_clientip; public InterfaceField $transparent_interface; @@ -439,7 +440,7 @@ class HAProxyBackend extends Model { allow_empty: true, help_text: 'The per server pass thru to apply to each server line.', ); - $this->advanced_backend = new StringField( + $this->advanced_backend = new Base64Field( default: '', allow_empty: true, help_text: 'The backend pass thru to apply to the backend section.', From 5622a45646c03572622c822a9e90c2253fb24e96 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 11 Jan 2025 13:35:50 -0700 Subject: [PATCH 03/34] tests: assert HAProxyBackend::advanced_backend is stored as b64 #640 --- .../Tests/APIModelsHAProxyBackendTestCase.inc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyBackendTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyBackendTestCase.inc index 278e84aee..ce471efd0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyBackendTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyBackendTestCase.inc @@ -139,4 +139,18 @@ class APIModelsHAProxyBackendTestCase extends TestCase { $backend->delete(); $this->assert_is_false(HAProxyBackend::query(id: $backend->id)->exists()); } + + /** + * Ensure that the HAProxyBackend::$advanced_backend property is base64 encoded internally. + */ + public function test_advanced_backend_is_base64(): void { + # Ensure the advanced_backend property is base64 encoded when converted to internal config value + $backend = new HAProxyBackend(advanced_backend: 'test'); + $this->assert_equals($backend->advanced_backend->to_internal(), base64_encode('test')); + + # Ensure the advanced_backend property is base64 decoded when converted from internal config value + $backend = new HAProxyBackend(); + $backend->advanced_backend->from_internal(base64_encode('test')); + $this->assert_equals($backend->advanced_backend->value, 'test'); + } } From f826c6b43e0202e7c6cd5713abd9084fd867c90d Mon Sep 17 00:00:00 2001 From: cclare Date: Thu, 16 Jan 2025 12:30:55 -0500 Subject: [PATCH 04/34] Add ssloffload and ha_certificates to HAProxyFrontend --- .../pkg/RESTAPI/Models/HAProxyFrontend.inc | 13 +++++ .../Models/HAProxyFrontendCertificate.inc | 47 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc index 5c1090fb1..40432a7f2 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc @@ -96,6 +96,19 @@ class HAProxyFrontend extends Model { allow_null: true, help_text: 'The default backend to use for this frontend.', ); + $this->ssloffload = new ForeignModelField( + model_name: 'Certificate', + model_field: 'refid', + default: null, + allow_null: true, + help_text: 'The default SSL/TLS certificate to use for this frontend.', + ); + $this->ha_certificates = new NestedModelField( + model_class: 'HAProxyFrontendCertificate', + default: [], + allow_empty: true, + help_text: 'The additional SSL/TLS certificates to use on this frontend.', + ); $this->socket_stats = new BooleanField( default: false, indicates_true: 'yes', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc new file mode 100644 index 000000000..5c7b648ab --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc @@ -0,0 +1,47 @@ +parent_model_class = 'HAProxyFrontend'; + $this->config_path = 'ha_certificates/item'; + $this->verbose_name = 'HAProxy Frontend Certificates'; + $this->packages = ['pfSense-pkg-haproxy']; + $this->package_includes = ['haproxy/haproxy.inc', 'haproxy/haproxy_utils.inc']; + $this->many = true; + + $this->ssl_certificate = new ForeignModelField( + model_name: 'Certificate', + model_field: 'refid', + default: null, + allow_null: true, + help_text: 'The default SSL/TLS certificate to use for this frontend.', + ); + } + + /** + * Ensures haproxy is marked as dirty before applying. + */ + public function pre_apply(): void { + touch('/var/run/haproxy.conf.dirty'); + } + + /** + * Applies changes to the HAProxy configuration. + */ + public function apply(): void { + (new HAProxyApplyDispatcher(async: $this->async))->spawn_process(); + } + +} From fd31cc70bb8dcc3143b847f43f3611c9df9fafd9 Mon Sep 17 00:00:00 2001 From: cclare Date: Thu, 16 Jan 2025 12:41:43 -0500 Subject: [PATCH 05/34] Correct SSL offload cert field name, correct help text and doc string --- .../files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc | 4 ++-- .../local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc index 40432a7f2..90357d6bb 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontend.inc @@ -96,12 +96,12 @@ class HAProxyFrontend extends Model { allow_null: true, help_text: 'The default backend to use for this frontend.', ); - $this->ssloffload = new ForeignModelField( + $this->ssloffloadcert = new ForeignModelField( model_name: 'Certificate', model_field: 'refid', default: null, allow_null: true, - help_text: 'The default SSL/TLS certificate to use for this frontend.', + help_text: 'The default SSL/TLS certificate refid to use for this frontend.', ); $this->ha_certificates = new NestedModelField( model_class: 'HAProxyFrontendCertificate', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc index 5c7b648ab..89edc3399 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc @@ -7,7 +7,7 @@ use RESTAPI\Dispatchers\HAProxyApplyDispatcher; use RESTAPI\Fields\ForeignModelField; /** - * Defines a Model for HAProxy Frontend Access Control Lists. + * Defines a Model for additional HAProxy Frontend Certificates. */ class HAProxyFrontendCertificate extends Model { public ForeignModelField $refid; @@ -26,7 +26,7 @@ class HAProxyFrontendCertificate extends Model { model_field: 'refid', default: null, allow_null: true, - help_text: 'The default SSL/TLS certificate to use for this frontend.', + help_text: 'The SSL/TLS certificate refid to add to this frontend.', ); } From b1b545a986d11738c9b992189b13f72a77b67723 Mon Sep 17 00:00:00 2001 From: cclare Date: Thu, 16 Jan 2025 14:23:15 -0500 Subject: [PATCH 06/34] Remove whitespace to make prettier happy --- .../local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc index 89edc3399..b256d6cf0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc @@ -27,7 +27,7 @@ class HAProxyFrontendCertificate extends Model { default: null, allow_null: true, help_text: 'The SSL/TLS certificate refid to add to this frontend.', - ); + ); } /** @@ -43,5 +43,4 @@ class HAProxyFrontendCertificate extends Model { public function apply(): void { (new HAProxyApplyDispatcher(async: $this->async))->spawn_process(); } - } From b8f82fb2014de1612e129cd3b2c0348d2e4d8453 Mon Sep 17 00:00:00 2001 From: cclare Date: Mon, 20 Jan 2025 16:59:09 -0500 Subject: [PATCH 07/34] Add HAProxy certificate endpoint, add parent constructor/fix parameter name in HAProxyFrontendCertificate --- .../local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc index b256d6cf0..86bc9b1f6 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc @@ -10,7 +10,7 @@ use RESTAPI\Fields\ForeignModelField; * Defines a Model for additional HAProxy Frontend Certificates. */ class HAProxyFrontendCertificate extends Model { - public ForeignModelField $refid; + public ForeignModelField $ssl_certificate; public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], ...$options) { # Set model attributes @@ -28,6 +28,9 @@ class HAProxyFrontendCertificate extends Model { allow_null: true, help_text: 'The SSL/TLS certificate refid to add to this frontend.', ); + + parent::__construct($id, $parent_id, $data, ...$options); + } /** From 70a4ff744866022aa06709105affef694917072e Mon Sep 17 00:00:00 2001 From: cclare Date: Tue, 21 Jan 2025 11:35:24 -0500 Subject: [PATCH 08/34] Actually add ServicesHAProxyFrontendCertificateEndpoint --- ...icesHAProxyFrontendCertificateEndpoint.inc | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesHAProxyFrontendCertificateEndpoint.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesHAProxyFrontendCertificateEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesHAProxyFrontendCertificateEndpoint.inc new file mode 100644 index 000000000..06e53282e --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesHAProxyFrontendCertificateEndpoint.inc @@ -0,0 +1,23 @@ +url = '/api/v2/services/haproxy/frontend/certificate'; + $this->model_name = 'HAProxyFrontendCertificate'; + $this->request_method_options = ['GET', 'POST', 'PATCH', 'DELETE']; + + # Construct the parent Endpoint object + parent::__construct(); + } +} From cc1002839ff6145c7743aaf8ea152bbc08c2d174 Mon Sep 17 00:00:00 2001 From: cclare Date: Tue, 21 Jan 2025 11:38:57 -0500 Subject: [PATCH 09/34] Remove extra line to make prettier happy --- .../usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc | 1 - 1 file changed, 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc index 86bc9b1f6..0f2b5b396 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyFrontendCertificate.inc @@ -30,7 +30,6 @@ class HAProxyFrontendCertificate extends Model { ); parent::__construct($id, $parent_id, $data, ...$options); - } /** From e39f4f28a5ef8b3d55b2ff7edfd125ebc12c0866 Mon Sep 17 00:00:00 2001 From: cclare Date: Wed, 22 Jan 2025 15:34:56 -0500 Subject: [PATCH 10/34] Add tests for HAProxy SSL objects --- .../APIModelsHAProxyFrontendTestCase.inc | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc index 359862311..3dd2e622d 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc @@ -3,20 +3,34 @@ namespace RESTAPI\Tests; use RESTAPI\Core\TestCase; +use RESTAPI\Models\Certificate; use RESTAPI\Models\HAProxyBackend; use RESTAPI\Models\HAProxyFrontend; use RESTAPI\Models\HAProxyFrontendACL; use RESTAPI\Models\HAProxyFrontendAction; use RESTAPI\Models\HAProxyFrontendAddress; +use RESTAPI\Models\HAProxyFrontendCertificate; class APIModelsHAProxyFrontendTestCase extends TestCase { private HAProxyBackend $backend; + private Certificate $main_crt; + private Certificate $alt_crt; public array $required_packages = ['pfSense-pkg-haproxy']; /** * Setup the test case. */ public function setup(): void { + $this->main_crt = new Certificate( + descr: 'example_main_certificate', + crt: '-----BEGIN CERTIFICATE-----\nMIIEETCCAvmgAwIBAgIUFj+2UXmP+7z2RqSU1NquTnaJu3owDQYJKoZIhvcNAQEL\nBQAwgZcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjEQMA4GA1UEBwwH\nTGFuc2luZzEcMBoGA1UECgwTRXhhbXBsZSBDb21wYW55IExMQzELMAkGA1UECwwC\nSVQxFDASBgNVBAMMC2V4YW1wbGUuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxl\nQGV4YW1wbGUuY29tMB4XDTI1MDEyMTE3MjU0OFoXDTM1MDEyMTE3MjU0OFowgZcx\nCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjEQMA4GA1UEBwwHTGFuc2lu\nZzEcMBoGA1UECgwTRXhhbXBsZSBDb21wYW55IExMQzELMAkGA1UECwwCSVQxFDAS\nBgNVBAMMC2V4YW1wbGUuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1w\nbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt3Rc+FmQB/fk\n33faRhm9Gg9a67RHHCeEdB6OubfvSXN8/InYP6o+Ln5Wp8xmYqBtHcjnfO/iIYOH\nbl87pvKR7AiPSE1IfMSZxp8xiOhbUJwPt9QOXQA8mExW0M740+PNBQd8k+UXUb0g\n3ammsIo4NlNYvhle8l8Q2YBsK7KwsxLp6PjNLmnKOFm+BldALz1Sj3ER8XGuOfsZ\nALqxQiG87vTO2kSBSTqmorie5fgIcVVKkKhNmSfo9TWVKAr2rHBl2ep0L8o2QgeY\nuA/mPrlxc1V/JeybbENQqkiJVNz1F2120HKXGxHNv2VzWHN0rOAThG8Tmx4ej/FQ\nMRPsCKNqpwIDAQABo1MwUTAdBgNVHQ4EFgQUTlotGm0X/eXy7IpexMyOfHDtSPsw\nHwYDVR0jBBgwFoAUTlotGm0X/eXy7IpexMyOfHDtSPswDwYDVR0TAQH/BAUwAwEB\n/zANBgkqhkiG9w0BAQsFAAOCAQEAQKfpuM1jY86fd+oyfyEQJPkm4optyAdn/6FH\n+jsHejgAzwgMhLYldG8pEXR21zJZMIZuv9NqLhTJTbfbK7hZ4S3PhodYBU24xjyu\ndMzCqpW1nS8T/Hi1ULKoWQ/7fBaR1iLwA2gIvmKCWfTb0WrObxNZe3l95q1srli4\nN1XPMvmD53aBU4Yx4+pKQyPkS0tjUl2+sq/Ry1uh1Wz/2TIxn2zPfL0wnidXTwGQ\n33raRCxt+kJ0a7dmKo7ejh5dMx0o1yXeuttBiMddTJsK8GkHmX/ZJXZlSLAkJV3p\nEP/ZM3VAYeoj7iS5NC5XqXmOewiOor4ok75x2CoeoudAKENf1Q==\n-----END CERTIFICATE-----\n', + prv: '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC3dFz4WZAH9+Tf\nd9pGGb0aD1rrtEccJ4R0Ho65t+9Jc3z8idg/qj4uflanzGZioG0dyOd87+Ihg4du\nXzum8pHsCI9ITUh8xJnGnzGI6FtQnA+31A5dADyYTFbQzvjT480FB3yT5RdRvSDd\nqaawijg2U1i+GV7yXxDZgGwrsrCzEuno+M0uaco4Wb4GV0AvPVKPcRHxca45+xkA\nurFCIbzu9M7aRIFJOqaiuJ7l+AhxVUqQqE2ZJ+j1NZUoCvascGXZ6nQvyjZCB5i4\nD+Y+uXFzVX8l7JtsQ1CqSIlU3PUXbXbQcpcbEc2/ZXNYc3Ss4BOEbxObHh6P8VAx\nE+wIo2qnAgMBAAECggEACkAZtmoGsV30BK/P2zfdSDilwLgaJbloM2FxDHEkZnW/\nUjr/gWXT7fQdh72qK4ZBjTb9sY4+aSyjY2g1zzkMLI85Nksp1YgvUW5GrcV8iK9X\nn31dRMFadrtZOcyUEal6T0wg9VXebNuaRlhlhHAMqQMU2SEAxvPHNL1HY1UA04M5\neWlwgb+WN8ocQgAWT42L/Ys3bLWilA9HfTgotLcM2AonF7MhR8kKubRzCBupaRYI\nRLD+EPJpvz7hbvLjTi8OUc2Ksl8L2ZhuIVLqYljBWWHCEj+z97i5oaUSMo8ZujVo\nMPGQkM6Blh8viR9TSVXH/o+iGnlGgABn3yESNrUUTQKBgQD0Dq1EIXtiXtTOud6O\nI4l9tieb9y9hZXCgaemr2IiH5OpsnALf5aBqNA7WhRFvBN5Jby6F5XhSLPHthQLr\nWWouyJIFUuTxGMBQE3JFOTxn+mikH1nsiuZTqYvZtbfSyiuJA2qMpvegowX+Y0gX\n3HCIUBmNOZW44GsQyYxlzBZeDQKBgQDAboLRGnDTZF+Kg+XcrmTOchW3vrzhIaN+\n25wLxYBBuL8po0XchA2lR9DYl/z+HLl24Qv3x/kWDfqw3YMjeSuJDCiRW6k2nyOs\nBq9iIWJyG120retNeil/ee+Oi4Ot6heMBLhsiPokNiyQErFjFVDja0NWNI3RLT3O\nXl0NnC3ygwKBgQDPIWmyCgySl1NbQ+p7jfORqrgBKY+fxJ0Kt/jmrPZrB0Bh/yJ9\nbYBwwk/ZhsQeCe6yOlcssm0kqRqGcD3jClcQ747mT80YRd5p16dC0unOgXqv2B/U\nnqoOVTXpBs3Fa2n5ddnHF6nQPqvGk/JKtf8X4YteDYJhjq2vaK7scoKFPQKBgQCK\nz20IP6WBp9cPiUPH4/kNvtgRusJvOB/DQ9GV4Ds7rXQgKgocHxnkzyBFDaZO/BOB\nQKesbpEsqpra+H8/mImiC6Y77L3si/Fkh1H+XIyyOTFoQ0kIk4XZ0fDPh65ORx4D\nP+h1Sn3+nyGYMBPLoLW/x0FErxZZ/OOnuQQw7Cds1wJ/BRP+3rVtm8hof1hk7gRS\nUlhuViYUScnBfuuCGp5Fdj1tfuMdqDA0IB7u+7V4sIy4z8WBmeZFsjE67eMTv9uR\nC6tjrdA8DUjiUnSZTwBWzjPZ8u87BtD4y3TZqdhA/a2yd1u6L9Uw7g/JzDpAiMIG\n5Wj8TdEgyCArcswKTBRvZg==\n-----END PRIVATE KEY-----\n' + ); + $this->alt_crt = new Certificate( + descr: 'example_alt_certificate', + crt: '-----BEGIN CERTIFICATE-----\nMIIEITCCAwmgAwIBAgIUEuRnc2GSFACHYusB06+IAjZ/XZ0wDQYJKoZIhvcNAQEL\nBQAwgZ8xCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhJbGxpbm9pczEQMA4GA1UEBwwH\nQ2hpY2FnbzEcMBoGA1UECgwTRXhhbXBsZSBDb21wYW55IExMQzELMAkGA1UECwwC\nSVQxGDAWBgNVBAMMD2FsdC5leGFtcGxlLmNvbTEmMCQGCSqGSIb3DQEJARYXZXhh\nbXBsZUBhbHQuZXhhbXBsZS5jb20wHhcNMjUwMTIxMTcyODQxWhcNMzUwMTIxMTcy\nODQxWjCBnzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAwDgYDVQQH\nDAdDaGljYWdvMRwwGgYDVQQKDBNFeGFtcGxlIENvbXBhbnkgTExDMQswCQYDVQQL\nDAJJVDEYMBYGA1UEAwwPYWx0LmV4YW1wbGUuY29tMSYwJAYJKoZIhvcNAQkBFhdl\neGFtcGxlQGFsdC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK/xDL+IK455MhTv86XU/PSAzrrPNbD1f03KxybPr/xX2AFTnIYkez53\nHWMnpizn5e1HoOqXHynptE/wGLsgE105H7aGZ8t52jbqifIIUhTi+HBRC2Yg4YRf\n6PbR+1lvMaCofotz4zMtZtpp/XiOXrSHqulGcV+bSgtu2uxu9wIfwxN2Mvg/8sk7\noqNimwLpuRU0ZqAYt6dFsxsvCZC8gCywYL45ML2KxociWAjFDp0obj7g/j5XSxEj\nAz5KfZdJhxl0NSCD+ewWUYYsnCRWHbojtqlQnGY4QXTYZ6TLGyOB5jPYK7cRCMXb\npsveo24c29UBStiJ1u87gu+PGqUhpVsCAwEAAaNTMFEwHQYDVR0OBBYEFGhql+aY\nSxnUlNLett0Oqb+O6OcqMB8GA1UdIwQYMBaAFGhql+aYSxnUlNLett0Oqb+O6Ocq\nMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKQh880ZOtQeW2VH\nMqILfcF/qf/Ge5kCmS/8mTAAZinvSuCISblcBIMgei3L7LObav48u2BmNFor1o1o\naB4iSMLRyEGNrnYhCy4IrCWw7Y2YjzYJz5oEUy9jJ9S4zOZGV/qNVIhiBSJN274Z\nU8Q8kmF/Tpze1G65AipUgbaw3fx8VgvlOotemtDNsIwMA9WMDnXMqVOBIs5yWWp9\nT9f6Y8to/UcNm0JS7LExn9xTi5WAxstUzk5tWsvJ5pzEppUymSdkSpzObRcqRLZa\n5YeFbd+svKfAue8iukzwuukDsHl8lmwkSASFRwdh3391JC7lpxenAFjs0I1eXUBs\nBz371as=\n-----END CERTIFICATE-----\n', + prv: '-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCv8Qy/iCuOeTIU\n7/Ol1Pz0gM66zzWw9X9Nyscmz6/8V9gBU5yGJHs+dx1jJ6Ys5+XtR6Dqlx8p6bRP\n8Bi7IBNdOR+2hmfLedo26onyCFIU4vhwUQtmIOGEX+j20ftZbzGgqH6Lc+MzLWba\naf14jl60h6rpRnFfm0oLbtrsbvcCH8MTdjL4P/LJO6KjYpsC6bkVNGagGLenRbMb\nLwmQvIAssGC+OTC9isaHIlgIxQ6dKG4+4P4+V0sRIwM+Sn2XSYcZdDUgg/nsFlGG\nLJwkVh26I7apUJxmOEF02GekyxsjgeYz2Cu3EQjF26bL3qNuHNvVAUrYidbvO4Lv\njxqlIaVbAgMBAAECgf9u/iKMsdrIhqyRiM6sTzFFDf1c3FuCx10INmRsl5juGHys\nWZLlDR6yyXJAm0K6EZF7nzRkyfFe/5BA3ba8Vf/hT6gx/Zh9ROHkwxFDHvypMIaK\nVJZcV2HtoJPXIaDvSraEI8exMeqi3oGESFkfNLGKMgwgHdBoAA82Jzj07wvkyhjG\nuB2KZ0x8bDOuan9nM8EfJ6ncN8/biOolPfOv/ojUDjp0yN//gkPQTij50Dj8dxEU\nwobi9AE3diszC8gjj5i4mX14qWKC6/xxGcSHoxsPYUJt7nYGb5RRZciqChroob0o\nDP8VpQR/SnqY8OU97xExbjS0zjvlhipPgiMbGKECgYEA3SVdbR1niZrNEly2S9y1\nsL+pzl2t6KA6pd6XFvlMqgqAtCo9MOVDXkqfptKt9oT+MC8SkbwFFIYLWXHMBNcO\nqfVCIK1mp5scHRgiBdmjo0yEXIsJ5J1y+bm8mTlyGJHbefh7FQoHnrJ/7X9WXuQo\nZWetv196PQuOiLUL49NukGECgYEAy6vQBznsessCeetXOHOO7ap9dieoWzFK0YIX\nZaA9jX+REgIhhIxgWkBcsMQAlm5B74YskYcbsrUm510xn0stzzh91O6DyoM3PJJc\nrqeUJU+HEYELcuZCVy4RV2+XMjSLPc5Sivgbe0mOfHKz3szNIwGKZGuRrWIT6djA\nUyuuvzsCgYA7Qdzj9SIeTD2xMuiiMVT6NJFyu8Vy9SRh9+AyhsDVO5U7MapN5NEF\nfieDkyaMTRyzmpl2NZSC6Fw4LncFwP6r6g07JlvAg56n6SSMvsHLayB4j8Up3krF\nwdBYmn0JOmQ+LagFfjeGfbwrzpg9OxPcAEkdY58kpmkeFq1F8gzJYQKBgQCrb0d/\njaYcBd1julCEV+Cq9KL+XYs8l1Ue3J3NEQA+pm359ok4BODllt9ueszz0oL1lr7V\nECCkYA8LGEq4hpNcpXRlUw7j22cXOSuCZ9QvQ62xcims4Vxd/YzjSC7AN7IsqAqa\npxSaxeOFpRb6JvFU0esnc9P/WVrhr2zTlSVWGQKBgCFaRF3yP/Tc8EvYEhCPgRLv\nsM5FZUe/86gWoWUhSoXcPpW+CipjatcB3kLxXXcYMFYl4cVe5KwTemMmDgMMiMdP\nwn+R2SGvU89GTCTWmlUeD4sesNVFcHMe2Rw0pBf1BTh5KIsA9BXRe13bWHU+x8T2\nSIeb+boJuWOH4flIIBud\n-----END PRIVATE KEY-----\n' + ); $this->backend = new HAProxyBackend( name: 'example_backend', balance: '', @@ -72,6 +86,8 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { transparent_interface: 'lan', ); $this->backend->create(); + $this->main_crt->create(); + $this->alt_crt->create(); } /** @@ -79,6 +95,8 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { */ public function teardown(): void { $this->backend->delete(); + $this->main_crt->delete(); + $this->alt_crt->delete(); } /** @@ -122,6 +140,12 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { forwardfor: false, httpclose: 'http-keep-alive', advanced: '', + ssloffloadcert: $this->main_crt->refid->value, + ha_certificates: [ + [ + 'ssl_certificate' => $this->alt_crt->refid->value + ], + ] ); $frontend->create(); @@ -135,6 +159,7 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { $this->assert_equals($read_frontend->backend_serverpool->value, 'example_backend'); $this->assert_equals($read_frontend->client_timeout->value, 30000); $this->assert_equals($read_frontend->httpclose->value, 'http-keep-alive'); + $this->assert_equals($read_frontend->ssloffloadcert->value, $this->main_crt->refid->value); # Ensure the nested HAProxyBackend object linked up with the foreign object correctly $this->assert_is_true($read_frontend->backend_serverpool->get_related_model() instanceof HAProxyBackend); @@ -164,6 +189,11 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { $this->assert_equals($read_frontend->a_actionitems->value[0]['acl'], 'mail_acl'); $this->assert_equals($read_frontend->a_actionitems->value[0]['lua_function'], 'example function'); + # Ensure the nested HAProxyFronendCertificate object was created successful + $this->assert_is_not_empty($read_frontend->ha_certificates->value); + $this->assert_is_true(HAProxyFrontendCertificate::query(parent_id: $frontend->id, id: 0)->exists()); + $this->assert_equals($read_frontend->ha_certificates->value[0]['ssl_certificate'], $this->alt_crt->refid->value); + # Update the object and ensure the changes were successful $frontend->status->value = 'disabled'; $frontend->update(); From c29bbfc4bf88c68475ef7425be0fa7c4d13da3d2 Mon Sep 17 00:00:00 2001 From: cclare Date: Wed, 22 Jan 2025 16:08:44 -0500 Subject: [PATCH 11/34] Prettier fixes on HAProxy frontend test case --- .../Tests/APIModelsHAProxyFrontendTestCase.inc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc index 3dd2e622d..b656a05da 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsHAProxyFrontendTestCase.inc @@ -24,12 +24,12 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { $this->main_crt = new Certificate( descr: 'example_main_certificate', crt: '-----BEGIN CERTIFICATE-----\nMIIEETCCAvmgAwIBAgIUFj+2UXmP+7z2RqSU1NquTnaJu3owDQYJKoZIhvcNAQEL\nBQAwgZcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjEQMA4GA1UEBwwH\nTGFuc2luZzEcMBoGA1UECgwTRXhhbXBsZSBDb21wYW55IExMQzELMAkGA1UECwwC\nSVQxFDASBgNVBAMMC2V4YW1wbGUuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxl\nQGV4YW1wbGUuY29tMB4XDTI1MDEyMTE3MjU0OFoXDTM1MDEyMTE3MjU0OFowgZcx\nCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjEQMA4GA1UEBwwHTGFuc2lu\nZzEcMBoGA1UECgwTRXhhbXBsZSBDb21wYW55IExMQzELMAkGA1UECwwCSVQxFDAS\nBgNVBAMMC2V4YW1wbGUuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1w\nbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt3Rc+FmQB/fk\n33faRhm9Gg9a67RHHCeEdB6OubfvSXN8/InYP6o+Ln5Wp8xmYqBtHcjnfO/iIYOH\nbl87pvKR7AiPSE1IfMSZxp8xiOhbUJwPt9QOXQA8mExW0M740+PNBQd8k+UXUb0g\n3ammsIo4NlNYvhle8l8Q2YBsK7KwsxLp6PjNLmnKOFm+BldALz1Sj3ER8XGuOfsZ\nALqxQiG87vTO2kSBSTqmorie5fgIcVVKkKhNmSfo9TWVKAr2rHBl2ep0L8o2QgeY\nuA/mPrlxc1V/JeybbENQqkiJVNz1F2120HKXGxHNv2VzWHN0rOAThG8Tmx4ej/FQ\nMRPsCKNqpwIDAQABo1MwUTAdBgNVHQ4EFgQUTlotGm0X/eXy7IpexMyOfHDtSPsw\nHwYDVR0jBBgwFoAUTlotGm0X/eXy7IpexMyOfHDtSPswDwYDVR0TAQH/BAUwAwEB\n/zANBgkqhkiG9w0BAQsFAAOCAQEAQKfpuM1jY86fd+oyfyEQJPkm4optyAdn/6FH\n+jsHejgAzwgMhLYldG8pEXR21zJZMIZuv9NqLhTJTbfbK7hZ4S3PhodYBU24xjyu\ndMzCqpW1nS8T/Hi1ULKoWQ/7fBaR1iLwA2gIvmKCWfTb0WrObxNZe3l95q1srli4\nN1XPMvmD53aBU4Yx4+pKQyPkS0tjUl2+sq/Ry1uh1Wz/2TIxn2zPfL0wnidXTwGQ\n33raRCxt+kJ0a7dmKo7ejh5dMx0o1yXeuttBiMddTJsK8GkHmX/ZJXZlSLAkJV3p\nEP/ZM3VAYeoj7iS5NC5XqXmOewiOor4ok75x2CoeoudAKENf1Q==\n-----END CERTIFICATE-----\n', - prv: '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC3dFz4WZAH9+Tf\nd9pGGb0aD1rrtEccJ4R0Ho65t+9Jc3z8idg/qj4uflanzGZioG0dyOd87+Ihg4du\nXzum8pHsCI9ITUh8xJnGnzGI6FtQnA+31A5dADyYTFbQzvjT480FB3yT5RdRvSDd\nqaawijg2U1i+GV7yXxDZgGwrsrCzEuno+M0uaco4Wb4GV0AvPVKPcRHxca45+xkA\nurFCIbzu9M7aRIFJOqaiuJ7l+AhxVUqQqE2ZJ+j1NZUoCvascGXZ6nQvyjZCB5i4\nD+Y+uXFzVX8l7JtsQ1CqSIlU3PUXbXbQcpcbEc2/ZXNYc3Ss4BOEbxObHh6P8VAx\nE+wIo2qnAgMBAAECggEACkAZtmoGsV30BK/P2zfdSDilwLgaJbloM2FxDHEkZnW/\nUjr/gWXT7fQdh72qK4ZBjTb9sY4+aSyjY2g1zzkMLI85Nksp1YgvUW5GrcV8iK9X\nn31dRMFadrtZOcyUEal6T0wg9VXebNuaRlhlhHAMqQMU2SEAxvPHNL1HY1UA04M5\neWlwgb+WN8ocQgAWT42L/Ys3bLWilA9HfTgotLcM2AonF7MhR8kKubRzCBupaRYI\nRLD+EPJpvz7hbvLjTi8OUc2Ksl8L2ZhuIVLqYljBWWHCEj+z97i5oaUSMo8ZujVo\nMPGQkM6Blh8viR9TSVXH/o+iGnlGgABn3yESNrUUTQKBgQD0Dq1EIXtiXtTOud6O\nI4l9tieb9y9hZXCgaemr2IiH5OpsnALf5aBqNA7WhRFvBN5Jby6F5XhSLPHthQLr\nWWouyJIFUuTxGMBQE3JFOTxn+mikH1nsiuZTqYvZtbfSyiuJA2qMpvegowX+Y0gX\n3HCIUBmNOZW44GsQyYxlzBZeDQKBgQDAboLRGnDTZF+Kg+XcrmTOchW3vrzhIaN+\n25wLxYBBuL8po0XchA2lR9DYl/z+HLl24Qv3x/kWDfqw3YMjeSuJDCiRW6k2nyOs\nBq9iIWJyG120retNeil/ee+Oi4Ot6heMBLhsiPokNiyQErFjFVDja0NWNI3RLT3O\nXl0NnC3ygwKBgQDPIWmyCgySl1NbQ+p7jfORqrgBKY+fxJ0Kt/jmrPZrB0Bh/yJ9\nbYBwwk/ZhsQeCe6yOlcssm0kqRqGcD3jClcQ747mT80YRd5p16dC0unOgXqv2B/U\nnqoOVTXpBs3Fa2n5ddnHF6nQPqvGk/JKtf8X4YteDYJhjq2vaK7scoKFPQKBgQCK\nz20IP6WBp9cPiUPH4/kNvtgRusJvOB/DQ9GV4Ds7rXQgKgocHxnkzyBFDaZO/BOB\nQKesbpEsqpra+H8/mImiC6Y77L3si/Fkh1H+XIyyOTFoQ0kIk4XZ0fDPh65ORx4D\nP+h1Sn3+nyGYMBPLoLW/x0FErxZZ/OOnuQQw7Cds1wJ/BRP+3rVtm8hof1hk7gRS\nUlhuViYUScnBfuuCGp5Fdj1tfuMdqDA0IB7u+7V4sIy4z8WBmeZFsjE67eMTv9uR\nC6tjrdA8DUjiUnSZTwBWzjPZ8u87BtD4y3TZqdhA/a2yd1u6L9Uw7g/JzDpAiMIG\n5Wj8TdEgyCArcswKTBRvZg==\n-----END PRIVATE KEY-----\n' + prv: '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC3dFz4WZAH9+Tf\nd9pGGb0aD1rrtEccJ4R0Ho65t+9Jc3z8idg/qj4uflanzGZioG0dyOd87+Ihg4du\nXzum8pHsCI9ITUh8xJnGnzGI6FtQnA+31A5dADyYTFbQzvjT480FB3yT5RdRvSDd\nqaawijg2U1i+GV7yXxDZgGwrsrCzEuno+M0uaco4Wb4GV0AvPVKPcRHxca45+xkA\nurFCIbzu9M7aRIFJOqaiuJ7l+AhxVUqQqE2ZJ+j1NZUoCvascGXZ6nQvyjZCB5i4\nD+Y+uXFzVX8l7JtsQ1CqSIlU3PUXbXbQcpcbEc2/ZXNYc3Ss4BOEbxObHh6P8VAx\nE+wIo2qnAgMBAAECggEACkAZtmoGsV30BK/P2zfdSDilwLgaJbloM2FxDHEkZnW/\nUjr/gWXT7fQdh72qK4ZBjTb9sY4+aSyjY2g1zzkMLI85Nksp1YgvUW5GrcV8iK9X\nn31dRMFadrtZOcyUEal6T0wg9VXebNuaRlhlhHAMqQMU2SEAxvPHNL1HY1UA04M5\neWlwgb+WN8ocQgAWT42L/Ys3bLWilA9HfTgotLcM2AonF7MhR8kKubRzCBupaRYI\nRLD+EPJpvz7hbvLjTi8OUc2Ksl8L2ZhuIVLqYljBWWHCEj+z97i5oaUSMo8ZujVo\nMPGQkM6Blh8viR9TSVXH/o+iGnlGgABn3yESNrUUTQKBgQD0Dq1EIXtiXtTOud6O\nI4l9tieb9y9hZXCgaemr2IiH5OpsnALf5aBqNA7WhRFvBN5Jby6F5XhSLPHthQLr\nWWouyJIFUuTxGMBQE3JFOTxn+mikH1nsiuZTqYvZtbfSyiuJA2qMpvegowX+Y0gX\n3HCIUBmNOZW44GsQyYxlzBZeDQKBgQDAboLRGnDTZF+Kg+XcrmTOchW3vrzhIaN+\n25wLxYBBuL8po0XchA2lR9DYl/z+HLl24Qv3x/kWDfqw3YMjeSuJDCiRW6k2nyOs\nBq9iIWJyG120retNeil/ee+Oi4Ot6heMBLhsiPokNiyQErFjFVDja0NWNI3RLT3O\nXl0NnC3ygwKBgQDPIWmyCgySl1NbQ+p7jfORqrgBKY+fxJ0Kt/jmrPZrB0Bh/yJ9\nbYBwwk/ZhsQeCe6yOlcssm0kqRqGcD3jClcQ747mT80YRd5p16dC0unOgXqv2B/U\nnqoOVTXpBs3Fa2n5ddnHF6nQPqvGk/JKtf8X4YteDYJhjq2vaK7scoKFPQKBgQCK\nz20IP6WBp9cPiUPH4/kNvtgRusJvOB/DQ9GV4Ds7rXQgKgocHxnkzyBFDaZO/BOB\nQKesbpEsqpra+H8/mImiC6Y77L3si/Fkh1H+XIyyOTFoQ0kIk4XZ0fDPh65ORx4D\nP+h1Sn3+nyGYMBPLoLW/x0FErxZZ/OOnuQQw7Cds1wJ/BRP+3rVtm8hof1hk7gRS\nUlhuViYUScnBfuuCGp5Fdj1tfuMdqDA0IB7u+7V4sIy4z8WBmeZFsjE67eMTv9uR\nC6tjrdA8DUjiUnSZTwBWzjPZ8u87BtD4y3TZqdhA/a2yd1u6L9Uw7g/JzDpAiMIG\n5Wj8TdEgyCArcswKTBRvZg==\n-----END PRIVATE KEY-----\n', ); $this->alt_crt = new Certificate( descr: 'example_alt_certificate', crt: '-----BEGIN CERTIFICATE-----\nMIIEITCCAwmgAwIBAgIUEuRnc2GSFACHYusB06+IAjZ/XZ0wDQYJKoZIhvcNAQEL\nBQAwgZ8xCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhJbGxpbm9pczEQMA4GA1UEBwwH\nQ2hpY2FnbzEcMBoGA1UECgwTRXhhbXBsZSBDb21wYW55IExMQzELMAkGA1UECwwC\nSVQxGDAWBgNVBAMMD2FsdC5leGFtcGxlLmNvbTEmMCQGCSqGSIb3DQEJARYXZXhh\nbXBsZUBhbHQuZXhhbXBsZS5jb20wHhcNMjUwMTIxMTcyODQxWhcNMzUwMTIxMTcy\nODQxWjCBnzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAwDgYDVQQH\nDAdDaGljYWdvMRwwGgYDVQQKDBNFeGFtcGxlIENvbXBhbnkgTExDMQswCQYDVQQL\nDAJJVDEYMBYGA1UEAwwPYWx0LmV4YW1wbGUuY29tMSYwJAYJKoZIhvcNAQkBFhdl\neGFtcGxlQGFsdC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK/xDL+IK455MhTv86XU/PSAzrrPNbD1f03KxybPr/xX2AFTnIYkez53\nHWMnpizn5e1HoOqXHynptE/wGLsgE105H7aGZ8t52jbqifIIUhTi+HBRC2Yg4YRf\n6PbR+1lvMaCofotz4zMtZtpp/XiOXrSHqulGcV+bSgtu2uxu9wIfwxN2Mvg/8sk7\noqNimwLpuRU0ZqAYt6dFsxsvCZC8gCywYL45ML2KxociWAjFDp0obj7g/j5XSxEj\nAz5KfZdJhxl0NSCD+ewWUYYsnCRWHbojtqlQnGY4QXTYZ6TLGyOB5jPYK7cRCMXb\npsveo24c29UBStiJ1u87gu+PGqUhpVsCAwEAAaNTMFEwHQYDVR0OBBYEFGhql+aY\nSxnUlNLett0Oqb+O6OcqMB8GA1UdIwQYMBaAFGhql+aYSxnUlNLett0Oqb+O6Ocq\nMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKQh880ZOtQeW2VH\nMqILfcF/qf/Ge5kCmS/8mTAAZinvSuCISblcBIMgei3L7LObav48u2BmNFor1o1o\naB4iSMLRyEGNrnYhCy4IrCWw7Y2YjzYJz5oEUy9jJ9S4zOZGV/qNVIhiBSJN274Z\nU8Q8kmF/Tpze1G65AipUgbaw3fx8VgvlOotemtDNsIwMA9WMDnXMqVOBIs5yWWp9\nT9f6Y8to/UcNm0JS7LExn9xTi5WAxstUzk5tWsvJ5pzEppUymSdkSpzObRcqRLZa\n5YeFbd+svKfAue8iukzwuukDsHl8lmwkSASFRwdh3391JC7lpxenAFjs0I1eXUBs\nBz371as=\n-----END CERTIFICATE-----\n', - prv: '-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCv8Qy/iCuOeTIU\n7/Ol1Pz0gM66zzWw9X9Nyscmz6/8V9gBU5yGJHs+dx1jJ6Ys5+XtR6Dqlx8p6bRP\n8Bi7IBNdOR+2hmfLedo26onyCFIU4vhwUQtmIOGEX+j20ftZbzGgqH6Lc+MzLWba\naf14jl60h6rpRnFfm0oLbtrsbvcCH8MTdjL4P/LJO6KjYpsC6bkVNGagGLenRbMb\nLwmQvIAssGC+OTC9isaHIlgIxQ6dKG4+4P4+V0sRIwM+Sn2XSYcZdDUgg/nsFlGG\nLJwkVh26I7apUJxmOEF02GekyxsjgeYz2Cu3EQjF26bL3qNuHNvVAUrYidbvO4Lv\njxqlIaVbAgMBAAECgf9u/iKMsdrIhqyRiM6sTzFFDf1c3FuCx10INmRsl5juGHys\nWZLlDR6yyXJAm0K6EZF7nzRkyfFe/5BA3ba8Vf/hT6gx/Zh9ROHkwxFDHvypMIaK\nVJZcV2HtoJPXIaDvSraEI8exMeqi3oGESFkfNLGKMgwgHdBoAA82Jzj07wvkyhjG\nuB2KZ0x8bDOuan9nM8EfJ6ncN8/biOolPfOv/ojUDjp0yN//gkPQTij50Dj8dxEU\nwobi9AE3diszC8gjj5i4mX14qWKC6/xxGcSHoxsPYUJt7nYGb5RRZciqChroob0o\nDP8VpQR/SnqY8OU97xExbjS0zjvlhipPgiMbGKECgYEA3SVdbR1niZrNEly2S9y1\nsL+pzl2t6KA6pd6XFvlMqgqAtCo9MOVDXkqfptKt9oT+MC8SkbwFFIYLWXHMBNcO\nqfVCIK1mp5scHRgiBdmjo0yEXIsJ5J1y+bm8mTlyGJHbefh7FQoHnrJ/7X9WXuQo\nZWetv196PQuOiLUL49NukGECgYEAy6vQBznsessCeetXOHOO7ap9dieoWzFK0YIX\nZaA9jX+REgIhhIxgWkBcsMQAlm5B74YskYcbsrUm510xn0stzzh91O6DyoM3PJJc\nrqeUJU+HEYELcuZCVy4RV2+XMjSLPc5Sivgbe0mOfHKz3szNIwGKZGuRrWIT6djA\nUyuuvzsCgYA7Qdzj9SIeTD2xMuiiMVT6NJFyu8Vy9SRh9+AyhsDVO5U7MapN5NEF\nfieDkyaMTRyzmpl2NZSC6Fw4LncFwP6r6g07JlvAg56n6SSMvsHLayB4j8Up3krF\nwdBYmn0JOmQ+LagFfjeGfbwrzpg9OxPcAEkdY58kpmkeFq1F8gzJYQKBgQCrb0d/\njaYcBd1julCEV+Cq9KL+XYs8l1Ue3J3NEQA+pm359ok4BODllt9ueszz0oL1lr7V\nECCkYA8LGEq4hpNcpXRlUw7j22cXOSuCZ9QvQ62xcims4Vxd/YzjSC7AN7IsqAqa\npxSaxeOFpRb6JvFU0esnc9P/WVrhr2zTlSVWGQKBgCFaRF3yP/Tc8EvYEhCPgRLv\nsM5FZUe/86gWoWUhSoXcPpW+CipjatcB3kLxXXcYMFYl4cVe5KwTemMmDgMMiMdP\nwn+R2SGvU89GTCTWmlUeD4sesNVFcHMe2Rw0pBf1BTh5KIsA9BXRe13bWHU+x8T2\nSIeb+boJuWOH4flIIBud\n-----END PRIVATE KEY-----\n' + prv: '-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCv8Qy/iCuOeTIU\n7/Ol1Pz0gM66zzWw9X9Nyscmz6/8V9gBU5yGJHs+dx1jJ6Ys5+XtR6Dqlx8p6bRP\n8Bi7IBNdOR+2hmfLedo26onyCFIU4vhwUQtmIOGEX+j20ftZbzGgqH6Lc+MzLWba\naf14jl60h6rpRnFfm0oLbtrsbvcCH8MTdjL4P/LJO6KjYpsC6bkVNGagGLenRbMb\nLwmQvIAssGC+OTC9isaHIlgIxQ6dKG4+4P4+V0sRIwM+Sn2XSYcZdDUgg/nsFlGG\nLJwkVh26I7apUJxmOEF02GekyxsjgeYz2Cu3EQjF26bL3qNuHNvVAUrYidbvO4Lv\njxqlIaVbAgMBAAECgf9u/iKMsdrIhqyRiM6sTzFFDf1c3FuCx10INmRsl5juGHys\nWZLlDR6yyXJAm0K6EZF7nzRkyfFe/5BA3ba8Vf/hT6gx/Zh9ROHkwxFDHvypMIaK\nVJZcV2HtoJPXIaDvSraEI8exMeqi3oGESFkfNLGKMgwgHdBoAA82Jzj07wvkyhjG\nuB2KZ0x8bDOuan9nM8EfJ6ncN8/biOolPfOv/ojUDjp0yN//gkPQTij50Dj8dxEU\nwobi9AE3diszC8gjj5i4mX14qWKC6/xxGcSHoxsPYUJt7nYGb5RRZciqChroob0o\nDP8VpQR/SnqY8OU97xExbjS0zjvlhipPgiMbGKECgYEA3SVdbR1niZrNEly2S9y1\nsL+pzl2t6KA6pd6XFvlMqgqAtCo9MOVDXkqfptKt9oT+MC8SkbwFFIYLWXHMBNcO\nqfVCIK1mp5scHRgiBdmjo0yEXIsJ5J1y+bm8mTlyGJHbefh7FQoHnrJ/7X9WXuQo\nZWetv196PQuOiLUL49NukGECgYEAy6vQBznsessCeetXOHOO7ap9dieoWzFK0YIX\nZaA9jX+REgIhhIxgWkBcsMQAlm5B74YskYcbsrUm510xn0stzzh91O6DyoM3PJJc\nrqeUJU+HEYELcuZCVy4RV2+XMjSLPc5Sivgbe0mOfHKz3szNIwGKZGuRrWIT6djA\nUyuuvzsCgYA7Qdzj9SIeTD2xMuiiMVT6NJFyu8Vy9SRh9+AyhsDVO5U7MapN5NEF\nfieDkyaMTRyzmpl2NZSC6Fw4LncFwP6r6g07JlvAg56n6SSMvsHLayB4j8Up3krF\nwdBYmn0JOmQ+LagFfjeGfbwrzpg9OxPcAEkdY58kpmkeFq1F8gzJYQKBgQCrb0d/\njaYcBd1julCEV+Cq9KL+XYs8l1Ue3J3NEQA+pm359ok4BODllt9ueszz0oL1lr7V\nECCkYA8LGEq4hpNcpXRlUw7j22cXOSuCZ9QvQ62xcims4Vxd/YzjSC7AN7IsqAqa\npxSaxeOFpRb6JvFU0esnc9P/WVrhr2zTlSVWGQKBgCFaRF3yP/Tc8EvYEhCPgRLv\nsM5FZUe/86gWoWUhSoXcPpW+CipjatcB3kLxXXcYMFYl4cVe5KwTemMmDgMMiMdP\nwn+R2SGvU89GTCTWmlUeD4sesNVFcHMe2Rw0pBf1BTh5KIsA9BXRe13bWHU+x8T2\nSIeb+boJuWOH4flIIBud\n-----END PRIVATE KEY-----\n', ); $this->backend = new HAProxyBackend( name: 'example_backend', @@ -143,9 +143,9 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { ssloffloadcert: $this->main_crt->refid->value, ha_certificates: [ [ - 'ssl_certificate' => $this->alt_crt->refid->value + 'ssl_certificate' => $this->alt_crt->refid->value, ], - ] + ], ); $frontend->create(); @@ -192,7 +192,10 @@ class APIModelsHAProxyFrontendTestCase extends TestCase { # Ensure the nested HAProxyFronendCertificate object was created successful $this->assert_is_not_empty($read_frontend->ha_certificates->value); $this->assert_is_true(HAProxyFrontendCertificate::query(parent_id: $frontend->id, id: 0)->exists()); - $this->assert_equals($read_frontend->ha_certificates->value[0]['ssl_certificate'], $this->alt_crt->refid->value); + $this->assert_equals( + $read_frontend->ha_certificates->value[0]['ssl_certificate'], + $this->alt_crt->refid->value, + ); # Update the object and ensure the changes were successful $frontend->status->value = 'disabled'; From ede2f0c31c92d6a35038c91e1ed3cc4572d666eb Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 6 Feb 2025 20:55:44 -0700 Subject: [PATCH 12/34] fix!: change BINDZone refresh, retry, expire and minimum fields to StringFields #653 --- .../usr/local/pkg/RESTAPI/Models/BINDZone.inc | 28 +++++++++++-------- .../Tests/APIModelsBINDZoneTestCase.inc | 16 +++++------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/BINDZone.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/BINDZone.inc index 42921e8e6..f72481dff 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/BINDZone.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/BINDZone.inc @@ -35,10 +35,10 @@ class BINDZone extends Model { public StringField $nameserver; public StringField $mail; public IntegerField $serial; - public IntegerField $refresh; - public IntegerField $retry; - public IntegerField $expire; - public IntegerField $minimum; + public StringField $refresh; + public StringField $retry; + public StringField $expire; + public StringField $minimum; public BooleanField $enable_updatepolicy; public StringField $updatepolicy; public ForeignModelField $allowupdate; @@ -161,30 +161,34 @@ class BINDZone extends Model { conditions: ['type' => ['master', 'redirect']], help_text: 'The SOA serial number for this zone.', ); - $this->refresh = new IntegerField( + $this->refresh = new StringField( default: null, allow_null: true, conditions: ['type' => ['master', 'redirect']], - help_text: 'The SOA refresh interval (in seconds) for this zone.', + help_text: 'The SOA refresh interval for this zone. TTL-style time-unit suffixes are ' . + 'supported (e.g. 1h, 1d, 1w), otherwise time in seconds is assumed.', ); - $this->retry = new IntegerField( + $this->retry = new StringField( default: null, allow_null: true, conditions: ['type' => ['master', 'redirect']], - help_text: 'The SOA retry interval (in seconds) for this zone.', + help_text: 'The SOA retry interval for this zone. TTL-style time-unit suffixes are ' . + 'supported (e.g. 1h, 1d, 1w), otherwise time in seconds is assumed.', ); - $this->expire = new IntegerField( + $this->expire = new StringField( default: null, allow_null: true, conditions: ['type' => ['master', 'redirect']], - help_text: 'The SOA expiry interval (in seconds) for this zone.', + help_text: 'The SOA expiry interval for this zone. TTL-style time-unit suffixes are ' . + 'supported (e.g. 1h, 1d, 1w), otherwise time in seconds is assumed.', ); - $this->minimum = new IntegerField( + $this->minimum = new StringField( default: null, allow_null: true, conditions: ['type' => ['master', 'redirect']], help_text: 'The SOA minimum TTL interval (in seconds) for this zone. This is also referred to as the ' . - 'negative TTL.', + 'negative TTL. TTL-style time-unit suffixes are supported (e.g. 1h, 1d, 1w), otherwise time in ' . + 'seconds is assumed.', ); $this->enable_updatepolicy = new BooleanField( default: false, diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsBINDZoneTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsBINDZoneTestCase.inc index ee65c1316..ab01ceafb 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsBINDZoneTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsBINDZoneTestCase.inc @@ -49,10 +49,10 @@ class APIModelsBINDZoneTestCase extends TestCase { nameserver: 'ns1.example.com', mail: 'admin.example.com', serial: 123456, - refresh: 3605, - retry: 605, - expire: 86405, - minimum: 2605, + refresh: '3605', + retry: '605', + expire: '86405', + minimum: '2605', records: [ ['name' => 'a.example.com.', 'type' => 'A', 'rdata' => '4.3.2.1'], ['name' => 'mx.example.com.', 'type' => 'MX', 'rdata' => 'mail.example.com.', 'priority' => 5], @@ -92,10 +92,10 @@ class APIModelsBINDZoneTestCase extends TestCase { $zone->nameserver->value = 'ns2.example.com'; $zone->mail->value = 'admin2.example.com'; $zone->serial->value = 654321; - $zone->refresh->value = 3606; - $zone->retry->value = 606; - $zone->expire->value = 86406; - $zone->minimum->value = 2606; + $zone->refresh->value = '3606'; + $zone->retry->value = '606'; + $zone->expire->value = '86406'; + $zone->minimum->value = '2606'; $zone->records->value = [ ['name' => 'a2.example.com.', 'type' => 'A', 'rdata' => '5.5.5.5'], ['name' => 'mx2.example.com.', 'type' => 'MX', 'rdata' => 'mail2.example.com.', 'priority' => 15], From 671162e87467435063de02e6967cca84fbeb7e57 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 18 Mar 2025 18:43:36 -0600 Subject: [PATCH 13/34] fix!: better utilize internal service handlers #634, #635 --- .../usr/local/pkg/RESTAPI/Core/Endpoint.inc | 6 +- .../usr/local/pkg/RESTAPI/Core/Model.inc | 11 ++- .../usr/local/pkg/RESTAPI/Models/Service.inc | 76 ++++++++----------- 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Endpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Endpoint.inc index ccbc0482d..16aa82cf9 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Endpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Endpoint.inc @@ -1147,8 +1147,10 @@ class Endpoint { * Creates a new object for the assigned Model using the data submitted in a POST request. */ protected function post(): Model|ModelSet { - # POST request cannot include an ID, strip the ID if present - unset($this->request_data['id']); + # POST requests cannot include an ID unless auto_create_id is off + if ($this->model->auto_create_id) { + unset($this->request_data['id']); + } # Construct the model from representation using the client's request data $this->model->from_representation(data: $this->request_data); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc index b491f2d81..0bec530c1 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc @@ -89,6 +89,15 @@ class Model { */ public string $id_type = 'integer'; + /** + * @var bool $auto_create_id + * Enables or disables automatic creation of an ID for this Model object when the `create()` method is called. If + * set to `true`, the ID will be automatically generated. If set to `false`, an ID must be provided before the + * `create()` method is called. This applies exclusively to Models with $many enabled and is usually only + * relevant to Model classes that use an internal callable method. + */ + public bool $auto_create_id = true; + /** * @var mixed $parent_id * For Models acting as children to a different Model class, this property will contain the ID of the parent model @@ -2036,7 +2045,7 @@ class Model { */ final public function create(bool $apply = false): Model { # Ensure all object Fields and validations succeed for proceeding. - if ($this->validate(create_id: true)) { + if ($this->validate(requires_id: !$this->auto_create_id, create_id: $this->auto_create_id)) { # Check if creating this object would put the total number of objects over the $many_maximum $this->check_many_maximum(); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/Service.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/Service.inc index c47734694..a7b38aa51 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/Service.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/Service.inc @@ -20,20 +20,17 @@ class Service extends Model { public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], mixed ...$options) { # Set Model attributes $this->internal_callable = 'get_services'; + $this->auto_create_id = false; // We don't create services with create(), we just control existing ones $this->many = true; # Set Model Fields - $this->name = new StringField( - required: true, - choices_callable: 'get_service_name_choices', - help_text: 'The internal name of the service.', - ); $this->action = new StringField( required: true, choices: ['start', 'stop', 'restart'], write_only: true, help_text: 'The action to perform against this service.', ); + $this->name = new StringField(read_only: true, help_text: 'The internal name of the service.'); $this->description = new StringField(read_only: true, help_text: 'The full descriptive name of the service.'); $this->enabled = new BooleanField( read_only: true, @@ -57,59 +54,52 @@ class Service extends Model { * @return array An array of available pfSense services and their current statuses. */ public static function get_services(): array { - # Variables - $services = get_services(); - - # Loop through each service and set the proper values - foreach ($services as $id => $service) { - # Ensure the $enabled and $status values are always present - $services[$id]['enabled'] = is_service_enabled($service['name']); - $services[$id]['status'] = is_service_running($service['name']); - } - - return $services; - } - - /** - * Obtains all internal service choices for the `name` field. - * @return array An array of available service names. - */ - public function get_service_name_choices(): array { - $choices = []; - foreach ($this->get_services() as $service) { - $choices[] = $service['name']; - } - return $choices; - } - - /** - * Gets the representation ID for a given service name. - * @param string $name The internal service to obtain the ID for. - * @return int The representation ID of the service with this name. - */ - public function get_id_by_name(string $name): int { - return $this->query(['name' => $name])->first()->id; + return get_services(); } /** * Starts or stops a specified service. */ - public function _create() { + public function _create(): void { + # Obtain the extras for this service. Provides additional info required for some services to be controlled. + $extras = $this->_format_extra_data($this->get_services()[$this->id]); + # Trigger the requested service action switch ($this->action->value) { case 'start': - service_control_start(name: $this->name->value, extras: []); + service_control_start(name: $this->name->value, extras: $extras); break; case 'stop': - service_control_stop(name: $this->name->value, extras: []); + service_control_stop(name: $this->name->value, extras: $extras); break; case 'restart': - service_control_restart(name: $this->name->value, extras: []); + service_control_restart(name: $this->name->value, extras: $extras); break; } - # Populate the current status of the service with this name so this object is up-to-date. - $this->id = $this->get_id_by_name($this->name->value); + # Recheck the service status after the action has been performed to ensure the correct status is returned $this->from_internal(); } + + /** + * Formats the 'extra' service data required for some services to be restarted to the weird format pfSense + * sometimes expects. In the webConfigurator, this process is obfuscated by AJAX calls and JavaScript so there + * is no direct function to format this data. This function is a workaround to provide the correct data format. + * + * @param array $service The service data to format. + * @return array The formatted service data suitable for the service control functions 'extras' parameter. + */ + public function _format_extra_data(array $service): array { + # Format OpenVPN service extra data for service control + if ($service['name'] === 'openvpn') { + # 'mode' changes to 'vpnmode' + $service['vpnmode'] = $service['mode']; + unset($service['mode']); + + # 'vpnid' changes to just 'id' + $service['id'] = $service['vpnid']; + } + + return $service; + } } From 4885bcd207234c812df2f62e85855fa1e09e7bc3 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 18 Mar 2025 18:46:47 -0600 Subject: [PATCH 14/34] tests: adjust Service model tests for new structure --- .../Tests/APIModelsServiceTestCase.inc | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc index c4483fb60..dad1786e6 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc @@ -22,37 +22,12 @@ class APIModelsServiceTestCase extends TestCase { } } - /** - * Checks that the `get_service_name_choices()` method correctly identifies all available service names. - */ - public function test_get_service_name_choices(): void { - # Ensure expected services are found in the method's response - $expected_services = ['unbound', 'ntpd', 'syslogd', 'dhcpd', 'dpinger', 'sshd']; - - # Loop through each expected service and ensure it is found - foreach ($expected_services as $service) { - $this->assert_is_true(in_array($service, (new Service())->get_service_name_choices())); - } - } - - /** - * Checks that the `get_id_by_name()` correctly obtains the ID of the Service object with a specific `name` - */ - public function test_get_id_by_name(): void { - # Create a Service object to test with - $test_service = new Service(); - - # Obtain the ID of the `sshd` service and ensure the correct ID was given - $sshd_id = $test_service->get_id_by_name('sshd'); - $this->assert_equals($sshd_id, $test_service->query(['name' => 'sshd'])->first()->id); - } - /** * Checks that a Service can be stopped, started and restarted. */ public function test_can_perform_service_actions(): void { - # Define a Service to test with - $test_service = new Service(data: ['name' => 'ntpd']); + # Obtain a Service to test with + $test_service = Service::query(name: "ntpd")->first(); # Ensure the service can be stopped $test_service->action->value = 'stop'; From 4cc887486c42dc2efc16fa80a9ea8dc23037faea Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 18 Mar 2025 18:48:59 -0600 Subject: [PATCH 15/34] style: run prettier on changed files --- .../usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc index dad1786e6..fb62b1778 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsServiceTestCase.inc @@ -27,7 +27,7 @@ class APIModelsServiceTestCase extends TestCase { */ public function test_can_perform_service_actions(): void { # Obtain a Service to test with - $test_service = Service::query(name: "ntpd")->first(); + $test_service = Service::query(name: 'ntpd')->first(); # Ensure the service can be stopped $test_service->action->value = 'stop'; From 1c90611d3d74633b860e0c55dd50470097296ddc Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 19 Mar 2025 21:17:42 -0600 Subject: [PATCH 16/34] tests: adjust WireGuardSettings tests to work with new Service structure --- .../pkg/RESTAPI/Tests/APIModelsWireGuardSettingsTestCase.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardSettingsTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardSettingsTestCase.inc index 4dde87a45..aa1912c51 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardSettingsTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardSettingsTestCase.inc @@ -19,12 +19,12 @@ class APIModelsWireGuardSettingsTestCase extends TestCase { # Enable WireGuard and ensure the service is running $wg = new WireGuardSettings(enable: true, async: false); $wg->update(apply: true); - $this->assert_is_true(Service::query(name: 'wireguard', status: true)->exists()); + $this->assert_is_true(is_service_running('wireguard')); # Disable WireGuard and ensure the service is stopped $wg->enable->value = false; $wg->update(apply: true); - $this->assert_is_true(Service::query(name: 'wireguard', status: false)->exists()); + $this->assert_is_false(is_service_running('wireguard')); } /** From 6a5950acceaf6562b52d9093181c14fe0d351362 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 19 Mar 2025 22:12:58 -0600 Subject: [PATCH 17/34] feat: add model and endpoint for DHCPRelay #121 --- .../Endpoints/ServicesDHCPRelayEndpoint.inc | 22 ++++ .../local/pkg/RESTAPI/Models/DHCPRelay.inc | 109 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesDHCPRelayEndpoint.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesDHCPRelayEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesDHCPRelayEndpoint.inc new file mode 100644 index 000000000..ac0cd4de7 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesDHCPRelayEndpoint.inc @@ -0,0 +1,22 @@ +url = '/api/v2/services/dhcp_relay'; + $this->model_name = 'DHCPRelay'; + $this->request_method_options = ['GET', 'PATCH']; + + # Construct the parent Endpoint object + parent::__construct(); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc new file mode 100644 index 000000000..d133aa4cb --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc @@ -0,0 +1,109 @@ +config_path = 'dhrelay'; + $this->many = false; + $this->always_apply = true; + + # Set model fields + $this->enable = new BooleanField(default: false, help_text: 'Enables or disables the DHCP relay.'); + $this->interface = new InterfaceField( + default: [], + many: true, + help_text: 'The downstream interfaces to listen on for DHCP requests.', + ); + $this->agentoption = new BooleanField( + default: false, + help_text: 'Enables or disables appending the circuit ID (interface number) and the agent ID to the ' . + 'DHCP request.', + ); + $this->carpstatusvip = new ForeignModelField( + model_name: 'VirtualIP', + model_field: 'uniqid', + allowed_keywords: ['none'], + default: 'none', + help_text: 'DHCP Relay will be stopped when the chosen VIP is in BACKUP status, and started ' . + 'in MASTER status.', + ); + $this->server = new StringField( + required: true, + many: true, + many_minimum: 1, + validators: [new IPAddressValidator(allow_ipv6: false)], + help_text: 'The IPv4 addresses of the DHCP server to relay requests to.', + ); + + parent::__construct($id, $parent_id, $data, ...$options); + } + + /** + * Provide extra validation to the 'enable' field. + * @param bool $enable The value to be validated. + * @return bool The validated value. + * @throws ConflictError If the DHCP relay is being enabled but a DHCP Server is running. + */ + public function validate_enable(bool $enable): bool { + if ($enable and DHCPServer::query(enable: true)->exists()) { + throw new ConflictError( + message: 'DHCP Relay cannot be enabled while DHCP Server is enabled on any interface.', + response_id: 'DHCP_RELAY_CANNOT_BE_ENABLED_WHILE_DHCP_SERVER_IS_ENABLED', + ); + } + + return $enable; + } + + /** + * Override the default 'to_internal()' method to ensure the carpstatusvip field is prefixed with '_vip'. + */ + public function to_internal(): array|string { + $internal_data = parent::to_internal(); + + if ($this->carpstatusvip->value !== 'none') { + $internal_data['carpstatusvip'] = "_vip{$internal_data['carpstatusvip']}"; + } + + return $internal_data; + } + + /** + * Add custom logic for when the 'carpstatusvip' field is being set from its internal value. This is used to + * remove the _vip prefix from the value so the framework can locate it's related VirtualIP model. + */ + public function from_internal_carpstatusvip(string $value): string { + if (str_starts_with($value, '_vip')) { + return substr($value, 4); + } + + return $value; + } + + /** + * Apply changes to the DHCP relay + */ + public function apply(): void { + services_dhcrelay_configure(); + filter_configure(); + } +} From c2a16d89be5c59c137203647539b360c0f45d5cb Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 21 Mar 2025 21:52:52 -0600 Subject: [PATCH 18/34] fix: use DHCPRelay model in DHCPServer validation checks instead of direct config --- .../usr/local/pkg/RESTAPI/Models/DHCPServer.inc | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc index 636d418fc..7cd9c0f64 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc @@ -316,14 +316,13 @@ class DHCPServer extends Model { } # Do not allow the DHCP server to be enabled if this interface is already running a DHCP relay - # TODO: Replace this with a Model query when the DHCPRelay Model is developed - foreach ($this->get_config('dhcrelay', []) as $dhcp_relay) { - if ($this->id === $dhcp_relay['interface']) { - throw new ValidationError( - message: 'DHCP server cannot be enabled because a DHCP relay is already running.', - response_id: 'DHCP_SERVER_CANNOT_BE_ENABLED_WITH_DHCP_RELAY', - ); - } + $dhcp_relay = new DHCPRelay(); + if (in_array($this->interface->value, $dhcp_relay->interface->value)) { + throw new ValidationError( + message: "DHCP server cannot be enabled while a DHCP Relay is enabled on " . + "interface {$this->interface->value}.", + response_id: 'DHCP_SERVER_CANNOT_BE_ENABLED_WITH_DHCP_RELAY', + ); } return $enable; From e1e0bb069103de4bc4e8509d20ca80534e97c7db Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 21 Mar 2025 21:53:34 -0600 Subject: [PATCH 19/34] style: run prettier on changed files --- .../files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc index 7cd9c0f64..0c7ea5e68 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc @@ -319,7 +319,7 @@ class DHCPServer extends Model { $dhcp_relay = new DHCPRelay(); if (in_array($this->interface->value, $dhcp_relay->interface->value)) { throw new ValidationError( - message: "DHCP server cannot be enabled while a DHCP Relay is enabled on " . + message: 'DHCP server cannot be enabled while a DHCP Relay is enabled on ' . "interface {$this->interface->value}.", response_id: 'DHCP_SERVER_CANNOT_BE_ENABLED_WITH_DHCP_RELAY', ); From f6535a266f1ec955eccea99230ec4d86359c1018 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 21 Mar 2025 22:22:00 -0600 Subject: [PATCH 20/34] docs: improve updates and versioning documentation --- docs/INSTALL_AND_CONFIG.md | 50 +++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/INSTALL_AND_CONFIG.md b/docs/INSTALL_AND_CONFIG.md index 699518b68..efc5f88d0 100644 --- a/docs/INSTALL_AND_CONFIG.md +++ b/docs/INSTALL_AND_CONFIG.md @@ -70,16 +70,60 @@ pfsense-restapi delete ## Updating the package +Before updating the package, it is recommended to enable the REST API's 'Keep Backup' setting to ensure that your +REST API configurations, keys and access lists are not lost during the update process. It is also highly recommended to +read and understand both the release change notes on GitHub and the [versioning policy](#versioning-policy) to ensure +you do not unintentionally introduce breaking changes to your environment. + +!!! Tip + While the package is updating, the REST API may be unavailable for a short period of time. Updates typically complete + within a minute, but may vary depending on network environment and conditions. It is recommended to + schedule updates during off-peak hours to minimize impact to your integrations. In the event that the update fails, + or takes an excessive amount of time, it is recommended to uninstall and reinstall the package. + +### From the pfSense webConfigurator + +You can easily update or revert the package version from the pfSense webConfigurator by navigating to 'System' -> +'REST API' -> 'Updates' and select the desired version. Click 'Save' to apply the desired version. + +### From the API + +You can update or revert the package to a specified version by sending a request to the [PATCH +/api/v2/system/restapi/version](https://pfrest.org/api-docs/#/SYSTEM/patchSystemRESTAPIVersionEndpoint) endpoint. +Simply set the `install_version` field to the desired version and send the request. + +### From the command line + You can update the package to latest version available to your pfSense version by running the following command: ```bash pfsense-restapi update ``` -## Reverting the package to a specific version - -If you need to revert or upgrade the package to a specific version, you can do so by running the following command: +If you need to revert or upgrade the package to a _specific_ version, you can do so by running the following command: ```bash pfsense-restapi revert ``` + +## Versioning policy + +The REST API package loosely follows [Semantic Versioning](https://semver.org/). The versioning policy is as follows: + +- Major version changes (e.g. 1.x.x to 2.x.x) may include major breaking changes and are not guaranteed to be backwards + compatible. Major changes will often include significant changes to the framework, endpoints, schemas, and/or behavior. +- Minor version changes (e.g. 2.0.x to 2.1.x) may include new features, bug fixes, and/or minor breaking changes. Breaking + changes will be isolated to specific endpoints and will be documented in the release notes. +- Patch version changes (e.g. 2.0.0 to 2.0.1) will only include bug fixes and very small enhancements. Patches will + never contain breaking changes or significant new features that could impact existing functionality. + +### Pre-release versions + +Pre-release versions are occasionally made available to the public to allow for testing of new features and fixes. +Pre-releases will be notated as such on GitHub and are not considered as release candidates within the REST API package's +update features by default. You may opt-in to pre-release updates by enabling the 'Allow Pre-releases' setting in the +REST API settings. + +!!! Warning + Pre-release versions may contain breaking changes, bugs, and/or incomplete features. It is recommended to only use + pre-release versions in testing environments and never in production. \ No newline at end of file From e7408bf38e539628bc97320699ce1f8fe4852ccc Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 22 Mar 2025 09:06:52 -0600 Subject: [PATCH 21/34] fix: use correct DHCP relay config path --- .../files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc index d133aa4cb..59870b790 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPRelay.inc @@ -22,7 +22,7 @@ class DHCPRelay extends Model { public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], mixed ...$options) { # Set model attributes - $this->config_path = 'dhrelay'; + $this->config_path = 'dhcrelay'; $this->many = false; $this->always_apply = true; From 4614bd52c880119d67877731b1267b81aa073db8 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 22 Mar 2025 09:07:24 -0600 Subject: [PATCH 22/34] fix: prevent any DHCPServer from being enabled if DHCPRelay is enabled --- .../files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc index 0c7ea5e68..4a0a18a50 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/DHCPServer.inc @@ -315,12 +315,11 @@ class DHCPServer extends Model { ); } - # Do not allow the DHCP server to be enabled if this interface is already running a DHCP relay + # Do not allow the DHCP server to be enabled if any interface is running a DHCP relay $dhcp_relay = new DHCPRelay(); - if (in_array($this->interface->value, $dhcp_relay->interface->value)) { + if ($dhcp_relay->enable->value) { throw new ValidationError( - message: 'DHCP server cannot be enabled while a DHCP Relay is enabled on ' . - "interface {$this->interface->value}.", + message: 'DHCP server cannot be enabled while the DHCP Relay is enabled.', response_id: 'DHCP_SERVER_CANNOT_BE_ENABLED_WITH_DHCP_RELAY', ); } From dde6702b8d7e6d304ed2e2c470b4f98672662ebe Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 22 Mar 2025 09:40:12 -0600 Subject: [PATCH 23/34] tests: add tests for DHCPRelay model #121 --- .../Tests/APIModelsDHCPRelayTestCase.inc | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc new file mode 100644 index 000000000..56698f81b --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc @@ -0,0 +1,108 @@ +model_objects as $dhcp_server) { + $dhcp_server->enable->value = false; + $dhcp_server->update(); + } + if ($apply) { + (new DHCPServer())->apply(); + } + } + + /** + * Checks that the DHCPRelay cannot be enabled if any DHCPServer is already enabled. + */ + public function test_dhcp_relay_cannot_enable_if_dhcp_server_enabled(): void { + # Disable all running DHCPServers + $this->disable_dhcp_servers(); + + # Ensure we can enable the DHCPRelay + $this->assert_does_not_throw( + callable: function () { + $dhcp_relay = new DHCPRelay(enable: true); + $dhcp_relay->update(); + }, + ); + + # Disable the DHCPRelay + $dhcp_relay = new DHCPRelay(enable: false); + $dhcp_relay->update(); + + # Enable a DHCPServer + $dhcp_server = new DHCPServer(id: 'lan', enable: true); + $dhcp_server->update(); + + # Ensure we cannot enable the DHCPRelay + $this->assert_throws_response( + response_id: 'DHCP_RELAY_CANNOT_BE_ENABLED_WHILE_DHCP_SERVER_IS_ENABLED', + code: 409, + callable: function () use ($dhcp_relay) { + $dhcp_relay = new DHCPRelay(enable: true); + $dhcp_relay->update(); + }, + ); + } + + /** + * Checks that the to_internal() method correctly appends the '_vip' prefix to the 'carpstatusvip' field. + */ + public function test_dhcp_relay_to_internal_carpstatusvip(): void { + $dhcp_relay = new DHCPRelay(carpstatusvip: 'examplecarpvip'); + $this->assert_equals($dhcp_relay->to_internal()['carpstatusvip'], '_vipexamplecarpvip'); + } + + /** + * Checks that the from_internal_carpstatusvip() method correctly removes the '_vip' prefix from the 'carpstatusvip' + * field when the value is loaded from the internal data. + */ + public function test_dhcp_relay_from_internal_carpstatusvip(): void { + $dhcp_relay = new DHCPRelay(); + $this->assert_equals($dhcp_relay->from_internal_carpstatusvip('_vipexamplecarpvip'), 'examplecarpvip'); + } + + /** + * Checks that the DHCP relay service is running after enabling and disabled after disabling the DHCPRelay. + */ + public function test_dhcp_relay_service_running(): void { + # Disable all running DHCPServers + $this->disable_dhcp_servers(apply: true); + + # Ensure the DHCPRelay is enabled + $dhcp_relay = new DHCPRelay(enable: true, server: ['1.2.3.4']); + $dhcp_relay->update(); + + # Ensure the DHCP relay service is running with the correct arguments + $dhcrelay_process = new Command('ps aux | grep dhcrelay'); + $lan_if = $this->env['PFREST_LAN_IF']; + $wan_if = $this->env['PFREST_WAN_IF']; + $this->assert_str_contains( + $dhcrelay_process->output, + "/usr/local/sbin/dhcrelay -id $lan_if -iu $wan_if -a -m replace 1.2.3.4", + ); + + # Disable the DHCPRelay + $dhcp_relay = new DHCPRelay(enable: false); + $dhcp_relay->update(); + + # Ensure the DHCP relay service is not running + $dhcrelay_pid = new Command('pgrep dhcrelay'); + $this->assert_equals($dhcrelay_pid->result_code, 1); + + # Re-enable the DHCPServer on LAN + $dhcp_server = new DHCPServer(id: 'lan', enable: true); + $dhcp_server->update(apply: true); + } +} From 8289e30a49395df61d722213dca9a0f83a5ced6c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 22 Mar 2025 13:30:27 -0600 Subject: [PATCH 24/34] tests: correct usage of DHCPRelay and DHCPServer tests --- .../usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc | 2 +- .../usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc index 56698f81b..0a6d4c100 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc @@ -32,7 +32,7 @@ class APIModelsDHCPRelayTestCase extends TestCase { # Ensure we can enable the DHCPRelay $this->assert_does_not_throw( callable: function () { - $dhcp_relay = new DHCPRelay(enable: true); + $dhcp_relay = new DHCPRelay(enable: true, server: ["1.2.3.4"]); $dhcp_relay->update(); }, ); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc index e2796646a..a2a397d92 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc @@ -43,7 +43,7 @@ class APIModelsDHCPServerTestCase extends TestCase { $dhcp_server = new DHCPServer(id: 'lan', async: false); # Temporarily change the `lan` interface to use dhcp IPv4 - Model::set_config('dhcrelay/0/interface', 'lan'); + Model::set_config('dhcrelay/interface', 'lan'); # Try to enable the DHCP server $dhcp_server->validate_enable(enable: true); From 67fba19b3b1fc91f8e29db35e793941f407dec35 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 22 Mar 2025 13:30:51 -0600 Subject: [PATCH 25/34] style: run prettier on changed files --- .../usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc index 0a6d4c100..da9087db5 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc @@ -32,7 +32,7 @@ class APIModelsDHCPRelayTestCase extends TestCase { # Ensure we can enable the DHCPRelay $this->assert_does_not_throw( callable: function () { - $dhcp_relay = new DHCPRelay(enable: true, server: ["1.2.3.4"]); + $dhcp_relay = new DHCPRelay(enable: true, server: ['1.2.3.4']); $dhcp_relay->update(); }, ); From 53ad6920f3b33d8b52d6f0493152f8e8fb3c580a Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 24 Mar 2025 21:42:36 -0600 Subject: [PATCH 26/34] tests: adjustments to DHCPServer and DHCPRelay model tests --- .../local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc | 6 +++--- .../local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc index da9087db5..7a63a596f 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc @@ -18,7 +18,7 @@ class APIModelsDHCPRelayTestCase extends TestCase { $dhcp_server->update(); } if ($apply) { - (new DHCPServer())->apply(); + (new DHCPServer(async: false))->apply(); } } @@ -81,7 +81,7 @@ class APIModelsDHCPRelayTestCase extends TestCase { $this->disable_dhcp_servers(apply: true); # Ensure the DHCPRelay is enabled - $dhcp_relay = new DHCPRelay(enable: true, server: ['1.2.3.4']); + $dhcp_relay = new DHCPRelay(enable: true, interface: ["lan"], server: ['1.2.3.4']); $dhcp_relay->update(); # Ensure the DHCP relay service is running with the correct arguments @@ -90,7 +90,7 @@ class APIModelsDHCPRelayTestCase extends TestCase { $wan_if = $this->env['PFREST_WAN_IF']; $this->assert_str_contains( $dhcrelay_process->output, - "/usr/local/sbin/dhcrelay -id $lan_if -iu $wan_if -a -m replace 1.2.3.4", + "/usr/local/sbin/dhcrelay -id $lan_if -iu $wan_if 1.2.3.4", ); # Disable the DHCPRelay diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc index a2a397d92..4ab751120 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc @@ -34,7 +34,7 @@ class APIModelsDHCPServerTestCase extends TestCase { /** * Ensures that a DHCP server cannot be enabled on an interface is already running a DHCP relay. */ - public function test_cannot_enable_dhcp_server_with_relay_running() { + public function test_cannot_enable_dhcp_server_with_relay_running(): void { $this->assert_throws_response( response_id: 'DHCP_SERVER_CANNOT_BE_ENABLED_WITH_DHCP_RELAY', code: 400, @@ -43,7 +43,7 @@ class APIModelsDHCPServerTestCase extends TestCase { $dhcp_server = new DHCPServer(id: 'lan', async: false); # Temporarily change the `lan` interface to use dhcp IPv4 - Model::set_config('dhcrelay/interface', 'lan'); + Model::set_config('dhcrelay/enable', ""); # Try to enable the DHCP server $dhcp_server->validate_enable(enable: true); From 32fe485cc2591bcfbc69fb64f4bbbc0f3d111523 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 27 Mar 2025 21:00:29 -0600 Subject: [PATCH 27/34] docs: provide methods of scripting initial installation --- docs/INSTALL_AND_CONFIG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/INSTALL_AND_CONFIG.md b/docs/INSTALL_AND_CONFIG.md index efc5f88d0..0cd0a40ae 100644 --- a/docs/INSTALL_AND_CONFIG.md +++ b/docs/INSTALL_AND_CONFIG.md @@ -47,7 +47,9 @@ pkg-static -C /dev/null add https://github.com/jaredhendrickson13/pfsense-api/re the [releases page](https://github.com/jaredhendrickson13/pfsense-api/releases) to find the package built for your version of pfSense. - When updating pfSense, **you must reinstall this package afterward** as pfSense removes unofficial packages during - system updates and has no way to automatically reinstall them. + system updates and has no way to automatically reinstall them. + - If you're looking for a method of programmatically installing the package without SSH, check out + [pfsense-vshell](https://github.com/jaredhendrickson13/pfsense-vshell)! ## Configuring the package From 9693698016b1debd2172122b22af71114389f038 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 27 Mar 2025 21:41:43 -0600 Subject: [PATCH 28/34] docs: add security policy to mkdocs site --- docs/SECURITY.md | 26 +++++++++++++++++--------- docs/index.md | 6 +++--- mkdocs.yml | 1 + 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index e0249a66d..f99c78f8d 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -1,21 +1,29 @@ # Security Policy -## Supported Versions +Security is a top priority for this project. The REST API package is designed to be secure and provide granular controls +to help admins implement a multi-layered security approach to API access. This document outlines the security policy +for the project and provides information on how to report a security vulnerability. -Below are versions that are currently supported and will receive security updates when available. +## Supported Versions -| Version | Supported | -|---------| ------------------ | -| 2.2.x | :white_check_mark: | -| 1.7.x | :white_check_mark: | -| <=1.6.x | :x: | +Currently, there are two supported versions of the package: the v2 package (pfSense-pkg-RESTAPI) and the legacy v1 +package (pfSense-pkg-API). The v2 package is the latest version of the package and is actively developed and fully +maintained. The legacy v1 package is no longer actively developed and is only receiving compatibility fixes and critical +security updates when necessary. It is highly recommended to regularly update to the latest version of the package to +ensure you are receiving important bug fixes and security updates. ## Reporting a Vulnerability -Should you discover a vulnerability in the pfSense-API code, please report the issue in one of the following ways: +Should you discover a vulnerability in the codebase, please report the issue using the order of preference below: 1. A pull request with code that fixes the discovered vulnerability 2. A private email to either the project owner or the respective code owner 3. As a last resort, you may open a public issue on the repository -Please note this is an independent and open-source project and no bug bounty or reward can be granted. +Please do not disclose the details of the vulnerability publicly until it has been addressed by the maintainers. +The maintainers will work to address the vulnerability as quickly as possible and will provide updates on the issue +as they become available. + +!!! Notice + This is an open-source project and is developed and maintained by members of the pfSense community. No bug bounty + can be offered for reported vulnerabilities. diff --git a/docs/index.md b/docs/index.md index 02c93525d..7e4502f29 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,9 +40,9 @@ opening a pull request. - Jared Hendrickson Jared Hendrickson - github@jaredhendrickson.com !!! Important - Unless your inquiry is regarding a security vulnerability or other sensitive matter, please do not contact the - maintainers directly. Instead, please [open an issue](https://github.com/jaredhendrickson13/pfsense-api/issues/new/choose) - to report a bug or request a feature. For general questions or help requests, please [open a discussion](https://github.com/jaredhendrickson13/pfsense-api/discussions/new/choose). + Unless your inquiry is regarding a [security vulnerability](SECURITY.md) or other sensitive matter, please do not + contact the maintainers directly. Instead, please [open an issue](https://github.com/jaredhendrickson13/pfsense-api/issues/new/choose) to report a bug or request a feature. For + general questions or help requests, please [open a discussion](https://github.com/jaredhendrickson13/pfsense-api/discussions/new/choose). ## Disclaimers diff --git a/mkdocs.yml b/mkdocs.yml index d4c930e79..2c1ee0ce0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,6 +17,7 @@ nav: - Advanced Topics: - Introduction: ADVANCED_TOPICS_INTRO.md - Contributing & Development: CONTRIBUTING.md + - Security Policy: SECURITY.md - Building Custom Authentication: BUILDING_CUSTOM_AUTH_CLASSES.md - Building Custom Query Filters: BUILDING_CUSTOM_QUERY_FILTER_CLASSES.md - Building Custom Models: BUILDING_CUSTOM_MODEL_CLASSES.md From 38c0a3d12e97519cff448cc2a765267188caf023 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 27 Mar 2025 21:42:41 -0600 Subject: [PATCH 29/34] docs: whitespace fix in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb759714c..bcc675b9c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ![Docs](https://img.shields.io/website?url=https%3A%2F%2Fpfrest.org&label=Documentation) The pfSense REST API package is an unofficial, open-source REST and GraphQL API for pfSense CE and pfSense Plus -firewalls.It is designed to be light-weight, fast, and easy to use. This guide will help you get started with the REST +firewalls. It is designed to be light-weight, fast, and easy to use. This guide will help you get started with the REST API package and provide you with the information you need to configure and use the package effectively. ## Key Features From c389a4fc0e8a56d1aa79dedd3e91f9128522d15e Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 27 Mar 2025 21:43:54 -0600 Subject: [PATCH 30/34] style: run prettier on changed files --- .../usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc | 2 +- .../usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc index 7a63a596f..1f723fd7b 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPRelayTestCase.inc @@ -81,7 +81,7 @@ class APIModelsDHCPRelayTestCase extends TestCase { $this->disable_dhcp_servers(apply: true); # Ensure the DHCPRelay is enabled - $dhcp_relay = new DHCPRelay(enable: true, interface: ["lan"], server: ['1.2.3.4']); + $dhcp_relay = new DHCPRelay(enable: true, interface: ['lan'], server: ['1.2.3.4']); $dhcp_relay->update(); # Ensure the DHCP relay service is running with the correct arguments diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc index 4ab751120..9c0600c06 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerTestCase.inc @@ -43,7 +43,7 @@ class APIModelsDHCPServerTestCase extends TestCase { $dhcp_server = new DHCPServer(id: 'lan', async: false); # Temporarily change the `lan` interface to use dhcp IPv4 - Model::set_config('dhcrelay/enable', ""); + Model::set_config('dhcrelay/enable', ''); # Try to enable the DHCP server $dhcp_server->validate_enable(enable: true); From 94a1c447ee1b107316cd957df22f1b9c5374c811 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 28 Mar 2025 09:20:27 -0600 Subject: [PATCH 31/34] docs(oas): add id field to POST endpoints with auto_create_id off --- .../files/usr/local/pkg/RESTAPI/Schemas/OpenAPISchema.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Schemas/OpenAPISchema.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Schemas/OpenAPISchema.inc index cb06afe2a..3e6754a68 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Schemas/OpenAPISchema.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Schemas/OpenAPISchema.inc @@ -184,7 +184,7 @@ class OpenAPISchema extends Schema { } # For non `many` Endpoints with `many` Models, add the ID to the schema and make it required - if (!$endpoint->many and $model->many and $request_method !== 'post') { + if (!$endpoint->many and $model->many and ($request_method !== 'post' or !$model->auto_create_id)) { $schema = [ 'schema' => [ 'allOf' => [ From 436fd173d18d46cf13ff557e2228d08d41b3d993 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 28 Mar 2025 10:01:25 -0600 Subject: [PATCH 32/34] ci: add stale issues/pr job --- .github/workflows/schedule_weekly.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/schedule_weekly.yml diff --git a/.github/workflows/schedule_weekly.yml b/.github/workflows/schedule_weekly.yml new file mode 100644 index 000000000..bfe2a756f --- /dev/null +++ b/.github/workflows/schedule_weekly.yml @@ -0,0 +1,26 @@ +name: Weekly Schedule + +on: + workflow_dispatch: # Enable manual triggering + schedule: + - cron: '0 18 * * 1' + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + env: + DAYS_BEFORE_STALE: 60 + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: | + This issue has been automatically marked as stale because it has had no recent activity in the last ${{ env.DAYS_BEFORE_STALE }} days. + stale-pr-message: | + This pull request has been automatically marked as stale because it has had no recent activity in the last ${{ env.DAYS_BEFORE_STALE }} days. + days-before-stale: ${{ env.DAYS_BEFORE_STALE }} + days-before-close: 0 + exempt-issue-labels: 'feature request,enhancement' From dd67086340e8a90a35a7ef429e23f59bd7e5d818 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 28 Mar 2025 10:02:10 -0600 Subject: [PATCH 33/34] style: run prettier on changed files --- .github/workflows/schedule_weekly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/schedule_weekly.yml b/.github/workflows/schedule_weekly.yml index bfe2a756f..c2156345e 100644 --- a/.github/workflows/schedule_weekly.yml +++ b/.github/workflows/schedule_weekly.yml @@ -3,7 +3,7 @@ name: Weekly Schedule on: workflow_dispatch: # Enable manual triggering schedule: - - cron: '0 18 * * 1' + - cron: "0 18 * * 1" permissions: issues: write @@ -23,4 +23,4 @@ jobs: This pull request has been automatically marked as stale because it has had no recent activity in the last ${{ env.DAYS_BEFORE_STALE }} days. days-before-stale: ${{ env.DAYS_BEFORE_STALE }} days-before-close: 0 - exempt-issue-labels: 'feature request,enhancement' + exempt-issue-labels: "feature request,enhancement" From d3fe0e1ee7927520b54f4446c32da285f3d81130 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 28 Mar 2025 15:31:24 -0600 Subject: [PATCH 34/34] ci: make verified bugs exempt from stale issues --- .github/workflows/schedule_weekly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/schedule_weekly.yml b/.github/workflows/schedule_weekly.yml index c2156345e..9aecee777 100644 --- a/.github/workflows/schedule_weekly.yml +++ b/.github/workflows/schedule_weekly.yml @@ -23,4 +23,4 @@ jobs: This pull request has been automatically marked as stale because it has had no recent activity in the last ${{ env.DAYS_BEFORE_STALE }} days. days-before-stale: ${{ env.DAYS_BEFORE_STALE }} days-before-close: 0 - exempt-issue-labels: "feature request,enhancement" + exempt-issue-labels: "feature request,enhancement,bug"