diff --git a/net/frr/pkg-descr b/net/frr/pkg-descr
index 77b799b58..a8ef2122d 100644
--- a/net/frr/pkg-descr
+++ b/net/frr/pkg-descr
@@ -14,6 +14,7 @@ Plugin Changelog
1.26
* Fix Model migration errors
+* Add BGP password authentication
1.25
diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php
index 8eb6bd129..9f505632e 100644
--- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php
+++ b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php
@@ -45,6 +45,8 @@ class BgpController extends ApiMutableModelControllerBase
"description",
"address",
"remoteas",
+ "password",
+ "localip",
"updatesource",
"nexthopself",
"multihop",
diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml
index d9f7f012d..8665c2bce 100644
--- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml
+++ b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml
@@ -22,6 +22,20 @@
textNeighbor AS.
+
+ neighbor.password
+
+ text
+ true
+ Set a password for BGP authentication.
+
+
+ neighbor.localip
+
+ text
+ true
+ Set the local IP connecting to the neighbor. This is only required for BGP authentication.
+ neighbor.updatesource
diff --git a/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml b/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml
index 3a052bd4e..33dd74147 100644
--- a/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml
+++ b/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml
@@ -57,6 +57,12 @@
14294967295
+
+ N
+
+
+ N
+ N
diff --git a/net/frr/src/opnsense/scripts/frr/register_sas b/net/frr/src/opnsense/scripts/frr/register_sas
new file mode 100755
index 000000000..a56c07ef6
--- /dev/null
+++ b/net/frr/src/opnsense/scripts/frr/register_sas
@@ -0,0 +1,68 @@
+#!/usr/local/bin/python3
+"""
+ Copyright (c) 2022 Ad Schellevis
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+"""
+import os
+import subprocess
+import tempfile
+from configparser import ConfigParser
+
+if __name__ == '__main__':
+ frr_sad = {}
+ frr_sa_database = "/usr/local/etc/frr/sa_policies.conf"
+ # stage 1, read required FRR policies
+ if os.path.exists(frr_sa_database):
+ cnf = ConfigParser()
+ cnf.read(frr_sa_database)
+ for section in cnf.sections():
+ if cnf.has_option(section, 'src') and cnf.has_option(section, 'dst'):
+ policy_key = "%s %s" % (cnf.get(section, 'src'), cnf.get(section, 'dst'))
+ frr_sad[policy_key] = {}
+ for prop in cnf.items(section):
+ frr_sad[policy_key][prop[0]] = prop[1]
+
+ # stage 2, red current installed policies which seems to originate from FRR
+ registered_policies = []
+ current_policy = None
+ for line in subprocess.run(["/sbin/setkey", "-D"], capture_output=True, text=True).stdout.split('\n'):
+ parts = line.strip().split()
+ if not line.startswith('\t') and len(parts) > 1:
+ current_policy = {"src": parts[0], "dst": parts[1]}
+ elif len(parts) > 2 and parts[0] == 'A:' and parts[1] == 'tcp-md5':
+ # Let's assume we're the only ones registering these types of entries
+ registered_policies.append(current_policy)
+
+ # flush changes to temp file and load with setkey
+ temp_filename = None
+ with tempfile.NamedTemporaryFile(mode='wt', delete=False) as fo:
+ temp_filename = fo.name
+ for policy in registered_policies:
+ fo.write("delete -4 %(src)s %(dst)s tcp 0x1000;\n" % policy)
+ for new_policy in frr_sad:
+ fo.write('add -4 %(src)s %(dst)s %(protocol)s %(spi)s -A %(aalgo)s "%(key)s";\n' % frr_sad[new_policy])
+
+ if temp_filename:
+ subprocess.run(["/sbin/setkey", "-f", fo.name], capture_output=True, text=True)
diff --git a/net/frr/src/opnsense/scripts/quagga/setup.sh b/net/frr/src/opnsense/scripts/quagga/setup.sh
index 5cb140a6b..f91e271fd 100755
--- a/net/frr/src/opnsense/scripts/quagga/setup.sh
+++ b/net/frr/src/opnsense/scripts/quagga/setup.sh
@@ -18,3 +18,6 @@ chown -R $user:$group /var/run/frr
# logfile (if used)
touch /var/log/frr.log
chown $user:$group /var/log/frr.log
+
+# register Security Associations
+/usr/local/opnsense/scripts/frr/register_sas
diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS
index 30b11bedb..c2e0542d6 100644
--- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS
+++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS
@@ -4,6 +4,7 @@ ospfd.conf:/usr/local/etc/frr/ospfd.conf
ospfd_carp.conf:/usr/local/etc/frr/ospfd_carp.conf
ospf6d.conf:/usr/local/etc/frr/ospf6d.conf
ripd.conf:/usr/local/etc/frr/ripd.conf
+sa_policies.conf:/usr/local/etc/frr/sa_policies.conf
frr:/etc/rc.conf.d/frr
zebra.conf:/usr/local/etc/frr/zebra.conf
vtysh.conf:/usr/local/etc/frr/vtysh.conf
diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf
index 3f43144fd..499ffe8e1 100644
--- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf
+++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf
@@ -57,6 +57,9 @@ router bgp {{ OPNsense.quagga.bgp.asnumber }}
{% if 'bfd' in neighbor and neighbor.bfd == '1' %}
neighbor {{ neighbor.address }} bfd
{% endif %}
+{% if 'password' in neighbor and neighbor.password != '' %}
+ neighbor {{ neighbor.address }} password {{ neighbor.password }}
+{% endif %}
{% if ':' not in neighbor.address and 'updatesource' in neighbor and neighbor.updatesource != '' %}
neighbor {{ neighbor.address }} update-source {{ physical_interface(neighbor.updatesource) }}
{% endif %}
diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/sa_policies.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/sa_policies.conf
new file mode 100644
index 000000000..bb1587cf3
--- /dev/null
+++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/sa_policies.conf
@@ -0,0 +1,24 @@
+{% if helpers.exists('OPNsense.quagga.bgp.enabled') and OPNsense.quagga.bgp.enabled == '1' %}
+{% if helpers.exists('OPNsense.quagga.bgp.neighbors.neighbor') %}
+{% for neighbor in helpers.toList('OPNsense.quagga.bgp.neighbors.neighbor') %}
+{% if neighbor.enabled == '1' and neighbor.password|default('') != '' %}
+[policy_{{neighbor['@uuid']}}_in]
+src={{ neighbor.address }}
+dst={{ neighbor.localip }}
+protocol=tcp
+spi=0x1000
+aalgo=tcp-md5
+key={{ neighbor.password }}
+
+[policy_{{neighbor['@uuid']}}_out]
+src={{ neighbor.localip }}
+dst={{ neighbor.address }}
+protocol=tcp
+spi=0x1000
+aalgo=tcp-md5
+key={{ neighbor.password }}
+
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %}