summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/container.py57
-rwxr-xr-xsrc/conf_mode/interfaces_macsec.py10
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py117
-rwxr-xr-xsrc/conf_mode/pki.py2
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py21
-rwxr-xr-xsrc/conf_mode/service_snmp.py28
-rwxr-xr-xsrc/conf_mode/service_suricata.py161
-rwxr-xr-xsrc/conf_mode/system_login.py50
-rwxr-xr-xsrc/conf_mode/system_option.py18
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py43
10 files changed, 252 insertions, 255 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 3efeb9b40..ded370a7a 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -29,7 +29,7 @@ from vyos.configdict import node_changed
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
-from vyos.cpu import get_core_count
+from vyos.utils.cpu import get_core_count
from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import cmd
@@ -43,6 +43,7 @@ from vyos.template import render
from vyos.xml_ref import default_value
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
config_containers = '/etc/containers/containers.conf'
@@ -50,16 +51,19 @@ config_registry = '/etc/containers/registries.conf'
config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
+
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
return cmd(command)
+
def network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)
+
# Common functions
def get_config(config=None):
if config:
@@ -86,21 +90,22 @@ def get_config(config=None):
# registry is a tagNode with default values - merge the list from
# default_values['registry'] into the tagNode variables
if 'registry' not in container:
- container.update({'registry' : {}})
+ container.update({'registry': {}})
default_values = default_value(base + ['registry'])
for registry in default_values:
- tmp = {registry : {}}
+ tmp = {registry: {}}
container['registry'] = dict_merge(tmp, container['registry'])
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
- if tmp: container.update({'network_remove' : tmp})
+ if tmp: container.update({'network_remove': tmp})
tmp = node_changed(conf, base + ['name'])
- if tmp: container.update({'container_remove' : tmp})
+ if tmp: container.update({'container_remove': tmp})
return container
+
def verify(container):
# bail out early - looks like removal from running config
if not container:
@@ -125,8 +130,8 @@ def verify(container):
# of image upgrade and deletion.
image = container_config['image']
if run(f'podman image exists {image}') != 0:
- Warning(f'Image "{image}" used in container "{name}" does not exist '\
- f'locally. Please use "add container image {image}" to add it '\
+ Warning(f'Image "{image}" used in container "{name}" does not exist ' \
+ f'locally. Please use "add container image {image}" to add it ' \
f'to the system! Container "{name}" will not be started!')
if 'cpu_quota' in container_config:
@@ -167,11 +172,11 @@ def verify(container):
# We can not use the first IP address of a network prefix as this is used by podman
if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
'reserved for the container engine!')
if cnt_ipv4 > 1 or cnt_ipv6 > 1:
- raise ConfigError(f'Only one IP address per address family can be used for '\
+ raise ConfigError(f'Only one IP address per address family can be used for ' \
f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
if 'device' in container_config:
@@ -186,6 +191,13 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for var, cfg in container_config['sysctl']['parameter'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'sysctl parameter {var} has no value assigned!')
+ if var.startswith('net.') and 'allow_host_networks' in container_config:
+ raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!')
+
if 'environment' in container_config:
for var, cfg in container_config['environment'].items():
if 'value' not in cfg:
@@ -219,7 +231,8 @@ def verify(container):
# Can not set both allow-host-networks and network at the same time
if {'allow_host_networks', 'network'} <= set(container_config):
- raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
+ raise ConfigError(
+ f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
# gid cannot be set without uid
if 'gid' in container_config and 'uid' not in container_config:
@@ -235,8 +248,10 @@ def verify(container):
raise ConfigError(f'prefix for network "{network}" must be defined!')
for prefix in network_config['prefix']:
- if is_ipv4(prefix): v4_prefix += 1
- elif is_ipv6(prefix): v6_prefix += 1
+ if is_ipv4(prefix):
+ v4_prefix += 1
+ elif is_ipv6(prefix):
+ v6_prefix += 1
if v4_prefix > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
@@ -262,6 +277,7 @@ def verify(container):
return None
+
def generate_run_arguments(name, container_config):
image = container_config['image']
cpu_quota = container_config['cpu_quota']
@@ -269,6 +285,12 @@ def generate_run_arguments(name, container_config):
shared_memory = container_config['shared_memory']
restart = container_config['restart']
+ # Add sysctl options
+ sysctl_opt = ''
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for k, v in container_config['sysctl']['parameter'].items():
+ sysctl_opt += f" --sysctl {k}={v['value']}"
+
# Add capability options. Should be in uppercase
capabilities = ''
if 'capability' in container_config:
@@ -341,7 +363,7 @@ def generate_run_arguments(name, container_config):
if 'allow_host_pid' in container_config:
host_pid = '--pid host'
- container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} ' \
+ container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}'
@@ -375,6 +397,7 @@ def generate_run_arguments(name, container_config):
return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
+
def generate(container):
# bail out early - looks like removal from running config
if not container:
@@ -387,7 +410,7 @@ def generate(container):
for network, network_config in container['network'].items():
tmp = {
'name': network,
- 'id' : sha256(f'{network}'.encode()).hexdigest(),
+ 'id': sha256(f'{network}'.encode()).hexdigest(),
'driver': 'bridge',
'network_interface': f'pod-{network}',
'subnets': [],
@@ -399,7 +422,7 @@ def generate(container):
}
}
for prefix in network_config['prefix']:
- net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
+ net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
if is_ipv6(prefix):
@@ -418,11 +441,12 @@ def generate(container):
file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
run_args = generate_run_arguments(name, container_config)
- render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args,},
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, },
formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
return None
+
def apply(container):
# Delete old containers if needed. We can't delete running container
# Option "--force" allows to delete containers with any status
@@ -485,6 +509,7 @@ def apply(container):
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/interfaces_macsec.py b/src/conf_mode/interfaces_macsec.py
index eb0ca9a8b..3ede4377a 100755
--- a/src/conf_mode/interfaces_macsec.py
+++ b/src/conf_mode/interfaces_macsec.py
@@ -103,9 +103,9 @@ def verify(macsec):
# Logic to check static configuration
if dict_search('security.static', macsec) != None:
- # tx-key must be defined
+ # key must be defined
if dict_search('security.static.key', macsec) == None:
- raise ConfigError('Static MACsec tx-key must be defined.')
+ raise ConfigError('Static MACsec key must be defined.')
tx_len = len(dict_search('security.static.key', macsec))
@@ -119,12 +119,12 @@ def verify(macsec):
if 'peer' not in macsec['security']['static']:
raise ConfigError('Must have at least one peer defined for static MACsec')
- # For every enabled peer, make sure a MAC and rx-key is defined
+ # For every enabled peer, make sure a MAC and key is defined
for peer, peer_config in macsec['security']['static']['peer'].items():
if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config):
- raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.')
+ raise ConfigError('Every enabled MACsec static peer must have a MAC address and key defined!')
- # check rx-key length against cipher suite
+ # check key length against cipher suite
rx_len = len(peer_config['key'])
if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN:
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index d429f6e21..3484e5873 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -16,11 +16,14 @@
import ipaddress
import jmespath
+import logging
import os
from sys import exit
+from logging.handlers import SysLogHandler
from vyos.config import Config
+from vyos.configdict import is_node_changed
from vyos.template import render
from vyos.utils.process import cmd
from vyos.utils.process import run
@@ -32,6 +35,18 @@ airbag.enable()
nftables_cgnat_config = '/run/nftables-cgnat.nft'
+# Logging
+logger = logging.getLogger('cgnat')
+logger.setLevel(logging.DEBUG)
+
+syslog_handler = SysLogHandler(address="/dev/log")
+syslog_handler.setLevel(logging.INFO)
+
+formatter = logging.Formatter('%(name)s: %(message)s')
+syslog_handler.setFormatter(formatter)
+
+logger.addHandler(syslog_handler)
+
class IPOperations:
def __init__(self, ip_prefix: str):
@@ -104,6 +119,38 @@ class IPOperations:
+ [self.ip_network.broadcast_address]
]
+ def get_prefix_by_ip_range(self) -> list[ipaddress.IPv4Network]:
+ """Return the common prefix for the address range
+
+ Example:
+ % ip = IPOperations('100.64.0.1-100.64.0.5')
+ % ip.get_prefix_by_ip_range()
+ [IPv4Network('100.64.0.1/32'), IPv4Network('100.64.0.2/31'), IPv4Network('100.64.0.4/31')]
+ """
+ # We do not need to convert the IP range to network
+ # if it is already in network format
+ if self.ip_network:
+ return [self.ip_network]
+
+ # Raise an error if the IP range is not in the correct format
+ if '-' not in self.ip_prefix:
+ raise ValueError(
+ 'Invalid IP range format. Please provide the IP range in CIDR format or with "-" separator.'
+ )
+ # Split the IP range and convert it to IP address objects
+ range_start, range_end = self.ip_prefix.split('-')
+ range_start = ipaddress.IPv4Address(range_start)
+ range_end = ipaddress.IPv4Address(range_end)
+
+ # Return the summarized IP networks list
+ return list(ipaddress.summarize_address_range(range_start, range_end))
+
+
+def _delete_conntrack_entries(source_prefixes: list[ipaddress.IPv4Network]) -> None:
+ """Delete all conntrack entries for the list of prefixes"""
+ for source_prefix in source_prefixes:
+ run(f'conntrack -D -s {source_prefix}')
+
def generate_port_rules(
external_hosts: list,
@@ -174,12 +221,31 @@ def get_config(config=None):
with_recursive_defaults=True,
)
+ effective_config = conf.get_config_dict(
+ base,
+ get_first_key=True,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ effective=True,
+ )
+
+ # Check if the pool configuration has changed
+ if not conf.exists(base) or is_node_changed(conf, base + ['pool']):
+ config['delete_conntrack_entries'] = {}
+
+ # add running config
+ if effective_config:
+ config['effective'] = effective_config
+
+ if not conf.exists(base):
+ config['deleted'] = {}
+
return config
def verify(config):
# bail out early - looks like removal from running config
- if not config:
+ if 'deleted' in config:
return None
if 'pool' not in config:
@@ -283,7 +349,7 @@ def verify(config):
def generate(config):
- if not config:
+ if 'deleted' in config:
return None
proto_maps = []
@@ -348,13 +414,54 @@ def generate(config):
def apply(config):
- if not config:
+ if 'deleted' in config:
# Cleanup cgnat
cmd('nft delete table ip cgnat')
if os.path.isfile(nftables_cgnat_config):
os.unlink(nftables_cgnat_config)
- return None
- cmd(f'nft --file {nftables_cgnat_config}')
+ else:
+ cmd(f'nft --file {nftables_cgnat_config}')
+
+ # Delete conntrack entries
+ # if the pool configuration has changed
+ if 'delete_conntrack_entries' in config and 'effective' in config:
+ # Prepare the list of internal pool prefixes
+ internal_pool_prefix_list: list[ipaddress.IPv4Network] = []
+
+ # Get effective rules configurations
+ for rule_config in config['effective'].get('rule', {}).values():
+ # Get effective internal pool configuration
+ internal_pool = rule_config['source']['pool']
+ # Find the internal IP ranges for the internal pool
+ internal_ip_ranges: list[str] = config['effective']['pool']['internal'][
+ internal_pool
+ ]['range']
+ # Get the IP prefixes for the internal IP range
+ for internal_range in internal_ip_ranges:
+ ip_prefix: list[ipaddress.IPv4Network] = IPOperations(
+ internal_range
+ ).get_prefix_by_ip_range()
+ # Add the IP prefixes to the list of all internal pool prefixes
+ internal_pool_prefix_list += ip_prefix
+
+ # Delete required sources for conntrack
+ _delete_conntrack_entries(internal_pool_prefix_list)
+
+ # Logging allocations
+ if 'log_allocation' in config:
+ allocations = config['proto_map_elements']
+ allocations = allocations.split(',')
+ for allocation in allocations:
+ try:
+ # Split based on the delimiters used in the nft data format
+ internal_host, rest = allocation.split(' : ')
+ external_host, port_range = rest.split(' . ')
+ # Log the parsed data
+ logger.info(
+ f'Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}')
+ except ValueError as e:
+ # Log error message
+ logger.error(f"Error processing line '{allocation}': {e}")
if __name__ == '__main__':
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index f37cac524..4a0e86f32 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -232,7 +232,7 @@ def get_config(config=None):
path = search['path']
path_str = ' '.join(path + found_path)
- print(f'PKI: Updating config: {path_str} {item_name}')
+ #print(f'PKI: Updating config: {path_str} {item_name}')
if path[0] == 'interfaces':
ifname = found_path[0]
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 40eb13e23..db870aae5 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 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
@@ -86,7 +86,8 @@ def get_config(config=None):
monitoring['custom_scripts_dir'] = custom_scripts_dir
monitoring['hostname'] = get_hostname()
monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False)
- monitoring['nft_chains'] = get_nft_filter_chains()
+ if conf.exists('firewall'):
+ monitoring['nft_chains'] = get_nft_filter_chains()
# Redefine azure group-metrics 'single-table' and 'table-per-metric'
if 'azure_data_explorer' in monitoring:
@@ -113,6 +114,9 @@ def get_config(config=None):
if not conf.exists(base + ['azure-data-explorer']):
del monitoring['azure_data_explorer']
+ if not conf.exists(base + ['loki']):
+ del monitoring['loki']
+
return monitoring
def verify(monitoring):
@@ -159,6 +163,19 @@ def verify(monitoring):
if 'url' not in monitoring['splunk']:
raise ConfigError(f'Monitoring splunk "url" is mandatory!')
+ # Verify Loki
+ if 'loki' in monitoring:
+ if 'url' not in monitoring['loki']:
+ raise ConfigError(f'Monitoring loki "url" is mandatory!')
+ if 'authentication' in monitoring['loki']:
+ if (
+ 'username' not in monitoring['loki']['authentication']
+ or 'password' not in monitoring['loki']['authentication']
+ ):
+ raise ConfigError(
+ f'Authentication "username" and "password" are mandatory!'
+ )
+
return None
def generate(monitoring):
diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py
index 6565ffd60..6f025cc23 100755
--- a/src/conf_mode/service_snmp.py
+++ b/src/conf_mode/service_snmp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2023 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 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
@@ -26,10 +26,12 @@ from vyos.snmpv3_hashgen import plaintext_to_md5
from vyos.snmpv3_hashgen import plaintext_to_sha1
from vyos.snmpv3_hashgen import random
from vyos.template import render
-from vyos.utils.process import call
-from vyos.utils.permission import chmod_755
+from vyos.utils.configfs import delete_cli_node
+from vyos.utils.configfs import add_cli_node
from vyos.utils.dict import dict_search
from vyos.utils.network import is_addr_assigned
+from vyos.utils.process import call
+from vyos.utils.permission import chmod_755
from vyos.version import get_version_data
from vyos import ConfigError
from vyos import airbag
@@ -192,12 +194,8 @@ def generate(snmp):
return None
if 'v3' in snmp:
- # net-snmp is now regenerating the configuration file in the background
- # thus we need to re-open and re-read the file as the content changed.
- # After that we can no read the encrypted password from the config and
- # replace the CLI plaintext password with its encrypted version.
- os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos'
-
+ # SNMPv3 uses a hashed password. If CLI defines a plaintext password,
+ # we will hash it in the background and replace the CLI node!
if 'user' in snmp['v3']:
for user, user_config in snmp['v3']['user'].items():
if dict_search('auth.type', user_config) == 'sha':
@@ -212,8 +210,9 @@ def generate(snmp):
snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp
del snmp['v3']['user'][user]['auth']['plaintext_password']
- call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null')
- call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null')
+ cli_base = ['service', 'snmp', 'v3', 'user', user, 'auth']
+ delete_cli_node(cli_base + ['plaintext-password'])
+ add_cli_node(cli_base + ['encrypted-password'], value=tmp)
if dict_search('privacy.plaintext_password', user_config) is not None:
tmp = hash(dict_search('privacy.plaintext_password', user_config),
@@ -222,8 +221,9 @@ def generate(snmp):
snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp
del snmp['v3']['user'][user]['privacy']['plaintext_password']
- call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null')
- call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null')
+ cli_base = ['service', 'snmp', 'v3', 'user', user, 'privacy']
+ delete_cli_node(cli_base + ['plaintext-password'])
+ add_cli_node(cli_base + ['encrypted-password'], value=tmp)
# Write client config file
render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp)
@@ -246,7 +246,7 @@ def apply(snmp):
return None
# start SNMP daemon
- call(f'systemctl restart {systemd_service}')
+ call(f'systemctl reload-or-restart {systemd_service}')
# Enable AgentX in FRR
# This should be done for each daemon individually because common command
diff --git a/src/conf_mode/service_suricata.py b/src/conf_mode/service_suricata.py
deleted file mode 100755
index 69b369e0b..000000000
--- a/src/conf_mode/service_suricata.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2024 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.base import Warning
-from vyos.config import Config
-from vyos.template import render
-from vyos.utils.process import call
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-config_file = '/run/suricata/suricata.yaml'
-rotate_file = '/etc/logrotate.d/suricata'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['service', 'suricata']
-
- if not conf.exists(base):
- return None
-
- suricata = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, with_recursive_defaults=True)
-
- return suricata
-
-# https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
-def topological_sort(source):
- sorted_nodes = []
- permanent_marks = set()
- temporary_marks = set()
-
- def visit(n, v):
- if n in permanent_marks:
- return
- if n in temporary_marks:
- raise ConfigError('At least one cycle exists in the referenced groups')
-
- temporary_marks.add(n)
-
- for m in v.get('group', []):
- m = m.lstrip('!')
- if m not in source:
- raise ConfigError(f'Undefined referenced group "{m}"')
- visit(m, source[m])
-
- temporary_marks.remove(n)
- permanent_marks.add(n)
- sorted_nodes.append((n, v))
-
- while len(permanent_marks) < len(source):
- n = next(n for n in source.keys() if n not in permanent_marks)
- visit(n, source[n])
-
- return sorted_nodes
-
-def verify(suricata):
- if not suricata:
- return None
-
- if 'interface' not in suricata:
- raise ConfigError('No interfaces configured!')
-
- if 'address_group' not in suricata:
- raise ConfigError('No address-group configured!')
-
- if 'port_group' not in suricata:
- raise ConfigError('No port-group configured!')
-
- try:
- topological_sort(suricata['address_group'])
- except (ConfigError,StopIteration) as e:
- raise ConfigError(f'Invalid address-group: {e}')
-
- try:
- topological_sort(suricata['port_group'])
- except (ConfigError,StopIteration) as e:
- raise ConfigError(f'Invalid port-group: {e}')
-
-def generate(suricata):
- if not suricata:
- for file in [config_file, rotate_file]:
- if os.path.isfile(file):
- os.unlink(file)
-
- return None
-
- # Config-related formatters
- def to_var(s:str):
- return s.replace('-','_').upper()
-
- def to_val(s:str):
- return s.replace('-',':')
-
- def to_ref(s:str):
- if s[0] == '!':
- return '!$' + to_var(s[1:])
- return '$' + to_var(s)
-
- def to_config(kind:str):
- def format_group(group):
- (name, value) = group
- property = [to_val(property) for property in value.get(kind,[])]
- group = [to_ref(group) for group in value.get('group',[])]
- return (to_var(name), property + group)
- return format_group
-
- # Format the address group
- suricata['address_group'] = map(to_config('address'),
- topological_sort(suricata['address_group']))
-
- # Format the port group
- suricata['port_group'] = map(to_config('port'),
- topological_sort(suricata['port_group']))
-
- render(config_file, 'ids/suricata.j2', {'suricata': suricata})
- render(rotate_file, 'ids/suricata_logrotate.j2', suricata)
- return None
-
-def apply(suricata):
- systemd_service = 'suricata.service'
- if not suricata or 'interface' not in suricata:
- # Stop suricata service if removed
- call(f'systemctl stop {systemd_service}')
- else:
- Warning('To fetch the latest rules, use "update suricata"; '
- 'To periodically fetch the latest rules, '
- 'use the task scheduler!')
- call(f'systemctl restart {systemd_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/system_login.py b/src/conf_mode/system_login.py
index 20121f170..439fa645b 100755
--- a/src/conf_mode/system_login.py
+++ b/src/conf_mode/system_login.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2023 VyOS maintainers and contributors
+# Copyright (C) 2020-2024 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
@@ -26,14 +26,15 @@ from time import sleep
from vyos.config import Config
from vyos.configverify import verify_vrf
-from vyos.defaults import directories
from vyos.template import render
from vyos.template import is_ipv4
+from vyos.utils.auth import get_current_user
+from vyos.utils.configfs import delete_cli_node
+from vyos.utils.configfs import add_cli_node
from vyos.utils.dict import dict_search
from vyos.utils.file import chown
from vyos.utils.process import cmd
from vyos.utils.process import call
-from vyos.utils.process import rc_cmd
from vyos.utils.process import run
from vyos.utils.process import DEVNULL
from vyos import ConfigError
@@ -125,10 +126,9 @@ def verify(login):
# This check is required as the script is also executed from vyos-router
# init script and there is no SUDO_USER environment variable available
# during system boot.
- if 'SUDO_USER' in os.environ:
- cur_user = os.environ['SUDO_USER']
- if cur_user in login['rm_users']:
- raise ConfigError(f'Attempting to delete current user: {cur_user}')
+ tmp = get_current_user()
+ if tmp in login['rm_users']:
+ raise ConfigError(f'Attempting to delete current user: {tmp}')
if 'user' in login:
system_users = getpwall()
@@ -221,35 +221,13 @@ def generate(login):
login['user'][user]['authentication']['encrypted_password'] = encrypted_password
del login['user'][user]['authentication']['plaintext_password']
- # remove old plaintext password and set new encrypted password
- env = os.environ.copy()
- env['vyos_libexec_dir'] = directories['base']
-
# Set default commands for re-adding user with encrypted password
- del_user_plain = f"system login user {user} authentication plaintext-password"
- add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'"
-
- lvl = env['VYATTA_EDIT_LEVEL']
- # We're in config edit level, for example "edit system login"
- # Change default commands for re-adding user with encrypted password
- if lvl != '/':
- # Replace '/system/login' to 'system login'
- lvl = lvl.strip('/').split('/')
- # Convert command str to list
- del_user_plain = del_user_plain.split()
- # New command exclude level, for example "edit system login"
- del_user_plain = del_user_plain[len(lvl):]
- # Convert string to list
- del_user_plain = " ".join(del_user_plain)
-
- add_user_encrypt = add_user_encrypt.split()
- add_user_encrypt = add_user_encrypt[len(lvl):]
- add_user_encrypt = " ".join(add_user_encrypt)
-
- ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env)
- if ret: raise ConfigError(out)
- ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env)
- if ret: raise ConfigError(out)
+ del_user_plain = ['system', 'login', 'user', user, 'authentication', 'plaintext-password']
+ add_user_encrypt = ['system', 'login', 'user', user, 'authentication', 'encrypted-password']
+
+ delete_cli_node(del_user_plain)
+ add_cli_node(add_user_encrypt, value=encrypted_password)
+
else:
try:
if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config):
@@ -283,8 +261,6 @@ def generate(login):
if os.path.isfile(tacacs_nss_config_file):
os.unlink(tacacs_nss_config_file)
-
-
# NSS must always be present on the system
render(nss_config_file, 'login/nsswitch.conf.j2', login,
permission=0o644, user='root', group='root')
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index a2e5db575..ad4c0deae 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -24,6 +24,9 @@ from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
from vyos.system import grub_util
from vyos.template import render
+from vyos.utils.dict import dict_search
+from vyos.utils.file import write_file
+from vyos.utils.kernel import check_kmod
from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_addr_assigned
@@ -35,6 +38,8 @@ airbag.enable()
curlrc_config = r'/etc/curlrc'
ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
+usb_autosuspend = r'/etc/udev/rules.d/40-usb-autosuspend.rules'
+kernel_dynamic_debug = r'/sys/kernel/debug/dynamic_debug/control'
time_format_to_locale = {
'12-hour': 'en_US.UTF-8',
'24-hour': 'en_GB.UTF-8'
@@ -85,6 +90,7 @@ def verify(options):
def generate(options):
render(curlrc_config, 'system/curlrc.j2', options)
render(ssh_config, 'system/ssh_config.j2', options)
+ render(usb_autosuspend, 'system/40_usb_autosuspend.j2', options)
cmdline_options = []
if 'kernel' in options:
@@ -155,6 +161,18 @@ def apply(options):
time_format = time_format_to_locale.get(options['time_format'])
cmd(f'localectl set-locale LC_TIME={time_format}')
+ cmd('udevadm control --reload-rules')
+
+ # Enable/disable dynamic debugging for kernel modules
+ modules = ['wireguard']
+ modules_enabled = dict_search('kernel.debug', options) or []
+ for module in modules:
+ if module in modules_enabled:
+ check_kmod(module)
+ write_file(kernel_dynamic_debug, f'module {module} +p')
+ else:
+ write_file(kernel_dynamic_debug, f'module {module} -p')
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 8159fedea..42785134f 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -21,14 +21,17 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configverify import verify_pki_certificate
from vyos.configverify import verify_pki_ca_certificate
-from vyos.pki import wrap_certificate
+from vyos.pki import find_chain
+from vyos.pki import encode_certificate
+from vyos.pki import load_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
-from vyos.utils.process import call
+from vyos.utils.dict import dict_search
+from vyos.utils.file import write_file
from vyos.utils.network import check_port_availability
-from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_listen_port_bind_service
-from vyos.utils.dict import dict_search
+from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from passlib.hash import sha512_crypt
from time import sleep
@@ -142,7 +145,8 @@ def verify(ocserv):
verify_pki_certificate(ocserv, ocserv['ssl']['certificate'])
if 'ca_certificate' in ocserv['ssl']:
- verify_pki_ca_certificate(ocserv, ocserv['ssl']['ca_certificate'])
+ for ca_cert in ocserv['ssl']['ca_certificate']:
+ verify_pki_ca_certificate(ocserv, ca_cert)
# Check network settings
if "network_settings" in ocserv:
@@ -219,25 +223,36 @@ def generate(ocserv):
if "ssl" in ocserv:
cert_file_path = os.path.join(cfg_dir, 'cert.pem')
cert_key_path = os.path.join(cfg_dir, 'cert.key')
- ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem')
+
if 'certificate' in ocserv['ssl']:
cert_name = ocserv['ssl']['certificate']
pki_cert = ocserv['pki']['certificate'][cert_name]
- with open(cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_cert['certificate']))
+ loaded_pki_cert = load_certificate(pki_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in ocserv['pki']['ca'].values()} if 'ca' in ocserv['pki'] else {}
+
+ cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
+
+ write_file(cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain))
if 'private' in pki_cert and 'key' in pki_cert['private']:
- with open(cert_key_path, 'w') as f:
- f.write(wrap_private_key(pki_cert['private']['key']))
+ write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
if 'ca_certificate' in ocserv['ssl']:
- ca_name = ocserv['ssl']['ca_certificate']
- pki_ca_cert = ocserv['pki']['ca'][ca_name]
+ ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem')
+ ca_chains = []
+
+ for ca_name in ocserv['ssl']['ca_certificate']:
+ pki_ca_cert = ocserv['pki']['ca'][ca_name]
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ ca_chains.append(
+ '\n'.join(encode_certificate(c) for c in ca_full_chain))
- with open(ca_cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_ca_cert['certificate']))
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
# Render config
render(ocserv_conf, 'ocserv/ocserv_config.j2', ocserv)