summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/conntrack.py140
-rwxr-xr-xsrc/conf_mode/conntrack_sync.py135
-rwxr-xr-xsrc/conf_mode/dhcp_server.py45
-rwxr-xr-xsrc/conf_mode/firewall.py73
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py2
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py3
-rwxr-xr-xsrc/conf_mode/interfaces-dummy.py8
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py7
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py8
-rwxr-xr-xsrc/conf_mode/interfaces-vti.py97
-rwxr-xr-xsrc/conf_mode/protocols_isis.py8
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py122
-rwxr-xr-xsrc/conf_mode/service_router-advert.py12
-rwxr-xr-xsrc/conf_mode/system_sysctl.py73
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py358
-rwxr-xr-xsrc/conf_mode/vpn_rsa-keys.py111
16 files changed, 1162 insertions, 40 deletions
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
new file mode 100755
index 000000000..4e6e39c0f
--- /dev/null
+++ b/src/conf_mode/conntrack.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.util import cmd
+from vyos.util import run
+from vyos.util import process_named_running
+from vyos.util import dict_search
+from vyos.template import render
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf'
+sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf'
+
+# Every ALG (Application Layer Gateway) consists of either a Kernel Object
+# also called a Kernel Module/Driver or some rules present in iptables
+module_map = {
+ 'ftp' : {
+ 'ko' : ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ },
+ 'h323' : {
+ 'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
+ },
+ 'nfs' : {
+ 'iptables' : ['VYATTA_CT_HELPER --table raw --proto tcp --dport 111 --jump CT --helper rpc',
+ 'VYATTA_CT_HELPER --table raw --proto udp --dport 111 --jump CT --helper rpc'],
+ },
+ 'pptp' : {
+ 'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ },
+ 'sip' : {
+ 'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
+ },
+ 'sqlnet' : {
+ 'iptables' : ['VYATTA_CT_HELPER --table raw --proto tcp --dport 1521 --jump CT --helper tns',
+ 'VYATTA_CT_HELPER --table raw --proto tcp --dport 1525 --jump CT --helper tns',
+ 'VYATTA_CT_HELPER --table raw --proto tcp --dport 1536 --jump CT --helper tns'],
+ },
+ 'tftp' : {
+ 'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ },
+}
+
+def resync_conntrackd():
+ tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')
+ if tmp > 0:
+ print('ERROR: error restarting conntrackd!')
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['system', 'conntrack']
+
+ conntrack = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ conntrack = dict_merge(default_values, conntrack)
+
+ return conntrack
+
+def verify(conntrack):
+ return None
+
+def generate(conntrack):
+ render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.tmpl', conntrack)
+ render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack)
+
+ return None
+
+def apply(conntrack):
+ # Depending on the enable/disable state of the ALG (Application Layer Gateway)
+ # modules we need to either insmod or rmmod the helpers.
+ for module, module_config in module_map.items():
+ if dict_search(f'modules.{module}.disable', conntrack) != None:
+ if 'ko' in module_config:
+ for mod in module_config['ko']:
+ # Only remove the module if it's loaded
+ if os.path.exists(f'/sys/module/{mod}'):
+ cmd(f'rmmod {mod}')
+ if 'iptables' in module_config:
+ for rule in module_config['iptables']:
+ print(f'iptables --delete {rule}')
+ cmd(f'iptables --delete {rule}')
+ else:
+ if 'ko' in module_config:
+ for mod in module_config['ko']:
+ cmd(f'modprobe {mod}')
+ if 'iptables' in module_config:
+ for rule in module_config['iptables']:
+ # Only install iptables rule if it does not exist
+ tmp = run(f'iptables --check {rule}')
+ if tmp > 0:
+ cmd(f'iptables --insert {rule}')
+
+
+ if process_named_running('conntrackd'):
+ # Reload conntrack-sync daemon to fetch new sysctl values
+ resync_conntrackd()
+
+ # We silently ignore all errors
+ # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
+ cmd(f'sysctl -f {sysctl_file}')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py
new file mode 100755
index 000000000..7f22fa2dd
--- /dev/null
+++ b/src/conf_mode/conntrack_sync.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configverify import verify_interface_exists
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.util import process_named_running
+from vyos.util import read_file
+from vyos.util import run
+from vyos.template import render
+from vyos.template import get_ipv4
+from vyos.validate import is_addr_assigned
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+config_file = '/run/conntrackd/conntrackd.conf'
+
+def resync_vrrp():
+ tmp = run('/usr/libexec/vyos/conf_mode/vrrp.py')
+ if tmp > 0:
+ print('ERROR: error restarting VRRP daemon!')
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'conntrack-sync']
+ if not conf.exists(base):
+ return None
+
+ conntrack = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ conntrack = dict_merge(default_values, conntrack)
+
+ conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize')
+ conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max')
+
+ conntrack['vrrp'] = conf.get_config_dict(['high-availability', 'vrrp', 'sync-group'],
+ get_first_key=True)
+
+ return conntrack
+
+def verify(conntrack):
+ if not conntrack:
+ return None
+
+ if 'interface' not in conntrack:
+ raise ConfigError('Interface not defined!')
+
+ for interface in conntrack['interface']:
+ verify_interface_exists(interface)
+ # Interface must not only exist, it must also carry an IP address
+ if len(get_ipv4(interface)) < 1:
+ raise ConfigError(f'Interface {interface} requires an IP address!')
+
+ if 'expect_sync' in conntrack:
+ if len(conntrack['expect_sync']) > 1 and 'all' in conntrack['expect_sync']:
+ raise ConfigError('Cannot configure all with other protocol')
+
+ if 'listen_address' in conntrack:
+ address = conntrack['listen_address']
+ if not is_addr_assigned(address):
+ raise ConfigError(f'Specified listen-address {address} not assigned to any interface!')
+
+ vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack)
+ if vrrp_group == None:
+ raise ConfigError(f'No VRRP sync-group defined!')
+ if vrrp_group not in conntrack['vrrp']:
+ raise ConfigError(f'VRRP sync-group {vrrp_group} not configured!')
+
+ return None
+
+def generate(conntrack):
+ if not conntrack:
+ if os.path.isfile(config_file):
+ os.unlink(config_file)
+ return None
+
+ render(config_file, 'conntrackd/conntrackd.conf.tmpl', conntrack)
+
+ return None
+
+def apply(conntrack):
+ if not conntrack:
+ # Failover mechanism daemon should be indicated that it no longer needs
+ # to execute conntrackd actions on transition. This is only required
+ # once when conntrackd is stopped and taken out of service!
+ if process_named_running('conntrackd'):
+ resync_vrrp()
+
+ call('systemctl stop conntrackd.service')
+ return None
+
+ # Failover mechanism daemon should be indicated that it needs to execute
+ # conntrackd actions on transition. This is only required once when conntrackd
+ # is started the first time!
+ if not process_named_running('conntrackd'):
+ resync_vrrp()
+
+ call('systemctl restart conntrackd.service')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 84a8736e8..cdee72e09 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -18,6 +18,8 @@ import os
from ipaddress import ip_address
from ipaddress import ip_network
+from netaddr import IPAddress
+from netaddr import IPRange
from sys import exit
from vyos.config import Config
@@ -25,6 +27,7 @@ from vyos.configdict import dict_merge
from vyos.template import render
from vyos.util import call
from vyos.util import dict_search
+from vyos.util import run
from vyos.validate import is_subnet_connected
from vyos.validate import is_addr_assigned
from vyos.xml import defaults
@@ -162,8 +165,7 @@ def verify(dhcp):
# Check if DHCP address range is inside configured subnet declaration
if 'range' in subnet_config:
- range_start = []
- range_stop = []
+ networks = []
for range, range_config in subnet_config['range'].items():
if not {'start', 'stop'} <= set(range_config):
raise ConfigError(f'DHCP range "{range}" start and stop address must be defined!')
@@ -178,18 +180,16 @@ def verify(dhcp):
raise ConfigError(f'DHCP range "{range}" stop address must be greater or equal\n' \
'to the ranges start address!')
- # Range start address must be unique
- if range_config['start'] in range_start:
- raise ConfigError('Conflicting DHCP lease range: Pool start\n' \
- 'address "{start}" defined multipe times!'.format(range_config))
+ for network in networks:
+ start = range_config['start']
+ stop = range_config['stop']
+ if start in network:
+ raise ConfigError(f'Range "{range}" start address "{start}" already part of another range!')
+ if stop in network:
+ raise ConfigError(f'Range "{range}" stop address "{stop}" already part of another range!')
- # Range stop address must be unique
- if range_config['stop'] in range_start:
- raise ConfigError('Conflicting DHCP lease range: Pool stop\n' \
- 'address "{stop}" defined multipe times!'.format(range_config))
-
- range_start.append(range_config['start'])
- range_stop.append(range_config['stop'])
+ tmp = IPRange(range_config['start'], range_config['stop'])
+ networks.append(tmp)
if 'failover' in subnet_config:
for key in ['local_address', 'peer_address', 'name', 'status']:
@@ -272,10 +272,25 @@ def generate(dhcp):
if not dhcp or 'disable' in dhcp:
return None
- # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw parameters
- # we can pass to ISC DHCPd
+ # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw
+ # parameters we can pass to ISC DHCPd
+ tmp_file = '/tmp/dhcpd.conf'
+ render(tmp_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ formater=lambda _: _.replace("&quot;", '"'))
+ # XXX: as we have the ability for a user to pass in "raw" options via VyOS
+ # CLI (see T3544) we now ask ISC dhcpd to test the newly rendered
+ # configuration
+ tmp = run(f'/usr/sbin/dhcpd -4 -q -t -cf {tmp_file}')
+ if tmp > 0:
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+ raise ConfigError('Configuration file errors encountered - check your options!')
+
+ # Now that we know that the newly rendered configuration is "good" we can
+ # render the "real" configuration
render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
+
return None
def apply(dhcp):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
new file mode 100755
index 000000000..8e6ce5b14
--- /dev/null
+++ b/src/conf_mode/firewall.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
+from vyos.configdict import leaf_node_changed
+from vyos.template import render
+from vyos.util import call
+from vyos import ConfigError
+from vyos import airbag
+from pprint import pprint
+airbag.enable()
+
+
+def get_config(config=None):
+
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['nfirewall']
+ firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ pprint(firewall)
+ return firewall
+
+def verify(firewall):
+ # bail out early - looks like removal from running config
+ if not firewall:
+ return None
+
+ return None
+
+def generate(firewall):
+ if not firewall:
+ return None
+
+ return None
+
+def apply(firewall):
+ if not firewall:
+ return None
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 0727b47a8..9cae29481 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -43,7 +43,7 @@ uacctd_conf_path = '/etc/pmacct/uacctd.conf'
iptables_nflog_table = 'raw'
iptables_nflog_chain = 'VYATTA_CT_PREROUTING_HOOK'
egress_iptables_nflog_table = 'mangle'
-egress_iptables_nflog_chain = 'POSTROUTING'
+egress_iptables_nflog_chain = 'FORWARD'
# helper functions
# check if node exists and return True if this is true
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 1a549f27d..431d65f1f 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -83,6 +83,9 @@ def get_config(config=None):
tmp = leaf_node_changed(conf, ['mode'])
if tmp: bond.update({'shutdown_required': {}})
+ tmp = leaf_node_changed(conf, ['lacp-rate'])
+ if tmp: bond.update({'shutdown_required': {}})
+
# determine which members have been removed
interfaces_removed = leaf_node_changed(conf, ['member', 'interface'])
if interfaces_removed:
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index 44fc9cb9e..55c783f38 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -14,8 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import os
-
from sys import exit
from vyos.config import Config
@@ -42,7 +40,7 @@ def get_config(config=None):
return dummy
def verify(dummy):
- if 'deleted' in dummy.keys():
+ if 'deleted' in dummy:
verify_bridge_delete(dummy)
return None
@@ -58,7 +56,7 @@ def apply(dummy):
d = DummyIf(dummy['ifname'])
# Remove dummy interface
- if 'deleted' in dummy.keys():
+ if 'deleted' in dummy:
d.remove()
else:
d.update(dummy)
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 34a054837..945a2ea9c 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -24,6 +24,7 @@ from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vlan_config
+from vyos.configverify import verify_mtu_parent
from vyos.ifconfig import MACVLANIf
from vyos import ConfigError
@@ -45,6 +46,9 @@ def get_config(config=None):
mode = leaf_node_changed(conf, ['mode'])
if mode: peth.update({'mode_old' : mode})
+ if 'source_interface' in peth:
+ peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'],
+ peth['source_interface'])
return peth
def verify(peth):
@@ -55,9 +59,10 @@ def verify(peth):
verify_source_interface(peth)
verify_vrf(peth)
verify_address(peth)
-
+ verify_mtu_parent(peth, peth['parent'])
# use common function to verify VLAN configuration
verify_vlan_config(peth)
+
return None
def generate(peth):
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 4e6c8a9ab..1575c83ef 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -109,6 +109,14 @@ def verify(tunnel):
if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
raise ConfigError('Can not disable PMTU discovery for given encapsulation')
+ if dict_search('parameters.ip.ignore_df', tunnel) != None:
+ if tunnel['encapsulation'] not in ['gretap']:
+ raise ConfigError('Option ignore-df can only be used on GRETAP tunnels!')
+
+ if dict_search('parameters.ip.no_pmtu_discovery', tunnel) == None:
+ raise ConfigError('Option ignore-df path MTU discovery to be disabled!')
+
+
def generate(tunnel):
return None
diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py
new file mode 100755
index 000000000..6ff23ae59
--- /dev/null
+++ b/src/conf_mode/interfaces-vti.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from netifaces import interfaces
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.ifconfig import VTIIf
+from vyos.util import dict_search
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ """
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
+ interface name will be added or a deleted flag
+ """
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'vti']
+ vti = get_interface_dict(conf, base)
+
+ # VTI is more then an interface - we retrieve the "real" configuration from
+ # the IPsec peer configuration which binds this VTI
+ conf.set_level([])
+ vti['ipsec'] = conf.get_config_dict(['vpn', 'ipsec', 'site-to-site', 'peer'],
+ key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ for peer, peer_config in vti['ipsec'].items():
+ if dict_search('vti.bind', peer_config) == vti['ifname']:
+ vti['remote'] = peer
+ if 'local_address' in peer_config:
+ vti['source_address'] = peer_config['local_address']
+ # we also need to "calculate" a per vti individual key
+ base = 0x900000
+ vti['key'] = base + int(vti['ifname'].lstrip('vti'))
+
+ return vti
+
+def verify(vti):
+ if 'deleted' in vti:
+ return None
+
+ ifname = vti['ifname']
+ found = False
+ for peer, peer_config in vti['ipsec'].items():
+ if dict_search('vti.bind', peer_config) == ifname:
+ found = True
+ # we can now stop processing the for loop
+ break
+ if not found:
+ tmp = vti['ifname']
+ raise ConfigError(f'Interface "{ifname}" not referenced in any VPN configuration!')
+
+ return None
+
+def generate(vti):
+ return None
+
+def apply(vti):
+ if vti['ifname'] in interfaces():
+ # Always delete the VTI interface in advance
+ VTIIf(**vti).remove()
+
+ if 'deleted' not in vti:
+ tmp = VTIIf(**vti)
+ tmp.update(vti)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index ef21e0055..c3a444f16 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -128,9 +128,11 @@ def verify(isis):
raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
# If md5 and plaintext-password set at the same time
- if 'area_password' in isis:
- if {'md5', 'plaintext_password'} <= set(isis['encryption']):
- raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!')
+ for password in ['area_password', 'domain_password']:
+ if password in isis:
+ if {'md5', 'plaintext_password'} <= set(isis[password]):
+ tmp = password.replace('_', '-')
+ raise ConfigError(f'Can use either md5 or plaintext-password for {tmp}!')
# If one param from delay set, but not set others
if 'spf_delay_ietf' in isis:
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
new file mode 100755
index 000000000..12dacdba0
--- /dev/null
+++ b/src/conf_mode/protocols_nhrp.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.config import Config
+from vyos.configdict import node_changed
+from vyos.template import render
+from vyos.util import process_named_running
+from vyos.util import run
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+opennhrp_conf = '/run/opennhrp/opennhrp.conf'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'nhrp']
+
+ nhrp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ nhrp['del_tunnels'] = node_changed(conf, base + ['tunnel'], key_mangling=('-', '_'))
+
+ if not conf.exists(base):
+ return nhrp
+
+ nhrp['if_tunnel'] = conf.get_config_dict(['interfaces', 'tunnel'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ nhrp['profile_map'] = {}
+ profile = conf.get_config_dict(['vpn', 'ipsec', 'profile'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ for name, profile_conf in profile.items():
+ if 'bind' in profile_conf and 'tunnel' in profile_conf['bind']:
+ interfaces = profile_conf['bind']['tunnel']
+ if isinstance(interfaces, str):
+ interfaces = [interfaces]
+ for interface in interfaces:
+ nhrp['profile_map'][interface] = name
+
+ return nhrp
+
+def verify(nhrp):
+ if 'tunnel' in nhrp:
+ for name, nhrp_conf in nhrp['tunnel'].items():
+ if not nhrp['if_tunnel'] or name not in nhrp['if_tunnel']:
+ raise ConfigError(f'Tunnel interface "{name}" does not exist')
+
+ tunnel_conf = nhrp['if_tunnel'][name]
+
+ if 'encapsulation' not in tunnel_conf or tunnel_conf['encapsulation'] != 'gre':
+ raise ConfigError(f'Tunnel "{name}" is not an mGRE tunnel')
+
+ if 'remote' in tunnel_conf:
+ raise ConfigError(f'Tunnel "{name}" cannot have a remote address defined')
+
+ if 'map' in nhrp_conf:
+ for map_name, map_conf in nhrp_conf['map'].items():
+ if 'nbma_address' not in map_conf:
+ raise ConfigError(f'nbma-address missing on map {map_name} on tunnel {name}')
+
+ if 'dynamic_map' in nhrp_conf:
+ for map_name, map_conf in nhrp_conf['dynamic_map'].items():
+ if 'nbma_domain_name' not in map_conf:
+ raise ConfigError(f'nbma-domain-name missing on dynamic-map {map_name} on tunnel {name}')
+ return None
+
+def generate(nhrp):
+ render(opennhrp_conf, 'nhrp/opennhrp.conf.tmpl', nhrp)
+ return None
+
+def apply(nhrp):
+ if 'tunnel' in nhrp:
+ for tunnel, tunnel_conf in nhrp['tunnel'].items():
+ if 'source_address' in tunnel_conf:
+ chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
+ source_address = tunnel_conf['source_address']
+
+ chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
+ if not chain_exists:
+ run(f'sudo iptables --new {chain}')
+ run(f'sudo iptables --append {chain} -p gre -s {source_address} -d 224.0.0.0/4 -j DROP')
+ run(f'sudo iptables --append {chain} -j RETURN')
+ run(f'sudo iptables --insert OUTPUT 2 -j {chain}')
+
+ for tunnel in nhrp['del_tunnels']:
+ chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
+ chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
+ if chain_exists:
+ run(f'sudo iptables --delete OUTPUT -j {chain}')
+ run(f'sudo iptables --flush {chain}')
+ run(f'sudo iptables --delete-chain {chain}')
+
+ action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
+ run(f'systemctl {action} opennhrp')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index 65eb11ce3..9afcdd63e 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2019 VyOS maintainers and contributors
+# Copyright (C) 2018-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -40,11 +40,14 @@ def get_config(config=None):
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_interface_values = defaults(base + ['interface'])
- # we deal with prefix defaults later on
+ # we deal with prefix, route defaults later on
if 'prefix' in default_interface_values:
del default_interface_values['prefix']
+ if 'route' in default_interface_values:
+ del default_interface_values['route']
default_prefix_values = defaults(base + ['interface', 'prefix'])
+ default_route_values = defaults(base + ['interface', 'route'])
if 'interface' in rtradv:
for interface in rtradv['interface']:
@@ -56,6 +59,11 @@ def get_config(config=None):
rtradv['interface'][interface]['prefix'][prefix] = dict_merge(
default_prefix_values, rtradv['interface'][interface]['prefix'][prefix])
+ if 'route' in rtradv['interface'][interface]:
+ for route in rtradv['interface'][interface]['route']:
+ rtradv['interface'][interface]['route'][route] = dict_merge(
+ default_route_values, rtradv['interface'][interface]['route'][route])
+
if 'name_server' in rtradv['interface'][interface]:
# always use a list when dealing with nameservers - eases the template generation
if isinstance(rtradv['interface'][interface]['name_server'], str):
diff --git a/src/conf_mode/system_sysctl.py b/src/conf_mode/system_sysctl.py
new file mode 100755
index 000000000..4f16d1ed6
--- /dev/null
+++ b/src/conf_mode/system_sysctl.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.util import cmd
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+config_file = r'/run/sysctl/99-vyos-sysctl.conf'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['system', 'sysctl']
+ if not conf.exists(base):
+ return None
+
+ sysctl = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return sysctl
+
+def verify(sysctl):
+ return None
+
+def generate(sysctl):
+ if not sysctl:
+ if os.path.isfile(config_file):
+ os.unlink(config_file)
+ return None
+
+ render(config_file, 'system/sysctl.conf.tmpl', sysctl)
+ return None
+
+def apply(sysctl):
+ if not sysctl:
+ return None
+
+ # We silently ignore all errors
+ # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
+ cmd(f'sysctl -f {config_file}')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 969266c30..4efedd995 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,51 +17,383 @@
import os
from sys import exit
+from time import sleep
from vyos.config import Config
+from vyos.configdict import leaf_node_changed
+from vyos.configverify import verify_interface_exists
+from vyos.ifconfig import Interface
from vyos.template import render
from vyos.util import call
from vyos.util import dict_search
+from vyos.util import get_interface_address
+from vyos.util import process_named_running
+from vyos.util import run
+from vyos.util import cidr_fit
from vyos import ConfigError
from vyos import airbag
-from pprint import pprint
airbag.enable()
+authby_translate = {
+ 'pre-shared-secret': 'secret',
+ 'rsa': 'rsasig',
+ 'x509': 'rsasig'
+}
+default_pfs = 'dh-group2'
+pfs_translate = {
+ 'dh-group1': 'modp768',
+ 'dh-group2': 'modp1024',
+ 'dh-group5': 'modp1536',
+ 'dh-group14': 'modp2048',
+ 'dh-group15': 'modp3072',
+ 'dh-group16': 'modp4096',
+ 'dh-group17': 'modp6144',
+ 'dh-group18': 'modp8192',
+ 'dh-group19': 'ecp256',
+ 'dh-group20': 'ecp384',
+ 'dh-group21': 'ecp512',
+ 'dh-group22': 'modp1024s160',
+ 'dh-group23': 'modp2048s224',
+ 'dh-group24': 'modp2048s256',
+ 'dh-group25': 'ecp192',
+ 'dh-group26': 'ecp224',
+ 'dh-group27': 'ecp224bp',
+ 'dh-group28': 'ecp256bp',
+ 'dh-group29': 'ecp384bp',
+ 'dh-group30': 'ecp512bp',
+ 'dh-group31': 'curve25519',
+ 'dh-group32': 'curve448'
+}
+
+any_log_modes = [
+ 'dmn', 'mgr', 'ike', 'chd','job', 'cfg', 'knl', 'net', 'asn',
+ 'enc', 'lib', 'esp', 'tls', 'tnc', 'imc', 'imv', 'pts'
+]
+
+ike_ciphers = {}
+esp_ciphers = {}
+
+mark_base = 0x900000
+
+CA_PATH = "/etc/ipsec.d/cacerts/"
+CRL_PATH = "/etc/ipsec.d/crls/"
+
+DHCP_BASE = "/var/lib/dhcp/dhclient"
+
+LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
+X509_PATH = '/config/auth/'
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['vpn', 'nipsec']
+ base = ['vpn', 'ipsec']
if not conf.exists(base):
return None
# retrieve common dictionary keys
- ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ ipsec = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces', 'interface'])
+ ipsec['l2tp_exists'] = conf.exists('vpn l2tp remote-access ipsec-settings ')
+ ipsec['nhrp_exists'] = conf.exists('protocols nhrp tunnel')
+ ipsec['rsa_keys'] = conf.get_config_dict(['vpn', 'rsa-keys'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ default_ike_pfs = None
+
+ if 'ike_group' in ipsec:
+ for group, ike_conf in ipsec['ike_group'].items():
+ if 'proposal' in ike_conf:
+ ciphers = []
+ for i in ike_conf['proposal']:
+ proposal = ike_conf['proposal'][i]
+ enc = proposal['encryption'] if 'encryption' in proposal else None
+ hash = proposal['hash'] if 'hash' in proposal else None
+ pfs = ('dh-group' + proposal['dh_group']) if 'dh_group' in proposal else default_pfs
+
+ if not default_ike_pfs:
+ default_ike_pfs = pfs
+
+ if enc and hash:
+ ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}")
+ ike_ciphers[group] = ','.join(ciphers) + '!'
+
+ if 'esp_group' in ipsec:
+ for group, esp_conf in ipsec['esp_group'].items():
+ pfs = esp_conf['pfs'] if 'pfs' in esp_conf else 'enable'
+
+ if pfs == 'disable':
+ pfs = None
+
+ if pfs == 'enable':
+ pfs = default_ike_pfs
+
+ if 'proposal' in esp_conf:
+ ciphers = []
+ for i in esp_conf['proposal']:
+ proposal = esp_conf['proposal'][i]
+ enc = proposal['encryption'] if 'encryption' in proposal else None
+ hash = proposal['hash'] if 'hash' in proposal else None
+ if enc and hash:
+ ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}")
+ esp_ciphers[group] = ','.join(ciphers) + '!'
+
return ipsec
+def get_rsa_local_key(ipsec):
+ return dict_search('local_key.file', ipsec['rsa_keys'])
+
+def verify_rsa_local_key(ipsec):
+ file = get_rsa_local_key(ipsec)
+
+ if not file:
+ return False
+
+ for path in LOCAL_KEY_PATHS:
+ full_path = os.path.join(path, file)
+ if os.path.exists(full_path):
+ return full_path
+
+ return False
+
+def verify_rsa_key(ipsec, key_name):
+ return dict_search(f'rsa_key_name.{key_name}.rsa_key', ipsec['rsa_keys'])
+
def verify(ipsec):
if not ipsec:
return None
+ if 'ipsec_interfaces' in ipsec and 'interface' in ipsec['ipsec_interfaces']:
+ interfaces = ipsec['ipsec_interfaces']['interface']
+ if isinstance(interfaces, str):
+ interfaces = [interfaces]
+
+ for ifname in interfaces:
+ verify_interface_exists(ifname)
+
+ if 'profile' in ipsec:
+ for profile, profile_conf in ipsec['profile'].items():
+ if 'esp_group' in profile_conf:
+ if 'esp_group' not in ipsec or profile_conf['esp_group'] not in ipsec['esp_group']:
+ raise ConfigError(f"Invalid esp-group on {profile} profile")
+ else:
+ raise ConfigError(f"Missing esp-group on {profile} profile")
+
+ if 'ike_group' in profile_conf:
+ if 'ike_group' not in ipsec or profile_conf['ike_group'] not in ipsec['ike_group']:
+ raise ConfigError(f"Invalid ike-group on {profile} profile")
+ else:
+ raise ConfigError(f"Missing ike-group on {profile} profile")
+
+ if 'authentication' not in profile_conf:
+ raise ConfigError(f"Missing authentication on {profile} profile")
+
+ if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:
+ for peer, peer_conf in ipsec['site_to_site']['peer'].items():
+ has_default_esp = False
+ if 'default_esp_group' in peer_conf:
+ has_default_esp = True
+ if 'esp_group' not in ipsec or peer_conf['default_esp_group'] not in ipsec['esp_group']:
+ raise ConfigError(f"Invalid esp-group on site-to-site peer {peer}")
+
+ if 'ike_group' in peer_conf:
+ if 'ike_group' not in ipsec or peer_conf['ike_group'] not in ipsec['ike_group']:
+ raise ConfigError(f"Invalid ike-group on site-to-site peer {peer}")
+ else:
+ raise ConfigError(f"Missing ike-group on site-to-site peer {peer}")
+
+ if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']:
+ raise ConfigError(f"Missing authentication on site-to-site peer {peer}")
+
+ if peer_conf['authentication']['mode'] == 'x509':
+ if 'x509' not in peer_conf['authentication']:
+ raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}")
+
+ if 'key' not in peer_conf['authentication']['x509']:
+ raise ConfigError(f"Missing x509 key on site-to-site peer {peer}")
+
+ if 'ca_cert_file' not in peer_conf['authentication']['x509'] or 'cert_file' not in peer_conf['authentication']['x509']:
+ raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}")
+
+ if 'file' not in peer_conf['authentication']['x509']['key']:
+ raise ConfigError(f"Missing x509 key file on site-to-site peer {peer}")
+
+ for key in ['ca_cert_file', 'cert_file', 'crl_file']:
+ if key in peer_conf['authentication']['x509']:
+ path = os.path.join(X509_PATH, peer_conf['authentication']['x509'][key])
+ if not os.path.exists(path):
+ raise ConfigError(f"File not found for {key} on site-to-site peer {peer}")
+
+ key_path = os.path.join(X509_PATH, peer_conf['authentication']['x509']['key']['file'])
+ if not os.path.exists(key_path):
+ raise ConfigError(f"Private key not found on site-to-site peer {peer}")
+
+ if peer_conf['authentication']['mode'] == 'rsa':
+ if not verify_rsa_local_key(ipsec):
+ raise ConfigError(f"Invalid key on rsa-keys local-key")
+
+ if 'rsa_key_name' not in peer_conf['authentication']:
+ raise ConfigError(f"Missing rsa-key-name on site-to-site peer {peer}")
+
+ if not verify_rsa_key(ipsec, peer_conf['authentication']['rsa_key_name']):
+ raise ConfigError(f"Invalid rsa-key-name on site-to-site peer {peer}")
+
+ if 'local_address' not in peer_conf and 'dhcp_interface' not in peer_conf:
+ raise ConfigError(f"Missing local-address or dhcp-interface on site-to-site peer {peer}")
+
+ if 'dhcp_interface' in peer_conf:
+ dhcp_interface = peer_conf['dhcp_interface']
+
+ verify_interface_exists(dhcp_interface)
+
+ if not os.path.exists(f'{DHCP_BASE}_{dhcp_interface}.conf'):
+ raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}")
+
+ address = Interface(dhcp_interface).get_addr()
+ if not address:
+ raise ConfigError(f"Failed to get address from dhcp-interface on site-to-site peer {peer}")
+
+ if 'vti' in peer_conf:
+ if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf:
+ raise ConfigError(f"A single local-address or dhcp-interface is required when using VTI on site-to-site peer {peer}")
+
+ if 'bind' in peer_conf['vti']:
+ vti_interface = peer_conf['vti']['bind']
+ if not os.path.exists(f'/sys/class/net/{vti_interface}'):
+ raise ConfigError(f'VTI interface {vti_interface} for site-to-site peer {peer} does not exist!')
+
+ if 'vti' not in peer_conf and 'tunnel' not in peer_conf:
+ raise ConfigError(f"No VTI or tunnel specified on site-to-site peer {peer}")
+
+ if 'tunnel' in peer_conf:
+ for tunnel, tunnel_conf in peer_conf['tunnel'].items():
+ if 'esp_group' not in tunnel_conf and not has_default_esp:
+ raise ConfigError(f"Missing esp-group on tunnel {tunnel} for site-to-site peer {peer}")
+
+ esp_group_name = tunnel_conf['esp_group'] if 'esp_group' in tunnel_conf else peer_conf['default_esp_group']
+
+ if esp_group_name not in ipsec['esp_group']:
+ raise ConfigError(f"Invalid esp-group on tunnel {tunnel} for site-to-site peer {peer}")
+
+ esp_group = ipsec['esp_group'][esp_group_name]
+
+ if 'mode' in esp_group and esp_group['mode'] == 'transport':
+ if 'protocol' in tunnel_conf and ((peer in ['any', '0.0.0.0']) or ('local_address' not in peer_conf or peer_conf['local_address'] in ['any', '0.0.0.0'])):
+ raise ConfigError(f"Fixed local-address or peer required when a protocol is defined with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
+
+ if ('local' in tunnel_conf and 'prefix' in tunnel_conf['local']) or ('remote' in tunnel_conf and 'prefix' in tunnel_conf['remote']):
+ raise ConfigError(f"Local/remote prefix cannot be used with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}")
+
def generate(ipsec):
- if not ipsec:
- return None
+ data = {}
- return ipsec
+ if ipsec:
+ data = ipsec
+ data['authby'] = authby_translate
+ data['ciphers'] = {'ike': ike_ciphers, 'esp': esp_ciphers}
+ data['marks'] = {}
+ data['rsa_local_key'] = verify_rsa_local_key(ipsec)
+ data['x509_path'] = X509_PATH
+
+ if 'site_to_site' in data and 'peer' in data['site_to_site']:
+ for peer, peer_conf in ipsec['site_to_site']['peer'].items():
+ if peer_conf['authentication']['mode'] == 'x509':
+ ca_cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['ca_cert_file'])
+ call(f'cp -f {ca_cert_file} {CA_PATH}')
+
+ if 'crl_file' in peer_conf['authentication']['x509']:
+ crl_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['crl_file'])
+ call(f'cp -f {crl_file} {CRL_PATH}')
+
+ local_ip = ''
+ if 'local_address' in peer_conf:
+ local_ip = peer_conf['local_address']
+ elif 'dhcp_interface' in peer_conf:
+ local_ip = Interface(peer_conf['dhcp_interface']).get_addr()
+
+ data['site_to_site']['peer'][peer]['local_address'] = local_ip
+
+ if 'vti' in peer_conf and 'bind' in peer_conf['vti']:
+ vti_interface = peer_conf['vti']['bind']
+ data['marks'][vti_interface] = get_mark(vti_interface)
+ else:
+ for tunnel, tunnel_conf in peer_conf['tunnel'].items():
+ local_prefix = dict_search('local.prefix', tunnel_conf)
+ remote_prefix = dict_search('remote.prefix', tunnel_conf)
+
+ if not local_prefix or not remote_prefix:
+ continue
+
+ passthrough = cidr_fit(local_prefix, remote_prefix)
+ data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
+
+ if 'logging' in ipsec and 'log_modes' in ipsec['logging']:
+ modes = ipsec['logging']['log_modes']
+ level = ipsec['logging']['log_level'] if 'log_level' in ipsec['logging'] else '1'
+ if isinstance(modes, str):
+ modes = [modes]
+ if 'any' in modes:
+ modes = any_log_modes
+ data['charondebug'] = f' {level}, '.join(modes) + ' ' + level
+
+ render("/etc/ipsec.conf", "ipsec/ipsec.conf.tmpl", data)
+ render("/etc/ipsec.secrets", "ipsec/ipsec.secrets.tmpl", data)
+ render("/etc/strongswan.d/interfaces_use.conf", "ipsec/interfaces_use.conf.tmpl", data)
+ render("/etc/swanctl/swanctl.conf", "ipsec/swanctl.conf.tmpl", data)
+
+def resync_l2tp(ipsec):
+ if ipsec and not ipsec['l2tp_exists']:
+ return
+
+ tmp = run('/usr/libexec/vyos/conf_mode/ipsec-settings.py')
+ if tmp > 0:
+ print('ERROR: failed to reapply L2TP IPSec settings!')
+
+def resync_nhrp(ipsec):
+ if ipsec and not ipsec['nhrp_exists']:
+ return
+
+ tmp = run('/usr/libexec/vyos/conf_mode/protocols_nhrp.py')
+ if tmp > 0:
+ print('ERROR: failed to reapply NHRP settings!')
def apply(ipsec):
if not ipsec:
- return None
+ call('sudo /usr/sbin/ipsec stop')
+ else:
+ should_start = ('profile' in ipsec or dict_search('site_to_site.peer', ipsec))
+
+ if not process_named_running('charon') and should_start:
+ args = f'--auto-update {ipsec["auto_update"]}' if 'auto_update' in ipsec else ''
+ call(f'sudo /usr/sbin/ipsec start {args}')
+ elif not should_start:
+ call('sudo /usr/sbin/ipsec stop')
+ elif ipsec['interface_change']:
+ call('sudo /usr/sbin/ipsec restart')
+ else:
+ call('sudo /usr/sbin/ipsec rereadall')
+ call('sudo /usr/sbin/ipsec reload')
+
+ if should_start:
+ sleep(2) # Give charon enough time to start
+ call('sudo /usr/sbin/swanctl -q')
+
+ resync_l2tp(ipsec)
+ resync_nhrp(ipsec)
- pprint(ipsec)
+def get_mark(vti_interface):
+ vti_num = int(vti_interface.lstrip('vti'))
+ return mark_base + vti_num
if __name__ == '__main__':
try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
+ ipsec = get_config()
+ verify(ipsec)
+ generate(ipsec)
+ apply(ipsec)
except ConfigError as e:
print(e)
exit(1)
diff --git a/src/conf_mode/vpn_rsa-keys.py b/src/conf_mode/vpn_rsa-keys.py
new file mode 100755
index 000000000..6cf7eba6e
--- /dev/null
+++ b/src/conf_mode/vpn_rsa-keys.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import base64
+import os
+import struct
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.util import call
+from vyos import ConfigError
+from vyos import airbag
+from Crypto.PublicKey.RSA import construct
+
+airbag.enable()
+
+LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/']
+LOCAL_OUTPUT = '/etc/ipsec.d/certs/localhost.pub'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['vpn', 'rsa-keys']
+ if not conf.exists(base):
+ return None
+
+ return conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
+
+def verify(conf):
+ if not conf:
+ return
+
+ if 'local_key' in conf and 'file' in conf['local_key']:
+ local_key = conf['local_key']['file']
+ if not local_key:
+ raise ConfigError(f'Invalid local-key')
+
+ if not get_local_key(local_key):
+ raise ConfigError(f'File not found for local-key: {local_key}')
+
+def get_local_key(local_key):
+ for path in LOCAL_KEY_PATHS:
+ full_path = os.path.join(path, local_key)
+ if os.path.exists(full_path):
+ return full_path
+ return False
+
+def generate(conf):
+ if not conf:
+ return
+
+ if 'local_key' in conf and 'file' in conf['local_key']:
+ local_key = conf['local_key']['file']
+ local_key_path = get_local_key(local_key)
+ call(f'sudo /usr/bin/openssl rsa -in {local_key_path} -pubout -out {LOCAL_OUTPUT}')
+
+ if 'rsa_key_name' in conf:
+ for key_name, key_conf in conf['rsa_key_name'].items():
+ if 'rsa_key' not in key_conf:
+ continue
+
+ remote_key = key_conf['rsa_key']
+
+ if remote_key[:2] == "0s": # Vyatta format
+ remote_key = migrate_from_vyatta_key(remote_key)
+ else:
+ remote_key = bytes('-----BEGIN PUBLIC KEY-----\n' + remote_key + '\n-----END PUBLIC KEY-----\n', 'utf-8')
+
+ with open(f'/etc/ipsec.d/certs/{key_name}.pub', 'wb') as f:
+ f.write(remote_key)
+
+def migrate_from_vyatta_key(data):
+ data = base64.b64decode(data[2:])
+ length = struct.unpack('B', data[:1])[0]
+ e = int.from_bytes(data[1:1+length], 'big')
+ n = int.from_bytes(data[1+length:], 'big')
+ pubkey = construct((n, e))
+ return pubkey.exportKey(format='PEM')
+
+def apply(conf):
+ if not conf:
+ return
+
+ call('sudo /usr/sbin/ipsec rereadall')
+ call('sudo /usr/sbin/ipsec reload')
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)