summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/completion/list_dumpable_interfaces.py4
-rwxr-xr-xsrc/conf_mode/container.py12
-rwxr-xr-xsrc/conf_mode/firewall.py12
-rwxr-xr-xsrc/conf_mode/interfaces_openvpn.py4
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py3
-rwxr-xr-xsrc/conf_mode/interfaces_wwan.py8
-rwxr-xr-xsrc/conf_mode/load-balancing_wan.py6
-rwxr-xr-xsrc/conf_mode/nat.py6
-rwxr-xr-xsrc/conf_mode/nat64.py4
-rwxr-xr-xsrc/conf_mode/nat66.py4
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py6
-rwxr-xr-xsrc/conf_mode/pki.py39
-rwxr-xr-xsrc/conf_mode/policy_route.py13
-rwxr-xr-xsrc/conf_mode/qos.py17
-rwxr-xr-xsrc/conf_mode/service_monitoring_prometheus.py16
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py4
-rwxr-xr-xsrc/conf_mode/system_conntrack.py10
-rwxr-xr-xsrc/conf_mode/system_host-name.py4
-rwxr-xr-xsrc/conf_mode/system_login.py42
-rwxr-xr-xsrc/conf_mode/system_option.py65
-rwxr-xr-xsrc/conf_mode/system_sysctl.py4
-rwxr-xr-xsrc/conf_mode/system_watchdog.py4
-rw-r--r--src/conf_mode/vpp_interfaces_loopback.py10
-rwxr-xr-xsrc/conf_mode/vrf.py24
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_firewall_input_filter.py8
-rwxr-xr-xsrc/helpers/vyos-boot-config-loader.py7
-rwxr-xr-xsrc/helpers/vyos-config-encrypt.py54
-rw-r--r--src/migration-scripts/https/5-to-64
-rw-r--r--src/migration-scripts/system/13-to-144
-rwxr-xr-xsrc/op_mode/bgp.py20
-rwxr-xr-xsrc/op_mode/bridge.py12
-rwxr-xr-xsrc/op_mode/cgnat.py4
-rwxr-xr-xsrc/op_mode/clear_conntrack.py6
-rwxr-xr-xsrc/op_mode/conntrack.py6
-rwxr-xr-xsrc/op_mode/conntrack_sync.py6
-rwxr-xr-xsrc/op_mode/container.py16
-rwxr-xr-xsrc/op_mode/dns.py6
-rw-r--r--src/op_mode/evpn.py6
-rwxr-xr-xsrc/op_mode/file.py10
-rwxr-xr-xsrc/op_mode/firewall.py16
-rwxr-xr-xsrc/op_mode/flow_accounting_op.py4
-rwxr-xr-xsrc/op_mode/format_disk.py7
-rw-r--r--src/op_mode/generate_psk.py4
-rwxr-xr-xsrc/op_mode/generate_ssh_server_key.py16
-rwxr-xr-xsrc/op_mode/generate_tech-support_archive.py8
-rwxr-xr-xsrc/op_mode/image_installer.py120
-rwxr-xr-xsrc/op_mode/interfaces.py23
-rwxr-xr-xsrc/op_mode/ipsec.py4
-rwxr-xr-xsrc/op_mode/lldp.py6
-rwxr-xr-xsrc/op_mode/load-balancing_wan.py6
-rw-r--r--src/op_mode/mtr_execute.py4
-rwxr-xr-xsrc/op_mode/multicast.py4
-rwxr-xr-xsrc/op_mode/nat.py6
-rwxr-xr-xsrc/op_mode/neighbor.py15
-rw-r--r--src/op_mode/ntp.py12
-rwxr-xr-xsrc/op_mode/pki.py49
-rwxr-xr-xsrc/op_mode/policy_route.py6
-rwxr-xr-xsrc/op_mode/powerctrl.py14
-rwxr-xr-xsrc/op_mode/qos.py10
-rwxr-xr-xsrc/op_mode/route.py12
-rwxr-xr-xsrc/op_mode/show_wwan.py4
-rwxr-xr-xsrc/op_mode/ssh.py10
-rwxr-xr-xsrc/op_mode/stp.py15
-rw-r--r--src/op_mode/tech_support.py41
-rwxr-xr-xsrc/op_mode/vrf.py6
-rwxr-xr-xsrc/op_mode/wireguard_client.py9
-rwxr-xr-xsrc/services/vyos-domain-resolver53
-rwxr-xr-xsrc/services/vyos-netlinkd6
-rwxr-xr-xsrc/system/keepalived-fifo.py10
-rw-r--r--src/tests/test_configverify.py4
-rwxr-xr-xsrc/udev/vyos_net_name4
-rwxr-xr-xsrc/validators/timezone4
72 files changed, 606 insertions, 396 deletions
diff --git a/src/completion/list_dumpable_interfaces.py b/src/completion/list_dumpable_interfaces.py
index f9748352f..15dae8f4e 100755
--- a/src/completion/list_dumpable_interfaces.py
+++ b/src/completion/list_dumpable_interfaces.py
@@ -4,9 +4,9 @@
import re
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
if __name__ == '__main__':
- out = cmd('tcpdump -D').split('\n')
+ out = cmdl(['tcpdump', '-D']).split('\n')
intfs = " ".join(map(lambda s: re.search(r'\d+\.(\S+)\s', s).group(1), out))
print(intfs)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 6c5385dfa..91f44d2d8 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -36,7 +36,7 @@ from vyos.utils.cpu import get_core_count
from vyos.utils.file import write_file
from vyos.utils.dict import dict_search
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.utils.network import gen_mac
from vyos.utils.network import get_host_identity
@@ -58,15 +58,15 @@ config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
-def _cmd(command):
+def _cmdl(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
- return cmd(command)
+ return cmdl(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}$')
+ c = _cmdl(['podman', 'network', 'ls', '--quiet', '--filter', f'name=^{name}$'])
return bool(c)
@@ -668,7 +668,7 @@ def apply(container):
if 'disable' in container_config:
# check if there is a container by that name running
- tmp = _cmd('podman ps -a --format "{{.Names}}"')
+ tmp = _cmdl(['podman', 'ps', '-a', '--format', '{{.Names}}'])
if name in tmp:
file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
call(f'systemctl stop vyos-container-{name}.service')
@@ -678,7 +678,7 @@ def apply(container):
continue
if 'container_restart' in container and name in container['container_restart']:
- cmd(f'systemctl restart vyos-container-{name}.service')
+ cmdl(['systemctl', 'restart', f'vyos-container-{name}.service'])
if disabled_new:
call('systemctl daemon-reload')
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 29181b4ef..a1a7b8529 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -30,12 +30,13 @@ from vyos.ethtool import Ethtool
from vyos.firewall import fqdn_config_parse
from vyos.geoip import geoip_refresh, geoip_update
from vyos.template import render
+from vyos.utils.convert import human_to_seconds
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.file import write_file
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import rc_cmd
from vyos.utils.network import get_vrf_members
from vyos.utils.network import get_interface_vrf
@@ -543,6 +544,13 @@ def verify(firewall):
for group_name, group in firewall['group']['remote_group'].items():
if 'url' not in group:
raise ConfigError(f'remote-group {group_name} must have a url configured')
+ if 'interval' in group:
+ interval = human_to_seconds(group['interval'])
+ if not 60 <= interval <= 2419200:
+ raise ConfigError(
+ f'remote-group {group_name} interval must be '
+ 'between 60 seconds and 4 weeks'
+ )
offload_chains_v4 = set()
offload_chains_v6 = set()
@@ -733,7 +741,7 @@ def apply(firewall):
raise ConfigError(f'Failed to apply firewall: {output}')
# Apply firewall global-options sysctl settings
- cmd(f'sysctl -f {sysctl_file}')
+ cmdl(['sysctl', '-f', sysctl_file])
call_dependents()
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index ec5840369..6746863b9 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -63,7 +63,7 @@ from vyos.utils.kernel import check_kmod
from vyos.utils.kernel import unload_kmod
from vyos.utils.process import call
from vyos.utils.permission import chown
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.network import is_addr_assigned
from vyos.utils.network import interface_exists
@@ -848,7 +848,7 @@ def apply(openvpn):
# or if address will be assign later
if 'local_host' in openvpn:
if not is_addr_assigned(openvpn['local_host']):
- cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1')
+ cmdl(['sysctl', '-w', 'net.ipv4.ip_nonlocal_bind=1'])
# No matching OpenVPN process running - maybe it got killed or none
# existed - nevertheless, spawn new OpenVPN process
diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py
index 1ad16e816..5ac93ff8b 100755
--- a/src/conf_mode/interfaces_wireless.py
+++ b/src/conf_mode/interfaces_wireless.py
@@ -322,7 +322,8 @@ def apply(wifi):
call(f'systemctl stop wpa_supplicant@{interface}.service')
if 'deleted' in wifi:
- WiFiIf(**wifi).remove()
+ if interface_exists(interface):
+ WiFiIf(**wifi).remove()
# run the dependents and return
call_dependents()
diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py
index 0fe67508a..59303196d 100755
--- a/src/conf_mode/interfaces_wwan.py
+++ b/src/conf_mode/interfaces_wwan.py
@@ -33,7 +33,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import WWANIf
from vyos.utils.dict import dict_search
from vyos.utils.network import is_wwan_connected
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import call
from vyos.utils.process import DEVNULL
from vyos.utils.process import is_systemd_service_active
@@ -138,13 +138,13 @@ def apply(wwan):
# required to serve all modems. Activate ModemManager on first invocation
# of any WWAN interface.
if not is_systemd_service_active(service_name):
- cmd(f'systemctl start {service_name}')
+ cmdl(['systemctl', 'start', service_name])
counter = 100
# Wait until a modem is detected and then we can continue
while counter > 0:
counter -= 1
- tmp = cmd('mmcli -L')
+ tmp = cmdl(['mmcli', '-L'])
if tmp != 'No modems were found':
break
sleep(0.250)
@@ -169,7 +169,7 @@ def apply(wwan):
# We are the last WWAN interface - there are no other WWAN interfaces
# remaining, thus we can stop ModemManager and free resources.
if 'other_interfaces' not in wwan:
- cmd(f'systemctl stop {service_name}')
+ cmdl(['systemctl', 'stop', service_name])
# Clean CRON helper script which is used for to re-connect when
# RF signal is lost
if os.path.exists(cron_script):
diff --git a/src/conf_mode/load-balancing_wan.py b/src/conf_mode/load-balancing_wan.py
index 3f2433fa1..502872a13 100755
--- a/src/conf_mode/load-balancing_wan.py
+++ b/src/conf_mode/load-balancing_wan.py
@@ -19,7 +19,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
from vyos.utils.dict import dict_search_args
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -150,9 +150,9 @@ def generate(lb):
def apply(lb):
if not lb:
- cmd(f'systemctl stop {service}')
+ cmdl(['systemctl', 'stop', service])
else:
- cmd(f'systemctl restart {service}')
+ cmdl(['systemctl', 'restart', service])
call_dependents()
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 8763da886..fe3359446 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -27,7 +27,7 @@ from vyos.utils.kernel import check_kmod
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
from vyos.utils.file import write_file
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.utils.process import call
from vyos.utils.network import interface_exists
@@ -241,8 +241,8 @@ def generate(nat):
def apply(nat):
check_kmod(k_mod)
- cmd(f'nft --file {nftables_nat_config}')
- cmd(f'nft --file {nftables_static_nat_conf}')
+ cmdl(['nft', '--file', nftables_nat_config])
+ cmdl(['nft', '--file', nftables_static_nat_conf])
if not nat or 'deleted' in nat:
os.unlink(nftables_nat_config)
diff --git a/src/conf_mode/nat64.py b/src/conf_mode/nat64.py
index b7d0b586d..8afeeafe4 100755
--- a/src/conf_mode/nat64.py
+++ b/src/conf_mode/nat64.py
@@ -32,7 +32,7 @@ from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.kernel import check_kmod
from vyos.utils.kernel import unload_kmod
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.utils.system import sysctl_read
@@ -63,7 +63,7 @@ def verify(nat64) -> None:
base_rule = base + ['source', 'rule']
# Load in existing instances so we can destroy any unknown
- lines = cmd('jool instance display --csv').splitlines()
+ lines = cmdl(['jool', 'instance', 'display', '--csv']).splitlines()
for _, instance, _ in csv.reader(lines):
match = INSTANCE_REGEX.fullmatch(instance)
if not match:
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index c3637c6b9..999e320eb 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -25,7 +25,7 @@ from vyos.template import render
from vyos.utils.dict import dict_search
from vyos.utils.kernel import check_kmod
from vyos.utils.network import interface_exists
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.template import is_ipv6
from vyos import ConfigError
@@ -142,7 +142,7 @@ def generate(nat):
def apply(nat):
check_kmod(k_mod)
- cmd(f'nft --file {nftables_nat66_config}')
+ cmdl(['nft', '--file', nftables_nat66_config])
if not nat or 'deleted' in nat:
os.unlink(nftables_nat66_config)
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index 312688b53..ac5b10c55 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -25,7 +25,7 @@ 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 cmdl
from vyos.utils.process import run
from vyos import ConfigError
from vyos import airbag
@@ -416,11 +416,11 @@ def generate(config):
def apply(config):
if 'deleted' in config:
# Cleanup cgnat
- cmd('nft delete table ip cgnat')
+ cmdl(['nft', 'delete', 'table', 'ip', 'cgnat'])
if os.path.isfile(nftables_cgnat_config):
os.unlink(nftables_cgnat_config)
else:
- cmd(f'nft --file {nftables_cgnat_config}')
+ cmdl(['nft', '--file', nftables_cgnat_config])
# Delete conntrack entries
# if the pool configuration has changed
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 349abb6fc..fdc0a8fd3 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -48,7 +48,7 @@ from vyos.utils.dict import dict_set_nested
from vyos.utils.file import read_file
from vyos.utils.network import check_port_availability
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
@@ -124,7 +124,8 @@ def certbot_delete(certificate):
if not boot_configuration_complete():
return
if os.path.exists(f'{vyos_certbot_dir}/renewal/{certificate}.conf'):
- cmd(f'certbot delete --non-interactive --config-dir {vyos_certbot_dir} --cert-name {certificate}')
+ cmdl(['certbot', 'delete', '--non-interactive', '--config-dir',
+ vyos_certbot_dir, '--cert-name', certificate])
def certbot_request(name: str, config: dict, dry_run: bool=True) -> None:
# We do not call certbot when booting the system - there is no need to do so and
@@ -134,11 +135,14 @@ def certbot_request(name: str, config: dict, dry_run: bool=True) -> None:
if not boot_configuration_complete():
return None
- domains = '--domains ' + ' --domains '.join(config['domain_name'])
- tmp = f'certbot certonly --non-interactive --config-dir {vyos_certbot_dir} --cert-name {name} '\
- f'--standalone --agree-tos --no-eff-email --expand --server {config["url"]} '\
- f'--email {config["email"]} --key-type rsa --rsa-key-size {config["rsa_key_size"]} '\
- f'{domains}'
+ tmp = ['certbot', 'certonly', '--non-interactive', '--config-dir',
+ vyos_certbot_dir, '--cert-name', name,
+ '--standalone', '--agree-tos', '--no-eff-email', '--expand',
+ '--server', config['url'],
+ '--email', config['email'], '--key-type', 'rsa',
+ '--rsa-key-size', str(config['rsa_key_size'])]
+ for domain in config['domain_name']:
+ tmp += ['--domains', domain]
listen_address = None
if 'listen_address' in config:
@@ -149,21 +153,22 @@ def certbot_request(name: str, config: dict, dry_run: bool=True) -> None:
if ('used_by' in config and 'haproxy' in config['used_by'] and
is_systemd_service_running(systemd_services['haproxy']) and
not check_port_availability(listen_address, 80)):
- tmp += f' --http-01-address 127.0.0.1 --http-01-port {internal_ports["certbot_haproxy"]}'
+ tmp += ['--http-01-address', '127.0.0.1', '--http-01-port',
+ str(internal_ports["certbot_haproxy"])]
elif listen_address:
- tmp += f' --http-01-address {listen_address}'
+ tmp += ['--http-01-address', listen_address]
# verify() does not need to actually request a cert but only test for plausibility
if dry_run:
- tmp += ' --dry-run'
+ tmp += ['--dry-run']
- cmd(tmp, raising=ConfigError, message=f'Certbot request failed for "{name}"!')
+ cmdl(tmp, raising=ConfigError, message=f'Certbot request failed for "{name}"!')
return None
def certbot_renew(config: dict, force: bool=False) -> None:
""" Renew all certificates managed via certbot """
- tmp = f'certbot renew --no-random-sleep-on-renew ' \
- f'--config-dir {vyos_certbot_dir}'
+ tmp = ['certbot', 'renew', '--no-random-sleep-on-renew',
+ '--config-dir', vyos_certbot_dir]
# Determine services using ACME based certificates
pre_hook_services = []
@@ -178,18 +183,18 @@ def certbot_renew(config: dict, force: bool=False) -> None:
for service in pre_hook_services:
if service in systemd_services:
stop_services.append(systemd_services[service])
- tmp += ' --pre-hook "systemctl stop ' + ' '.join(stop_services) + '"'
+ tmp += ['--pre-hook', 'systemctl stop ' + ' '.join(stop_services)]
if force:
- tmp += ' --force-renewal'
+ tmp += ['--force-renewal']
try:
- print(cmd(tmp, raising=ConfigError, message=f'Certbot renew failed!'))
+ print(cmdl(tmp, raising=ConfigError, message=f'Certbot renew failed!'))
except ConfigError as e:
print(e)
for service in stop_services:
print(f'Restarting "{service}" with non-renewed certificate...')
- cmd(f'systemctl restart {service}')
+ cmdl(['systemctl', 'restart', service])
return None
def get_config(config=None):
diff --git a/src/conf_mode/policy_route.py b/src/conf_mode/policy_route.py
index ca12ebe7b..1d0dbd5a9 100755
--- a/src/conf_mode/policy_route.py
+++ b/src/conf_mode/policy_route.py
@@ -28,7 +28,7 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.file import write_file
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import interface_exists
@@ -241,7 +241,7 @@ def update_domain_resolver(policy):
def apply_table_marks(policy):
for route in ['route', 'route6']:
if route in policy:
- cmd_str = 'ip' if route == 'route' else 'ip -6'
+ cmd_list = ['ip'] if route == 'route' else ['ip', '-6']
tables = []
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
@@ -265,11 +265,12 @@ def apply_table_marks(policy):
continue
tables.append(vrf_table_id)
table_mark = mark_offset - vrf_table_id
- cmd(f'{cmd_str} rule add pref {vrf_table_id} fwmark {table_mark} table {vrf_table_id}')
+ cmdl(cmd_list + ['rule', 'add', 'pref', str(vrf_table_id),
+ 'fwmark', str(table_mark), 'table', str(vrf_table_id)])
def cleanup_table_marks():
- for cmd_str in ['ip', 'ip -6']:
- json_rules = cmd(f'{cmd_str} -j -N rule list')
+ for cmd_list in [['ip'], ['ip', '-6']]:
+ json_rules = cmdl(cmd_list + ['-j', '-N', 'rule', 'list'])
rules = loads(json_rules)
for rule in rules:
if 'fwmark' not in rule or 'table' not in rule:
@@ -279,7 +280,7 @@ def cleanup_table_marks():
if fwmark[:2] == '0x':
fwmark = int(fwmark, 16)
if (int(fwmark) == (mark_offset - table)):
- cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}')
+ cmdl(cmd_list + ['rule', 'del', 'fwmark', str(fwmark), 'table', str(table)])
def apply(policy):
install_result = run(f'nft --file {nftables_conf}')
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index 1e6797c60..d1c002887 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -37,6 +37,7 @@ from vyos.qos import RateLimiter
from vyos.qos import RoundRobin
from vyos.qos import TrafficShaper
from vyos.qos import TrafficShaperHFSC
+from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import run
from vyos import ConfigError
@@ -351,13 +352,23 @@ def generate(qos):
def apply_interface(qos, ifname):
""" Clear and re-apply QoS for a single interface only. """
- run(f'tc qdisc del dev {ifname} parent ffff:')
+ interface_config = dict_search_args(qos, 'interface', ifname)
+ if not interface_config:
+ interface_config = {}
+
+ # Only tear down the ingress qdisc if this interface actually has an
+ # ingress QoS policy bound. Otherwise this would remove an unrelated
+ # ingress redirect/mirror configuration (see set_mirror_redirect())
+ # that call_dependents() just re-created, and it would never come
+ # back as only directions present in interface_config are re-applied
+ # below.
+ if 'ingress' in interface_config:
+ run(f'tc qdisc del dev {ifname} parent ffff:')
run(f'tc qdisc del dev {ifname} root')
- if not qos or 'interface' not in qos or ifname not in qos['interface']:
+ if not interface_config:
return None
- interface_config = qos['interface'][ifname]
if not verify_interface_exists(qos, ifname, state_required=True, warning_only=True):
# When shaper is bound to a dialup (e.g. PPPoE) interface it is
# possible that it is yet not available when the QoS code runs.
diff --git a/src/conf_mode/service_monitoring_prometheus.py b/src/conf_mode/service_monitoring_prometheus.py
index 79b557329..2f1b3885e 100755
--- a/src/conf_mode/service_monitoring_prometheus.py
+++ b/src/conf_mode/service_monitoring_prometheus.py
@@ -67,6 +67,22 @@ def get_config(config=None):
with_recursive_defaults=True,
)
+ if 'frr_exporter' in monitoring:
+ # Optional collectors to enable, translated from the CLI node name
+ # to the upstream frr_exporter collector flag
+ collector_flags = {'bgp_l2_vpn': 'bgpl2vpn', 'pim': 'pim'}
+ collector = monitoring['frr_exporter'].get('collector', {})
+ monitoring['frr_exporter']['optional_collectors'] = [
+ flag for node, flag in collector_flags.items() if node in collector
+ ]
+ # peer-description carries a default value (json) which is merged into
+ # the config dict by with_recursive_defaults - the BGP peer description
+ # collector option is opt-in, so drop the default if it was never set
+ if not conf.exists(
+ base + ['frr-exporter', 'collector', 'bgp', 'peer-description']
+ ):
+ collector.get('bgp', {}).pop('peer_description', None)
+
tmp = is_node_changed(conf, base + ['node-exporter', 'vrf'])
if tmp:
monitoring.update({'node_exporter_restart_required': {}})
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 2271f240f..9ce73eaf8 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -28,7 +28,7 @@ from vyos.ifconfig import Section
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.permission import chown
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -42,7 +42,7 @@ systemd_override = '/run/systemd/system/telegraf.service.d/10-override.conf'
def get_nft_filter_chains():
""" Get nft chains for table filter """
try:
- nft = cmd('nft --json list table ip vyos_filter')
+ nft = cmdl(['nft', '--json', 'list', 'table', 'ip', 'vyos_filter'])
except Exception:
print('nft table ip vyos_filter not found')
return []
diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py
index e6710223a..559aebcc8 100755
--- a/src/conf_mode/system_conntrack.py
+++ b/src/conf_mode/system_conntrack.py
@@ -25,7 +25,7 @@ from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.file import write_file
-from vyos.utils.process import cmd, call
+from vyos.utils.process import cmdl, call
from vyos.utils.process import rc_cmd
from vyos.template import render
from vyos import ConfigError
@@ -230,8 +230,7 @@ def apply(conntrack):
# Add modules before nftables uses them
if add_modules:
- module_str = ' '.join(add_modules)
- cmd(f'modprobe -a {module_str}')
+ cmdl(['modprobe', '-a'] + add_modules)
# Load new nftables ruleset
install_result, output = rc_cmd(f'nft --file {nftables_ct_file}')
@@ -240,8 +239,7 @@ def apply(conntrack):
# Remove modules after nftables stops using them
if rm_modules:
- module_str = ' '.join(rm_modules)
- cmd(f'rmmod {module_str}')
+ cmdl(['rmmod'] + rm_modules)
try:
call_dependents()
@@ -252,7 +250,7 @@ def apply(conntrack):
# We silently ignore all errors
# See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
- cmd(f'sysctl -f {sysctl_file}')
+ cmdl(['sysctl', '-f', sysctl_file])
if 'log' in conntrack:
call(f'systemctl restart vyos-conntrack-logger.service')
diff --git a/src/conf_mode/system_host-name.py b/src/conf_mode/system_host-name.py
index 2267508af..49dd9958d 100755
--- a/src/conf_mode/system_host-name.py
+++ b/src/conf_mode/system_host-name.py
@@ -26,7 +26,7 @@ from vyos.configdict import leaf_node_changed
from vyos.defaults import systemd_services
from vyos.ifconfig import Section
from vyos.template import is_ip
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import call
from vyos.utils.process import process_named_running
from vyos import ConfigError
@@ -170,7 +170,7 @@ def apply(config):
# rsyslog runs into a race condition at boot time with systemd
# restart rsyslog only if the hostname changed.
- hostname_old = cmd('hostnamectl --static')
+ hostname_old = cmdl(['hostnamectl', '--static'])
call(f'hostnamectl set-hostname --static {hostname_new}')
# Restart services that use the hostname
diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py
index fec4cfa6d..01c2af339 100755
--- a/src/conf_mode/system_login.py
+++ b/src/conf_mode/system_login.py
@@ -47,7 +47,7 @@ from vyos.utils.dict import dict_search
from vyos.utils.file import move_recursive
from vyos.utils.network import is_addr_assigned
from vyos.utils.permission import chown
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import call
from vyos.utils.process import run
from vyos.utils.process import DEVNULL
@@ -365,34 +365,36 @@ def apply(login):
for user, user_config in login['user'].items():
# make new user using vyatta shell and make home directory (-m),
# default group of 100 (users)
- command = 'useradd --create-home --no-user-group '
+ command_name = 'useradd'
+ command = ['useradd', '--create-home', '--no-user-group']
# check if user already exists:
if user in get_local_users():
# update existing account
- command = 'usermod'
+ command_name = 'usermod'
+ command = ['usermod']
# all accounts use /bin/vbash
- command += ' --shell /bin/vbash'
- # we need to use '' quotes when passing formatted data to the shell
- # else it will not work as some data parts are lost in translation
+ command += ['--shell', '/bin/vbash']
+ # no shell is involved when using cmdl(), so no quoting is required
+ # even if the values contain whitespace
tmp = dict_search('authentication.encrypted_password', user_config)
- if tmp: command += f" --password '{tmp}'"
+ if tmp: command += ['--password', str(tmp)]
tmp = dict_search('full_name', user_config)
- if tmp: command += f" --comment '{tmp}'"
+ if tmp: command += ['--comment', str(tmp)]
home_directory = dict_search('home_directory', user_config)
if not home_directory:
home_directory = f'/home/{user}'
- command += f" --home '{home_directory}'"
+ command += ['--home', str(home_directory)]
if 'operator' not in user_config:
- command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk,_kea,vpp'
+ command += ['--groups', 'frr,frrvty,vyattacfg,sudo,adm,dip,disk,_kea,vpp']
- command += f' {user}'
+ command += [user]
try:
- cmd(command)
+ cmdl(command)
# we should not rely on the value stored in user_config['home_directory'], as a
# crazy user will choose username root or any other system user which will fail.
#
@@ -424,7 +426,7 @@ def apply(login):
#
# More details: https://github.com/vyos/vyos-1x/pull/4678#pullrequestreview-3169648265
backup_directory = f"/var/.users_backups/{user}"
- if command.startswith('useradd') and os.path.exists(backup_directory):
+ if command_name == 'useradd' and os.path.exists(backup_directory):
move_recursive(backup_directory, home_dir)
chown(home_dir, user=user, group='users', recursive=True)
@@ -455,7 +457,7 @@ def apply(login):
lock_unlock = '--unlock'
if 'disable' in user_config:
lock_unlock = '--lock'
- cmd(f'usermod {lock_unlock} {user}')
+ cmdl(['usermod', lock_unlock, user])
if 'rm_users' in login:
for user in login['rm_users']:
@@ -497,27 +499,27 @@ def apply(login):
raise ConfigError(f'Deleting user "{user}" raised exception: {e}')
# Enable/disable RADIUS in PAM configuration
- cmd('pam-auth-update --disable radius-mandatory radius-optional')
+ cmdl(['pam-auth-update', '--disable', 'radius-mandatory', 'radius-optional'])
if 'radius' in login:
if login['radius'].get('security_mode', '') == 'mandatory':
pam_profile = 'radius-mandatory'
else:
pam_profile = 'radius-optional'
- cmd(f'pam-auth-update --enable {pam_profile}')
+ cmdl(['pam-auth-update', '--enable', pam_profile])
# Enable/disable TACACS+ in PAM configuration
- cmd('pam-auth-update --disable tacplus-mandatory tacplus-optional')
+ cmdl(['pam-auth-update', '--disable', 'tacplus-mandatory', 'tacplus-optional'])
if 'tacacs' in login:
if login['tacacs'].get('security_mode', '') == 'mandatory':
pam_profile = 'tacplus-mandatory'
else:
pam_profile = 'tacplus-optional'
- cmd(f'pam-auth-update --enable {pam_profile}')
+ cmdl(['pam-auth-update', '--enable', pam_profile])
# Enable/disable Google authenticator
- cmd('pam-auth-update --disable mfa-google-authenticator')
+ cmdl(['pam-auth-update', '--disable', 'mfa-google-authenticator'])
if enable_otp:
- cmd('pam-auth-update --enable mfa-google-authenticator')
+ cmdl(['pam-auth-update', '--enable', 'mfa-google-authenticator'])
call_dependents()
return None
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 2ea6bfd06..8951adcff 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import platform
import psutil
import re
@@ -36,7 +37,7 @@ from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.file import read_file
from vyos.utils.kernel import check_kmod
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_addr_assigned
from vyos.utils.network import is_intf_addr_assigned
@@ -51,7 +52,14 @@ from vyos.vpp.config_resource_checks.resource_defaults import default_resource_m
airbag.enable()
+arch_map = {
+ "x86_64": "x86_64-linux-gnu",
+ "aarch64": "aarch64-linux-gnu",
+}
+arch = arch_map.get(platform.machine())
curlrc_config = r'/etc/curlrc'
+openssl_config = '/etc/ssl/openssl.cnf'
+openssl_fipsmodule_config = '/run/ssl/fipsmodule.cnf'
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'
@@ -203,6 +211,11 @@ def get_config(config=None):
def verify(options):
+ if 'fips' in options:
+ fips_module = f'/usr/lib/{arch}/ossl-modules/fips.so'
+ if not os.path.exists(fips_module):
+ raise ConfigError(f'OpenSSL FIPS module not found: {fips_module}')
+
if 'http_client' in options:
config = options['http_client']
if 'source_interface' in config:
@@ -305,7 +318,19 @@ def verify(options):
def generate(options):
+
+ if 'fips' in options:
+ # ensure directory exists
+ os.makedirs(os.path.dirname(openssl_fipsmodule_config), exist_ok=True)
+ cmdl(['openssl', 'fipsinstall', '-module',
+ f'/usr/lib/{arch}/ossl-modules/fips.so',
+ '-out', openssl_fipsmodule_config])
+ else:
+ if os.path.exists(openssl_fipsmodule_config):
+ os.remove(openssl_fipsmodule_config)
+
render(curlrc_config, 'system/curlrc.j2', options)
+ render(openssl_config, 'openssl/openssl.cnf.j2', options)
render(ssh_config, 'system/ssh_config.j2', options)
render(usb_autosuspend, 'system/40_usb_autosuspend.j2', options)
@@ -484,12 +509,12 @@ def apply(options):
kexec_required, cmdline_new = generate_cmdline_for_kexec(options)
if kexec_required:
if not boot_configuration_complete() and os.getenv('VYOS_CONFIGD'):
- cmd(
- 'kexec -l /boot/vmlinuz --initrd=/boot/initrd.img '
- f'--command-line="{cmdline_new}" --kexec-file-syscall'
- )
+ cmdl([
+ 'kexec', '-l', '/boot/vmlinuz', '--initrd=/boot/initrd.img',
+ f'--command-line={cmdline_new}', '--kexec-file-syscall',
+ ])
os.sync()
- cmd('systemctl kexec')
+ cmdl(['systemctl', 'kexec'])
elif boot_configuration_complete():
Warning(
'Kernel configuration options have changed. '
@@ -499,9 +524,9 @@ def apply(options):
# System bootup beep
beep_service = 'vyos-beep.service'
if 'startup_beep' in options:
- cmd(f'systemctl enable {beep_service}')
+ cmdl(['systemctl', 'enable', beep_service])
else:
- cmd(f'systemctl disable {beep_service}')
+ cmdl(['systemctl', 'disable', beep_service])
# Ctrl-Alt-Delete action
if os.path.exists(systemd_action_file):
@@ -512,6 +537,10 @@ def apply(options):
elif options['ctrl_alt_delete'] == 'poweroff':
os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file)
+ # FIPS
+ if 'fips' in options:
+ Warning('FIPS 140-3 support is incomplete and may behave unexpectedly!')
+
# Configure HTTP client
if 'http_client' not in options:
if os.path.exists(curlrc_config):
@@ -531,37 +560,35 @@ def apply(options):
# tuned - performance tuning
if 'performance' in options:
- cmd('systemctl restart tuned.service')
+ cmdl(['systemctl', 'restart', 'tuned.service'])
# wait until daemon has started before sending configuration
while not is_systemd_service_running('tuned.service'):
sleep(0.250)
- performance = ' '.join(
- list(tuned_profiles[profile] for profile in options['performance'])
- )
- cmd(f'tuned-adm profile {performance}')
+ performance = [tuned_profiles[profile] for profile in options['performance']]
+ cmdl(['tuned-adm', 'profile'] + performance)
else:
- cmd('systemctl stop tuned.service')
+ cmdl(['systemctl', 'stop', 'tuned.service'])
call_dependents()
# Keyboard layout - there will be always the default key inside the dict
# but we check for key existence anyway
if 'keyboard_layout' in options:
- cmd('loadkeys {keyboard_layout}'.format(**options))
+ cmdl(['loadkeys', options['keyboard_layout']])
# Enable/diable root-partition-auto-resize SystemD service
if 'root_partition_auto_resize' in options:
- cmd('systemctl enable root-partition-auto-resize.service')
+ cmdl(['systemctl', 'enable', 'root-partition-auto-resize.service'])
else:
- cmd('systemctl disable root-partition-auto-resize.service')
+ cmdl(['systemctl', 'disable', 'root-partition-auto-resize.service'])
# Time format 12|24-hour
if 'time_format' in options:
time_format = time_format_to_locale.get(options['time_format'])
- cmd(f'localectl set-locale LC_TIME={time_format}')
+ cmdl(['localectl', 'set-locale', f'LC_TIME={time_format}'])
# Reload UDEV, required for USB auto suspend
- cmd('udevadm control --reload-rules')
+ cmdl(['udevadm', 'control', '--reload-rules'])
# Enable/disable dynamic debugging for kernel modules
modules = ['wireguard']
diff --git a/src/conf_mode/system_sysctl.py b/src/conf_mode/system_sysctl.py
index 8e018ec0b..d9b54b551 100755
--- a/src/conf_mode/system_sysctl.py
+++ b/src/conf_mode/system_sysctl.py
@@ -20,7 +20,7 @@ from sys import exit
from vyos.config import Config
from vyos.template import render
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -59,7 +59,7 @@ def apply(sysctl):
# We silently ignore all errors
# See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
- cmd(f'sysctl -f {config_file}')
+ cmdl(['sysctl', '-f', config_file])
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/system_watchdog.py b/src/conf_mode/system_watchdog.py
index 8c050f333..a2b70262f 100755
--- a/src/conf_mode/system_watchdog.py
+++ b/src/conf_mode/system_watchdog.py
@@ -22,7 +22,7 @@ from vyos.config import Config
from vyos.base import Warning
from vyos.template import render
from vyos.utils.kernel import load_module
-from vyos.utils.process import call, cmd
+from vyos.utils.process import call, cmdl
from vyos import ConfigError
from vyos import airbag
@@ -89,7 +89,7 @@ def _verify_watchdog_module(module: str) -> None:
# Ensure the module looks like a watchdog driver and not an arbitrary module.
# Use modinfo filename location as the heuristic.
- filename = cmd(['modinfo', '-F', 'filename', module], raising=ConfigError)
+ filename = cmdl(['modinfo', '-F', 'filename', module], raising=ConfigError)
filename_l = filename.strip().lower()
# Accept modules located under drivers/watchdog, plus explicit exception for
diff --git a/src/conf_mode/vpp_interfaces_loopback.py b/src/conf_mode/vpp_interfaces_loopback.py
index 2f7b59354..c1046fdca 100644
--- a/src/conf_mode/vpp_interfaces_loopback.py
+++ b/src/conf_mode/vpp_interfaces_loopback.py
@@ -24,6 +24,8 @@ from vyos.configdep import set_dependents, call_dependents
from vyos.utils.process import is_systemd_service_active
from vyos.ifconfig.vpp import VPPLoopbackInterface
+from vyos.vpp.config_deps import deps_bridge_dict
+from vyos.vpp.config_verify import verify_vpp_remove_bridge_interface
def get_config(config=None) -> dict:
@@ -55,6 +57,12 @@ def get_config(config=None) -> dict:
no_tag_node_value_mangle=True,
)
+ # Bridge dependency - reattach as BVI after this loopback is recreated
+ config['bridge_members'] = deps_bridge_dict(conf)
+ if ifname in config['bridge_members']:
+ for bridge_iface in config['bridge_members'][ifname]:
+ set_dependents('vpp_interfaces_bridge', conf, bridge_iface)
+
# NAT dependency
if conf.exists(['vpp', 'nat', 'nat44']):
set_dependents('vpp_nat_nat44', conf)
@@ -73,6 +81,8 @@ def verify(config):
if 'remove_vpp' in config:
return None
+ verify_vpp_remove_bridge_interface(config)
+
if not is_systemd_service_active('vpp.service'):
raise ConfigError(
'Cannot configure VPP loopback interface: vpp.service is not running'
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index e0dc43283..b5b9552e2 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -36,7 +36,7 @@ from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import get_vrf_members
from vyos.utils.network import interface_exists
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import is_systemd_service_running
from vyos.utils.process import popen
from vyos.utils.system import sysctl_write
@@ -65,8 +65,8 @@ def has_rule(af : str, priority : int, table : str=None):
"""
if af not in ['-4', '-6']:
raise ValueError()
- command = f'ip --detail --json {af} rule show'
- for tmp in loads(cmd(command)):
+ command = ['ip', '--detail', '--json', af, 'rule', 'show']
+ for tmp in loads(cmdl(command)):
if 'priority' in tmp and 'table' in tmp:
if tmp['priority'] == priority and tmp['table'] == table:
return True
@@ -80,7 +80,7 @@ def is_nft_vrf_zone_rule_setup() -> bool:
"""
Check if an nftables connection tracking rule already exists
"""
- tmp = loads(cmd('sudo nft -j list table inet vrf_zones'))
+ tmp = loads(cmdl(['nft', '-j', 'list', 'table', 'inet', 'vrf_zones'], sudo=True))
num_rules = len(search("nftables[].rule[].chain", tmp))
return bool(num_rules)
@@ -259,12 +259,13 @@ def apply(vrf):
vrf_iface.set_dhcpv6(False)
# Remove nftables conntrack zone map item
- nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ \'"{tmp}"\' }}'
+ nft_del_element = ['delete', 'element', 'inet', 'vrf_zones', 'ct_iface_map',
+ '{', f'"{tmp}"', '}']
# Check if deleting is possible first to avoid raising errors
- _, err = popen(f'nft --check {nft_del_element}')
+ _, err = popen(f'nft --check {" ".join(nft_del_element)}')
if not err:
# Remove map element
- cmd(f'nft {nft_del_element}')
+ cmdl(['nft'] + nft_del_element)
# Remove WireGuard fwmark routing rules created for this VRF table
table_id = get_vrf_tableid(tmp)
@@ -345,8 +346,9 @@ def apply(vrf):
state = 'down' if 'disable' in config else 'up'
vrf_if.set_admin_state(state)
# Add nftables conntrack zone map item
- nft_add_element = f'add element inet vrf_zones ct_iface_map {{ \'"{name}"\' : {table} }}'
- cmd(f'nft {nft_add_element}')
+ nft_add_element = ['add', 'element', 'inet', 'vrf_zones', 'ct_iface_map',
+ '{', f'"{name}"', ':', str(table), '}']
+ cmdl(['nft'] + nft_add_element)
# Only call into nftables as long as there is nothing setup to avoid wasting
# CPU time and thus lengthen the commit process
@@ -355,11 +357,11 @@ def apply(vrf):
# Install nftables conntrack rules only once
if vrf['conntrack'] and not nft_vrf_zone_rule_setup:
for chain, rule in nftables_rules.items():
- cmd(f'nft add rule inet vrf_zones {chain} {rule}')
+ cmdl(f'nft add rule inet vrf_zones {chain} {rule}'.split())
if 'name' not in vrf or not vrf['conntrack']:
for chain, rule in nftables_rules.items():
- cmd(f'nft flush chain inet vrf_zones {chain}')
+ cmdl(f'nft flush chain inet vrf_zones {chain}'.split())
# Return default ip rule values
if 'name' not in vrf:
diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
index bb7515a90..31c94d66b 100755
--- a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
+++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
@@ -4,7 +4,7 @@ import json
import re
import time
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
def get_nft_filter_chains():
@@ -12,7 +12,7 @@ def get_nft_filter_chains():
Get list of nft chains for table filter
"""
try:
- nft = cmd('/usr/sbin/nft --json list table ip vyos_filter')
+ nft = cmdl(['/usr/sbin/nft', '--json', 'list', 'table', 'ip', 'vyos_filter'])
except Exception:
return []
nft = json.loads(nft)
@@ -30,9 +30,9 @@ def get_nftables_details(name):
"""
Get dict, counters packets and bytes for chain
"""
- command = f'/usr/sbin/nft list chain ip vyos_filter {name}'
+ command = ['/usr/sbin/nft', 'list', 'chain', 'ip', 'vyos_filter', name]
try:
- results = cmd(command)
+ results = cmdl(command)
except:
return {}
diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py
index 9826a6d3e..a3a66eedc 100755
--- a/src/helpers/vyos-boot-config-loader.py
+++ b/src/helpers/vyos-boot-config-loader.py
@@ -26,7 +26,7 @@ from vyos.defaults import config_status
from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.configtree import ConfigTree
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.file import write_file
STATUS_FILE = config_status
@@ -103,8 +103,9 @@ def failsafe(config_file_name):
'authentication',
'encrypted-password'])
- cmd(f"useradd --create-home --no-user-group --shell /bin/vbash --password '{passwd}' "\
- "--groups frr,frrvty,vyattacfg,sudo,adm,dip,disk vyos")
+ cmdl(['useradd', '--create-home', '--no-user-group', '--shell', '/bin/vbash',
+ '--password', passwd,
+ '--groups', 'frr,frrvty,vyattacfg,sudo,adm,dip,disk', 'vyos'])
if __name__ == '__main__':
if len(sys.argv) < 2:
diff --git a/src/helpers/vyos-config-encrypt.py b/src/helpers/vyos-config-encrypt.py
index 876a835ec..14e8d2243 100755
--- a/src/helpers/vyos-config-encrypt.py
+++ b/src/helpers/vyos-config-encrypt.py
@@ -25,7 +25,7 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory
from vyos.system.image import is_live_boot, get_running_image
from vyos.tpm import clear_tpm_key, read_tpm_key, write_tpm_key
from vyos.utils.io import ask_input, ask_yes_no
-from vyos.utils.process import cmd, run
+from vyos.utils.process import cmdl, run
from vyos.defaults import directories
persistpath_cmd = '/opt/vyatta/sbin/vyos-persistpath'
@@ -42,7 +42,7 @@ def load_config(key):
if not key:
return
- persist_path = cmd(persistpath_cmd).strip()
+ persist_path = cmdl([persistpath_cmd]).strip()
image_name = get_running_image()
image_path = os.path.join(persist_path, 'luks', image_name)
@@ -54,11 +54,11 @@ def load_config(key):
f.write(key)
key_file = f.name
- cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
+ cmdl(['cryptsetup', '-q', 'open', image_path, 'vyos_config', f'--key-file={key_file}'])
run(f'umount -l {mount_path}')
- cmd(f'mount /dev/mapper/vyos_config {mount_path}')
- cmd(f'chgrp -R vyattacfg {mount_path}')
+ cmdl(['mount', '/dev/mapper/vyos_config', mount_path])
+ cmdl(['chgrp', '-R', 'vyattacfg', mount_path])
os.unlink(key_file)
@@ -73,7 +73,7 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
pass
write_tpm_key(key)
- persist_path = cmd(persistpath_cmd).strip()
+ persist_path = cmdl([persistpath_cmd]).strip()
size = ask_input('Enter size of encrypted config partition (MB): ', numeric_only=True, default=512)
luks_folder = os.path.join(persist_path, 'luks')
@@ -86,7 +86,7 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
try:
# Create file for encrypted config
- cmd(f'fallocate -l {size}M {image_path}')
+ cmdl(['fallocate', '-l', f'{size}M', image_path])
# Write TPM key for slot #1
with NamedTemporaryFile(dir='/dev/shm', delete=False) as f:
@@ -94,7 +94,7 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
key_file = f.name
# Format and add main key to volume
- cmd(f'cryptsetup -q luksFormat {image_path} {key_file}')
+ cmdl(['cryptsetup', '-q', 'luksFormat', image_path, key_file])
if recovery_key:
# Write recovery key for slot 2
@@ -102,11 +102,11 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
f.write(recovery_key)
recovery_key_file = f.name
- cmd(f'cryptsetup -q luksAddKey {image_path} {recovery_key_file} --key-file={key_file}')
+ cmdl(['cryptsetup', '-q', 'luksAddKey', image_path, recovery_key_file, f'--key-file={key_file}'])
# Open encrypted volume and format with ext4
- cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
- cmd('mkfs.ext4 /dev/mapper/vyos_config')
+ cmdl(['cryptsetup', '-q', 'open', image_path, 'vyos_config', f'--key-file={key_file}'])
+ cmdl(['mkfs.ext4', '/dev/mapper/vyos_config'])
except Exception as e:
print('An error occurred while creating the encrypted config volume, aborting.')
@@ -119,14 +119,15 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
raise e
with TemporaryDirectory() as d:
- cmd(f'mount /dev/mapper/vyos_config {d}')
+ cmdl(['mount', '/dev/mapper/vyos_config', d])
# Move mount_path to encrypted volume
shutil.copytree(
mount_path, d, symlinks=True, copy_function=shutil.move, dirs_exist_ok=True
)
- cmd(f'chgrp -R vyattacfg {d}')
- cmd(f'umount {d}')
+ shutil.copytree(mount_path, d, copy_function=shutil.move, dirs_exist_ok=True)
+ cmdl(['chgrp', '-R', 'vyattacfg', d])
+ cmdl(['umount', d])
os.unlink(key_file)
@@ -134,8 +135,8 @@ def encrypt_config(key, recovery_key=None, is_tpm=True):
os.unlink(recovery_key_file)
run(f'umount -l {mount_path}')
- cmd(f'mount /dev/mapper/vyos_config {mount_path}')
- cmd(f'chgrp vyattacfg {mount_path}')
+ cmdl(['mount', '/dev/mapper/vyos_config', mount_path])
+ cmdl(['chgrp', 'vyattacfg', mount_path])
return True
@@ -153,7 +154,7 @@ def test_decrypt(key):
if not key:
return
- persist_path = cmd(persistpath_cmd).strip()
+ persist_path = cmdl([persistpath_cmd]).strip()
image_name = get_running_image()
image_path = os.path.join(persist_path, 'luks', image_name)
@@ -165,7 +166,7 @@ def test_decrypt(key):
key_file = f.name
try:
- cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
+ cmdl(['cryptsetup', '-q', 'open', image_path, 'vyos_config', f'--key-file={key_file}'])
os.unlink(key_file)
return True
except:
@@ -176,7 +177,7 @@ def decrypt_config(key):
if not key:
return
- persist_path = cmd(persistpath_cmd).strip()
+ persist_path = cmdl([persistpath_cmd]).strip()
image_name = get_running_image()
image_path = os.path.join(persist_path, 'luks', image_name)
original_config_path = os.path.join(persist_path, 'boot', image_name, 'rw', 'opt', 'vyatta', 'etc', 'config')
@@ -188,7 +189,7 @@ def decrypt_config(key):
f.write(key)
key_file = f.name
- cmd(f'cryptsetup -q open {image_path} vyos_config --key-file={key_file}')
+ cmdl(['cryptsetup', '-q', 'open', image_path, 'vyos_config', f'--key-file={key_file}'])
# unmount encrypted volume mount points
run(f'umount -Alq /dev/mapper/vyos_config')
@@ -202,22 +203,21 @@ def decrypt_config(key):
# Mount original persistence config path
if not os.path.exists(mount_path):
os.mkdir(mount_path)
- cmd(f'mount --bind {original_config_path} {mount_path}')
+ cmdl(['mount', '--bind', original_config_path, mount_path])
# Temporarily mount encrypted volume and migrate files to /config on rootfs
with TemporaryDirectory() as d:
- cmd(f'mount /dev/mapper/vyos_config {d}')
+ cmdl(['mount', '/dev/mapper/vyos_config', d])
# Move encrypted volume to /opt/vyatta/etc/config
shutil.copytree(
d, mount_path, symlinks=True, copy_function=shutil.move, dirs_exist_ok=True
)
- cmd(f'chgrp -R vyattacfg {mount_path}')
-
- cmd(f'umount {d}')
+ cmdl(['chgrp', '-R', 'vyattacfg', mount_path])
+ cmdl(['umount', d])
# Close encrypted volume
- cmd('cryptsetup -q close vyos_config')
+ cmdl(['cryptsetup', '-q', 'close', 'vyos_config'])
# Remove encrypted volume image file and key
if key_file:
@@ -248,7 +248,7 @@ if __name__ == '__main__':
args = parser.parse_args()
if args.disable or args.load:
- persist_path = cmd(persistpath_cmd).strip()
+ persist_path = cmdl([persistpath_cmd]).strip()
image_name = get_running_image()
image_path = os.path.join(persist_path, 'luks', image_name)
diff --git a/src/migration-scripts/https/5-to-6 b/src/migration-scripts/https/5-to-6
index 8c41f4b61..ece407a4b 100644
--- a/src/migration-scripts/https/5-to-6
+++ b/src/migration-scripts/https/5-to-6
@@ -21,7 +21,7 @@ import os
from vyos.configtree import ConfigTree
from vyos.defaults import directories
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
vyos_certbot_dir = directories['certbot']
@@ -45,7 +45,7 @@ def migrate(config: ConfigTree) -> None:
# This must be run as root
certbot_live = f'{vyos_certbot_dir}/live/' # we need the trailing /
if os.path.exists(certbot_live):
- tmp = cmd(f'sudo find {certbot_live} -maxdepth 1 -type d')
+ tmp = cmdl(['find', certbot_live, '-maxdepth', '1', '-type', 'd'], sudo=True)
tmp = tmp.split() # tmp = ['/config/auth/letsencrypt/live', '/config/auth/letsencrypt/live/router.vyos.net']
tmp.remove(certbot_live)
cert_name = tmp[0].replace(certbot_live, '')
diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14
index 3632b4b6a..0a0d4ef67 100644
--- a/src/migration-scripts/system/13-to-14
+++ b/src/migration-scripts/system/13-to-14
@@ -24,7 +24,7 @@
import re
from vyos.configtree import ConfigTree
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
tz_base = ['system', 'time-zone']
@@ -38,7 +38,7 @@ def migrate(config: ConfigTree) -> None:
# retrieve all valid timezones
try:
- tz_data_raw = cmd('timedatectl list-timezones')
+ tz_data_raw = cmdl(['timedatectl', 'list-timezones'])
except OSError:
tz_data_raw = ''
tz_data = tz_data_raw.split('\n')
diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py
index 7f0815433..a3b2fc6d0 100755
--- a/src/op_mode/bgp.py
+++ b/src/op_mode/bgp.py
@@ -81,7 +81,7 @@ ArgFamily = typing.Literal['inet', 'inet6', 'l2vpn']
ArgFamilyModifier = typing.Literal['unicast', 'labeled_unicast', 'multicast', 'vpn', 'flowspec']
def reset(command: str):
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
tokens = command.split()
@@ -101,35 +101,35 @@ def reset(command: str):
tokens[index] = "*"
command = " ".join(tokens)
- cmd(f'vtysh -c "{command}"')
+ cmdl(['vtysh', '-c', command])
def show_summary(raw: bool):
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
if raw:
from json import loads
- output = cmd(f"vtysh -c 'show bgp summary json'").strip()
+ output = cmdl(['vtysh', '-c', 'show bgp summary json']).strip()
# FRR 8.5 correctly returns an empty object when BGP is not running,
# we don't need to do anything special here
return loads(output)
else:
- output = cmd(f"vtysh -c 'show bgp summary'")
+ output = cmdl(['vtysh', '-c', 'show bgp summary'])
return output
def show_neighbors(raw: bool):
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
from vyos.utils.dict import dict_to_list
if raw:
from json import loads
- output = cmd(f"vtysh -c 'show bgp neighbors json'").strip()
+ output = cmdl(['vtysh', '-c', 'show bgp neighbors json']).strip()
d = loads(output)
return dict_to_list(d, save_key_to="neighbor")
else:
- output = cmd(f"vtysh -c 'show bgp neighbors'")
+ output = cmdl(['vtysh', '-c', 'show bgp neighbors'])
return output
def show(raw: bool,
@@ -152,8 +152,8 @@ def show(raw: bool,
frr_command = frr_command_template.render(kwargs)
frr_command = re.sub(r'\s+', ' ', frr_command)
- from vyos.utils.process import cmd
- output = cmd(f"vtysh -c '{frr_command}'")
+ from vyos.utils.process import cmdl
+ output = cmdl(['vtysh', '-c', frr_command])
if raw:
from json import loads
diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py
index 9056e16d4..24ab261d1 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -21,7 +21,7 @@ import typing
from tabulate import tabulate
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import rc_cmd
from vyos.utils.process import call
@@ -32,7 +32,7 @@ def _get_json_data():
"""
Get bridge data format JSON
"""
- return cmd(f'bridge --json link show')
+ return cmdl(['bridge', '--json', 'link', 'show'])
def _get_raw_data_summary():
@@ -51,7 +51,7 @@ def _get_raw_data_vlan(tunnel: bool = False):
show = 'show'
if tunnel:
show = 'tunnel'
- json_data = cmd(f'bridge --json --compressvlans vlan {show}')
+ json_data = cmdl(['bridge', '--json', '--compressvlans', 'vlan', show])
data_dict = json.loads(json_data)
return data_dict
@@ -86,7 +86,7 @@ def _get_raw_data_mdb(bridge):
"""Get MAC-address multicast group for the bridge brX
:return list
"""
- json_data = cmd(f'bridge --json mdb show br {bridge}')
+ json_data = cmdl(['bridge', '--json', 'mdb', 'show', 'br', bridge])
data_dict = json.loads(json_data)
return data_dict
@@ -235,13 +235,13 @@ def _get_bridge_detail_nexthop_group(iface):
def _get_bridge_detail_nexthop_group_raw(iface):
- out = cmd(f'vtysh -c "show interface {iface} nexthop-group"')
+ out = cmdl(['vtysh', '-c', f'show interface {iface} nexthop-group'])
return out
def _get_bridge_detail_raw(iface):
"""Get interface detail json statistics"""
- data = cmd(f'vtysh -c "show interface {iface} json"')
+ data = cmdl(['vtysh', '-c', f'show interface {iface} json'])
data_dict = json.loads(data)
return data_dict
diff --git a/src/op_mode/cgnat.py b/src/op_mode/cgnat.py
index d53f6158b..e877e98ff 100755
--- a/src/op_mode/cgnat.py
+++ b/src/op_mode/cgnat.py
@@ -23,14 +23,14 @@ from tabulate import tabulate
import vyos.opmode
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
CGNAT_TABLE = 'cgnat'
def _get_raw_data(external_address: str = '', internal_address: str = '') -> list[dict]:
"""Get CGNAT dictionary and filter by external or internal address if provided."""
- cmd_output = cmd(f'nft --json list table ip {CGNAT_TABLE}')
+ cmd_output = cmdl(['nft', '--json', 'list', 'table', 'ip', CGNAT_TABLE])
data = json.loads(cmd_output)
elements = data['nftables'][2]['map']['elem']
diff --git a/src/op_mode/clear_conntrack.py b/src/op_mode/clear_conntrack.py
index 2a4f19607..19d1f77d8 100755
--- a/src/op_mode/clear_conntrack.py
+++ b/src/op_mode/clear_conntrack.py
@@ -17,11 +17,11 @@
import sys
from vyos.utils.io import ask_yes_no
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import DEVNULL
if not ask_yes_no("This will clear all currently tracked and expected connections. Continue?"):
sys.exit(1)
else:
- cmd('/usr/sbin/conntrack -F', stderr=DEVNULL)
- cmd('/usr/sbin/conntrack -F expect', stderr=DEVNULL)
+ cmdl(['/usr/sbin/conntrack', '-F'], stderr=DEVNULL)
+ cmdl(['/usr/sbin/conntrack', '-F', 'expect'], stderr=DEVNULL)
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index 301d3cfb8..b6f2d944e 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -19,7 +19,7 @@ import typing
import xmltodict
from tabulate import tabulate
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.network import get_vrf_tableid
import vyos.opmode
@@ -34,7 +34,7 @@ def _get_xml_data(family, orig_zone=None):
if orig_zone is not None:
args.extend(['--orig-zone', str(orig_zone)])
- return cmd(['sudo', 'conntrack'] + args)
+ return cmdl(['conntrack'] + args, sudo=True)
def _xml_to_dict(xml):
@@ -67,7 +67,7 @@ def _get_raw_data(family, orig_zone=None):
def _get_raw_statistics():
entries = []
- data = cmd('sudo conntrack --stats')
+ data = cmdl(['conntrack', '--stats'], sudo=True)
data = data.replace(' \t', '').split('\n')
for entry in data:
entries.append(entry.split())
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 0da5b3b0b..85847eda3 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -27,7 +27,7 @@ from vyos.configquery import CliShellApiConfigQuery
from vyos.configquery import ConfigTreeQuery
from vyos.utils.commit import commit_in_progress
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
conntrackd_bin = '/usr/sbin/conntrackd'
@@ -99,7 +99,7 @@ def restart():
raise vyos.opmode.CommitInProgress('Cannot restart conntrackd while a commit is in progress')
syslog.syslog('Restarting conntrack sync service...')
- cmd('systemctl restart conntrackd.service')
+ cmdl(['systemctl', 'restart', 'conntrackd.service'])
# request resynchronization with other systems
request_sync()
# send bulk update of internal-cache to other systems
@@ -130,7 +130,7 @@ def reset_internal_cache():
def _show_cache(raw, opts):
is_configured()
- out = cmd(f'{conntrackd_bin} -C {conntrackd_config} {opts} -x')
+ out = cmdl([conntrackd_bin, '-C', conntrackd_config] + opts.split() + ['-x'])
return from_xml(raw, out)
def show_external_cache(raw: bool):
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index 81dc35098..0948a0d84 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -22,7 +22,7 @@ import subprocess
from pathlib import Path
from vyos.defaults import directories
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import rc_cmd
from vyos.utils.process import run
import vyos.opmode
@@ -56,8 +56,8 @@ def clean_layer(name: str) -> int:
purge_layer_by_id(layer_id)
# Reinitiate the container's overlay layer
- cmd(f"rm -f /run/{unit}.cid /run/{unit}.pid")
- cmd(f"systemctl reset-failed {unit}")
+ cmdl(['rm', '-f', f'/run/{unit}.cid', f'/run/{unit}.pid'])
+ cmdl(['systemctl', 'reset-failed', unit])
result = run(f"systemctl start {unit}")
return result
@@ -65,7 +65,7 @@ def _get_json_data(command: str) -> list:
"""
Get container command format JSON
"""
- return cmd(f'{command} --format json')
+ return cmdl(command.split() + ['--format', 'json'])
def _get_raw_data(command: str) -> list:
json_data = _get_json_data(command)
@@ -106,7 +106,7 @@ def delete_image(name: str, force: typing.Optional[bool] = False):
if name == 'all':
# gather list of all images and pass them to the removal list
- name = cmd('sudo podman image ls --quiet')
+ name = cmdl(['podman', 'image', 'ls', '--quiet'], sudo=True)
# If there are no container images left, we cannot delete them all
if not name: return
# replace newline with whitespace
@@ -145,7 +145,7 @@ def show_container(raw: bool):
if raw:
return container_data
else:
- return cmd(command)
+ return cmdl(command.split())
def show_image(raw: bool):
command = 'podman image ls'
@@ -153,7 +153,7 @@ def show_image(raw: bool):
if raw:
return container_data
else:
- return cmd(command)
+ return cmdl(command.split())
def show_network(raw: bool):
command = 'podman network ls'
@@ -161,7 +161,7 @@ def show_network(raw: bool):
if raw:
return container_data
else:
- return cmd(command)
+ return cmdl(command.split())
def restart(name: str):
from vyos.utils.process import rc_cmd
diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py
index 7c9f769f1..ee213d5da 100755
--- a/src/op_mode/dns.py
+++ b/src/op_mode/dns.py
@@ -23,7 +23,7 @@ import vyos.opmode
from tabulate import tabulate
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd, rc_cmd
+from vyos.utils.process import cmdl, rc_cmd
from vyos.template import is_ipv4, is_ipv6
_dynamic_cache_file = r'/run/ddclient/ddclient.cache'
@@ -116,10 +116,10 @@ def _get_dynamic_host_records_formatted(data):
return output
def _get_forwarding_statistics_raw() -> dict:
- command = cmd('rec_control get-all')
+ command = cmdl(['rec_control', 'get-all'])
data = _forwarding_data_to_dict(command)
data['cache-size'] = "{0:.2f} kbytes".format( int(
- cmd('rec_control get cache-bytes')) / 1024 )
+ cmdl(['rec_control', 'get', 'cache-bytes'])) / 1024 )
return data
def _get_forwarding_statistics_formatted(data):
diff --git a/src/op_mode/evpn.py b/src/op_mode/evpn.py
index a6dee0b34..c56881701 100644
--- a/src/op_mode/evpn.py
+++ b/src/op_mode/evpn.py
@@ -21,20 +21,20 @@ import typing
import json
import vyos.opmode
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
def show_evpn(raw: bool, command: typing.Optional[str]):
if raw:
command = f"{command} json"
evpnDict = {}
try:
- evpnDict['evpn'] = json.loads(cmd(f"vtysh -c '{command}'"))
+ evpnDict['evpn'] = json.loads(cmdl(['vtysh', '-c', command]))
except:
raise vyos.opmode.DataUnavailable(f"\"{command.replace(' json', '')}\" is invalid or has no JSON option")
return evpnDict
else:
- return cmd(f"vtysh -c '{command}'")
+ return cmdl(['vtysh', '-c', command])
if __name__ == '__main__':
try:
diff --git a/src/op_mode/file.py b/src/op_mode/file.py
index 8420c5355..a95ec594a 100755
--- a/src/op_mode/file.py
+++ b/src/op_mode/file.py
@@ -29,7 +29,7 @@ from vyos.remote import download
from vyos.remote import upload
from vyos.utils.io import ask_yes_no
from vyos.utils.io import print_error
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
@@ -85,7 +85,7 @@ def zealous_copy(source: str, destination: str) -> None:
os.chown(destination, stats.st_uid, stats.st_gid)
def get_file_type(path: str) -> str:
- return cmd(['file', '-sb', path])
+ return cmdl(['file', '-sb', path])
def print_header(string: str) -> None:
print('#' * 10, string, '#' * 10)
@@ -135,7 +135,7 @@ def print_file_data(path: str) -> None:
print(line, end='')
# All other binaries get hexdumped.
else:
- print(cmd(['hexdump', '-C', path]))
+ print(cmdl(['hexdump', '-C', path]))
def parse_image_path(image_path: str) -> str:
"""
@@ -169,7 +169,7 @@ def show_locally(path: str) -> None:
if os.path.isdir(location):
print_header('DIRECTORY LISTING')
print('Path:\t', location)
- print(cmd(['ls', '-hlFGL', '--group-directories-first', location]))
+ print(cmdl(['ls', '-hlFGL', '--group-directories-first', location]))
elif os.path.isfile(location):
print_file_info(location)
print()
@@ -179,7 +179,7 @@ def show_locally(path: str) -> None:
sys.exit(1)
sys.stdout.flush()
# Call `less(1)` and wait for it to terminate before going forward.
- cmd(['/usr/bin/less', '-X', temp.name], stdout=sys.stdout)
+ cmdl(['/usr/bin/less', '-X', temp.name], stdout=sys.stdout)
# The stream to the temporary file could break for any reason.
# It's much less fragile than if we streamed directly to the process stdin.
# But anything could still happen and we don't want to scare the user.
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index d5cd088e6..164baf911 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -23,7 +23,7 @@ import tabulate
import textwrap
from vyos.config import Config
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.dict import dict_search_args
signal(SIGPIPE, SIG_DFL)
@@ -67,13 +67,13 @@ def get_nftables_details(family, hook, priority):
aux=''
if hook == 'name' or hook == 'ipv6-name':
- command = f'nft list chain {suffix} vyos_filter {name_prefix}{priority}'
+ command = ['nft', 'list', 'chain', suffix, 'vyos_filter', f'{name_prefix}{priority}']
else:
up_hook = hook.upper()
- command = f'nft list chain {suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}'
+ command = ['nft', 'list', 'chain', suffix, 'vyos_filter', f'VYOS_{aux}{up_hook}_{priority}']
try:
- results = cmd(command)
+ results = cmdl(command)
except:
return {}
@@ -105,9 +105,9 @@ def get_nftables_state_details(family):
# no state policy for bridge
return {}
- command = f'nft list chain {suffix} vyos_filter VYOS_STATE_{name_suffix}'
+ command = ['nft', 'list', 'chain', suffix, 'vyos_filter', f'VYOS_STATE_{name_suffix}']
try:
- results = cmd(command)
+ results = cmdl(command)
except:
return {}
@@ -129,7 +129,7 @@ def get_nftables_group_members(family, table, name):
out = []
try:
- results_str = cmd(f'nft -j list set {prefix} {table} {name}')
+ results_str = cmdl(['nft', '-j', 'list', 'set', prefix, table, name])
results = json.loads(results_str)
except:
return out
@@ -157,7 +157,7 @@ def get_nftables_remote_group_members(family, table, name):
out = []
try:
- results_str = cmd(f'nft -j list set {prefix} {table} {name}')
+ results_str = cmdl(['nft', '-j', 'list', 'set', prefix, table, name])
results = json.loads(results_str)
except:
return out
diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py
index 078634610..74a475c1c 100755
--- a/src/op_mode/flow_accounting_op.py
+++ b/src/op_mode/flow_accounting_op.py
@@ -21,7 +21,7 @@ import ipaddress
from tabulate import tabulate
from vyos.utils.kernel import is_module_loaded
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.logger import syslog
from vyos.configquery import ConfigTreeQuery
from vyos import ipt_netflow
@@ -74,7 +74,7 @@ def _netflow_running():
# get list of interfaces
def _get_ifaces_dict():
# run command to get ifaces list
- out = cmd('/bin/ip link show')
+ out = cmdl(['/bin/ip', 'link', 'show'])
# read output
ifaces_out = out.splitlines()
diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py
index 095892791..63163f185 100755
--- a/src/op_mode/format_disk.py
+++ b/src/op_mode/format_disk.py
@@ -22,7 +22,7 @@ from datetime import datetime
from vyos.utils.io import ask_yes_no
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import DEVNULL
from vyos.utils.disk import device_from_id
@@ -66,11 +66,12 @@ def list_partitions(disk: str):
def delete_partition(disk: str, partition_idx: int):
- cmd(f'parted /dev/{disk} rm {partition_idx}')
+ cmdl(['parted', f'/dev/{disk}', 'rm', str(partition_idx)])
def format_disk_like(target: str, proto: str):
- cmd(f'sfdisk -d /dev/{proto} | sfdisk --force /dev/{target}')
+ dump = cmdl(['sfdisk', '-d', f'/dev/{proto}'])
+ cmdl(['sfdisk', '--force', f'/dev/{target}'], input=dump)
if __name__ == '__main__':
diff --git a/src/op_mode/generate_psk.py b/src/op_mode/generate_psk.py
index 816150dbf..b57951122 100644
--- a/src/op_mode/generate_psk.py
+++ b/src/op_mode/generate_psk.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
def validate_hex_size(value):
@@ -42,4 +42,4 @@ if __name__ == '__main__':
)
args = parser.parse_args()
- print(cmd(f'openssl rand -hex {args.hex_size}')) \ No newline at end of file
+ print(cmdl(['openssl', 'rand', '-hex', str(args.hex_size)])) \ No newline at end of file
diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py
index 459157afd..c575db51d 100755
--- a/src/op_mode/generate_ssh_server_key.py
+++ b/src/op_mode/generate_ssh_server_key.py
@@ -14,11 +14,13 @@
# 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 glob
+
from sys import exit
from vyos.defaults import directories
from vyos.utils.io import ask_yes_no
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.commit import commit_in_progress
if not ask_yes_no('Do you really want to remove the existing SSH host keys?'):
@@ -30,6 +32,12 @@ if commit_in_progress():
conf_mode_dir = directories['conf_mode']
-cmd('rm -v /etc/ssh/ssh_host_*')
-cmd('dpkg-reconfigure openssh-server')
-cmd(f'{conf_mode_dir}/service_ssh.py')
+host_key_files = glob.glob('/etc/ssh/ssh_host_*')
+if not host_key_files:
+ # Match cmdl()'s default behavior of raising OSError on a non-zero exit
+ # code (rm would fail with "No such file or directory" if the shell glob
+ # had not expanded to any files)
+ raise OSError('No such file or directory: /etc/ssh/ssh_host_*')
+cmdl(['rm', '-v'] + host_key_files)
+cmdl(['dpkg-reconfigure', 'openssh-server'])
+cmdl([f'{conf_mode_dir}/service_ssh.py'])
diff --git a/src/op_mode/generate_tech-support_archive.py b/src/op_mode/generate_tech-support_archive.py
index d64993b61..2719d231c 100755
--- a/src/op_mode/generate_tech-support_archive.py
+++ b/src/op_mode/generate_tech-support_archive.py
@@ -26,7 +26,7 @@ from tarfile import open as tar_open
from vyos.defaults import directories
from vyos.utils.process import call
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.file import get_name_from_path
from vyos.remote import upload
@@ -60,7 +60,7 @@ def __save_show_report_files(reports_dir: Path):
'--outdir',
str(reports_dir),
]
- output = cmd([script_path] + arguments)
+ output = cmdl([script_path] + arguments)
if output.strip():
print(output)
@@ -74,8 +74,8 @@ def __generate_archived_files(location_path: str) -> None:
"""
# sync/flush journald before archiving /var/log/journal
- cmd(['journalctl', '--sync'])
- cmd(['journalctl', '--flush'])
+ cmdl(['journalctl', '--sync'])
+ cmdl(['journalctl', '--flush'])
def __tar_filter(tarinfo):
# path inside tar, because we set arcname=... below
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 089f83670..5174ff3d2 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -69,7 +69,7 @@ from vyos.utils.io import select_entry
from vyos.utils.file import chmod_2775
from vyos.utils.file import read_file
from vyos.utils.file import write_file
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.utils.process import rc_cmd
from vyos.version import get_version_data
@@ -701,9 +701,10 @@ def download_file(local_file: str, remote_path: str, vrf: str,
download(local_file, remote_path, progressbar=progressbar,
check_space=check_space, raise_error=True)
else:
- vrf_cmd = f'ip vrf exec {vrf} {external_download_script} \
- --local-file {local_file} --remote-path {remote_path}'
- cmd(vrf_cmd, env=environ)
+ cmdl([external_download_script,
+ '--local-file', local_file,
+ '--remote-path', remote_path],
+ env=environ, vrf=vrf)
def image_fetch(image_path: str, vrf: str = None,
no_prompt: bool = False) -> Path:
@@ -811,6 +812,18 @@ def copy_ssh_known_hosts() -> bool:
return known_hosts_files and ask_yes_no(msg, default=True)
+def copy_bash_history() -> bool:
+ """Ask user to copy Bash history
+
+ Returns:
+ bool: user's decision
+ """
+
+ history_files = get_bash_history_files()
+ msg = 'Would you like to copy Bash history?'
+ return bool(history_files) and ask_yes_no(msg, default=False)
+
+
def console_hint() -> str:
pid = getppid() if 'SUDO_USER' in environ else getpid()
try:
@@ -895,7 +908,7 @@ def validate_compatibility(iso_path: str, force: bool = False) -> None:
"""
current_data = get_version_data()
current_flavor = current_data.get('flavor')
- current_architecture = current_data.get('architecture') or cmd('dpkg --print-architecture')
+ current_architecture = current_data.get('architecture') or cmdl(['dpkg', '--print-architecture'])
new_data = get_version_data(f'{iso_path}/version.json')
new_flavor = new_data.get('flavor')
@@ -1115,36 +1128,62 @@ def install_image() -> None:
exit(1)
-def get_known_hosts_files(for_root=True, for_users=True) -> list:
- """Collect all existing `known_hosts` files for root and/or users under /home"""
+def _get_users_relative_paths(
+ relative_paths: list, for_root=True, for_users=True
+) -> list:
+ """
+ Retrieve a list of file paths based on given relative paths, optionally
+ including files from the root directory and user home directories.
+ """
files = []
if for_root:
- base_files = ('/root/.ssh/known_hosts', '/etc/ssh/ssh_known_hosts')
- for file_path in base_files:
- root_known_hosts = Path(file_path)
- if root_known_hosts.exists():
- files.append(root_known_hosts)
+ for relative_path in relative_paths:
+ file_path = Path('/root') / relative_path
+ if file_path.exists():
+ files.append(file_path)
if for_users: # for each non-system user
for user in get_local_users():
home_dir = Path(get_user_home_dir(user))
if home_dir.exists():
- known_hosts = home_dir / '.ssh' / 'known_hosts'
- if known_hosts.exists():
- files.append(known_hosts)
+ for relative_path in relative_paths:
+ file_path = home_dir / relative_path
+ if file_path.exists():
+ files.append(file_path)
+
+ return [file_path.absolute() for file_path in files]
+
+
+def get_known_hosts_files(for_root=True, for_users=True) -> list:
+ """Collect all existing `known_hosts` files for root and/or users under /home"""
+
+ files = _get_users_relative_paths(
+ ['.ssh/known_hosts'],
+ for_root=for_root,
+ for_users=for_users,
+ )
+
+ if for_root:
+ # Add also additional global 'ssh_known_hosts' file
+ root_known_hosts = Path('/etc/ssh/ssh_known_hosts')
+ if root_known_hosts.exists():
+ files.append(root_known_hosts)
return files
+def _mkdir_and_copy_file(source_path: Path, target_path: Path):
+ """Create the target directory if it doesn't exist and copy a file from source to target"""
+
+ target_path.parent.mkdir(parents=True, exist_ok=True)
+ copy(source_path, target_path)
+
+
def migrate_known_hosts(target_dir: str):
"""Copy `known_hosts` for root and all users to the new image directory"""
- def _mkdir_and_copy_file(known_hosts_file, target_known_hosts):
- target_known_hosts.parent.mkdir(parents=True, exist_ok=True)
- copy(known_hosts_file, target_known_hosts)
-
# Copy root only files using default path
known_hosts_files = get_known_hosts_files(for_root=True, for_users=False)
for known_hosts_file in known_hosts_files:
@@ -1164,6 +1203,36 @@ def migrate_known_hosts(target_dir: str):
_mkdir_and_copy_file(known_hosts_file, target_known_hosts)
+def get_bash_history_files(for_root=True, for_users=True) -> list:
+ """Collect all existing `.bash_history` files for root and/or users under /home"""
+
+ return _get_users_relative_paths(
+ ['.bash_history'],
+ for_root=for_root,
+ for_users=for_users,
+ )
+
+
+def migrate_bash_history(target_dir: str):
+ """Copy `.bash_history` for root and all users to the new image directory"""
+
+ # Copy root's history using its default path
+ history_files = get_bash_history_files(for_root=True, for_users=False)
+ for history_file in history_files:
+ target_history = Path(f'{target_dir}{history_file}')
+ _mkdir_and_copy_file(history_file, target_history)
+
+ # Preserve non-root users' bash history the same way known_hosts
+ # is backed up: under /var/.users_backups/{user}, since regular
+ # user home directories are not otherwise migrated.
+ history_files = get_bash_history_files(for_root=False, for_users=True)
+ for history_file in history_files:
+ username = history_file.parent.name
+ base_dir = Path(f'{target_dir}/var/.users_backups/{username}')
+ target_history = base_dir / '.bash_history'
+ _mkdir_and_copy_file(history_file, target_history)
+
+
@compat.grub_cfg_update
def add_image(image_path: str, vrf: str = None, username: str = '',
password: str = '', no_prompt: bool = False, force: bool = False) -> None:
@@ -1289,19 +1358,28 @@ def add_image(image_path: str, vrf: str = None, username: str = '',
chmod_2775(target_config_dir)
Path(f'{target_config_dir}/.vyatta_config').touch()
- target_ssh_dir: str = f'{root_dir}/boot/{image_name}/rw/etc/ssh/'
+ # Default persistence directory
+ target_persist_dir: str = f'{root_dir}/boot/{image_name}/rw'
+
if no_prompt or copy_ssh_host_keys():
print('Copying SSH host keys')
+ target_ssh_dir: str = f'{target_persist_dir}/etc/ssh/'
Path(target_ssh_dir).mkdir(parents=True)
host_keys: list[str] = glob('/etc/ssh/ssh_host*')
for host_key in host_keys:
copy(host_key, target_ssh_dir)
- target_ssh_known_hosts_dir: str = f'{root_dir}/boot/{image_name}/rw'
if no_prompt or copy_ssh_known_hosts():
print('Copying SSH known_hosts files')
+ target_ssh_known_hosts_dir: str = target_persist_dir
migrate_known_hosts(target_ssh_known_hosts_dir)
+ # By default we should disable auto-copy under '--no-prompt' mode
+ if no_prompt is False and copy_bash_history():
+ print('Copying bash history')
+ target_history_dir: str = target_persist_dir
+ migrate_bash_history(target_history_dir)
+
# copy system image and kernel files
print('Copying system image files')
for file in Path(f'{DIR_ISO_MOUNT}/live').iterdir():
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
index 5c6f04476..20cc3f942 100755
--- a/src/op_mode/interfaces.py
+++ b/src/op_mode/interfaces.py
@@ -31,7 +31,7 @@ from vyos.utils.dict import dict_set_nested
from vyos.utils.io import catch_broken_pipe
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import interface_exists
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import rc_cmd
from vyos.utils.process import call
from vyos.configquery import op_mode_config_dict
@@ -100,7 +100,7 @@ def _split_text(text, used=0):
"""
no_tty = call('tty -s')
- returned = cmd('stty size') if not no_tty else ''
+ returned = cmdl(['stty', 'size']) if not no_tty else ''
returned = returned.split()
if len(returned) == 2:
_, columns = tuple(int(_) for _ in returned)
@@ -147,7 +147,7 @@ def _get_counter_val(prev, now):
return value
def _pppoe(ifname):
- out = cmd('ps -C pppd -f')
+ out = cmdl(['ps', '-C', 'pppd', '-f'])
if ifname in out:
return 'C'
if ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]:
@@ -198,14 +198,14 @@ def _get_raw_data(ifname: typing.Optional[str],
res_intf = {}
cache = interface.operational.load_counters()
- out = cmd(f'ip -json addr show {interface.ifname}')
+ out = cmdl(['ip', '-json', 'addr', 'show', interface.ifname])
res_intf_l = json.loads(out)
res_intf = res_intf_l[0]
if res_intf['link_type'] == 'tunnel6':
# Note that 'ip -6 tun show {interface.ifname}' is not json
# aware, so find in list
- out = cmd('ip -json -6 tun show')
+ out = cmdl(['ip', '-json', '-6', 'tun', 'show'])
tunnel = json.loads(out)
res_intf['tunnel6'] = _find_intf_by_ifname(tunnel,
interface.ifname)
@@ -302,15 +302,14 @@ def _get_counter_data(ifname: typing.Optional[str],
def _get_kernel_data(raw, ifname = None, detail = False,
statistics = False):
+ address_show_cmd = ['ip', '-j', '-d', '-s', 'address', 'show']
if ifname:
# Check if the interface exists
if not interface_exists(ifname):
raise vyos.opmode.IncorrectValue(f"{ifname} does not exist!")
- int_name = f'dev {ifname}'
- else:
- int_name = ''
+ address_show_cmd += ['dev', ifname]
- kernel_interface = json.loads(cmd(f'ip -j -d -s address show {int_name}'))
+ kernel_interface = json.loads(cmdl(address_show_cmd))
# Return early if raw
if raw:
@@ -345,7 +344,7 @@ def _format_kernel_data(data, detail, statistics):
if 'parentdev' in interface:
parentdev = interface['parentdev']
if re.match(r'^[0-9a-fA-F]{4}:', parentdev):
- dev_model = cmd(f'lspci -nn -s {parentdev}').split(']:')[1].strip()
+ dev_model = cmdl(['lspci', '-nn', '-s', parentdev]).split(']:')[1].strip()
# Get the IP addresses on interface
ip_list = []
@@ -664,7 +663,7 @@ def show_vlan_to_vni(raw: bool, intf_name: typing.Optional[str],
if not vid:
vid = "all"
- tunnel_data = json.loads(cmd(f"bridge -j vlan tunnelshow dev {intf_name} vid {vid}"))
+ tunnel_data = json.loads(cmdl(['bridge', '-j', 'vlan', 'tunnelshow', 'dev', intf_name, 'vid', vid]))
if not tunnel_data:
if vid == "all":
@@ -672,7 +671,7 @@ def show_vlan_to_vni(raw: bool, intf_name: typing.Optional[str],
else:
raise vyos.opmode.UnconfiguredObject(f"No VLAN-to-VNI mapping found for VLAN {vid}\n")
- statistics_data = json.loads(cmd(f"bridge -j -s vlan tunnelshow dev {intf_name} vid {vid}"))[0]
+ statistics_data = json.loads(cmdl(['bridge', '-j', '-s', 'vlan', 'tunnelshow', 'dev', intf_name, 'vid', vid]))[0]
mapping_config = op_mode_config_dict(['interfaces', 'vxlan', intf_name, 'vlan-to-vni'],
get_first_key=True)
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index b878789a7..9cb576414 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -23,7 +23,7 @@ from tabulate import tabulate
from vyos.utils.convert import convert_data
from vyos.utils.convert import seconds_to_human
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.configquery import ConfigTreeQuery
from vyos.base import Warning
@@ -60,7 +60,7 @@ def _get_output_swanctl_sas_from_list(ra_output_list: list) -> str:
output = ''
for sa_val in ra_output_list:
for sa in sa_val.values():
- swanctl_output: str = cmd(f'sudo swanctl -l --ike-id {sa["uniqueid"]}')
+ swanctl_output: str = cmdl(['swanctl', '-l', '--ike-id', str(sa["uniqueid"])], sudo=True)
output = f'{output}{swanctl_output}\n\n'
return output
diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py
index 0994b5d49..f03d72cf2 100755
--- a/src/op_mode/lldp.py
+++ b/src/op_mode/lldp.py
@@ -22,7 +22,7 @@ import typing
from tabulate import tabulate
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.dict import dict_search
import vyos.opmode
@@ -53,7 +53,7 @@ def _get_raw_data(interface=None, detail=False):
tmp += f' details'
if interface:
tmp += f' ports {interface}'
- output = cmd(tmp)
+ output = cmdl(tmp.split())
data = json.loads(output)
if not data:
return []
@@ -151,7 +151,7 @@ def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Op
tmp = 'lldpcli -f text show neighbors details'
if interface:
tmp += f' ports {interface}'
- return cmd(tmp)
+ return cmdl(tmp.split())
if __name__ == "__main__":
try:
diff --git a/src/op_mode/load-balancing_wan.py b/src/op_mode/load-balancing_wan.py
index 6f1d00dcd..4b9233f0f 100755
--- a/src/op_mode/load-balancing_wan.py
+++ b/src/op_mode/load-balancing_wan.py
@@ -21,7 +21,7 @@ import sys
from datetime import datetime
from vyos.config import Config
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
import vyos.opmode
@@ -87,7 +87,7 @@ def show_summary(raw: bool):
@_verify
def show_connection(raw: bool):
- res = cmd('sudo conntrack -L -n')
+ res = cmdl(['conntrack', '-L', '-n'], sudo=True)
lines = res.split("\n")
filtered_lines = [line for line in lines if re.search(r' mark=[1-9]', line)]
@@ -99,7 +99,7 @@ def show_connection(raw: bool):
@_verify
def show_status(raw: bool):
- res = cmd('sudo nft list chain ip vyos_wanloadbalance wlb_mangle_prerouting')
+ res = cmdl(['nft', 'list', 'chain', 'ip', 'vyos_wanloadbalance', 'wlb_mangle_prerouting'], sudo=True)
lines = res.split("\n")
filtered_lines = [line.replace("\t", "") for line in lines[3:-2] if 'meta mark set' not in line]
diff --git a/src/op_mode/mtr_execute.py b/src/op_mode/mtr_execute.py
index b97e46a1f..a541b3add 100644
--- a/src/op_mode/mtr_execute.py
+++ b/src/op_mode/mtr_execute.py
@@ -23,7 +23,7 @@ from json import loads
from vyos.utils.network import interface_list
from vyos.utils.network import vrf_list
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import call
import vyos.opmode
@@ -199,7 +199,7 @@ def mtr(
command = options[key]['mtr'].format(command=command, value=val)
if json:
- output = cmd(f'{command} {host}')
+ output = cmdl(command.split() + [host])
if for_api:
output = loads(output)
print(output)
diff --git a/src/op_mode/multicast.py b/src/op_mode/multicast.py
index 096d01665..29de9c29e 100755
--- a/src/op_mode/multicast.py
+++ b/src/op_mode/multicast.py
@@ -19,7 +19,7 @@ import sys
import typing
from tabulate import tabulate
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
import vyos.opmode
@@ -32,7 +32,7 @@ def _get_raw_data(family, interface=None):
tmp = f'{tmp} -j maddr show'
if interface:
tmp = f'{tmp} dev {interface}'
- output = cmd(tmp)
+ output = cmdl(tmp.split())
data = json.loads(output)
if not data:
return []
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index f97d7dc0f..434273622 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -25,7 +25,7 @@ from tabulate import tabulate
import vyos.opmode
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.dict import dict_search
ArgDirection = typing.Literal['source', 'destination']
@@ -43,7 +43,7 @@ def _get_xml_translation(direction, family, address=None):
tmp = f'conntrack --dump --family {family} {opt} --output xml'
if address:
tmp += f' --src {address}'
- return cmd(tmp)
+ return cmdl(tmp.split())
def _xml_to_dict(xml):
@@ -67,7 +67,7 @@ def _get_json_data(direction, family):
if direction == 'destination':
chain = 'PREROUTING'
family = 'ip6' if family == 'inet6' else 'ip'
- return cmd(f'nft --json list chain {family} vyos_nat {chain}')
+ return cmdl(['nft', '--json', 'list', 'chain', family, 'vyos_nat', chain])
def _get_raw_data_rules(direction, family):
diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py
index 9bbad94e1..ab784f4b3 100755
--- a/src/op_mode/neighbor.py
+++ b/src/op_mode/neighbor.py
@@ -38,23 +38,18 @@ ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent']
def get_raw_data(family, interface=None, state=None):
from json import loads
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
+ neigh_cmd = ['ip', '--family', family, '--json', 'neighbor', 'list']
if interface:
if not interface_exists(interface):
raise ValueError(f"Interface '{interface}' does not exist in the system")
- interface = f"dev {interface}"
- else:
- interface = ""
+ neigh_cmd += ['dev', interface]
if state:
- state = f"nud {state}"
- else:
- state = ""
-
- neigh_cmd = f"ip --family {family} --json neighbor list {interface} {state}"
+ neigh_cmd += ['nud', state]
- data = loads(cmd(neigh_cmd))
+ data = loads(cmdl(neigh_cmd))
return data
diff --git a/src/op_mode/ntp.py b/src/op_mode/ntp.py
index 42d0c1b00..2125e434c 100644
--- a/src/op_mode/ntp.py
+++ b/src/op_mode/ntp.py
@@ -20,7 +20,7 @@ from itertools import chain
import vyos.opmode
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
def _get_raw_data(command: str) -> dict:
# Returns returns chronyc output as a dictionary
@@ -88,7 +88,7 @@ def _get_raw_data(command: str) -> dict:
# Get -c option command line output, splitlines,
# and save comma-separated values as a flat list
- output = cmd(command).splitlines()
+ output = cmdl(command.split()).splitlines()
values = csv.reader(output)
values = list(chain.from_iterable(values))
@@ -128,7 +128,7 @@ def show_activity(raw: bool):
else:
command = _extend_command_vrf() + command
command += f" activity"
- return cmd(command)
+ return cmdl(command.split())
def show_sources(raw: bool):
_is_configured()
@@ -140,7 +140,7 @@ def show_sources(raw: bool):
else:
command = _extend_command_vrf() + command
command += f" sources -v"
- return cmd(command)
+ return cmdl(command.split())
def show_tracking(raw: bool):
_is_configured()
@@ -152,7 +152,7 @@ def show_tracking(raw: bool):
else:
command = _extend_command_vrf() + command
command += f" tracking"
- return cmd(command)
+ return cmdl(command.split())
def show_sourcestats(raw: bool):
_is_configured()
@@ -164,7 +164,7 @@ def show_sourcestats(raw: bool):
else:
command = _extend_command_vrf() + command
command += f" sourcestats -v"
- return cmd(command)
+ return cmdl(command.split())
if __name__ == '__main__':
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 482dc91af..912840f80 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -50,7 +50,7 @@ from vyos.pki import verify_certificate
from vyos.utils.io import ask_input
from vyos.utils.io import ask_yes_no
from vyos.utils.misc import install_into_config
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
@@ -234,11 +234,11 @@ def install_certificate(
# Show/install conf commands for certificate
prefix = 'ca' if is_ca else 'certificate'
- base = f'pki {prefix} {name}'
+ base = ['pki', prefix, name]
config_paths = []
if cert:
cert_pem = ''.join(encode_certificate(cert).strip().split('\n')[1:-1])
- config_paths.append(f"{base} certificate '{cert_pem}'")
+ config_paths.append(base + ['certificate', cert_pem])
if private_key:
key_pem = ''.join(
@@ -246,9 +246,9 @@ def install_certificate(
.strip()
.split('\n')[1:-1]
)
- config_paths.append(f"{base} private key '{key_pem}'")
+ config_paths.append(base + ['private', 'key', key_pem])
if key_passphrase:
- config_paths.append(f'{base} private password-protected')
+ config_paths.append(base + ['private', 'password-protected'])
install_into_config(conf, config_paths)
@@ -256,13 +256,13 @@ def install_certificate(
def install_crl(ca_name, crl):
# Show/install conf commands for crl
crl_pem = ''.join(encode_certificate(crl).strip().split('\n')[1:-1])
- install_into_config(conf, [f"pki ca {ca_name} crl '{crl_pem}'"])
+ install_into_config(conf, [['pki', 'ca', ca_name, 'crl', crl_pem]])
def install_dh_parameters(name, params):
# Show/install conf commands for dh params
dh_pem = ''.join(encode_dh_parameters(params).strip().split('\n')[1:-1])
- install_into_config(conf, [f"pki dh {name} parameters '{dh_pem}'"])
+ install_into_config(conf, [['pki', 'dh', name, 'parameters', dh_pem]])
def install_ssh_key(name, public_key, private_key, passphrase=None):
@@ -273,10 +273,10 @@ def install_ssh_key(name, public_key, private_key, passphrase=None):
username = os.getlogin()
type_key_split = key_openssh.split(' ')
- base = f'system login user {username} authentication public-keys {name}'
+ base = ['system', 'login', 'user', username, 'authentication', 'public-keys', name]
install_into_config(
conf,
- [f"{base} key '{type_key_split[1]}'", f"{base} type '{type_key_split[0]}'"],
+ [base + ['key', type_key_split[1]], base + ['type', type_key_split[0]]],
)
print(
encode_private_key(
@@ -301,7 +301,7 @@ def install_keypair(
if install_public_key:
install_public_pem = ''.join(public_key_pem.strip().split('\n')[1:-1])
config_paths.append(
- f"pki key-pair {name} public key '{install_public_pem}'"
+ ['pki', 'key-pair', name, 'public', 'key', install_public_pem]
)
else:
print('Public key:')
@@ -316,10 +316,10 @@ def install_keypair(
if install_private_key:
install_private_pem = ''.join(private_key_pem.strip().split('\n')[1:-1])
config_paths.append(
- f"pki key-pair {name} private key '{install_private_pem}'"
+ ['pki', 'key-pair', name, 'private', 'key', install_private_pem]
)
if passphrase:
- config_paths.append(f'pki key-pair {name} private password-protected')
+ config_paths.append(['pki', 'key-pair', name, 'private', 'password-protected'])
else:
print('Private key:')
print(private_key_pem)
@@ -329,8 +329,8 @@ def install_keypair(
def install_openvpn_key(name, key_data, key_version='1'):
config_paths = [
- f"pki openvpn shared-secret {name} key '{key_data}'",
- f"pki openvpn shared-secret {name} version '{key_version}'",
+ ['pki', 'openvpn', 'shared-secret', name, 'key', key_data],
+ ['pki', 'openvpn', 'shared-secret', name, 'version', key_version],
]
install_into_config(conf, config_paths)
@@ -345,7 +345,7 @@ def install_wireguard_key(interface, private_key, public_key):
# Check if we are running in a config session - if yes, we can directly write to the CLI
install_into_config(
- conf, [f"interfaces wireguard {interface} private-key '{private_key}'"]
+ conf, [['interfaces', 'wireguard', interface, 'private-key', private_key]]
)
print(f"Corresponding public-key to use on peer system is: '{public_key}'")
@@ -360,7 +360,7 @@ def install_wireguard_psk(interface, peer, psk):
# Check if we are running in a config session - if yes, we can directly write to the CLI
install_into_config(
- conf, [f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'"]
+ conf, [['interfaces', 'wireguard', interface, 'peer', peer, 'preshared-key', psk]]
)
@@ -883,7 +883,10 @@ def generate_keypair(name, install=False, file=False):
def generate_openvpn_key(name, install=False, file=False):
- result = cmd('openvpn --genkey secret /dev/stdout | grep -o "^[^#]*"')
+ raw_result = cmdl(['openvpn', '--genkey', 'secret', '/dev/stdout'])
+ # equivalent to the old `grep -o "^[^#]*"`, applied per line: keep
+ # everything before the first '#' on each line (comment lines become empty)
+ result = '\n'.join(line.split('#', 1)[0] for line in raw_result.splitlines())
if not result:
print('Failed to generate OpenVPN key')
@@ -911,8 +914,8 @@ def generate_openvpn_key(name, install=False, file=False):
def generate_wireguard_key(interface=None, install=False):
- private_key = cmd('wg genkey')
- public_key = cmd('wg pubkey', input=private_key)
+ private_key = cmdl(['wg', 'genkey'])
+ public_key = cmdl(['wg', 'pubkey'], input=private_key)
if interface and install:
install_wireguard_key(interface, private_key, public_key)
@@ -922,7 +925,7 @@ def generate_wireguard_key(interface=None, install=False):
def generate_wireguard_psk(interface=None, peer=None, install=False):
- psk = cmd('wg genpsk')
+ psk = cmdl(['wg', 'genpsk'])
if interface and peer and install:
install_wireguard_psk(interface, peer, psk)
else:
@@ -1443,11 +1446,11 @@ def renew_certbot(raw: bool, force: typing.Optional[bool] = False):
# a bad time
Warning(f'Directory "{certbot_config}" missing. Reinitializing PKI '
'subsystem...\n\n')
- out = cmd(f'sudo sg vyattacfg -c "{vyos_conf_scripts_dir}/pki.py"')
+ out = cmdl(['sg', 'vyattacfg', '-c', f'{vyos_conf_scripts_dir}/pki.py'], sudo=True)
elif force:
- out = cmd(f'sudo sg vyattacfg -c "{vyos_conf_scripts_dir}/pki.py certbot_renew_force"')
+ out = cmdl(['sg', 'vyattacfg', '-c', f'{vyos_conf_scripts_dir}/pki.py certbot_renew_force'], sudo=True)
else:
- out = cmd(f'sudo sg vyattacfg -c "{vyos_conf_scripts_dir}/pki.py certbot_renew"')
+ out = cmdl(['sg', 'vyattacfg', '-c', f'{vyos_conf_scripts_dir}/pki.py certbot_renew'], sudo=True)
print(out)
diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py
index 966dc80f9..33ab40ecd 100755
--- a/src/op_mode/policy_route.py
+++ b/src/op_mode/policy_route.py
@@ -19,7 +19,7 @@ import re
import tabulate
from vyos.config import Config
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
def get_config_policy(conf, name=None, ipv6=False):
config_path = ['policy']
@@ -33,9 +33,9 @@ def get_config_policy(conf, name=None, ipv6=False):
def get_nftables_details(name, ipv6=False):
suffix = '6' if ipv6 else ''
- command = f'sudo nft list chain ip{suffix} mangle VYOS_PBR{suffix}_{name}'
+ command = ['nft', 'list', 'chain', f'ip{suffix}', 'mangle', f'VYOS_PBR{suffix}_{name}']
try:
- results = cmd(command)
+ results = cmdl(command, sudo=True)
except:
return {}
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index a60c33dc6..0b5eaeacd 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -116,11 +116,11 @@ def check_unsaved_config():
pass
def execute_shutdown(time, reboot=True, ask=True):
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
check_unsaved_config()
- host = cmd("hostname --fqdn")
+ host = cmdl(['hostname', '--fqdn'])
action = "reboot" if reboot else "poweroff"
if not ask:
@@ -133,17 +133,17 @@ def execute_shutdown(time, reboot=True, ask=True):
chk_vyatta_based_reboots()
###
- out = cmd(f'/sbin/shutdown {action_cmd} now', stderr=STDOUT)
+ out = cmdl(['/sbin/shutdown', action_cmd, 'now'], stderr=STDOUT)
print(out.split(",", 1)[0])
return
elif len(time) == 1:
# Assume the argument is just time
ts = parse_time(time[0])
if ts:
- cmd(f'/sbin/shutdown {action_cmd} {time[0]}', stderr=STDOUT)
+ cmdl(['/sbin/shutdown', action_cmd, time[0]], stderr=STDOUT)
# Inform all other logged in users about the reboot/shutdown
wall_msg = f'System {action} is scheduled {time[0]}'
- cmd(f'/usr/bin/wall "{wall_msg}"')
+ cmdl(['/usr/bin/wall', wall_msg])
else:
exit(f'Invalid time "{time[0]}". The valid format is HH:MM')
elif len(time) == 2:
@@ -155,10 +155,10 @@ def execute_shutdown(time, reboot=True, ask=True):
td = t - datetime.now()
t2 = 1 + int(td.total_seconds())//60 # Get total minutes
- cmd(f'/sbin/shutdown {action_cmd} {t2}', stderr=STDOUT)
+ cmdl(['/sbin/shutdown', action_cmd, str(t2)], stderr=STDOUT)
# Inform all other logged in users about the reboot/shutdown
wall_msg = f'System {action} is scheduled {time[1]} {time[0]}'
- cmd(f'/usr/bin/wall "{wall_msg}"')
+ cmdl(['/usr/bin/wall', wall_msg])
else:
if not ts:
exit(f'Invalid time "{time[0]}". Uses 24 Hour Clock format')
diff --git a/src/op_mode/qos.py b/src/op_mode/qos.py
index 47a7a3d59..ae391f2b5 100755
--- a/src/op_mode/qos.py
+++ b/src/op_mode/qos.py
@@ -22,7 +22,7 @@ from tabulate import tabulate
import vyos.opmode
from vyos.configquery import op_mode_config_dict
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.network import interface_exists
def detailed_output(dataset, headers):
@@ -91,8 +91,8 @@ def show_shaper(raw: bool, ifname: typing.Optional[str], classn: typing.Optional
if not policy_name:
continue
- class_data = json.loads(cmd(f"tc -j -s class show dev {i}"))
- qdisc_data = json.loads(cmd(f"tc -j qdisc show dev {i}"))
+ class_data = json.loads(cmdl(['tc', '-j', '-s', 'class', 'show', 'dev', i]))
+ qdisc_data = json.loads(cmdl(['tc', '-j', 'qdisc', 'show', 'dev', i]))
if class_dict:
# Gather qdisc information (e.g. Queue Type)
@@ -224,13 +224,13 @@ def show_cake(raw: bool, ifname: typing.Optional[str]):
if not interface_exists(ifname):
raise vyos.opmode.Error(f"{ifname} does not exist!")
- cake_data = json.loads(cmd(f"tc -j -s qdisc show dev {ifname}"))[0]
+ cake_data = json.loads(cmdl(['tc', '-j', '-s', 'qdisc', 'show', 'dev', ifname]))[0]
if cake_data:
if cake_data.get('kind') == 'cake':
if raw:
return {'qos': {ifname: cake_data}}
else:
- print(cmd(f"tc -s qdisc show dev {ifname}"))
+ print(cmdl(['tc', '-s', 'qdisc', 'show', 'dev', ifname]))
if __name__ == '__main__':
try:
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index b11b0ccc2..30a56f86a 100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
@@ -57,7 +57,7 @@ frr_command_template = Template("""
ArgFamily = typing.Literal['inet', 'inet6']
def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf: typing.Optional[str]):
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
if family == 'inet':
family_cmd = 'ip'
@@ -83,7 +83,8 @@ def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf:
if raw:
from json import loads
- output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd} json'").strip()
+ frr_command = re.sub(r'\s+', ' ', f'show {family_cmd} route {vrf_cmd} summary {table_cmd} json').strip()
+ output = cmdl(['vtysh', '-c', frr_command]).strip()
# If there are no routes in a table, its "JSON" output is an empty string,
# as of FRR 8.4.1
@@ -92,7 +93,8 @@ def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf:
else:
return {}
else:
- output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd}'")
+ frr_command = re.sub(r'\s+', ' ', f'show {family_cmd} route {vrf_cmd} summary {table_cmd}').strip()
+ output = cmdl(['vtysh', '-c', frr_command])
return output
def show(raw: bool,
@@ -119,8 +121,8 @@ def show(raw: bool,
frr_command = frr_command_template.render(kwargs)
frr_command = re.sub(r'\s+', ' ', frr_command)
- from vyos.utils.process import cmd
- output = cmd(f"vtysh -c '{frr_command}'")
+ from vyos.utils.process import cmdl
+ output = cmdl(['vtysh', '-c', frr_command])
if raw:
from json import loads
diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py
index 05e7d0e75..53b94d411 100755
--- a/src/op_mode/show_wwan.py
+++ b/src/op_mode/show_wwan.py
@@ -18,7 +18,7 @@ import argparse
from sys import exit
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
parser = argparse.ArgumentParser()
parser.add_argument("--model", help="Get module model", action="store_true")
@@ -36,7 +36,7 @@ required.add_argument("--interface", help="WWAN interface name, e.g. wwan0", req
def qmi_cmd(device, command, silent=False):
try:
- tmp = cmd(f'qmicli --device={device} --device-open-proxy {command}')
+ tmp = cmdl(['qmicli', f'--device={device}', '--device-open-proxy'] + command.split())
tmp = tmp.replace(f'[{cdc}] ', '')
if not silent:
# skip first line as this only holds the info headline
diff --git a/src/op_mode/ssh.py b/src/op_mode/ssh.py
index a4442c301..b74f37e5e 100755
--- a/src/op_mode/ssh.py
+++ b/src/op_mode/ssh.py
@@ -19,7 +19,7 @@ import json
import sys
import glob
import vyos.opmode
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.configquery import ConfigTreeQuery
from tabulate import tabulate
@@ -35,9 +35,9 @@ def show_fingerprints(raw: bool, ascii: bool):
for keyfile in publickeys:
try:
if ascii:
- keydata = cmd("ssh-keygen -l -v -E sha256 -f " + keyfile).splitlines()
+ keydata = cmdl(['ssh-keygen', '-l', '-v', '-E', 'sha256', '-f', keyfile]).splitlines()
else:
- keydata = cmd("ssh-keygen -l -E sha256 -f " + keyfile).splitlines()
+ keydata = cmdl(['ssh-keygen', '-l', '-E', 'sha256', '-f', keyfile]).splitlines()
type = keydata[0].split(None)[-1].strip("()")
key_size = keydata[0].split(None)[0]
fingerprint = keydata[0].split(None)[1]
@@ -70,12 +70,12 @@ def show_dynamic_protection(raw: bool):
attackers = []
try:
# IPv4
- attackers = attackers + json.loads(cmd("nft -j list set ip sshguard attackers"))["nftables"][1]["set"]["elem"]
+ attackers = attackers + json.loads(cmdl(['nft', '-j', 'list', 'set', 'ip', 'sshguard', 'attackers']))["nftables"][1]["set"]["elem"]
except:
pass
try:
# IPv6
- attackers = attackers + json.loads(cmd("nft -j list set ip6 sshguard attackers"))["nftables"][1]["set"]["elem"]
+ attackers = attackers + json.loads(cmdl(['nft', '-j', 'list', 'set', 'ip6', 'sshguard', 'attackers']))["nftables"][1]["set"]["elem"]
except:
pass
if attackers:
diff --git a/src/op_mode/stp.py b/src/op_mode/stp.py
index c2a897183..457125f12 100755
--- a/src/op_mode/stp.py
+++ b/src/op_mode/stp.py
@@ -20,7 +20,7 @@ import json
from tabulate import tabulate
import vyos.opmode
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.network import interface_exists
def detailed_output(dataset, headers):
@@ -34,7 +34,7 @@ def detailed_output(dataset, headers):
def _get_bridge_vlan_data(iface):
allowed_vlans = []
native_vlan = None
- vlanData = json.loads(cmd(f"bridge -j -d vlan show"))
+ vlanData = json.loads(cmdl(['bridge', '-j', '-d', 'vlan', 'show']))
for vlans in vlanData:
if vlans['ifname'] == iface:
for allowed in vlans['vlans']:
@@ -69,8 +69,8 @@ def _get_stp_data(ifname, brInfo, brStatus):
#tmpInfo['bridge_id'] = brInfo.get('linkinfo').get('info_data').get('bridge_id')
#tmpInfo['root_id'] = brInfo.get('linkinfo').get('info_data').get('root_id')
- tmpInfo['bridge_id'] = cmd(f"cat /sys/class/net/{brInfo.get('ifname')}/bridge/bridge_id").split('.')
- tmpInfo['root_id'] = cmd(f"cat /sys/class/net/{brInfo.get('ifname')}/bridge/root_id").split('.')
+ tmpInfo['bridge_id'] = cmdl(['cat', f"/sys/class/net/{brInfo.get('ifname')}/bridge/bridge_id"]).split('.')
+ tmpInfo['root_id'] = cmdl(['cat', f"/sys/class/net/{brInfo.get('ifname')}/bridge/root_id"]).split('.')
# The "/sys/class/net" structure stores the IDs without separators like ':' or '.'
# This adds a ':' after every 2 characters to make it resemble a MAC Address
@@ -125,12 +125,15 @@ def show_stp(raw: bool, ifname: typing.Optional[str], detail: bool):
else:
ifname = ""
- bridgeInfo = json.loads(cmd(f"ip -j -d -s link show type bridge {ifname}"))
+ ip_link_cmd = ['ip', '-j', '-d', '-s', 'link', 'show', 'type', 'bridge']
+ if ifname:
+ ip_link_cmd.append(ifname)
+ bridgeInfo = json.loads(cmdl(ip_link_cmd))
if not bridgeInfo:
raise vyos.opmode.Error(f"No Bridges configured!")
- bridgeStatus = json.loads(cmd(f"bridge -j -s -d link show"))
+ bridgeStatus = json.loads(cmdl(['bridge', '-j', '-s', '-d', 'link', 'show']))
for bridges in bridgeInfo:
output_list = []
diff --git a/src/op_mode/tech_support.py b/src/op_mode/tech_support.py
index 6055cbf15..bf8bfd24c 100644
--- a/src/op_mode/tech_support.py
+++ b/src/op_mode/tech_support.py
@@ -19,7 +19,7 @@ import json
import vyos.opmode
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.base import Warning
def _get_version_data():
@@ -42,7 +42,7 @@ def _get_cpus():
return get_cpus()
def _get_process_stats():
- return cmd('top --iterations 1 --batch-mode --accum-time-toggle')
+ return cmdl(['top', '--iterations', '1', '--batch-mode', '--accum-time-toggle'])
def _get_storage():
from vyos.utils.disk import get_persistent_storage_stats
@@ -51,10 +51,10 @@ def _get_storage():
def _get_devices():
devices = {}
- devices["pci"] = cmd("lspci")
+ devices["pci"] = cmdl(["lspci"])
try:
- devices["usb"] = cmd("lsusb")
+ devices["usb"] = cmdl(["lsusb"])
except OSError:
Warning("Could not retrieve information about USB devices")
devices["usb"] = {}
@@ -67,7 +67,7 @@ def _get_memory():
return read_file("/proc/meminfo")
def _get_processes():
- res = cmd("ps aux")
+ res = cmdl(["ps", "aux"])
return res
@@ -82,7 +82,7 @@ def _get_interrupts():
def _get_partitions():
# XXX: as of parted 3.5, --json is completely broken
# and cannot be used (outputs malformed JSON syntax)
- res = cmd(f"parted --list")
+ res = cmdl(["parted", "--list"])
return res
@@ -139,13 +139,13 @@ def _get_routes(proto):
data = {}
- summary = cmd(f"vtysh -c 'show {proto} route summary json'")
+ summary = cmdl(["vtysh", "-c", f"show {proto} route summary json"])
summary = loads(summary)
data["summary"] = summary
if summary["routesTotal"] < MAX_ROUTES:
- rib_routes = cmd(f"vtysh -c 'show {proto} route json'")
+ rib_routes = cmdl(["vtysh", "-c", f"show {proto} route json"])
data["routes"] = loads(rib_routes)
if summary["routesTotalFib"] < MAX_ROUTES:
@@ -164,25 +164,25 @@ def _get_ipv6_routes():
def _get_ospfv2():
# XXX: OSPF output when it's not configured is an empty string,
# which is not a valid JSON
- output = cmd("vtysh -c 'show ip ospf json'")
+ output = cmdl(["vtysh", "-c", "show ip ospf json"])
if output:
return json.loads(output)
else:
return {}
def _get_ospfv3():
- output = cmd("vtysh -c 'show ipv6 ospf6 json'")
+ output = cmdl(["vtysh", "-c", "show ipv6 ospf6 json"])
if output:
return json.loads(output)
else:
return {}
def _get_bgp_summary():
- output = cmd("vtysh -c 'show bgp summary json'")
+ output = cmdl(["vtysh", "-c", "show bgp summary json"])
return json.loads(output)
def _get_isis():
- output = cmd("vtysh -c 'show isis summary json'")
+ output = cmdl(["vtysh", "-c", "show isis summary json"])
if output:
return json.loads(output)
else:
@@ -190,31 +190,32 @@ def _get_isis():
def _get_arp_table():
from json import loads
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
- arp_table = cmd("ip --json -4 neighbor show")
+ arp_table = cmdl(["ip", "--json", "-4", "neighbor", "show"])
return loads(arp_table)
def _get_ndp_table():
from json import loads
- arp_table = cmd("ip --json -6 neighbor show")
+ arp_table = cmdl(["ip", "--json", "-6", "neighbor", "show"])
return loads(arp_table)
def _get_nftables_rules():
- nft_rules = cmd("nft list ruleset")
+ nft_rules = cmdl(["nft", "list", "ruleset"])
return nft_rules
def _get_connections():
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
- return cmd("ss -apO")
+ return cmdl(["ss", "-apO"])
def _get_system_packages():
from re import split
- from vyos.utils.process import cmd
+ from vyos.utils.process import cmdl
- dpkg_out = cmd(''' dpkg-query -W -f='${Package} ${Version} ${Architecture} ${db:Status-Abbrev}\n' ''')
+ dpkg_out = cmdl(['dpkg-query', '-W', '-f',
+ '${Package} ${Version} ${Architecture} ${db:Status-Abbrev}\n'])
pkg_lines = split(r'\n+', dpkg_out)
# Discard the header, it's five lines long
diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py
index a13b48866..d125d513a 100755
--- a/src/op_mode/vrf.py
+++ b/src/op_mode/vrf.py
@@ -21,7 +21,7 @@ import typing
from tabulate import tabulate
from vyos.utils.network import get_vrf_members
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
import vyos.opmode
@@ -31,14 +31,14 @@ def _get_raw_data(name=None):
If vrf name is set - get only this name data
If vrf name set and not found - return []
"""
- output = cmd('ip --json --brief link show type vrf')
+ output = cmdl(['ip', '--json', '--brief', 'link', 'show', 'type', 'vrf'])
data = json.loads(output)
if not data:
return []
if name:
is_vrf_exists = True if [vrf for vrf in data if vrf.get('ifname') == name] else False
if is_vrf_exists:
- output = cmd(f'ip --json --brief link show dev {name}')
+ output = cmdl(['ip', '--json', '--brief', 'link', 'show', 'dev', name])
data = json.loads(output)
return data
return []
diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py
index 04d91cc47..40d2e69d5 100755
--- a/src/op_mode/wireguard_client.py
+++ b/src/op_mode/wireguard_client.py
@@ -23,7 +23,7 @@ from ipaddress import ip_interface
from vyos.ifconfig import Section
from vyos.template import is_ipv4
from vyos.template import is_ipv6
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import popen
if os.geteuid() != 0:
@@ -71,8 +71,11 @@ if __name__ == '__main__':
if interface not in Section.interfaces('wireguard'):
exit(f'WireGuard interface "{interface}" does not exist!')
- wg_pubkey = cmd(f'wg show {interface} | grep "public key"').split(':')[-1].lstrip()
- wg_port = cmd(f'wg show {interface} | grep "listening port"').split(':')[-1].lstrip()
+ wg_show_out = cmdl(['wg', 'show', interface])
+ wg_pubkey_line = next((l for l in wg_show_out.splitlines() if 'public key' in l), '')
+ wg_pubkey = wg_pubkey_line.split(':')[-1].lstrip()
+ wg_port_line = next((l for l in wg_show_out.splitlines() if 'listening port' in l), '')
+ wg_port = wg_port_line.split(':')[-1].lstrip()
# Generate WireGuard private key
privkey,_ = popen('wg genkey')
diff --git a/src/services/vyos-domain-resolver b/src/services/vyos-domain-resolver
index 60fcb7df4..07416406e 100755
--- a/src/services/vyos-domain-resolver
+++ b/src/services/vyos-domain-resolver
@@ -27,11 +27,12 @@ from vyos.firewall import fqdn_resolve
from vyos.ifconfig import WireGuardIf
from vyos.remote import download
from vyos.utils.commit import commit_in_progress
+from vyos.utils.convert import human_to_seconds
from vyos.utils.dict import dict_search_args
from vyos.utils.kernel import WIREGUARD_REKEY_AFTER_TIME
from vyos.utils.file import makedir, chmod_775, write_file, read_file
from vyos.utils.network import is_valid_ipv4_address_or_range, is_valid_ipv6_address_or_range
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import run
from vyos.xml_ref import get_defaults
@@ -45,6 +46,7 @@ base_interfaces = ['interfaces']
firewall_config_dir = "/config/firewall"
domain_state = {}
+remote_group_last_update = {}
ipv4_tables = {
'ip vyos_mangle',
@@ -117,7 +119,7 @@ def nft_output(table, set_name, ip_list):
def nft_valid_sets():
try:
valid_sets = []
- sets_json = cmd('nft --json list sets')
+ sets_json = cmdl(['nft', '--json', 'list', 'sets'])
sets_obj = json.loads(sets_json)
for obj in sets_obj['nftables']:
@@ -131,9 +133,16 @@ def nft_valid_sets():
except:
return []
+def remote_group_interval(remote_config):
+ # Per-group update interval, falling back to the global resolver interval
+ if 'interval' in remote_config:
+ return human_to_seconds(remote_config['interval'])
+ return timeout
+
def update_remote_group(config):
conf_lines = []
count = 0
+ now = time.time()
valid_sets = nft_valid_sets()
remote_groups = dict_search_args(config, 'group', 'remote_group')
@@ -146,6 +155,10 @@ def update_remote_group(config):
for set_name, remote_config in remote_groups.items():
if 'url' not in remote_config:
continue
+ interval = remote_group_interval(remote_config)
+ last_update = remote_group_last_update.get(set_name, 0)
+ if now - last_update < interval:
+ continue
nft_ip_set_name = f'R_{set_name}'
nft_ip6_set_name = f'R6_{set_name}'
@@ -157,7 +170,12 @@ def update_remote_group(config):
# Attempt to download file, use cached version if download fails
try:
download(list_file, remote_config['url'], raise_error=True)
+ remote_group_last_update[set_name] = now
except Exception as err:
+ # Retry failed downloads at the resolver cadence instead of
+ # waiting the full group interval
+ retry = min(interval, timeout)
+ remote_group_last_update[set_name] = now - interval + retry
url = urlsplit(remote_config['url'])
_, _, netloc = url.netloc.rpartition('@')
redacted_url = urlunsplit(url._replace(netloc=netloc, query='', fragment=''))
@@ -204,10 +222,11 @@ def update_remote_group(config):
count += 1
- nft_conf_str = "\n".join(conf_lines) + "\n"
- code = run(f'nft --file -', input=nft_conf_str)
+ if count:
+ nft_conf_str = "\n".join(conf_lines) + "\n"
+ code = run(f'nft --file -', input=nft_conf_str)
- logger.info(f'Updated {count} remote-groups in firewall - result: {code}')
+ logger.info(f'Updated {count} remote-groups in firewall - result: {code}')
def update_fqdn(config, node):
@@ -320,9 +339,25 @@ if __name__ == '__main__':
logger.info(f'interval: {timeout}s - cache: {cache}')
+ remote_groups = dict_search_args(firewall, 'group', 'remote_group') or {}
+
+ last_fqdn_update = 0
while True:
- update_fqdn(firewall, 'firewall')
- update_fqdn(nat, 'nat')
+ now = time.time()
+
+ if now - last_fqdn_update >= timeout:
+ last_fqdn_update = now
+ update_fqdn(firewall, 'firewall')
+ update_fqdn(nat, 'nat')
+ update_interfaces(interfaces, 'interfaces')
+
update_remote_group(firewall)
- update_interfaces(interfaces, 'interfaces')
- time.sleep(timeout)
+
+ # Sleep until the next scheduled update
+ next_due = [last_fqdn_update + timeout]
+ for set_name, remote_config in remote_groups.items():
+ if 'url' not in remote_config:
+ continue
+ next_due.append(remote_group_last_update.get(set_name, 0) +
+ remote_group_interval(remote_config))
+ time.sleep(max(min(next_due) - time.time(), 1))
diff --git a/src/services/vyos-netlinkd b/src/services/vyos-netlinkd
index ddc8b1bf3..2cad26070 100755
--- a/src/services/vyos-netlinkd
+++ b/src/services/vyos-netlinkd
@@ -34,7 +34,7 @@ from vyos.ifconfig import Section
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.commit import commit_in_progress2
from vyos.utils.dict import dict_search
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import stop_systemd_unit
@@ -149,7 +149,7 @@ def _handle_dhcp_events(operstate: Optional[str], ifname: str) -> None:
# we will re-start the service and thus cancel the backoff time.
if 'dhcp' in tmp:
syslog.syslog(syslog.LOG_DEBUG, f'Restarting {systemdV4_service}...')
- cmd(f'systemctl restart {systemdV4_service}')
+ cmdl(['systemctl', 'restart', systemdV4_service])
if 'dhcpv6' in tmp:
v6_restart = True
@@ -158,7 +158,7 @@ def _handle_dhcp_events(operstate: Optional[str], ifname: str) -> None:
if v6_restart:
syslog.syslog(syslog.LOG_DEBUG, f'Restarting {systemdV6_service}...')
- cmd(f'systemctl restart {systemdV6_service}')
+ cmdl(['systemctl', 'restart', systemdV6_service])
return None
diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py
index c6c3fa9d0..5f8f3442b 100755
--- a/src/system/keepalived-fifo.py
+++ b/src/system/keepalived-fifo.py
@@ -26,7 +26,7 @@ from queue import Queue
from logging.handlers import SysLogHandler
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.dict import dict_search
from vyos.utils.commit import commit_in_progress
@@ -39,7 +39,7 @@ logger.addHandler(logs_handler_syslog)
logger.setLevel(logging.DEBUG)
mdns_running_file = '/run/mdns_vrrp_active'
-mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns_repeater.py'
+mdns_update_command = '/usr/libexec/vyos/conf_mode/service_mdns_repeater.py'
# class for all operations
class KeepalivedFifo:
@@ -92,7 +92,7 @@ class KeepalivedFifo:
def _run_command(self, command):
logger.debug(f'Running the command: {command}')
try:
- cmd(command)
+ cmdl(command.split())
except OSError as err:
logger.error(f'Unable to execute command "{command}": {err}')
@@ -127,7 +127,7 @@ class KeepalivedFifo:
# check and run commands for VRRP instances
if n_type == 'INSTANCE':
if os.path.exists(mdns_running_file):
- cmd(mdns_update_command)
+ cmdl(mdns_update_command.split(), sudo=True)
tmp = dict_search(f'group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict)
if tmp != None:
@@ -135,7 +135,7 @@ class KeepalivedFifo:
# check and run commands for VRRP sync groups
elif n_type == 'GROUP':
if os.path.exists(mdns_running_file):
- cmd(mdns_update_command)
+ cmdl(mdns_update_command.split(), sudo=True)
tmp = dict_search(f'sync_group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict)
if tmp != None:
diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py
index 8f80365d7..72ed8c084 100644
--- a/src/tests/test_configverify.py
+++ b/src/tests/test_configverify.py
@@ -14,7 +14,7 @@
from unittest import TestCase
from vyos.configverify import verify_diffie_hellman_length
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
dh_file = '/tmp/dh.pem'
@@ -27,5 +27,5 @@ class TestDictSearch(TestCase):
def test_dh_key_512(self):
key_len = '512'
- cmd(f'openssl dhparam -out {dh_file} {key_len}')
+ cmdl(['openssl', 'dhparam', '-out', dh_file, key_len])
self.assertTrue(verify_diffie_hellman_length(dh_file, key_len))
diff --git a/src/udev/vyos_net_name b/src/udev/vyos_net_name
index 568734cb7..f5c3b9f1b 100755
--- a/src/udev/vyos_net_name
+++ b/src/udev/vyos_net_name
@@ -25,7 +25,7 @@ from sys import argv
from vyos.configtree import ConfigTree
from vyos.defaults import directories
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.locking import Lock
from vyos.migrate import ConfigMigrate
@@ -87,7 +87,7 @@ def get_biosdevname(ifname: str) -> str:
time.sleep(1)
try:
- biosname = cmd(f'/sbin/biosdevname --policy all_ethN -i {ifname}')
+ biosname = cmdl(['/sbin/biosdevname', '--policy', 'all_ethN', '-i', ifname])
except Exception as e:
logger.error(f'biosdevname error: {e}')
biosname = ''
diff --git a/src/validators/timezone b/src/validators/timezone
index dd3e0654d..fe8e10cb6 100755
--- a/src/validators/timezone
+++ b/src/validators/timezone
@@ -17,7 +17,7 @@
import argparse
import sys
-from vyos.utils.process import cmd
+from vyos.utils.process import cmdl
if __name__ == '__main__':
@@ -25,7 +25,7 @@ if __name__ == '__main__':
parser.add_argument("--validate", action="store", required=True, help="Check if timezone is valid")
args = parser.parse_args()
- tz_data = cmd('timedatectl list-timezones')
+ tz_data = cmdl(['timedatectl', 'list-timezones'])
tz_data = tz_data.split('\n')
if args.validate not in tz_data: