summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/conntrack.py81
-rwxr-xr-xsrc/conf_mode/firewall.py12
-rwxr-xr-xsrc/conf_mode/policy-local-route.py103
-rwxr-xr-xsrc/conf_mode/service_mdns-repeater.py24
-rwxr-xr-xsrc/conf_mode/system-login.py27
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py15
6 files changed, 169 insertions, 93 deletions
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 50089508a..4cece6921 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -20,6 +20,7 @@ import re
from sys import exit
from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
from vyos.utils.process import process_named_running
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
@@ -39,27 +40,35 @@ nftables_ct_file = r'/run/nftables-ct.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'],
+ 'ftp': {
+ 'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'],
+ 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return']
},
- 'h323' : {
- 'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
+ 'h323': {
+ 'ko': ['nf_nat_h323', 'nf_conntrack_h323'],
+ 'nftables': ['ct helper set "ras_udp" udp dport {1719} return',
+ 'ct helper set "q931_tcp" tcp dport {1720} return']
},
- 'nfs' : {
- 'nftables' : ['ct helper set "rpc_tcp" tcp dport {111} return',
- 'ct helper set "rpc_udp" udp dport {111} return']
+ 'nfs': {
+ 'nftables': ['ct helper set "rpc_tcp" tcp dport {111} return',
+ 'ct helper set "rpc_udp" udp dport {111} return']
},
- 'pptp' : {
- 'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'pptp': {
+ 'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'],
+ 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'],
+ 'ipv4': True
},
- 'sip' : {
- 'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
+ 'sip': {
+ 'ko': ['nf_nat_sip', 'nf_conntrack_sip'],
+ 'nftables': ['ct helper set "sip_tcp" tcp dport {5060,5061} return',
+ 'ct helper set "sip_udp" udp dport {5060,5061} return']
},
- 'sqlnet' : {
- 'nftables' : ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
+ 'sqlnet': {
+ 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
},
- 'tftp' : {
- 'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'tftp': {
+ 'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'],
+ 'nftables': ['ct helper set "tftp_udp" udp dport {69} return']
},
}
@@ -70,11 +79,6 @@ valid_groups = [
'port_group'
]
-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
@@ -97,6 +101,9 @@ def get_config(config=None):
conntrack['module_map'] = module_map
+ if conf.exists(['service', 'conntrack-sync']):
+ set_dependents('conntrack_sync', conf)
+
return conntrack
def verify(conntrack):
@@ -177,26 +184,40 @@ def generate(conntrack):
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.
+
+ add_modules = []
+ rm_modules = []
+
for module, module_config in module_map.items():
- if dict_search(f'modules.{module}', conntrack) is None:
+ if dict_search_args(conntrack, 'modules', module) is 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}')
+ unloaded = [mod for mod in module_config['ko'] if os.path.exists(f'/sys/module/{mod}')]
+ rm_modules.extend(unloaded)
else:
if 'ko' in module_config:
- for mod in module_config['ko']:
- cmd(f'modprobe {mod}')
+ add_modules.extend(module_config['ko'])
+
+ # Add modules before nftables uses them
+ if add_modules:
+ module_str = ' '.join(add_modules)
+ cmd(f'modprobe -a {module_str}')
# Load new nftables ruleset
install_result, output = rc_cmd(f'nft -f {nftables_ct_file}')
if install_result == 1:
raise ConfigError(f'Failed to apply configuration: {output}')
- if process_named_running('conntrackd'):
- # Reload conntrack-sync daemon to fetch new sysctl values
- resync_conntrackd()
+ # Remove modules after nftables stops using them
+ if rm_modules:
+ module_str = ' '.join(rm_modules)
+ cmd(f'rmmod {module_str}')
+
+ try:
+ call_dependents()
+ except ConfigError:
+ # Ignore config errors on dependent due to being called too early. Example:
+ # ConfigError("ConfigError('Interface ethN requires an IP address!')")
+ pass
# We silently ignore all errors
# See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 3d799318e..f6480ab0a 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 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
@@ -173,6 +173,16 @@ def verify_rule(firewall, rule_conf, ipv6):
if not dict_search_args(firewall, 'flowtable', offload_target):
raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system')
+ if rule_conf['action'] != 'synproxy' and 'synproxy' in rule_conf:
+ raise ConfigError('"synproxy" option allowed only for action synproxy')
+ if rule_conf['action'] == 'synproxy':
+ if 'state' in rule_conf:
+ raise ConfigError('For action "synproxy" state cannot be defined')
+ if not rule_conf.get('synproxy', {}).get('tcp'):
+ raise ConfigError('synproxy TCP MSS is not defined')
+ if rule_conf.get('protocol', {}) != 'tcp':
+ raise ConfigError('For action "synproxy" the protocol must be set to TCP')
+
if 'queue_options' in rule_conf:
if 'queue' not in rule_conf['action']:
raise ConfigError('queue-options defined, but action queue needed and it is not defined')
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 79526f82a..d3c307cdc 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 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
@@ -16,6 +16,7 @@
import os
+from itertools import product
from sys import exit
from netifaces import interfaces
@@ -54,6 +55,7 @@ def get_config(config=None):
fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])
rule_def = {}
if src:
rule_def = dict_merge({'source' : src}, rule_def)
@@ -63,6 +65,8 @@ def get_config(config=None):
rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
if dst:
rule_def = dict_merge({'destination' : dst}, rule_def)
+ if proto:
+ rule_def = dict_merge({'protocol' : proto}, rule_def)
dict = dict_merge({dict_id : {rule : rule_def}}, dict)
pbr.update(dict)
@@ -78,6 +82,7 @@ def get_config(config=None):
fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])
# keep track of changes in configuration
# otherwise we might remove an existing node although nothing else has changed
changed = False
@@ -119,6 +124,13 @@ def get_config(config=None):
changed = True
if len(dst) > 0:
rule_def = dict_merge({'destination' : dst}, rule_def)
+ if proto is None:
+ if 'protocol' in rule_config:
+ rule_def = dict_merge({'protocol': rule_config['protocol']}, rule_def)
+ else:
+ changed = True
+ if len(proto) > 0:
+ rule_def = dict_merge({'protocol' : proto}, rule_def)
if changed:
dict = dict_merge({dict_id : {rule : rule_def}}, dict)
pbr.update(dict)
@@ -137,18 +149,22 @@ def verify(pbr):
pbr_route = pbr[route]
if 'rule' in pbr_route:
for rule in pbr_route['rule']:
- if 'source' not in pbr_route['rule'][rule] \
- and 'destination' not in pbr_route['rule'][rule] \
- and 'fwmark' not in pbr_route['rule'][rule] \
- and 'inbound_interface' not in pbr_route['rule'][rule]:
- raise ConfigError('Source or destination address or fwmark or inbound-interface is required!')
- else:
- if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
- raise ConfigError('Table set is required!')
- if 'inbound_interface' in pbr_route['rule'][rule]:
- interface = pbr_route['rule'][rule]['inbound_interface']
- if interface not in interfaces():
- raise ConfigError(f'Interface "{interface}" does not exist')
+ if (
+ 'source' not in pbr_route['rule'][rule] and
+ 'destination' not in pbr_route['rule'][rule] and
+ 'fwmark' not in pbr_route['rule'][rule] and
+ 'inbound_interface' not in pbr_route['rule'][rule] and
+ 'protocol' not in pbr_route['rule'][rule]
+ ):
+ raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!')
+
+ if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
+ raise ConfigError('Table set is required!')
+
+ if 'inbound_interface' in pbr_route['rule'][rule]:
+ interface = pbr_route['rule'][rule]['inbound_interface']
+ if interface not in interfaces():
+ raise ConfigError(f'Interface "{interface}" does not exist')
return None
@@ -166,20 +182,22 @@ def apply(pbr):
for rule_rm in ['rule_remove', 'rule6_remove']:
if rule_rm in pbr:
v6 = " -6" if rule_rm == 'rule6_remove' else ""
+
for rule, rule_config in pbr[rule_rm].items():
- rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['']
- for src in rule_config['source']:
+ source = rule_config.get('source', [''])
+ destination = rule_config.get('destination', [''])
+ fwmark = rule_config.get('fwmark', [''])
+ inbound_interface = rule_config.get('inbound_interface', [''])
+ protocol = rule_config.get('protocol', [''])
+
+ for src, dst, fwmk, iif, proto in product(source, destination, fwmark, inbound_interface, protocol):
f_src = '' if src == '' else f' from {src} '
- rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['']
- for dst in rule_config['destination']:
- f_dst = '' if dst == '' else f' to {dst} '
- rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else ['']
- for fwmk in rule_config['fwmark']:
- f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
- rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else ['']
- for iif in rule_config['inbound_interface']:
- f_iif = '' if iif == '' else f' iif {iif} '
- call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
+ f_dst = '' if dst == '' else f' to {dst} '
+ f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
+ f_iif = '' if iif == '' else f' iif {iif} '
+ f_proto = '' if proto == '' else f' ipproto {proto} '
+
+ call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
# Generate new config
for route in ['local_route', 'local_route6']:
@@ -187,27 +205,26 @@ def apply(pbr):
continue
v6 = " -6" if route == 'local_route6' else ""
-
pbr_route = pbr[route]
+
if 'rule' in pbr_route:
for rule, rule_config in pbr_route['rule'].items():
- table = rule_config['set']['table']
-
- rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all']
- for src in rule_config['source'] or ['all']:
- f_src = '' if src == '' else f' from {src} '
- rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all']
- for dst in rule_config['destination']:
- f_dst = '' if dst == '' else f' to {dst} '
- f_fwmk = ''
- if 'fwmark' in rule_config:
- fwmk = rule_config['fwmark']
- f_fwmk = f' fwmark {fwmk} '
- f_iif = ''
- if 'inbound_interface' in rule_config:
- iif = rule_config['inbound_interface']
- f_iif = f' iif {iif} '
- call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}')
+ table = rule_config['set'].get('table', '')
+ source = rule_config.get('source', ['all'])
+ destination = rule_config.get('destination', ['all'])
+ fwmark = rule_config.get('fwmark', '')
+ inbound_interface = rule_config.get('inbound_interface', '')
+ protocol = rule_config.get('protocol', '')
+
+ for src in source:
+ f_src = f' from {src} ' if src else ''
+ for dst in destination:
+ f_dst = f' to {dst} ' if dst else ''
+ f_fwmk = f' fwmark {fwmark} ' if fwmark else ''
+ f_iif = f' iif {inbound_interface} ' if inbound_interface else ''
+ f_proto = f' ipproto {protocol} ' if protocol else ''
+
+ call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_fwmk}{f_iif} lookup {table}')
return None
diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py
index a2c90b537..6909731ff 100755
--- a/src/conf_mode/service_mdns-repeater.py
+++ b/src/conf_mode/service_mdns-repeater.py
@@ -18,7 +18,7 @@ import os
from json import loads
from sys import exit
-from netifaces import ifaddresses, interfaces, AF_INET
+from netifaces import ifaddresses, interfaces, AF_INET, AF_INET6
from vyos.config import Config
from vyos.ifconfig.vrrp import VRRP
@@ -36,18 +36,22 @@ def get_config(config=None):
conf = config
else:
conf = Config()
+
base = ['service', 'mdns', 'repeater']
- mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ if not conf.exists(base):
+ return None
+
+ mdns = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
if mdns:
mdns['vrrp_exists'] = conf.exists('high-availability vrrp')
return mdns
def verify(mdns):
- if not mdns:
- return None
-
- if 'disable' in mdns:
+ if not mdns or 'disable' in mdns:
return None
# We need at least two interfaces to repeat mDNS advertisments
@@ -60,10 +64,14 @@ def verify(mdns):
if interface not in interfaces():
raise ConfigError(f'Interface "{interface}" does not exist!')
- if AF_INET not in ifaddresses(interface):
+ if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface):
raise ConfigError('mDNS repeater requires an IPv4 address to be '
f'configured on interface "{interface}"')
+ if mdns['ip_version'] in ['ipv6', 'both'] and AF_INET6 not in ifaddresses(interface):
+ raise ConfigError('mDNS repeater requires an IPv6 address to be '
+ f'configured on interface "{interface}"')
+
return None
# Get VRRP states from interfaces, returns only interfaces where state is MASTER
@@ -92,7 +100,7 @@ def generate(mdns):
if len(mdns['interface']) < 2:
return None
- render(config_file, 'mdns-repeater/avahi-daemon.j2', mdns)
+ render(config_file, 'mdns-repeater/avahi-daemon.conf.j2', mdns)
return None
def apply(mdns):
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 02c97afaa..87a269499 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -104,6 +104,9 @@ def get_config(config=None):
# prune TACACS global defaults if not set by user
if login.from_defaults(['tacacs']):
del login['tacacs']
+ # same for RADIUS
+ if login.from_defaults(['radius']):
+ del login['radius']
# create a list of all users, cli and users
all_users = list(set(local_users + cli_users))
@@ -377,17 +380,23 @@ def apply(login):
except Exception as e:
raise ConfigError(f'Deleting user "{user}" raised exception: {e}')
- # Enable RADIUS in PAM configuration
- pam_cmd = '--remove'
+ # Enable/disable RADIUS in PAM configuration
+ cmd('pam-auth-update --disable radius-mandatory radius-optional')
if 'radius' in login:
- pam_cmd = '--enable'
- cmd(f'pam-auth-update --package {pam_cmd} radius')
-
- # Enable/Disable TACACS in PAM configuration
- pam_cmd = '--remove'
+ if login['radius'].get('security_mode', '') == 'mandatory':
+ pam_profile = 'radius-mandatory'
+ else:
+ pam_profile = 'radius-optional'
+ cmd(f'pam-auth-update --enable {pam_profile}')
+
+ # Enable/disable TACACS+ in PAM configuration
+ cmd('pam-auth-update --disable tacplus-mandatory tacplus-optional')
if 'tacacs' in login:
- pam_cmd = '--enable'
- cmd(f'pam-auth-update --package {pam_cmd} tacplus')
+ if login['tacacs'].get('security_mode', '') == 'mandatory':
+ pam_profile = 'tacplus-mandatory'
+ else:
+ pam_profile = 'tacplus-optional'
+ cmd(f'pam-auth-update --enable {pam_profile}')
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index fa271cbdb..9e9385ddb 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -29,7 +29,10 @@ from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
from vyos.defaults import directories
from vyos.ifconfig import Interface
+from vyos.pki import encode_certificate
from vyos.pki import encode_public_key
+from vyos.pki import find_chain
+from vyos.pki import load_certificate
from vyos.pki import load_private_key
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
@@ -431,15 +434,23 @@ def generate_pki_files_x509(pki, x509_conf):
ca_cert_name = x509_conf['ca_certificate']
ca_cert_data = dict_search_args(pki, 'ca', ca_cert_name, 'certificate')
ca_cert_crls = dict_search_args(pki, 'ca', ca_cert_name, 'crl') or []
+ ca_index = 1
crl_index = 1
+ ca_cert = load_certificate(ca_cert_data)
+ pki_ca_certs = [load_certificate(ca['certificate']) for ca in pki['ca'].values()]
+
+ ca_cert_chain = find_chain(ca_cert, pki_ca_certs)
+
cert_name = x509_conf['certificate']
cert_data = dict_search_args(pki, 'certificate', cert_name, 'certificate')
key_data = dict_search_args(pki, 'certificate', cert_name, 'private', 'key')
protected = 'passphrase' in x509_conf
- with open(os.path.join(CA_PATH, f'{ca_cert_name}.pem'), 'w') as f:
- f.write(wrap_certificate(ca_cert_data))
+ for ca_cert_obj in ca_cert_chain:
+ with open(os.path.join(CA_PATH, f'{ca_cert_name}_{ca_index}.pem'), 'w') as f:
+ f.write(encode_certificate(ca_cert_obj))
+ ca_index += 1
for crl in ca_cert_crls:
with open(os.path.join(CRL_PATH, f'{ca_cert_name}_{crl_index}.pem'), 'w') as f: