WIP net/frr: Add BGP password support (#2800)

Add BGP password support [https://github.com/opnsense/plugins/pull/2645]

Try to figure out which entries belong to FRR before removing them, so neighbour changes won't be left on the machine after apply.
Flush our desired configuration into /usr/local/etc/frr/sa_policies.conf for easy reading and testing.

Since we don't know if passwords have changed, we will have to drop SA's first. When this is a bit bumpy, we may also try to alter the existing SA's, this shouldn't be too hard to add later on.

Co-authored-by: Michael <m.muenz@gmail.com>
This commit is contained in:
Ad Schellevis
2022-02-03 10:35:06 +01:00
committed by GitHub
co-authored by Michael
parent ec080d9d06
commit 3e64ff9251
9 changed files with 122 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@ Plugin Changelog
1.26
* Fix Model migration errors
* Add BGP password authentication
1.25
@@ -45,6 +45,8 @@ class BgpController extends ApiMutableModelControllerBase
"description",
"address",
"remoteas",
"password",
"localip",
"updatesource",
"nexthopself",
"multihop",
@@ -22,6 +22,20 @@
<type>text</type>
<help>Neighbor AS.</help>
</field>
<field>
<id>neighbor.password</id>
<label>BGP MD5 Password</label>
<type>text</type>
<advanced>true</advanced>
<help>Set a password for BGP authentication.</help>
</field>
<field>
<id>neighbor.localip</id>
<label>Local Initiater IP</label>
<type>text</type>
<advanced>true</advanced>
<help>Set the local IP connecting to the neighbor. This is only required for BGP authentication.</help>
</field>
<field>
<id>neighbor.updatesource</id>
<label>Update-Source Interface</label>
@@ -57,6 +57,12 @@
<MinimumValue>1</MinimumValue>
<MaximumValue>4294967295</MaximumValue>
</remoteas>
<password type="TextField">
<Required>N</Required>
</password>
<localip type="NetworkField">
<Required>N</Required>
</localip>
<updatesource type="InterfaceField">
<default></default>
<Required>N</Required>
+68
View File
@@ -0,0 +1,68 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2022 Ad Schellevis <ad@opnsense.org>
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)
@@ -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
@@ -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
@@ -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 %}
@@ -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 %}