diff options
| -rw-r--r-- | data/templates/openvpn/server.conf.j2 | 4 | ||||
| -rw-r--r-- | interface-definitions/interfaces_openvpn.xml.in | 56 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_interfaces_openvpn.py | 55 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces_openvpn.py | 16 | 
4 files changed, 130 insertions, 1 deletions
| diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index 6ac525443..b6e90ed18 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.server_bridge is vyos_defined and server.server_bridge.disable is not vyos_defined %} +server-bridge {{ server.server_bridge.gateway }} {{ server.server_bridge.subnet_mask }} {{ server.server_bridge.start }} {{ server.server_bridge.stop if server.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..46c22ac44 100644 --- a/interface-definitions/interfaces_openvpn.xml.in +++ b/interface-definitions/interfaces_openvpn.xml.in @@ -445,6 +445,62 @@                    </leafNode>                  </children>                </tagNode> +              <node name="server-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..53cfa6cf5 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -627,5 +627,60 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):              self.assertNotIn(interface, interfaces()) +    def test_openvpn_server_server_bridge(self): +        # Create OpenVPN server interface using server-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', 'server-bridge', 'gateway', gw_subnet]) +        self.cli_set(path + ['server', 'server-bridge', 'start', start_subnet]) +        self.cli_set(path + ['server', 'server-bridge', 'stop', stop_subnet]) +        self.cli_set(path + ['server', '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) +        elf.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..fcbba30ce 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.server_bridge', openvpn): +            # check if server-bridge is a tap interfaces +            if not openvpn['device_type'] == 'tap' and dict_search('server.server_bridge', openvpn): +               raise ConfigError('Must specify "device-type tap" with server-bridge mode') +            elif not (dict_search('server.server_bridge.start', openvpn) and dict_search('server.server_bridge.stop', openvpn)): +                raise ConfigError('Server server-bridge requires both start and stop addresses') +            else: +                v4PoolStart = IPv4Address(dict_search('server.server_bridge.start', openvpn)) +                v4PoolStop = IPv4Address(dict_search('server.server_bridge.stop', openvpn)) +                if v4PoolStart > v4PoolStop: +                    raise ConfigError(f'Server server-bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}') + +                v4PoolSize = int(v4PoolStop) - int(v4PoolStart) +                if v4PoolSize >= 65536: +                    raise ConfigError(f'Server 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') | 
