summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/firewall.py26
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py32
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py2
-rwxr-xr-xsrc/conf_mode/nat.py29
-rwxr-xr-xsrc/conf_mode/nat66.py11
-rwxr-xr-xsrc/conf_mode/policy.py132
-rwxr-xr-xsrc/conf_mode/protocols_isis.py22
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py52
-rwxr-xr-xsrc/conf_mode/service_console-server.py7
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py289
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py5
-rwxr-xr-xsrc/conf_mode/ssh.py3
-rwxr-xr-xsrc/conf_mode/system-login.py8
-rwxr-xr-xsrc/conf_mode/system_update_check.py93
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py6
15 files changed, 380 insertions, 337 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index eeb57bd30..cbd9cbe90 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -179,6 +179,20 @@ def verify_rule(firewall, rule_conf, ipv6):
if 'action' not in rule_conf:
raise ConfigError('Rule action must be defined')
+ if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf:
+ raise ConfigError('Action set to jump, but no jump-target specified')
+
+ if 'jump_target' in rule_conf:
+ if 'jump' not in rule_conf['action']:
+ raise ConfigError('jump-target defined, but action jump needed and it is not defined')
+ target = rule_conf['jump_target']
+ if not ipv6:
+ if target not in dict_search_args(firewall, 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
+ else:
+ if target not in dict_search_args(firewall, 'ipv6_name'):
+ raise ConfigError(f'Invalid jump-target. Firewall ipv6-name {target} does not exist on the system')
+
if 'fragment' in rule_conf:
if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
@@ -287,6 +301,18 @@ def verify(firewall):
for name in ['name', 'ipv6_name']:
if name in firewall:
for name_id, name_conf in firewall[name].items():
+ if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
+ raise ConfigError('default-action set to jump, but no default-jump-target specified')
+ if 'default_jump_target' in name_conf:
+ target = name_conf['default_jump_target']
+ if 'jump' not in name_conf['default_action']:
+ raise ConfigError('default-jump-target defined,but default-action jump needed and it is not defined')
+ if name_conf['default_jump_target'] == name_id:
+ raise ConfigError(f'Loop detected on default-jump-target.')
+ ## Now need to check that default-jump-target exists (other firewall chain/name)
+ if target not in dict_search_args(firewall, name):
+ raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system')
+
if 'rule' in name_conf:
for rule_id, rule_conf in name_conf['rule'].items():
verify_rule(firewall, rule_conf, name == 'ipv6_name')
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 61bab2feb..8d738f55e 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -14,16 +14,12 @@
# 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 copy import deepcopy
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import get_interface_dict
-from vyos.configdict import node_changed
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -50,17 +46,20 @@ def get_config(config=None):
ifname, wireguard = get_interface_dict(conf, base)
# Check if a port was changed
- wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port'])
+ tmp = is_node_changed(conf, base + [ifname, 'port'])
+ if tmp: wireguard['port_changed'] = {}
# Determine which Wireguard peer has been removed.
# Peers can only be removed with their public key!
- dict = {}
- tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_'))
- for peer in (tmp or []):
- public_key = leaf_node_changed(conf, base + [ifname, 'peer', peer, 'public_key'])
- if public_key:
- dict = dict_merge({'peer_remove' : {peer : {'public_key' : public_key[0]}}}, dict)
- wireguard.update(dict)
+ if 'peer' in wireguard:
+ peer_remove = {}
+ for peer, peer_config in wireguard['peer'].items():
+ # T4702: If anything on a peer changes we remove the peer first and re-add it
+ if is_node_changed(conf, base + [ifname, 'peer', peer]):
+ if 'public_key' in peer_config:
+ peer_remove = dict_merge({'peer_remove' : {peer : peer_config['public_key']}}, peer_remove)
+ if peer_remove:
+ wireguard.update(peer_remove)
return wireguard
@@ -81,12 +80,11 @@ def verify(wireguard):
if 'peer' not in wireguard:
raise ConfigError('At least one Wireguard peer is required!')
- if 'port' in wireguard and wireguard['port_changed']:
+ if 'port' in wireguard and 'port_changed' in wireguard:
listen_port = int(wireguard['port'])
if check_port_availability('0.0.0.0', listen_port, 'udp') is not True:
- raise ConfigError(
- f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface'
- )
+ raise ConfigError(f'UDP port {listen_port} is busy or unavailable and '
+ 'cannot be used for the interface!')
# run checks on individual configured WireGuard peer
for tmp in wireguard['peer']:
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
index 97b3a6396..a14a992ae 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -116,7 +116,7 @@ def generate(wwan):
# disconnect - e.g. happens during RF signal loss. The script watches every
# WWAN interface - so there is only one instance.
if not os.path.exists(cron_script):
- write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py')
+ write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n')
return None
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index e75418ba5..8b1a5a720 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -147,14 +147,10 @@ def verify(nat):
Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
- if addr != None:
- if addr != 'masquerade' and not is_ip_network(addr):
- for ip in addr.split('-'):
- if not is_addr_assigned(ip):
- Warning(f'IP address {ip} does not exist on the system!')
- elif 'exclude' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'translation address not specified')
+ if addr != None and addr != 'masquerade' and not is_ip_network(addr):
+ for ip in addr.split('-'):
+ if not is_addr_assigned(ip):
+ Warning(f'IP address {ip} does not exist on the system!')
# common rule verification
verify_rule(config, err_msg)
@@ -167,14 +163,8 @@ def verify(nat):
if 'inbound_interface' not in config:
raise ConfigError(f'{err_msg}\n' \
'inbound-interface not specified')
- else:
- if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
-
-
- if dict_search('translation.address', config) == None and 'exclude' not in config:
- raise ConfigError(f'{err_msg}\n' \
- 'translation address not specified')
+ elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
# common rule verification
verify_rule(config, err_msg)
@@ -193,6 +183,9 @@ def verify(nat):
return None
def generate(nat):
+ if not os.path.exists(nftables_nat_config):
+ nat['first_install'] = True
+
render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat)
@@ -201,7 +194,9 @@ def generate(nat):
if tmp > 0:
raise ConfigError('Configuration file errors encountered!')
- tmp = run(f'nft -c -f {nftables_nat_config}')
+ tmp = run(f'nft -c -f {nftables_static_nat_conf}')
+ if tmp > 0:
+ raise ConfigError('Configuration file errors encountered!')
return None
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index f64102d88..d8f913b0c 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -36,7 +36,7 @@ airbag.enable()
k_mod = ['nft_nat', 'nft_chain_nat']
-nftables_nat66_config = '/tmp/vyos-nat66-rules.nft'
+nftables_nat66_config = '/run/nftables_nat66.nft'
ndppd_config = '/run/ndppd/ndppd.conf'
def get_handler(json, chain, target):
@@ -147,6 +147,9 @@ def verify(nat):
return None
def generate(nat):
+ if not os.path.exists(nftables_nat66_config):
+ nat['first_install'] = True
+
render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)
render(ndppd_config, 'ndppd/ndppd.conf.j2', nat, permission=0o755)
return None
@@ -154,15 +157,15 @@ def generate(nat):
def apply(nat):
if not nat:
return None
- cmd(f'{nftables_nat66_config}')
+
+ cmd(f'nft -f {nftables_nat66_config}')
+
if 'deleted' in nat or not dict_search('source.rule', nat):
cmd('systemctl stop ndppd')
if os.path.isfile(ndppd_config):
os.unlink(ndppd_config)
else:
cmd('systemctl restart ndppd')
- if os.path.isfile(nftables_nat66_config):
- os.unlink(nftables_nat66_config)
return None
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index 3008a20e0..a0d288e91 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -23,8 +23,42 @@ from vyos.util import dict_search
from vyos import ConfigError
from vyos import frr
from vyos import airbag
+
airbag.enable()
+
+def community_action_compatibility(actions: dict) -> bool:
+ """
+ Check compatibility of values in community and large community sections
+ :param actions: dictionary with community
+ :type actions: dict
+ :return: true if compatible, false if not
+ :rtype: bool
+ """
+ if ('none' in actions) and ('replace' in actions or 'add' in actions):
+ return False
+ if 'replace' in actions and 'add' in actions:
+ return False
+ if ('delete' in actions) and ('none' in actions or 'replace' in actions):
+ return False
+ return True
+
+
+def extcommunity_action_compatibility(actions: dict) -> bool:
+ """
+ Check compatibility of values in extended community sections
+ :param actions: dictionary with community
+ :type actions: dict
+ :return: true if compatible, false if not
+ :rtype: bool
+ """
+ if ('none' in actions) and (
+ 'rt' in actions or 'soo' in actions or 'bandwidth' in actions or 'bandwidth_non_transitive' in actions):
+ return False
+ if ('bandwidth_non_transitive' in actions) and ('bandwidth' not in actions):
+ return False
+ return True
+
def routing_policy_find(key, dictionary):
# Recursively traverse a dictionary and extract the value assigned to
# a given key as generator object. This is made for routing policies,
@@ -46,6 +80,7 @@ def routing_policy_find(key, dictionary):
for result in routing_policy_find(key, d):
yield result
+
def get_config(config=None):
if config:
conf = config
@@ -53,7 +88,8 @@ def get_config(config=None):
conf = Config()
base = ['policy']
- policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ policy = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
no_tag_node_value_mangle=True)
# We also need some additional information from the config, prefix-lists
@@ -67,12 +103,14 @@ def get_config(config=None):
policy = dict_merge(tmp, policy)
return policy
+
def verify(policy):
if not policy:
return None
for policy_type in ['access_list', 'access_list6', 'as_path_list',
- 'community_list', 'extcommunity_list', 'large_community_list',
+ 'community_list', 'extcommunity_list',
+ 'large_community_list',
'prefix_list', 'prefix_list6', 'route_map']:
# Bail out early and continue with next policy type
if policy_type not in policy:
@@ -97,15 +135,18 @@ def verify(policy):
if 'source' not in rule_config:
raise ConfigError(f'A source {mandatory_error}')
- if int(instance) in range(100, 200) or int(instance) in range(2000, 2700):
+ if int(instance) in range(100, 200) or int(
+ instance) in range(2000, 2700):
if 'destination' not in rule_config:
- raise ConfigError(f'A destination {mandatory_error}')
+ raise ConfigError(
+ f'A destination {mandatory_error}')
if policy_type == 'access_list6':
if 'source' not in rule_config:
raise ConfigError(f'A source {mandatory_error}')
- if policy_type in ['as_path_list', 'community_list', 'extcommunity_list',
+ if policy_type in ['as_path_list', 'community_list',
+ 'extcommunity_list',
'large_community_list']:
if 'regex' not in rule_config:
raise ConfigError(f'A regex {mandatory_error}')
@@ -115,10 +156,10 @@ def verify(policy):
raise ConfigError(f'A prefix {mandatory_error}')
if rule_config in entries:
- raise ConfigError(f'Rule "{rule}" contains a duplicate prefix definition!')
+ raise ConfigError(
+ f'Rule "{rule}" contains a duplicate prefix definition!')
entries.append(rule_config)
-
# route-maps tend to be a bit more complex so they get their own verify() section
if 'route_map' in policy:
for route_map, route_map_config in policy['route_map'].items():
@@ -127,19 +168,23 @@ def verify(policy):
for rule, rule_config in route_map_config['rule'].items():
# Specified community-list must exist
- tmp = dict_search('match.community.community_list', rule_config)
+ tmp = dict_search('match.community.community_list',
+ rule_config)
if tmp and tmp not in policy.get('community_list', []):
raise ConfigError(f'community-list {tmp} does not exist!')
# Specified extended community-list must exist
tmp = dict_search('match.extcommunity', rule_config)
if tmp and tmp not in policy.get('extcommunity_list', []):
- raise ConfigError(f'extcommunity-list {tmp} does not exist!')
+ raise ConfigError(
+ f'extcommunity-list {tmp} does not exist!')
# Specified large-community-list must exist
- tmp = dict_search('match.large_community.large_community_list', rule_config)
+ tmp = dict_search('match.large_community.large_community_list',
+ rule_config)
if tmp and tmp not in policy.get('large_community_list', []):
- raise ConfigError(f'large-community-list {tmp} does not exist!')
+ raise ConfigError(
+ f'large-community-list {tmp} does not exist!')
# Specified prefix-list must exist
tmp = dict_search('match.ip.address.prefix_list', rule_config)
@@ -147,49 +192,87 @@ def verify(policy):
raise ConfigError(f'prefix-list {tmp} does not exist!')
# Specified prefix-list must exist
- tmp = dict_search('match.ipv6.address.prefix_list', rule_config)
+ tmp = dict_search('match.ipv6.address.prefix_list',
+ rule_config)
if tmp and tmp not in policy.get('prefix_list6', []):
raise ConfigError(f'prefix-list6 {tmp} does not exist!')
-
+
# Specified access_list6 in nexthop must exist
- tmp = dict_search('match.ipv6.nexthop.access_list', rule_config)
+ tmp = dict_search('match.ipv6.nexthop.access_list',
+ rule_config)
if tmp and tmp not in policy.get('access_list6', []):
raise ConfigError(f'access_list6 {tmp} does not exist!')
# Specified prefix-list6 in nexthop must exist
- tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config)
+ tmp = dict_search('match.ipv6.nexthop.prefix_list',
+ rule_config)
if tmp and tmp not in policy.get('prefix_list6', []):
raise ConfigError(f'prefix-list6 {tmp} does not exist!')
+ tmp = dict_search('set.community.delete', rule_config)
+ if tmp and tmp not in policy.get('community_list', []):
+ raise ConfigError(f'community-list {tmp} does not exist!')
+
+ tmp = dict_search('set.large_community.delete',
+ rule_config)
+ if tmp and tmp not in policy.get('large_community_list', []):
+ raise ConfigError(
+ f'large-community-list {tmp} does not exist!')
+
+ if 'set' in rule_config:
+ rule_action = rule_config['set']
+ if 'community' in rule_action:
+ if not community_action_compatibility(
+ rule_action['community']):
+ raise ConfigError(
+ f'Unexpected combination between action replace, add, delete or none in community')
+ if 'large_community' in rule_action:
+ if not community_action_compatibility(
+ rule_action['large_community']):
+ raise ConfigError(
+ f'Unexpected combination between action replace, add, delete or none in large-community')
+ if 'extcommunity' in rule_action:
+ if not extcommunity_action_compatibility(
+ rule_action['extcommunity']):
+ raise ConfigError(
+ f'Unexpected combination between none, rt, soo, bandwidth, bandwidth-non-transitive in extended-community')
# When routing protocols are active some use prefix-lists, route-maps etc.
# to apply the systems routing policy to the learned or redistributed routes.
# When the "routing policy" changes and policies, route-maps etc. are deleted,
# it is our responsibility to verify that the policy can not be deleted if it
# is used by any routing protocol
if 'protocols' in policy:
- for policy_type in ['access_list', 'access_list6', 'as_path_list', 'community_list',
- 'extcommunity_list', 'large_community_list', 'prefix_list', 'route_map']:
+ for policy_type in ['access_list', 'access_list6', 'as_path_list',
+ 'community_list',
+ 'extcommunity_list', 'large_community_list',
+ 'prefix_list', 'route_map']:
if policy_type in policy:
- for policy_name in list(set(routing_policy_find(policy_type, policy['protocols']))):
+ for policy_name in list(set(routing_policy_find(policy_type,
+ policy[
+ 'protocols']))):
found = False
if policy_name in policy[policy_type]:
found = True
# BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related
# list - we need to go the extra mile here and check both prefix-lists
- if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in policy['prefix_list6']:
+ if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \
+ policy['prefix_list6']:
found = True
if not found:
- tmp = policy_type.replace('_','-')
- raise ConfigError(f'Can not delete {tmp} "{policy_name}", still in use!')
+ tmp = policy_type.replace('_', '-')
+ raise ConfigError(
+ f'Can not delete {tmp} "{policy_name}", still in use!')
return None
+
def generate(policy):
if not policy:
return None
policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy)
return None
+
def apply(policy):
bgp_daemon = 'bgpd'
zebra_daemon = 'zebra'
@@ -203,7 +286,8 @@ def apply(policy):
frr_cfg.modify_section(r'^bgp community-list .*')
frr_cfg.modify_section(r'^bgp extcommunity-list .*')
frr_cfg.modify_section(r'^bgp large-community-list .*')
- frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit',
+ remove_stop_mark=True)
if 'new_frr_config' in policy:
frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])
frr_cfg.commit_configuration(bgp_daemon)
@@ -214,13 +298,15 @@ def apply(policy):
frr_cfg.modify_section(r'^ipv6 access-list .*')
frr_cfg.modify_section(r'^ip prefix-list .*')
frr_cfg.modify_section(r'^ipv6 prefix-list .*')
- frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit',
+ remove_stop_mark=True)
if 'new_frr_config' in policy:
frr_cfg.add_before(frr.default_add_before, policy['new_frr_config'])
frr_cfg.commit_configuration(zebra_daemon)
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 5dafd26d0..cb8ea3be4 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -203,6 +203,28 @@ def verify(isis):
if list(set(global_range) & set(local_range)):
raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
+
+ # Check for a blank or invalid value per prefix
+ if dict_search('segment_routing.prefix', isis):
+ for prefix, prefix_config in isis['segment_routing']['prefix'].items():
+ if 'absolute' in prefix_config:
+ if prefix_config['absolute'].get('value') is None:
+ raise ConfigError(f'Segment routing prefix {prefix} absolute value cannot be blank.')
+ elif 'index' in prefix_config:
+ if prefix_config['index'].get('value') is None:
+ raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.')
+
+ # Check for explicit-null and no-php-flag configured at the same time per prefix
+ if dict_search('segment_routing.prefix', isis):
+ for prefix, prefix_config in isis['segment_routing']['prefix'].items():
+ if 'absolute' in prefix_config:
+ if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):
+ raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
+ f'and no-php-flag configured at the same time.')
+ elif 'index' in prefix_config:
+ if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
+ raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
+ f'and no-php-flag configured at the same time.')
return None
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 5b4874ba2..0582d32be 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -198,6 +198,58 @@ def verify(ospf):
if 'master' not in tmp or tmp['master'] != vrf:
raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
+ # Segment routing checks
+ if dict_search('segment_routing.global_block', ospf):
+ g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
+ g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
+
+ # If segment routing global block high or low value is blank, throw error
+ if not (g_low_label_value or g_high_label_value):
+ raise ConfigError('Segment routing global-block requires both low and high value!')
+
+ # If segment routing global block low value is higher than the high value, throw error
+ if int(g_low_label_value) > int(g_high_label_value):
+ raise ConfigError('Segment routing global-block low value must be lower than high value')
+
+ if dict_search('segment_routing.local_block', ospf):
+ if dict_search('segment_routing.global_block', ospf) == None:
+ raise ConfigError('Segment routing local-block requires global-block to be configured!')
+
+ l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf)
+ l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf)
+
+ # If segment routing local-block high or low value is blank, throw error
+ if not (l_low_label_value or l_high_label_value):
+ raise ConfigError('Segment routing local-block requires both high and low value!')
+
+ # If segment routing local-block low value is higher than the high value, throw error
+ if int(l_low_label_value) > int(l_high_label_value):
+ raise ConfigError('Segment routing local-block low value must be lower than high value')
+
+ # local-block most live outside global block
+ global_range = range(int(g_low_label_value), int(g_high_label_value) +1)
+ local_range = range(int(l_low_label_value), int(l_high_label_value) +1)
+
+ # Check for overlapping ranges
+ if list(set(global_range) & set(local_range)):
+ raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
+ f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
+
+ # Check for a blank or invalid value per prefix
+ if dict_search('segment_routing.prefix', ospf):
+ for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
+ if 'index' in prefix_config:
+ if prefix_config['index'].get('value') is None:
+ raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.')
+
+ # Check for explicit-null and no-php-flag configured at the same time per prefix
+ if dict_search('segment_routing.prefix', ospf):
+ for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
+ if 'index' in prefix_config:
+ if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
+ raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
+ f'and no-php-flag configured at the same time.')
+
return None
def generate(ospf):
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index a2e411e49..ee4fe42ab 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -61,6 +61,7 @@ def verify(proxy):
if not proxy:
return None
+ aliases = []
processes = process_iter(['name', 'cmdline'])
if 'device' in proxy:
for device, device_config in proxy['device'].items():
@@ -75,6 +76,12 @@ def verify(proxy):
if 'ssh' in device_config and 'port' not in device_config['ssh']:
raise ConfigError(f'Port "{device}" requires SSH port to be set!')
+ if 'alias' in device_config:
+ if device_config['alias'] in aliases:
+ raise ConfigError("Console aliases must be unique")
+ else:
+ aliases.append(device_config['alias'])
+
return None
def generate(proxy):
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 61f484129..e9afd6a55 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -15,266 +15,34 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
-from copy import deepcopy
-from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
from vyos.config import Config
+from vyos.configdict import get_accel_dict
+from vyos.configverify import verify_accel_ppp_base_service
+from vyos.configverify import verify_interface_exists
from vyos.template import render
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
-from vyos.util import call, get_half_cpus
+from vyos.util import call
+from vyos.util import dict_search
from vyos import ConfigError
-
from vyos import airbag
airbag.enable()
ipoe_conf = '/run/accel-pppd/ipoe.conf'
ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets'
-default_config_data = {
- 'auth_mode': 'local',
- 'auth_interfaces': [],
- 'chap_secrets_file': ipoe_chap_secrets, # used in Jinja2 template
- 'interfaces': [],
- 'dnsv4': [],
- 'dnsv6': [],
- 'client_named_ip_pool': [],
- 'client_ipv6_pool': [],
- 'client_ipv6_delegate_prefix': [],
- 'radius_server': [],
- 'radius_acct_inter_jitter': '',
- 'radius_acct_tmo': '3',
- 'radius_max_try': '3',
- 'radius_timeout': '3',
- 'radius_nas_id': '',
- 'radius_nas_ip': '',
- 'radius_source_address': '',
- 'radius_shaper_attr': '',
- 'radius_shaper_enable': False,
- 'radius_shaper_multiplier': '',
- 'radius_shaper_vendor': '',
- 'radius_dynamic_author': '',
- 'thread_cnt': get_half_cpus()
-}
-
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base_path = ['service', 'ipoe-server']
- if not conf.exists(base_path):
+ base = ['service', 'ipoe-server']
+ if not conf.exists(base):
return None
- conf.set_level(base_path)
- ipoe = deepcopy(default_config_data)
-
- for interface in conf.list_nodes(['interface']):
- tmp = {
- 'mode': 'L2',
- 'name': interface,
- 'shared': '1',
- # may need a config option, can be dhcpv4 or up for unclassified pkts
- 'sess_start': 'dhcpv4',
- 'range': None,
- 'ifcfg': '1',
- 'vlan_mon': []
- }
-
- conf.set_level(base_path + ['interface', interface])
-
- if conf.exists(['network-mode']):
- tmp['mode'] = conf.return_value(['network-mode'])
-
- if conf.exists(['network']):
- mode = conf.return_value(['network'])
- if mode == 'vlan':
- tmp['shared'] = '0'
-
- if conf.exists(['vlan-id']):
- tmp['vlan_mon'] += conf.return_values(['vlan-id'])
-
- if conf.exists(['vlan-range']):
- tmp['vlan_mon'] += conf.return_values(['vlan-range'])
-
- if conf.exists(['client-subnet']):
- tmp['range'] = conf.return_value(['client-subnet'])
-
- ipoe['interfaces'].append(tmp)
-
- conf.set_level(base_path)
-
- if conf.exists(['name-server']):
- for name_server in conf.return_values(['name-server']):
- if is_ipv4(name_server):
- ipoe['dnsv4'].append(name_server)
- else:
- ipoe['dnsv6'].append(name_server)
-
- if conf.exists(['authentication', 'mode']):
- ipoe['auth_mode'] = conf.return_value(['authentication', 'mode'])
-
- if conf.exists(['authentication', 'interface']):
- for interface in conf.list_nodes(['authentication', 'interface']):
- tmp = {
- 'name': interface,
- 'mac': []
- }
- for mac in conf.list_nodes(['authentication', 'interface', interface, 'mac-address']):
- client = {
- 'address': mac,
- 'rate_download': '',
- 'rate_upload': '',
- 'vlan_id': ''
- }
- conf.set_level(base_path + ['authentication', 'interface', interface, 'mac-address', mac])
-
- if conf.exists(['rate-limit', 'download']):
- client['rate_download'] = conf.return_value(['rate-limit', 'download'])
-
- if conf.exists(['rate-limit', 'upload']):
- client['rate_upload'] = conf.return_value(['rate-limit', 'upload'])
-
- if conf.exists(['vlan-id']):
- client['vlan'] = conf.return_value(['vlan-id'])
-
- tmp['mac'].append(client)
-
- ipoe['auth_interfaces'].append(tmp)
-
- conf.set_level(base_path)
-
- #
- # authentication mode radius servers and settings
- if conf.exists(['authentication', 'mode', 'radius']):
- for server in conf.list_nodes(['authentication', 'radius', 'server']):
- radius = {
- 'server' : server,
- 'key' : '',
- 'fail_time' : 0,
- 'port' : '1812',
- 'acct_port' : '1813'
- }
-
- conf.set_level(base_path + ['authentication', 'radius', 'server', server])
-
- if conf.exists(['fail-time']):
- radius['fail_time'] = conf.return_value(['fail-time'])
-
- if conf.exists(['port']):
- radius['port'] = conf.return_value(['port'])
-
- if conf.exists(['acct-port']):
- radius['acct_port'] = conf.return_value(['acct-port'])
-
- if conf.exists(['key']):
- radius['key'] = conf.return_value(['key'])
-
- if not conf.exists(['disable']):
- ipoe['radius_server'].append(radius)
-
- #
- # advanced radius-setting
- conf.set_level(base_path + ['authentication', 'radius'])
-
- if conf.exists(['acct-interim-jitter']):
- ipoe['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter'])
-
- if conf.exists(['acct-timeout']):
- ipoe['radius_acct_tmo'] = conf.return_value(['acct-timeout'])
-
- if conf.exists(['max-try']):
- ipoe['radius_max_try'] = conf.return_value(['max-try'])
-
- if conf.exists(['timeout']):
- ipoe['radius_timeout'] = conf.return_value(['timeout'])
-
- if conf.exists(['nas-identifier']):
- ipoe['radius_nas_id'] = conf.return_value(['nas-identifier'])
-
- if conf.exists(['nas-ip-address']):
- ipoe['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
-
- if conf.exists(['rate-limit', 'attribute']):
- ipoe['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute'])
-
- if conf.exists(['rate-limit', 'enable']):
- ipoe['radius_shaper_enable'] = True
-
- if conf.exists(['rate-limit', 'multiplier']):
- ipoe['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier'])
-
- if conf.exists(['rate-limit', 'vendor']):
- ipoe['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])
-
- if conf.exists(['source-address']):
- ipoe['radius_source_address'] = conf.return_value(['source-address'])
-
- # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA)
- if conf.exists(['dynamic-author']):
- dae = {
- 'port' : '',
- 'server' : '',
- 'key' : ''
- }
-
- if conf.exists(['dynamic-author', 'server']):
- dae['server'] = conf.return_value(['dynamic-author', 'server'])
-
- if conf.exists(['dynamic-author', 'port']):
- dae['port'] = conf.return_value(['dynamic-author', 'port'])
-
- if conf.exists(['dynamic-author', 'key']):
- dae['key'] = conf.return_value(['dynamic-author', 'key'])
-
- ipoe['radius_dynamic_author'] = dae
-
-
- conf.set_level(base_path)
- # Named client-ip-pool
- if conf.exists(['client-ip-pool', 'name']):
- for name in conf.list_nodes(['client-ip-pool', 'name']):
- tmp = {
- 'name': name,
- 'gateway_address': '',
- 'subnet': ''
- }
-
- if conf.exists(['client-ip-pool', 'name', name, 'gateway-address']):
- tmp['gateway_address'] += conf.return_value(['client-ip-pool', 'name', name, 'gateway-address'])
- if conf.exists(['client-ip-pool', 'name', name, 'subnet']):
- tmp['subnet'] += conf.return_value(['client-ip-pool', 'name', name, 'subnet'])
-
- ipoe['client_named_ip_pool'].append(tmp)
-
- if conf.exists(['client-ipv6-pool', 'prefix']):
- for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']):
- tmp = {
- 'prefix': prefix,
- 'mask': '64'
- }
-
- if conf.exists(['client-ipv6-pool', 'prefix', prefix, 'mask']):
- tmp['mask'] = conf.return_value(['client-ipv6-pool', 'prefix', prefix, 'mask'])
-
- ipoe['client_ipv6_pool'].append(tmp)
-
-
- if conf.exists(['client-ipv6-pool', 'delegate']):
- for prefix in conf.list_nodes(['client-ipv6-pool', 'delegate']):
- tmp = {
- 'prefix': prefix,
- 'mask': ''
- }
-
- if conf.exists(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix']):
- tmp['mask'] = conf.return_value(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix'])
-
- ipoe['client_ipv6_delegate_prefix'].append(tmp)
-
+ # retrieve common dictionary keys
+ ipoe = get_accel_dict(conf, base, ipoe_chap_secrets)
return ipoe
@@ -282,26 +50,17 @@ def verify(ipoe):
if not ipoe:
return None
- if not ipoe['interfaces']:
+ if 'interface' not in ipoe:
raise ConfigError('No IPoE interface configured')
- if len(ipoe['dnsv4']) > 2:
- raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
-
- if len(ipoe['dnsv6']) > 3:
- raise ConfigError('Not more then three IPv6 DNS name-servers can be configured')
-
- if ipoe['auth_mode'] == 'radius':
- if len(ipoe['radius_server']) == 0:
- raise ConfigError('RADIUS authentication requires at least one server')
+ for interface in ipoe['interface']:
+ verify_interface_exists(interface)
- for radius in ipoe['radius_server']:
- if not radius['key']:
- server = radius['server']
- raise ConfigError(f'Missing RADIUS secret key for server "{ server }"')
+ #verify_accel_ppp_base_service(ipoe, local_users=False)
- if ipoe['client_ipv6_delegate_prefix'] and not ipoe['client_ipv6_pool']:
- raise ConfigError('IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured!')
+ if 'client_ipv6_pool' in ipoe:
+ if 'delegate' in ipoe['client_ipv6_pool'] and 'prefix' not in ipoe['client_ipv6_pool']:
+ raise ConfigError('IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured!')
return None
@@ -312,27 +71,23 @@ def generate(ipoe):
render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe)
- if ipoe['auth_mode'] == 'local':
- render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', ipoe)
- os.chmod(ipoe_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
-
- else:
- if os.path.exists(ipoe_chap_secrets):
- os.unlink(ipoe_chap_secrets)
-
+ if dict_search('authentication.mode', ipoe) == 'local':
+ render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2',
+ ipoe, permission=0o640)
return None
def apply(ipoe):
+ systemd_service = 'accel-ppp@ipoe.service'
if ipoe == None:
- call('systemctl stop accel-ppp@ipoe.service')
+ call(f'systemctl stop {systemd_service}')
for file in [ipoe_conf, ipoe_chap_secrets]:
if os.path.exists(file):
os.unlink(file)
return None
- call('systemctl restart accel-ppp@ipoe.service')
+ call(f'systemctl reload-or-restart {systemd_service}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index dfe73094f..ba0249efd 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -27,7 +27,6 @@ from vyos.util import call
from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
-
airbag.enable()
pppoe_conf = r'/run/accel-pppd/pppoe.conf'
@@ -84,10 +83,6 @@ def generate(pppoe):
if dict_search('authentication.mode', pppoe) == 'local':
render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2',
pppoe, permission=0o640)
- else:
- if os.path.exists(pppoe_chap_secrets):
- os.unlink(pppoe_chap_secrets)
-
return None
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 2bbd7142a..8746cc701 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -73,6 +73,9 @@ def verify(ssh):
if not ssh:
return None
+ if 'rekey' in ssh and 'data' not in ssh['rekey']:
+ raise ConfigError(f'Rekey data is required!')
+
verify_vrf(ssh)
return None
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 3dcbc995c..dbd346fe4 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -40,6 +40,7 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
+autologout_file = "/etc/profile.d/autologout.sh"
radius_config_file = "/etc/pam_radius_auth.conf"
def get_local_users():
@@ -203,6 +204,13 @@ def generate(login):
if os.path.isfile(radius_config_file):
os.unlink(radius_config_file)
+ if 'timeout' in login:
+ render(autologout_file, 'login/autologout.j2', login,
+ permission=0o755, user='root', group='root')
+ else:
+ if os.path.isfile(autologout_file):
+ os.unlink(autologout_file)
+
return None
diff --git a/src/conf_mode/system_update_check.py b/src/conf_mode/system_update_check.py
new file mode 100755
index 000000000..08ecfcb81
--- /dev/null
+++ b/src/conf_mode/system_update_check.py
@@ -0,0 +1,93 @@
+#!/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/>.
+
+import os
+import json
+import jmespath
+
+from pathlib import Path
+from sys import exit
+
+from vyos.config import Config
+from vyos.util import call
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+base = ['system', 'update-check']
+service_name = 'vyos-system-update'
+service_conf = Path(f'/run/{service_name}.conf')
+motd_file = Path('/run/motd.d/10-vyos-update')
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ if not conf.exists(base):
+ return None
+
+ config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ return config
+
+
+def verify(config):
+ # bail out early - looks like removal from running config
+ if config is None:
+ return
+
+ if 'url' not in config:
+ raise ConfigError('URL is required!')
+
+
+def generate(config):
+ # bail out early - looks like removal from running config
+ if config is None:
+ # Remove old config and return
+ service_conf.unlink(missing_ok=True)
+ # MOTD used in /run/motd.d/10-update
+ motd_file.unlink(missing_ok=True)
+ return None
+
+ # Write configuration file
+ conf_json = json.dumps(config, indent=4)
+ service_conf.write_text(conf_json)
+
+ return None
+
+
+def apply(config):
+ if config:
+ if 'auto_check' in config:
+ call(f'systemctl restart {service_name}.service')
+ else:
+ call(f'systemctl stop {service_name}.service')
+
+
+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 c9061366d..77a425f8b 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -265,7 +265,7 @@ def verify(ipsec):
ike = ra_conf['ike_group']
if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2':
- raise ConfigError('IPSec remote-access connections requires IKEv2!')
+ raise ConfigError('IPsec remote-access connections requires IKEv2!')
else:
raise ConfigError(f"Missing ike-group on {name} remote-access config")
@@ -308,10 +308,10 @@ def verify(ipsec):
for pool in ra_conf['pool']:
if pool == 'dhcp':
if dict_search('remote_access.dhcp.server', ipsec) == None:
- raise ConfigError('IPSec DHCP server is not configured!')
+ raise ConfigError('IPsec DHCP server is not configured!')
elif pool == 'radius':
if dict_search('remote_access.radius.server', ipsec) == None:
- raise ConfigError('IPSec RADIUS server is not configured!')
+ raise ConfigError('IPsec RADIUS server is not configured!')
if dict_search('authentication.client_mode', ra_conf) != 'eap-radius':
raise ConfigError('RADIUS IP pool requires eap-radius client authentication!')