diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php index e8fb42d1b..f93f514e9 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php @@ -1103,4 +1103,265 @@ class SettingsController extends ApiControllerBase "name" ); } + + /** + * retrieve group settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getGroupAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('groups.group.'.$uuid); + if ($node != null) { + // return node + return array("group" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->groups->group->add(); + return array("group" => $node->getNodes()); + } + return array(); + } + + /** + * update group with given properties + * @param $uuid item unique id + * @return array + */ + public function setGroupAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("group")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('groups.group.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("group")); + return $this->save($mdlCP, $node, "group"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new group and set with attributes from post + * @return array + */ + public function addGroupAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("group")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->groups->group->Add(); + $node->setNodes($this->request->getPost("group")); + return $this->save($mdlCP, $node, "group"); + } + return $result; + } + + /** + * delete group by uuid + * @param $uuid item unique id + * @return array status + */ + public function delGroupAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->groups->group->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * toggle group by uuid (enable/disable) + * @param $uuid item unique id + * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle + * @return array status + */ + public function toggleGroupAction($uuid, $enabled = null) + { + + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('groups.group.' . $uuid); + if ($node != null) { + if ($enabled == "0" || $enabled == "1") { + $node->enabled = (string)$enabled; + } elseif ((string)$node->enabled == "1") { + $node->enabled = "0"; + } else { + $node->enabled = "1"; + } + $result['result'] = $node->enabled; + // if item has toggled, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + } + } + } + return $result; + } + + /** + * search groups + * @return array + */ + public function searchGroupsAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->groups->group); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "name", "description"), + "name" + ); + } + + /** + * retrieve user settings or return defaults + * @param $uuid item unique id + * @return array + */ + public function getUserAction($uuid = null) + { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('users.user.'.$uuid); + if ($node != null) { + // return node + return array("user" => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $mdlCP->users->user->add(); + return array("user" => $node->getNodes()); + } + return array(); + } + + /** + * update user with given properties + * @param $uuid item unique id + * @return array + */ + public function setUserAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("user")) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('users.user.'.$uuid); + if ($node != null) { + $node->setNodes($this->request->getPost("user")); + return $this->save($mdlCP, $node, "user"); + } + } + } + return array("result"=>"failed"); + } + + /** + * add new user and set with attributes from post + * @return array + */ + public function addUserAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost("user")) { + $mdlCP = new HAProxy(); + $node = $mdlCP->users->user->Add(); + $node->setNodes($this->request->getPost("user")); + return $this->save($mdlCP, $node, "user"); + } + return $result; + } + + /** + * delete user by uuid + * @param $uuid item unique id + * @return array status + */ + public function delUserAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + if ($mdlCP->users->user->del($uuid)) { + // if item is removed, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * toggle user by uuid (enable/disable) + * @param $uuid item unique id + * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle + * @return array status + */ + public function toggleUserAction($uuid, $enabled = null) + { + + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlCP = new HAProxy(); + if ($uuid != null) { + $node = $mdlCP->getNodeByReference('users.user.' . $uuid); + if ($node != null) { + if ($enabled == "0" || $enabled == "1") { + $node->enabled = (string)$enabled; + } elseif ((string)$node->enabled == "1") { + $node->enabled = "0"; + } else { + $node->enabled = "1"; + } + $result['result'] = $node->enabled; + // if item has toggled, serialize to config and save + $mdlCP->serializeToConfig(); + Config::getInstance()->save(); + } + } + } + return $result; + } + + /** + * search users + * @return array + */ + public function searchUsersAction() + { + $this->sessionClose(); + $mdlCP = new HAProxy(); + $grid = new UIModelGrid($mdlCP->users->user); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "name", "description"), + "name" + ); + } + } diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php index 265d1734d..604f1d4c7 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php @@ -51,6 +51,8 @@ class IndexController extends \OPNsense\Base\IndexController $this->view->formDialogHealthcheck = $this->getForm("dialogHealthcheck"); $this->view->formDialogAction = $this->getForm("dialogAction"); $this->view->formDialogAcl = $this->getForm("dialogAcl"); + $this->view->formDialogUser = $this->getForm("dialogUser"); + $this->view->formDialogGroup = $this->getForm("dialogGroup"); $this->view->formDialogLua = $this->getForm("dialogLua"); $this->view->formDialogErrorfile = $this->getForm("dialogErrorfile"); $this->view->formDialogMapfile = $this->getForm("dialogMapfile"); diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml index 886d9f206..f56065c94 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml @@ -467,6 +467,23 @@ text + + + header + + + + acl.allowedUsers + + select_multiple + true + + + acl.allowedGroups + + select_multiple + true + header diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml index bfb59e8e9..e00da8571 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml @@ -223,6 +223,31 @@ true + + + header + + + + backend.basicAuthEnabled + + checkbox + Enable HTTP Basic Authentication. + + + backend.basicAuthUsers + + select_multiple + true + Type username or choose from list. + + + backend.basicAuthGroups + + select_multiple + true + Type group or choose from list. + header diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml index af4f53a11..6372012b2 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml @@ -161,6 +161,31 @@ To import additional CRLs, go to CRL Manager.]]> Type CRL name or choose from list. + + + header + + + + frontend.basicAuthEnabled + + checkbox + + + + frontend.basicAuthUsers + + select_multiple + true + Type username or choose from list. + + + frontend.basicAuthGroups + + select_multiple + true + Type group or choose from list. + header diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml new file mode 100644 index 000000000..d868a472d --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogGroup.xml @@ -0,0 +1,27 @@ +
+ + group.enabled + + checkbox + Enable this group. + + + group.name + + text + Name to identify this group. + + + group.description + + text + Description for this group. + + + group.members + + select_multiple + true + Type username or choose from list. + +
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogUser.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogUser.xml new file mode 100644 index 000000000..7644058ee --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogUser.xml @@ -0,0 +1,26 @@ +
+ + user.enabled + + checkbox + Enable this user. + + + user.name + + text + Name to identify this user. + + + user.description + + text + Description for this user. + + + user.password + + password +
NOTE: Avoid using unencrypted passwords that start with a $-sign, because this indicates an encrypted password and will make it impossible to authenticate.
]]>
+
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml index ef67afc0f..417f1e51b 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml @@ -292,13 +292,18 @@ checkbox
- haproxy.general.stats.users - + haproxy.general.stats.allowedUsers + select_multiple - true - - Enter user:password here. Finish with TAB. + Type username or choose from list. + + + haproxy.general.stats.allowedGroups + + select_multiple + true + Type group or choose from list. haproxy.general.stats.customOptions diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index 3ada067d2..6b135564a 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -1,6 +1,6 @@ //OPNsense/HAProxy - 2.5.0 + 2.6.0 the HAProxy load balancer @@ -289,6 +289,30 @@ /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u Please provide a valid user and password, i.e. user:secret123. + + + + + Related user not found + Y + N + + + + + + Related group not found + Y + N + N @@ -429,6 +453,34 @@ Y Please select a valid CA from the list. + + 0 + N + + + + + + Related user not found + Y + N + + + + + + Related group not found + Y + N + 1 500000 @@ -830,6 +882,34 @@ Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". N + + 0 + N + + + + + + Related user not found + Y + N + + + + + + Related group not found + Y + N + /^([0-9]{1,8}(?:us|ms|s|m|h|d)?)/u Should be a number between 1 and 8 characters, optionally followed by either "d", "h", "m", "s", "ms" or "us". @@ -1166,6 +1246,7 @@ Y + HTTP Basic Auth: username/password from client matches selected User/Group Host starts with Host ends with Host matches @@ -1551,6 +1632,30 @@ N N + + + + + Related user not found + Y + N + + + + + + Related group not found + Y + N + @@ -1921,5 +2026,64 @@ + + + + Y + + + 1 + Y + + + /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + + + + + + Related user not found + Y + N + + + + + + + Y + + + 1 + Y + + + /^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$/u + Should be a string between 1 and 255 characters. + Y + + + /^.{1,255}$/u + Should be a string between 1 and 255 characters. + N + + + /^.{1,512}$/u + Should be a string between 1 and 512 characters. + Y + + + diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml index 3747da2ca..8e11c33d5 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml @@ -14,6 +14,8 @@ + + diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M2_6_0.php b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M2_6_0.php new file mode 100644 index 000000000..53bd4a432 --- /dev/null +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M2_6_0.php @@ -0,0 +1,56 @@ +general->stats->users)) { + + // Add new user for each entry + $UUIDlist = array(); + foreach (explode(',', (string)$model->general->stats->users) as $statsuser) { + $olddata = explode(':',$statsuser,2); + $userNode = $model->users->user->Add(); + $userNode->name = (string)$olddata[0]; + $userNode->description = 'stats user'; + $userNode->password = (string)$olddata[1]; + $userNode->enabled = 1; + $UUIDlist[] = $userNode->getAttributes()['uuid']; + } + + // Add collected UUIDs to new list of allowed users + $model->general->stats->allowedUsers = (string)implode(',', $UUIDlist); + } + } +} diff --git a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt index 6148888a7..f97a8f6ae 100644 --- a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt +++ b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt @@ -121,6 +121,32 @@ POSSIBILITY OF SUCH DAMAGE. } ); + $("#grid-users").UIBootgrid( + { search:'/api/haproxy/settings/searchUsers', + get:'/api/haproxy/settings/getUser/', + set:'/api/haproxy/settings/setUser/', + add:'/api/haproxy/settings/addUser/', + del:'/api/haproxy/settings/delUser/', + toggle:'/api/haproxy/settings/toggleUser/', + options: { + rowCount:[10,25,50,100,500,1000] + } + } + ); + + $("#grid-groups").UIBootgrid( + { search:'/api/haproxy/settings/searchGroups', + get:'/api/haproxy/settings/getGroup/', + set:'/api/haproxy/settings/setGroup/', + add:'/api/haproxy/settings/addGroup/', + del:'/api/haproxy/settings/delGroup/', + toggle:'/api/haproxy/settings/toggleGroup/', + options: { + rowCount:[10,25,50,100,500,1000] + } + } + ); + $("#grid-luas").UIBootgrid( { search:'/api/haproxy/settings/searchLuas', get:'/api/haproxy/settings/getLua/', @@ -439,6 +465,20 @@ POSSIBILITY OF SUCH DAMAGE. + + {# add automatically generated tabs #} {% for tab in mainForm['tabs']|default([]) %} {% if tab['subtabs']|default(false) %} @@ -541,6 +581,20 @@ POSSIBILITY OF SUCH DAMAGE. +
+
+

