summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-08-06 06:57:02 +0200
committerGitHub <noreply@github.com>2024-08-06 06:57:02 +0200
commit260f6d9365b4e40084fa28ba43dd5e35c4288640 (patch)
treeefc84054daa4a7a4170e6a935d65c62385e9df18
parentf51657532b546cc1e20df1a18b1111c46db045be (diff)
parent01da8d9cd91b9c35dcbbc9160f6377655d5c8983 (diff)
downloadvyos-1x-260f6d9365b4e40084fa28ba43dd5e35c4288640.tar.gz
vyos-1x-260f6d9365b4e40084fa28ba43dd5e35c4288640.zip
Merge pull request #3943 from vyos/mergify/bp/circinus/pr-3920
OPENVPN: T6555: add server-bridge options in mode server (backport #3920)
-rw-r--r--data/templates/openvpn/server.conf.j24
-rw-r--r--interface-definitions/interfaces_openvpn.xml.in56
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py51
-rwxr-xr-xsrc/conf_mode/interfaces_openvpn.py16
4 files changed, 126 insertions, 1 deletions
diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index 6ac525443..bc3a6127f 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -90,7 +90,9 @@ server-ipv6 {{ subnet }}
{% endif %}
{% endfor %}
{% endif %}
-
+{% if server.bridge is vyos_defined and server.bridge.disable is not vyos_defined %}
+server-bridge {{ server.bridge.gateway }} {{ server.bridge.subnet_mask }} {{ server.bridge.start }} {{ server.bridge.stop if server.bridge.stop is vyos_defined }}
+{% endif %}
{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }} {{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }}
{% endif %}
diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in
index 1860523c2..2d880feef 100644
--- a/interface-definitions/interfaces_openvpn.xml.in
+++ b/interface-definitions/interfaces_openvpn.xml.in
@@ -445,6 +445,62 @@
</leafNode>
</children>
</tagNode>
+ <node name="bridge">
+ <properties>
+ <help>Used with TAP device (layer 2)</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <leafNode name="start">
+ <properties>
+ <help>First IP address in the pool</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Last IP address in the pool</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="subnet-mask">
+ <properties>
+ <help>Subnet mask pushed to dynamic clients.</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 subnet mask</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="gateway">
+ <properties>
+ <help>Gateway IP address</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<node name="client-ip-pool">
<properties>
<help>Pool of client IPv4 addresses</help>
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 9ca661e87..d24ce831c 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -627,5 +627,56 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(interface, interfaces())
+ def test_openvpn_server_server_bridge(self):
+ # Create OpenVPN server interface using bridge.
+ # Validate configuration afterwards.
+ br_if = 'br0'
+ vtun_if = 'vtun5010'
+ auth_hash = 'sha256'
+ path = base_path + [vtun_if]
+ start_subnet = "192.168.0.100"
+ stop_subnet = "192.168.0.200"
+ mask_subnet = "255.255.255.0"
+ gw_subnet = "192.168.0.1"
+
+ self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
+ self.cli_set(path + ['device-type', 'tap'])
+ self.cli_set(path + ['encryption', 'data-ciphers', 'aes192'])
+ self.cli_set(path + ['hash', auth_hash])
+ self.cli_set(path + ['mode', 'server'])
+ self.cli_set(path + ['server', 'bridge', 'gateway', gw_subnet])
+ self.cli_set(path + ['server', 'bridge', 'start', start_subnet])
+ self.cli_set(path + ['server', 'bridge', 'stop', stop_subnet])
+ self.cli_set(path + ['server', 'bridge', 'subnet-mask', mask_subnet])
+ self.cli_set(path + ['keep-alive', 'failure-count', '5'])
+ self.cli_set(path + ['keep-alive', 'interval', '5'])
+ self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
+
+ self.cli_commit()
+
+ config_file = f'/run/openvpn/{vtun_if}.conf'
+ config = read_file(config_file)
+ self.assertIn(f'dev {vtun_if}', config)
+ self.assertIn(f'dev-type tap', config)
+ self.assertIn(f'proto udp', config) # default protocol
+ self.assertIn(f'auth {auth_hash}', config)
+ self.assertIn(f'data-ciphers AES-192-CBC', config)
+ self.assertIn(f'mode server', config)
+ self.assertIn(f'server-bridge {gw_subnet} {mask_subnet} {start_subnet} {stop_subnet}', config)
+ self.assertIn(f'keepalive 5 25', config)
+
+ # TLS options
+ self.assertIn(f'ca /run/openvpn/{vtun_if}_ca.pem', config)
+ self.assertIn(f'cert /run/openvpn/{vtun_if}_cert.pem', config)
+ self.assertIn(f'key /run/openvpn/{vtun_if}_cert.key', config)
+ self.assertIn(f'dh /run/openvpn/{vtun_if}_dh.pem', config)
+
+ # check that no interface remained after deleting them
+ self.cli_delete(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
+ self.cli_delete(base_path)
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index 320ab7b7b..d2665d9e5 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -378,6 +378,22 @@ def verify(openvpn):
if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1):
raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
+ if dict_search('server.bridge', openvpn):
+ # check if server bridge is a tap interfaces
+ if not openvpn['device_type'] == 'tap' and dict_search('server.bridge', openvpn):
+ raise ConfigError('Must specify "device-type tap" with server bridge mode')
+ elif not (dict_search('server.bridge.start', openvpn) and dict_search('server.bridge.stop', openvpn)):
+ raise ConfigError('Server bridge requires both start and stop addresses')
+ else:
+ v4PoolStart = IPv4Address(dict_search('server.bridge.start', openvpn))
+ v4PoolStop = IPv4Address(dict_search('server.bridge.stop', openvpn))
+ if v4PoolStart > v4PoolStop:
+ raise ConfigError(f'Server bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
+
+ v4PoolSize = int(v4PoolStop) - int(v4PoolStart)
+ if v4PoolSize >= 65536:
+ raise ConfigError(f'Server bridge is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
+
if dict_search('server.client_ip_pool', openvpn):
if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)):
raise ConfigError('Server client-ip-pool requires both start and stop addresses')