summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/firewall.py23
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces-virtual-ethernet.py105
-rwxr-xr-xsrc/conf_mode/pki.py71
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py132
-rwxr-xr-xsrc/conf_mode/policy-route.py106
-rwxr-xr-xsrc/conf_mode/policy.py5
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py5
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py5
9 files changed, 166 insertions, 288 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 783adec46..38a332be3 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -26,6 +26,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+from vyos.configdep import set_dependents, call_dependents
# from vyos.configverify import verify_interface_exists
from vyos.firewall import fqdn_config_parse
from vyos.firewall import geoip_update
@@ -41,8 +42,8 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-nat_conf_script = '/usr/libexec/vyos/conf_mode/nat.py'
-policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
+nat_conf_script = 'nat.py'
+policy_route_conf_script = 'policy-route.py'
nftables_conf = '/run/nftables.conf'
@@ -160,6 +161,9 @@ def get_config(config=None):
firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone])
firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
+ if firewall['group_resync']:
+ # Update nat and policy-route as firewall groups were updated
+ set_dependents('group_resync', conf)
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
diff = get_config_diff(conf)
@@ -464,18 +468,6 @@ def post_apply_trap(firewall):
cmd(base_cmd + ' '.join(objects))
-def resync_nat():
- # Update nat as firewall groups were updated
- tmp, out = rc_cmd(nat_conf_script)
- if tmp > 0:
- Warning(f'Failed to re-apply nat configuration! {out}')
-
-def resync_policy_route():
- # Update policy route as firewall groups were updated
- tmp, out = rc_cmd(policy_route_conf_script)
- if tmp > 0:
- Warning(f'Failed to re-apply policy route configuration! {out}')
-
def apply(firewall):
install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
@@ -484,8 +476,7 @@ def apply(firewall):
apply_sysfs(firewall)
if firewall['group_resync']:
- resync_nat()
- resync_policy_route()
+ call_dependents()
# T970 Enable a resolver (systemd daemon) that checks
# domain-group/fqdn addresses and update entries for domains by timeout
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index e02841831..b49c945cd 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -175,7 +175,7 @@ def generate(ethernet):
loaded_pki_cert = load_certificate(pki_cert['certificate'])
loaded_ca_certs = {load_certificate(c['certificate'])
- for c in ethernet['pki']['ca'].values()}
+ for c in ethernet['pki']['ca'].values()} if 'ca' in ethernet['pki'] else {}
cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
diff --git a/src/conf_mode/interfaces-virtual-ethernet.py b/src/conf_mode/interfaces-virtual-ethernet.py
new file mode 100755
index 000000000..53422ad2d
--- /dev/null
+++ b/src/conf_mode/interfaces-virtual-ethernet.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 sys import exit
+
+from netifaces import interfaces
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_address
+from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import VethIf
+
+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', 'virtual-ethernet']
+ ifname, veth = get_interface_dict(conf, base)
+
+ # We need to know all other veth related interfaces as veth requires a 1:1
+ # mapping for the peer-names. The Linux kernel automatically creates both
+ # interfaces, the local one and the peer-name, but VyOS also needs a peer
+ # interfaces configrued on the CLI so we can assign proper IP addresses etc.
+ veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ return veth
+
+
+def verify(veth):
+ if 'deleted' in veth:
+ verify_bridge_delete(veth)
+ # Prevent to delete veth interface which used for another "vethX peer-name"
+ for iface, iface_config in veth['other_interfaces'].items():
+ if veth['ifname'] in iface_config['peer_name']:
+ ifname = veth['ifname']
+ raise ConfigError(
+ f'Cannot delete "{ifname}" used for "interface {iface} peer-name"'
+ )
+ return None
+
+ verify_vrf(veth)
+ verify_address(veth)
+
+ if 'peer_name' not in veth:
+ raise ConfigError(f'Remote peer name must be set for "{veth["ifname"]}"!')
+
+ if veth['peer_name'] not in veth['other_interfaces']:
+ peer_name = veth['peer_name']
+ ifname = veth['ifname']
+ raise ConfigError(f'Used peer-name "{peer_name}" on interface "{ifname}" ' \
+ 'is not configured!')
+
+ return None
+
+
+def generate(peth):
+ return None
+
+def apply(veth):
+ # Check if the Veth interface already exists
+ if 'rebuild_required' in veth or 'deleted' in veth:
+ if veth['ifname'] in interfaces():
+ p = VethIf(veth['ifname'])
+ p.remove()
+
+ if 'deleted' not in veth:
+ p = VethIf(**veth)
+ p.update(veth)
+
+ 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/pki.py b/src/conf_mode/pki.py
index 29ed7b1b7..e8f3cc87a 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -16,20 +16,16 @@
from sys import exit
-import jmespath
-
from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.pki import is_ca_certificate
from vyos.pki import load_certificate
-from vyos.pki import load_certificate_request
from vyos.pki import load_public_key
from vyos.pki import load_private_key
from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
-from vyos.util import ask_input
-from vyos.util import call
from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.xml import defaults
@@ -121,6 +117,39 @@ def get_config(config=None):
get_first_key=True,
no_tag_node_value_mangle=True)
+ if 'changed' in pki:
+ for search in sync_search:
+ for key in search['keys']:
+ changed_key = sync_translate[key]
+
+ if changed_key not in pki['changed']:
+ continue
+
+ for item_name in pki['changed'][changed_key]:
+ node_present = False
+ if changed_key == 'openvpn':
+ node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
+ else:
+ node_present = dict_search_args(pki, changed_key, item_name)
+
+ if node_present:
+ search_dict = dict_search_args(pki['system'], *search['path'])
+
+ if not search_dict:
+ continue
+
+ for found_name, found_path in dict_search_recursive(search_dict, key):
+ if found_name == item_name:
+ path = search['path']
+ path_str = ' '.join(path + found_path)
+ print(f'pki: Updating config: {path_str} {found_name}')
+
+ if path[0] == 'interfaces':
+ ifname = found_path[0]
+ set_dependents(path[1], conf, ifname)
+ else:
+ set_dependents(path[1], conf)
+
return pki
def is_valid_certificate(raw_data):
@@ -259,37 +288,7 @@ def apply(pki):
return None
if 'changed' in pki:
- for search in sync_search:
- for key in search['keys']:
- changed_key = sync_translate[key]
-
- if changed_key not in pki['changed']:
- continue
-
- for item_name in pki['changed'][changed_key]:
- node_present = False
- if changed_key == 'openvpn':
- node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
- else:
- node_present = dict_search_args(pki, changed_key, item_name)
-
- if node_present:
- search_dict = dict_search_args(pki['system'], *search['path'])
-
- if not search_dict:
- continue
-
- for found_name, found_path in dict_search_recursive(search_dict, key):
- if found_name == item_name:
- path_str = ' '.join(search['path'] + found_path)
- print(f'pki: Updating config: {path_str} {found_name}')
-
- script = search['script']
- if found_path[0] == 'interfaces':
- ifname = found_path[2]
- call(f'VYOS_TAGNODE_VALUE={ifname} {script}')
- else:
- call(script)
+ call_dependents()
return None
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
deleted file mode 100755
index 58c5fd93d..000000000
--- a/src/conf_mode/policy-route-interface.py
+++ /dev/null
@@ -1,132 +0,0 @@
-#!/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
-import re
-
-from sys import argv
-from sys import exit
-
-from vyos.config import Config
-from vyos.ifconfig import Section
-from vyos.template import render
-from vyos.util import cmd
-from vyos.util import run
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- ifname = argv[1]
- ifpath = Section.get_config_path(ifname)
- if_policy_path = f'interfaces {ifpath} policy'
-
- if_policy = conf.get_config_dict(if_policy_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if_policy['ifname'] = ifname
- if_policy['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- return if_policy
-
-def verify_chain(table, chain):
- # Verify policy route applied
- code = run(f'nft list chain {table} {chain}')
- return code == 0
-
-def verify(if_policy):
- # bail out early - looks like removal from running config
- if not if_policy:
- return None
-
- for route in ['route', 'route6']:
- if route in if_policy:
- if route not in if_policy['policy']:
- raise ConfigError('Policy route not configured')
-
- route_name = if_policy[route]
-
- if route_name not in if_policy['policy'][route]:
- raise ConfigError(f'Invalid policy route name "{name}"')
-
- nft_prefix = 'VYOS_PBR6_' if route == 'route6' else 'VYOS_PBR_'
- nft_table = 'ip6 mangle' if route == 'route6' else 'ip mangle'
-
- if not verify_chain(nft_table, nft_prefix + route_name):
- raise ConfigError('Policy route did not apply')
-
- return None
-
-def generate(if_policy):
- return None
-
-def cleanup_rule(table, chain, ifname, new_name=None):
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- retval = None
- for line in results:
- if f'ifname "{ifname}"' in line:
- if new_name and f'jump {new_name}' in line:
- # new_name is used to clear rules for any previously referenced chains
- # returns true when rule exists and doesn't need to be created
- retval = True
- continue
-
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- cmd(f'nft delete rule {table} {chain} handle {handle_search[1]}')
- return retval
-
-def apply(if_policy):
- ifname = if_policy['ifname']
-
- route_chain = 'VYOS_PBR_PREROUTING'
- ipv6_route_chain = 'VYOS_PBR6_PREROUTING'
-
- if 'route' in if_policy:
- name = 'VYOS_PBR_' + if_policy['route']
- rule_exists = cleanup_rule('ip mangle', route_chain, ifname, name)
-
- if not rule_exists:
- cmd(f'nft insert rule ip mangle {route_chain} iifname {ifname} counter jump {name}')
- else:
- cleanup_rule('ip mangle', route_chain, ifname)
-
- if 'route6' in if_policy:
- name = 'VYOS_PBR6_' + if_policy['route6']
- rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name)
-
- if not rule_exists:
- cmd(f'nft insert rule ip6 mangle {ipv6_route_chain} iifname {ifname} counter jump {name}')
- else:
- cleanup_rule('ip6 mangle', ipv6_route_chain, ifname)
-
- 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/policy-route.py b/src/conf_mode/policy-route.py
index 00539b9c7..1d016695e 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
from json import loads
from sys import exit
@@ -25,7 +24,6 @@ from vyos.config import Config
from vyos.template import render
from vyos.util import cmd
from vyos.util import dict_search_args
-from vyos.util import dict_search_recursive
from vyos.util import run
from vyos import ConfigError
from vyos import airbag
@@ -34,48 +32,13 @@ airbag.enable()
mark_offset = 0x7FFFFFFF
nftables_conf = '/run/nftables_policy.conf'
-ROUTE_PREFIX = 'VYOS_PBR_'
-ROUTE6_PREFIX = 'VYOS_PBR6_'
-
-preserve_chains = [
- 'VYOS_PBR_PREROUTING',
- 'VYOS_PBR_POSTROUTING',
- 'VYOS_PBR6_PREROUTING',
- 'VYOS_PBR6_POSTROUTING'
-]
-
valid_groups = [
'address_group',
+ 'domain_group',
'network_group',
'port_group'
]
-group_set_prefix = {
- 'A_': 'address_group',
- 'A6_': 'ipv6_address_group',
-# 'D_': 'domain_group',
- 'M_': 'mac_group',
- 'N_': 'network_group',
- 'N6_': 'ipv6_network_group',
- 'P_': 'port_group'
-}
-
-def get_policy_interfaces(conf):
- out = {}
- interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
- def find_interfaces(iftype_conf, output={}, prefix=''):
- for ifname, if_conf in iftype_conf.items():
- if 'policy' in if_conf:
- output[prefix + ifname] = if_conf['policy']
- for vif in ['vif', 'vif_s', 'vif_c']:
- if vif in if_conf:
- output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
- return output
- for iftype, iftype_conf in interfaces.items():
- out.update(find_interfaces(iftype_conf))
- return out
-
def get_config(config=None):
if config:
conf = config
@@ -88,7 +51,6 @@ def get_config(config=None):
policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
- policy['interfaces'] = get_policy_interfaces(conf)
return policy
@@ -132,8 +94,8 @@ def verify_rule(policy, name, rule_conf, ipv6, rule_id):
side_conf = rule_conf[side]
if 'group' in side_conf:
- if {'address_group', 'network_group'} <= set(side_conf['group']):
- raise ConfigError('Only one address-group or network-group can be specified')
+ if len({'address_group', 'domain_group', 'network_group'} & set(side_conf['group'])) > 1:
+ raise ConfigError('Only one address-group, domain-group or network-group can be specified')
for group in valid_groups:
if group in side_conf['group']:
@@ -168,73 +130,11 @@ def verify(policy):
for rule_id, rule_conf in pol_conf['rule'].items():
verify_rule(policy, name, rule_conf, ipv6, rule_id)
- for ifname, if_policy in policy['interfaces'].items():
- name = dict_search_args(if_policy, 'route')
- ipv6_name = dict_search_args(if_policy, 'route6')
-
- if name and not dict_search_args(policy, 'route', name):
- raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}')
-
- if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name):
- raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}')
-
return None
-def cleanup_commands(policy):
- commands = []
- commands_chains = []
- commands_sets = []
- for table in ['ip mangle', 'ip6 mangle']:
- route_node = 'route' if table == 'ip mangle' else 'route6'
- chain_prefix = ROUTE_PREFIX if table == 'ip mangle' else ROUTE6_PREFIX
-
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
- if 'nftables' not in obj:
- continue
- for item in obj['nftables']:
- if 'chain' in item:
- chain = item['chain']['name']
- if chain in preserve_chains or not chain.startswith("VYOS_PBR"):
- continue
-
- if dict_search_args(policy, route_node, chain.replace(chain_prefix, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands_chains.append(f'delete chain {table} {chain}')
-
- if 'rule' in item:
- rule = item['rule']
- chain = rule['chain']
- handle = rule['handle']
-
- if chain not in preserve_chains:
- continue
-
- target, _ = next(dict_search_recursive(rule['expr'], 'target'))
-
- if target.startswith(chain_prefix):
- if dict_search_args(policy, route_node, target.replace(chain_prefix, "", 1)) == None:
- commands.append(f'delete rule {table} {chain} handle {handle}')
-
- if 'set' in item:
- set_name = item['set']['name']
-
- for prefix, group_type in group_set_prefix.items():
- if set_name.startswith(prefix):
- group_name = set_name.replace(prefix, "", 1)
- if dict_search_args(policy, 'firewall_group', group_type, group_name) != None:
- commands_sets.append(f'flush set {table} {set_name}')
- else:
- commands_sets.append(f'delete set {table} {set_name}')
-
- return commands + commands_chains + commands_sets
-
def generate(policy):
if not os.path.exists(nftables_conf):
policy['first_install'] = True
- else:
- policy['cleanup_commands'] = cleanup_commands(policy)
render(nftables_conf, 'firewall/nftables-policy.j2', policy)
return None
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index a0d288e91..331194fec 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -167,6 +167,11 @@ def verify(policy):
continue
for rule, rule_config in route_map_config['rule'].items():
+ # Action 'deny' cannot be used with "continue"
+ # FRR does not validate it T4827
+ if rule_config['action'] == 'deny' and 'continue' in rule_config:
+ raise ConfigError(f'rule {rule} "continue" cannot be used with action deny!')
+
# Specified community-list must exist
tmp = dict_search('match.community.community_list',
rule_config)
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 5da8e7b06..73af6595b 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -24,6 +24,7 @@ from vyos.template import render_to_string
from vyos.util import dict_search
from vyos.util import read_file
from vyos.util import sysctl_write
+from vyos.configverify import verify_interface_exists
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -46,6 +47,10 @@ def verify(mpls):
if not mpls:
return None
+ if 'interface' in mpls:
+ for interface in mpls['interface']:
+ verify_interface_exists(interface)
+
# Checks to see if LDP is properly configured
if 'ldp' in mpls:
# If router ID not defined
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index cfefcfbe8..b79e9847a 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -22,6 +22,7 @@ from sys import exit
from time import sleep
from time import time
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
@@ -438,6 +439,10 @@ def verify(ipsec):
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 dict_search('options.disable_route_autoinstall',
+ ipsec) == None:
+ Warning('It\'s recommended to use ipsec vty with the next command\n[set vpn ipsec option disable-route-autoinstall]')
+
if 'bind' in peer_conf['vti']:
vti_interface = peer_conf['vti']['bind']
if not os.path.exists(f'/sys/class/net/{vti_interface}'):