{{ lang._('User Management') }}

+

{{ lang._("Optionally HAProxy manages an internal list of users and groups, which can be used for HTTP Basic Authentication as well as access to HAProxy's internal statistic pages.") }}

+
    +
  • {{ lang._('%sUser:%s A username/password combination. Both secure (encrypted) and insecure (unencrypted) passwords can be used.') | format('', '') }}
  • +
  • {{ lang._('%sGroup:%s A optional list containing one or more users. Groups usually make it easier to manage permissions for a large number of users') | format('', '') }}
  • +
+

{{ lang._('Note that users and groups must be selected from the Backend Pool or Public Service configuration in order to be used for authentication. In addition to this users and groups may also be used in Rules/Conditions.') }}

+

{{ lang._("For more information on HAProxy's %suser/group management%s see the %sofficial documentation%s.") | format('', '', '', '') }}

+
+
+
+

{{ lang._('Advanced Features') }}

@@ -780,6 +834,76 @@ POSSIBILITY OF SUCH DAMAGE.
+
+ + + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('User ID') }}{{ lang._('Username') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Group ID') }}{{ lang._('Group') }}{{ lang._('Description') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+ +
+
+ + +
+
+
+
+
@@ -891,6 +1015,8 @@ POSSIBILITY OF SUCH DAMAGE. {{ partial("layout_partials/base_dialog",['fields':formDialogHealthcheck,'id':'DialogHealthcheck','label':lang._('Edit Health Monitor')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogAction,'id':'DialogAction','label':lang._('Edit Rule')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogAcl,'id':'DialogAcl','label':lang._('Edit Condition')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogUser,'id':'DialogUser','label':lang._('Edit User')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogGroup,'id':'DialogGroup','label':lang._('Edit Group')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogLua,'id':'DialogLua','label':lang._('Edit Lua Script')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogErrorfile,'id':'DialogErrorfile','label':lang._('Edit Error Message')])}} {{ partial("layout_partials/base_dialog",['fields':formDialogMapfile,'id':'DialogMapfile','label':lang._('Edit Map File')])}} diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf index 9565bd8a2..398e9561b 100644 --- a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -72,7 +72,14 @@ {% endif %} {% do acls_seen.append(acl_data.id) %} {% set acl_options = [] %} -{% if acl_data.expression == 'hdr_beg' %} +{% if acl_data.expression == 'http_auth' %} +{% if acl_data.allowedUsers|default("") != "" or acl_data.allowedGroups|default("") != "" %} +{% do acl_options.append('http_auth(acl_' ~ acl_data.id ~ ')') %} +{% else %} +{% set acl_enabled = '0' %} + # ERROR: missing parameters +{% endif %} +{% elif acl_data.expression == 'hdr_beg' %} {% if acl_data.hdr_beg|default("") != "" %} {% do acl_options.append('hdr_beg(host) -i ' ~ acl_data.hdr_beg) %} {% else %} @@ -623,6 +630,110 @@ {% endif %} {%- endmacro -%} +{# Macro expects a backend or frontend object. #} +{% macro BasicAuthConfig(proxy) -%} +{% if proxy is defined %} +{# # basic auth expects a HTTP frontend/backend #} +{% if (proxy.mode == 'http' and proxy.basicAuthEnabled == '1') %} +{# # use the frontend/backend ID to identify the autogenerated userlist #} + acl auth_ok http_auth(list_{{proxy.id}}) + http-request auth if !auth_ok +{% endif %} +{% else %} +# ERROR: BasicAuthConfig called with empty data +{% endif %} +{%- endmacro -%} + +{# Macro expects a backend or frontend object. #} +{% macro UserlistConfig(proxy) -%} +{% if proxy is defined %} +{# # basic auth expects a HTTP frontend/backend #} +{% if (proxy.mode == 'http' and proxy.basicAuthEnabled == '1') %} +{# # use the frontend/backend ID to identify the autogenerated userlist #} + acl auth_ok http_auth({{proxy.id}}) + http-request auth if !auth_ok +{% endif %} +{% else %} +# ERROR: BasicAuthConfig called with empty data +{% endif %} +{%- endmacro -%} + +{# Macro expects a CSV list of users and validates them. #} +{% macro UserlistAddUsers(linkedUserData,linkedGroupData) -%} +{# # remember all users to avoid duplicate entries #} +{% set users_seen = [] %} +{# # process all users #} +{% if linkedUserData is defined %} +{% for user in linkedUserData.split(",") %} +{% set user_data = helpers.getUUID(user) %} +{# # check if this user can (still) be found in configuration #} +{% if user_data == {} %} +# ERROR: user data not found ({{user}}) +{% else %} +{% if user_data.name in users_seen %} + # WARNING: skipping duplicate username ({{user_data.name}}) +{% else %} +{% do users_seen.append(user_data.name) %} +{# # check if using an encrypted password #} +{% if user_data.password|default("")|truncate(1, False, '', 0) == '$' %} +{% set user_pwsec = 'password' %} +{% else %} +{% set user_pwsec = 'insecure-password' %} +{% endif %} + user {{user_data.name}} {{user_pwsec}} {{user_data.password}} +{% endif %} +{% endif %} +{% endfor %} +{% else %} + # WARNING: UserlistAddUsers called with empty user data +{% endif %} +{# # process all group members #} +{% if linkedGroupData is defined %} +{% for group in linkedGroupData.split(",") %} +{% set group_data = helpers.getUUID(group) %} +{# # check if this group can (still) be found in configuration #} +{% if group_data == {} %} + # WARNING: group data not found ({{group}}) +{% else %} +{# # extract user list from group object #} +{% for user in group_data.members.split(",") %} +{% set user_data = helpers.getUUID(user) %} +{% if user_data.name in users_seen %} + # WARNING: skipping duplicate username ({{user_data.name}}) +{% else %} +{% do users_seen.append(user_data.name) %} +{# # check if using an encrypted password #} +{% if user_data.password|default("")|truncate(1, False, '', 0) == '$' %} +{% set user_pwsec = 'password' %} +{% else %} +{% set user_pwsec = 'insecure-password' %} +{% endif %} + user {{user_data.name}} {{user_pwsec}} {{user_data.password}} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% else %} + # WARNING: UserlistAddUsers called with empty group data +{% endif %} +{%- endmacro %} + +{# Macro expects a backend or frontend object. #} +{% macro AddUserlist(proxy) -%} +{% if proxy is defined %} +{% if (proxy.enabled|default("") == '1' and proxy.mode|default("") == 'http' and proxy.basicAuthEnabled|default("") == '1') %} +{# # call macro to generate list of unique users #} +{% set userlist_result = UserlistAddUsers(proxy.basicAuthUsers,proxy.basicAuthGroups) %} +{# # check result, skip when empty #} +{% if (userlist_result is defined and userlist_result|default("") != "" )%} +userlist list_{{proxy.id}} + # Origin: {{proxy.name}} +{{userlist_result}} +{% endif %} +{% endif %} +{% endif %} +{%- endmacro -%} + {% if not (helpers.exists('OPNsense.HAProxy.general') and OPNsense.HAProxy.general.enabled|default("0") == "1") %} # # NOTE: HAProxy is currently DISABLED @@ -747,6 +858,55 @@ defaults {% endif %} {% endif %} +{# ############################### #} +{# USERLISTS #} +{# ############################### #} +{# # NOTE: Yes, this config block is redundant and duplicates entries (on purpose). #} +{# # This makes it much easier for a user to compose this from the GUI. #} + +# autogenerated entries for ACLs +{% if helpers.exists('OPNsense.HAProxy.acls') %} +{% for acl in helpers.toList('OPNsense.HAProxy.acls.acl') %} +{% if (acl.allowedUsers|default("") != "") or (acl.allowedGroups|default("") != "") %} +{# # call macro to generate list of unique users #} +{% set userlist_result = UserlistAddUsers(acl.allowedUsers, acl.allowedGroups) %} +{# # check result, skip when empty #} +{% if (userlist_result is defined and userlist_result|default("") != "" )%} +userlist acl_{{acl.id}} + # Origin: {{acl.name}} +{{userlist_result}} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} + +# autogenerated entries for config in backends/frontends +{% if helpers.exists('OPNsense.HAProxy.frontends') %} +{% for frontend in helpers.toList('OPNsense.HAProxy.frontends.frontend') %} +{# # call macro to generate userlist #} +{{ AddUserlist(frontend) -}} +{% endfor %} +{% endif %} +{% if helpers.exists('OPNsense.HAProxy.backends') %} +{% for backend in helpers.toList('OPNsense.HAProxy.backends.backend') %} +{# # call macro to generate userlist #} +{{ AddUserlist(backend) -}} +{% endfor %} +{% endif %} + +# autogenerated entries for stats +{% if OPNsense.HAProxy.general.stats.remoteEnabled|default("") == "1" %} +{% if (OPNsense.HAProxy.general.stats.allowedUsers|default("") != "") or (OPNsense.HAProxy.general.stats.allowedGroups|default("") != "") %} +{# # call macro to generate list of unique users #} +{% set userlist_result = UserlistAddUsers(OPNsense.HAProxy.general.stats.allowedUsers, OPNsense.HAProxy.general.stats.allowedGroups) %} +{# # check result, skip when empty #} +{% if (userlist_result is defined and userlist_result|default("") != "" )%} +userlist stats_auth +{{userlist_result}} +{% endif %} +{% endif %} +{% endif %} + {# ############################### #} {# FRONTENDS #} {# ############################### #} @@ -875,6 +1035,8 @@ frontend {{frontend.name}} {% if frontend.logging_socketStats=='1' %} option socket-stats {% endif %} +{# # call macro to evaluate basic auth config #} +{{ BasicAuthConfig(frontend) -}} {# # action and ACL configuration #} {% if frontend.linkedActions|default("") != "" -%} {# # call macro to evaluate ACLs and actions #} @@ -1033,6 +1195,8 @@ backend {{backend.name}} {% if backend.tuning_retries|default("") != "" %} retries {{backend.tuning_retries}} {% endif %} +{# # call macro to evaluate basic auth config #} +{{ BasicAuthConfig(backend) -}} {# # action and ACL configuration #} {% if backend.linkedActions|default("") != "" -%} {# # call macro to evaluate ACLs and actions #} @@ -1216,15 +1380,12 @@ listen remote_statistics {% endfor %} mode http stats uri /haproxy?stats - stats realm HAProxy\ statistics stats hide-version {# # enable authentication? #} {% if OPNsense.HAProxy.general.stats.authEnabled|default("") == "1" %} -{% if OPNsense.HAProxy.general.stats.users|default("") != "" %} -{% for statsuser in OPNsense.HAProxy.general.stats.users.split(",") %} - stats auth {{statsuser}} -{% endfor %} -{% endif %} + acl auth_ok http_auth(stats_auth) + stats http-request allow if auth_ok + stats http-request auth realm HAProxy\ statistics {% endif %} {% if OPNsense.HAProxy.general.stats.customOptions|default("") != "" %} # WARNING: pass through options below this line @@ -1235,9 +1396,10 @@ listen remote_statistics {% else %} # ERROR: remote statistics disabled, because no listen address was specified {% endif %} -{% endif %} -{% else %} +{% else %} # statistics are DISABLED +{% endif %} + {% endif %} {% endif %}