summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/activation-scripts/20-ethernet_offload.py13
-rwxr-xr-xsrc/conf_mode/container.py4
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py4
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py40
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py78
-rwxr-xr-xsrc/conf_mode/nat66.py28
-rwxr-xr-xsrc/conf_mode/policy.py8
-rw-r--r--src/conf_mode/protocols_openfabric.py145
-rwxr-xr-xsrc/conf_mode/service_dns_forwarding.py20
-rwxr-xr-xsrc/conf_mode/service_ntp.py20
-rwxr-xr-xsrc/conf_mode/system_option.py45
-rwxr-xr-xsrc/conf_mode/system_syslog.py20
-rw-r--r--src/etc/sudoers.d/vyos3
-rwxr-xr-xsrc/op_mode/execute_bandwidth_test.sh (renamed from src/op_mode/monitor_bandwidth_test.sh)0
-rw-r--r--src/op_mode/execute_port-scan.py155
-rw-r--r--src/op_mode/interfaces_wireguard.py53
-rwxr-xr-xsrc/op_mode/restart.py42
-rwxr-xr-xsrc/op_mode/restart_frr.py2
-rwxr-xr-xsrc/op_mode/secure_boot.py50
-rwxr-xr-xsrc/op_mode/version.py9
-rwxr-xr-xsrc/op_mode/vpn_ike_sa.py2
-rwxr-xr-xsrc/services/vyos-configd189
-rwxr-xr-xsrc/services/vyos-http-api-server4
-rw-r--r--src/shim/vyshim.c45
-rw-r--r--src/systemd/podman.service16
-rw-r--r--src/systemd/podman.socket10
-rwxr-xr-xsrc/validators/interface-address2
-rwxr-xr-xsrc/validators/ip-address4
-rwxr-xr-xsrc/validators/ip-cidr4
-rwxr-xr-xsrc/validators/ip-host4
-rwxr-xr-xsrc/validators/ip-prefix4
-rwxr-xr-xsrc/validators/ipv44
-rwxr-xr-xsrc/validators/ipv4-address4
-rwxr-xr-xsrc/validators/ipv4-host4
-rwxr-xr-xsrc/validators/ipv4-multicast4
-rwxr-xr-xsrc/validators/ipv4-prefix4
-rwxr-xr-xsrc/validators/ipv64
-rwxr-xr-xsrc/validators/ipv6-address4
-rwxr-xr-xsrc/validators/ipv6-host4
-rwxr-xr-xsrc/validators/ipv6-multicast4
-rwxr-xr-xsrc/validators/ipv6-prefix4
41 files changed, 806 insertions, 257 deletions
diff --git a/src/activation-scripts/20-ethernet_offload.py b/src/activation-scripts/20-ethernet_offload.py
index 33b0ea469..ca7213512 100755
--- a/src/activation-scripts/20-ethernet_offload.py
+++ b/src/activation-scripts/20-ethernet_offload.py
@@ -17,9 +17,12 @@
# CLI. See https://vyos.dev/T3619#102254 for all the details.
# T3787: Remove deprecated UDP fragmentation offloading option
# T6006: add to activation-scripts: migration-scripts/interfaces/20-to-21
+# T6716: Honor the configured offload settings and don't automatically add
+# them to the config if the kernel has them set (unless its a live boot)
from vyos.ethtool import Ethtool
from vyos.configtree import ConfigTree
+from vyos.system.image import is_live_boot
def activate(config: ConfigTree):
base = ['interfaces', 'ethernet']
@@ -36,7 +39,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_generic_receive_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'gro'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'gro'])
# If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is
@@ -45,7 +48,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_generic_segmentation_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'gso'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'gso'])
# If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is
@@ -54,7 +57,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_large_receive_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'lro'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'lro'])
# If SG is enabled by the Kernel - we reflect this on the CLI. If SG is
@@ -63,7 +66,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_scatter_gather()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'sg'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'sg'])
# If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is
@@ -72,7 +75,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_tcp_segmentation_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'tso'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'tso'])
# Remove deprecated UDP fragmentation offloading option
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index ded370a7a..14387cbbf 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -421,6 +421,10 @@ def generate(container):
'driver': 'host-local'
}
}
+
+ if 'no_name_server' in network_config:
+ tmp['dns_enabled'] = False
+
for prefix in network_config['prefix']:
net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 5e5d5fba1..bbbfb0385 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -25,6 +25,7 @@ from vyos.configdict import is_source_interface
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_dhcpv6
+from vyos.configverify import verify_eapol
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
@@ -73,7 +74,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'bonding']
- ifname, bond = get_interface_dict(conf, base)
+ ifname, bond = get_interface_dict(conf, base, with_pki=True)
# To make our own life easier transfor the list of member interfaces
# into a dictionary - we will use this to add additional information
@@ -196,6 +197,7 @@ def verify(bond):
verify_dhcpv6(bond)
verify_vrf(bond)
verify_mirror_redirect(bond)
+ verify_eapol(bond)
# use common function to verify VLAN configuration
verify_vlan_config(bond)
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index 7b2c1ee0b..637db442a 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -53,20 +53,22 @@ def get_config(config=None):
tmp = node_changed(conf, base + [ifname, 'member', 'interface'])
if tmp:
if 'member' in bridge:
- bridge['member'].update({'interface_remove' : tmp })
+ bridge['member'].update({'interface_remove': {t: {} for t in tmp}})
else:
- bridge.update({'member' : {'interface_remove' : tmp }})
- for interface in tmp:
- # When using VXLAN member interfaces that are configured for Single
- # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
- # re-create VLAN to VNI mappings if required, but only if the interface
- # is already live on the system - this must not be done on first commit
- if interface.startswith('vxlan') and interface_exists(interface):
- set_dependents('vxlan', conf, interface)
- # When using Wireless member interfaces we need to inform hostapd
- # to properly set-up the bridge
- elif interface.startswith('wlan') and interface_exists(interface):
- set_dependents('wlan', conf, interface)
+ bridge.update({'member': {'interface_remove': {t: {} for t in tmp}}})
+ for interface in tmp:
+ # When using VXLAN member interfaces that are configured for Single
+ # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
+ # re-create VLAN to VNI mappings if required, but only if the interface
+ # is already live on the system - this must not be done on first commit
+ if interface.startswith('vxlan') and interface_exists(interface):
+ set_dependents('vxlan', conf, interface)
+ _, vxlan = get_interface_dict(conf, ['interfaces', 'vxlan'], ifname=interface)
+ bridge['member']['interface_remove'].update({interface: vxlan})
+ # When using Wireless member interfaces we need to inform hostapd
+ # to properly set-up the bridge
+ elif interface.startswith('wlan') and interface_exists(interface):
+ set_dependents('wlan', conf, interface)
if dict_search('member.interface', bridge) is not None:
for interface in list(bridge['member']['interface']):
@@ -118,6 +120,16 @@ def get_config(config=None):
return bridge
def verify(bridge):
+ # to delete interface or remove a member interface VXLAN first need to check if
+ # VXLAN does not require to be a member of a bridge interface
+ if dict_search('member.interface_remove', bridge):
+ for iface, iface_config in bridge['member']['interface_remove'].items():
+ if iface.startswith('vxlan') and dict_search('parameters.neighbor_suppress', iface_config) != None:
+ raise ConfigError(
+ f'To detach interface {iface} from bridge you must first '
+ f'disable "neighbor-suppress" parameter in the VXLAN interface {iface}'
+ )
+
if 'deleted' in bridge:
return None
@@ -192,7 +204,7 @@ def apply(bridge):
try:
call_dependents()
except ConfigError:
- raise ConfigError('Error updating member interface configuration after changing bridge!')
+ raise ConfigError(f'Error updating member interface {interface} configuration after changing bridge!')
return None
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index afc48ead8..34ce7bc47 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -31,32 +31,20 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bond_bridge_member
-from vyos.configverify import verify_pki_certificate
-from vyos.configverify import verify_pki_ca_certificate
+from vyos.configverify import verify_eapol
from vyos.ethtool import Ethtool
from vyos.ifconfig import EthernetIf
from vyos.ifconfig import BondIf
-from vyos.pki import find_chain
-from vyos.pki import encode_certificate
-from vyos.pki import load_certificate
-from vyos.pki import wrap_private_key
-from vyos.template import render
from vyos.template import render_to_string
-from vyos.utils.process import call
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_to_paths_values
from vyos.utils.dict import dict_set
from vyos.utils.dict import dict_delete
-from vyos.utils.file import write_file
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
-# XXX: wpa_supplicant works on the source interface
-cfg_dir = '/run/wpa_supplicant'
-wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf'
-
def update_bond_options(conf: Config, eth_conf: dict) -> list:
"""
Return list of blocked options if interface is a bond member
@@ -277,23 +265,6 @@ def verify_allowedbond_changes(ethernet: dict):
f' on interface "{ethernet["ifname"]}".' \
f' Interface is a bond member')
-def verify_eapol(ethernet: dict):
- """
- Common helper function used by interface implementations to perform
- recurring validation of EAPoL configuration.
- """
- if 'eapol' not in ethernet:
- return
-
- if 'certificate' not in ethernet['eapol']:
- raise ConfigError('Certificate must be specified when using EAPoL!')
-
- verify_pki_certificate(ethernet, ethernet['eapol']['certificate'], no_password_protected=True)
-
- if 'ca_certificate' in ethernet['eapol']:
- for ca_cert in ethernet['eapol']['ca_certificate']:
- verify_pki_ca_certificate(ethernet, ca_cert)
-
def verify(ethernet):
if 'deleted' in ethernet:
return None
@@ -346,51 +317,10 @@ def verify_ethernet(ethernet):
verify_vlan_config(ethernet)
return None
-
def generate(ethernet):
- # render real configuration file once
- wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet)
-
if 'deleted' in ethernet:
- # delete configuration on interface removal
- if os.path.isfile(wpa_supplicant_conf):
- os.unlink(wpa_supplicant_conf)
return None
- if 'eapol' in ethernet:
- ifname = ethernet['ifname']
-
- render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet)
-
- cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem')
- cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key')
-
- cert_name = ethernet['eapol']['certificate']
- pki_cert = ethernet['pki']['certificate'][cert_name]
-
- loaded_pki_cert = load_certificate(pki_cert['certificate'])
- loaded_ca_certs = {load_certificate(c['certificate'])
- for c in ethernet['pki']['ca'].values()} if 'ca' in ethernet['pki'] else {}
-
- cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
-
- write_file(cert_file_path,
- '\n'.join(encode_certificate(c) for c in cert_full_chain))
- write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
-
- if 'ca_certificate' in ethernet['eapol']:
- ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem')
- ca_chains = []
-
- for ca_cert_name in ethernet['eapol']['ca_certificate']:
- pki_ca_cert = ethernet['pki']['ca'][ca_cert_name]
- loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
- ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
- ca_chains.append(
- '\n'.join(encode_certificate(c) for c in ca_full_chain))
-
- write_file(ca_cert_file_path, '\n'.join(ca_chains))
-
ethernet['frr_zebra_config'] = ''
if 'deleted' not in ethernet:
ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet)
@@ -399,8 +329,6 @@ def generate(ethernet):
def apply(ethernet):
ifname = ethernet['ifname']
- # take care about EAPoL supplicant daemon
- eapol_action='stop'
e = EthernetIf(ifname)
if 'deleted' in ethernet:
@@ -408,10 +336,6 @@ def apply(ethernet):
e.remove()
else:
e.update(ethernet)
- if 'eapol' in ethernet:
- eapol_action='reload-or-restart'
-
- call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}')
zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index c44320f36..95dfae3a5 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -26,6 +26,7 @@ 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 run
from vyos.template import is_ipv6
from vyos import ConfigError
from vyos import airbag
@@ -48,6 +49,14 @@ def get_config(config=None):
if not conf.exists(base):
nat['deleted'] = ''
+ return nat
+
+ nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # Remove dynamic firewall groups if present:
+ if 'dynamic_group' in nat['firewall_group']:
+ del nat['firewall_group']['dynamic_group']
return nat
@@ -99,22 +108,33 @@ def verify(nat):
if not interface_exists(interface_name):
Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!')
+ if 'destination' in config and 'group' in config['destination']:
+ if len({'address_group', 'network_group', 'domain_group'} & set(config['destination']['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
+
return None
def generate(nat):
if not os.path.exists(nftables_nat66_config):
nat['first_install'] = True
- render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)
+ render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat)
+
+ # dry-run newly generated configuration
+ tmp = run(f'nft --check --file {nftables_nat66_config}')
+ if tmp > 0:
+ raise ConfigError('Configuration file errors encountered!')
+
return None
def apply(nat):
- if not nat:
- return None
-
check_kmod(k_mod)
cmd(f'nft --file {nftables_nat66_config}')
+
+ if not nat or 'deleted' in nat:
+ os.unlink(nftables_nat66_config)
+
call_dependents()
return None
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index 4df893ebf..a5963e72c 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -167,10 +167,10 @@ def verify(policy):
continue
for rule, rule_config in route_map_config['rule'].items():
- # Action 'deny' cannot be used with "continue"
- # FRR does not validate it T4827
- if rule_config['action'] == 'deny' and 'continue' in rule_config:
- raise ConfigError(f'rule {rule} "continue" cannot be used with action deny!')
+ # Action 'deny' cannot be used with "continue" or "on-match"
+ # FRR does not validate it T4827, T6676
+ if rule_config['action'] == 'deny' and ('continue' in rule_config or 'on_match' in rule_config):
+ raise ConfigError(f'rule {rule} "continue" or "on-match" cannot be used with action deny!')
# Specified community-list must exist
tmp = dict_search('match.community.community_list',
diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py
new file mode 100644
index 000000000..8e8c50c06
--- /dev/null
+++ b/src/conf_mode/protocols_openfabric.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from sys import exit
+
+from vyos.base import Warning
+from vyos.config import Config
+from vyos.configdict import node_changed
+from vyos.configverify import verify_interface_exists
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base_path = ['protocols', 'openfabric']
+
+ openfabric = conf.get_config_dict(base_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # Remove per domain MPLS configuration - get a list of all changed Openfabric domains
+ # (removed and added) so that they will be properly rendered for the FRR config.
+ openfabric['domains_all'] = list(conf.list_nodes(' '.join(base_path) + f' domain') +
+ node_changed(conf, base_path + ['domain']))
+
+ # Get a list of all interfaces
+ openfabric['interfaces_all'] = []
+ for domain in openfabric['domains_all']:
+ interfaces_modified = list(node_changed(conf, base_path + ['domain', domain, 'interface']) +
+ conf.list_nodes(' '.join(base_path) + f' domain {domain} interface'))
+ openfabric['interfaces_all'].extend(interfaces_modified)
+
+ if not conf.exists(base_path):
+ openfabric.update({'deleted': ''})
+
+ return openfabric
+
+def verify(openfabric):
+ # bail out early - looks like removal from running config
+ if not openfabric or 'deleted' in openfabric:
+ return None
+
+ if 'net' not in openfabric:
+ raise ConfigError('Network entity is mandatory!')
+
+ # last byte in OpenFabric area address must be 0
+ tmp = openfabric['net'].split('.')
+ if int(tmp[-1]) != 0:
+ raise ConfigError('Last byte of OpenFabric network entity title must always be 0!')
+
+ if 'domain' not in openfabric:
+ raise ConfigError('OpenFabric domain name is mandatory!')
+
+ interfaces_used = []
+
+ for domain, domain_config in openfabric['domain'].items():
+ # If interface not set
+ if 'interface' not in domain_config:
+ raise ConfigError(f'Interface used for routing updates in OpenFabric "{domain}" is mandatory!')
+
+ for iface, iface_config in domain_config['interface'].items():
+ verify_interface_exists(openfabric, iface)
+
+ # interface can be activated only on one OpenFabric instance
+ if iface in interfaces_used:
+ raise ConfigError(f'Interface {iface} is already used in different OpenFabric instance!')
+
+ if 'address_family' not in iface_config or len(iface_config['address_family']) < 1:
+ raise ConfigError(f'Need to specify address family for the interface "{iface}"!')
+
+ # If md5 and plaintext-password set at the same time
+ if 'password' in iface_config:
+ if {'md5', 'plaintext_password'} <= set(iface_config['password']):
+ raise ConfigError(f'Can use either md5 or plaintext-password for password for the interface!')
+
+ if iface == 'lo' and 'passive' not in iface_config:
+ Warning('For loopback interface passive mode is implied!')
+
+ interfaces_used.append(iface)
+
+ # If md5 and plaintext-password set at the same time
+ password = 'domain_password'
+ if password in domain_config:
+ if {'md5', 'plaintext_password'} <= set(domain_config[password]):
+ raise ConfigError(f'Can use either md5 or plaintext-password for domain-password!')
+
+ return None
+
+def generate(openfabric):
+ if not openfabric or 'deleted' in openfabric:
+ return None
+
+ openfabric['frr_fabricd_config'] = render_to_string('frr/fabricd.frr.j2', openfabric)
+ return None
+
+def apply(openfabric):
+ openfabric_daemon = 'fabricd'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ frr_cfg.load_configuration(openfabric_daemon)
+ for domain in openfabric['domains_all']:
+ frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True)
+
+ for interface in openfabric['interfaces_all']:
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
+
+ if 'frr_fabricd_config' in openfabric:
+ frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config'])
+
+ frr_cfg.commit_configuration(openfabric_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/service_dns_forwarding.py b/src/conf_mode/service_dns_forwarding.py
index 70686534f..e3bdbc9f8 100755
--- a/src/conf_mode/service_dns_forwarding.py
+++ b/src/conf_mode/service_dns_forwarding.py
@@ -224,6 +224,18 @@ def get_config(config=None):
dns['authoritative_zones'].append(zone)
+ if 'zone_cache' in dns:
+ # convert refresh interval to sec:
+ for _, zone_conf in dns['zone_cache'].items():
+ if 'options' in zone_conf \
+ and 'refresh' in zone_conf['options']:
+
+ if 'on_reload' in zone_conf['options']['refresh']:
+ interval = 0
+ else:
+ interval = zone_conf['options']['refresh']['interval']
+ zone_conf['options']['refresh']['interval'] = interval
+
return dns
def verify(dns):
@@ -259,8 +271,16 @@ def verify(dns):
if not 'system_name_server' in dns:
print('Warning: No "system name-server" configured')
+ if 'zone_cache' in dns:
+ for name, conf in dns['zone_cache'].items():
+ if ('source' not in conf) \
+ or ('url' in conf['source'] and 'axfr' in conf['source']):
+ raise ConfigError(f'Invalid configuration for zone "{name}": '
+ f'Please select one source type "url" or "axfr".')
+
return None
+
def generate(dns):
# bail out early - looks like removal from running config
if not dns:
diff --git a/src/conf_mode/service_ntp.py b/src/conf_mode/service_ntp.py
index 83880fd72..32563aa0e 100755
--- a/src/conf_mode/service_ntp.py
+++ b/src/conf_mode/service_ntp.py
@@ -17,6 +17,7 @@
import os
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
@@ -42,13 +43,21 @@ def get_config(config=None):
if not conf.exists(base):
return None
- ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, with_defaults=True)
+ ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
ntp['config_file'] = config_file
ntp['user'] = user_group
tmp = is_node_changed(conf, base + ['vrf'])
if tmp: ntp.update({'restart_required': {}})
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(**ntp.kwargs, recursive=True)
+ # Only defined PTP default port, if PTP feature is in use
+ if 'ptp' not in ntp:
+ del default_values['ptp']
+
+ ntp = config_dict_merge(default_values, ntp)
return ntp
def verify(ntp):
@@ -87,6 +96,15 @@ def verify(ntp):
if ipv6_addresses > 1:
raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ')
+ if 'server' in ntp:
+ for host, server in ntp['server'].items():
+ if 'ptp' in server:
+ if 'ptp' not in ntp:
+ raise ConfigError('PTP must be enabled for the NTP service '\
+ f'before it can be used for server "{host}"')
+ else:
+ break
+
return None
def generate(ntp):
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 52d0b7cda..a84572f83 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -19,11 +19,13 @@ import os
from sys import exit
from time import sleep
+
from vyos.config import Config
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
from vyos.system import grub_util
from vyos.template import render
+from vyos.utils.cpu import get_cpus
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.kernel import check_kmod
@@ -35,6 +37,7 @@ from vyos.configdep import set_dependents
from vyos.configdep import call_dependents
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
curlrc_config = r'/etc/curlrc'
@@ -42,10 +45,8 @@ ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
usb_autosuspend = r'/etc/udev/rules.d/40-usb-autosuspend.rules'
kernel_dynamic_debug = r'/sys/kernel/debug/dynamic_debug/control'
-time_format_to_locale = {
- '12-hour': 'en_US.UTF-8',
- '24-hour': 'en_GB.UTF-8'
-}
+time_format_to_locale = {'12-hour': 'en_US.UTF-8', '24-hour': 'en_GB.UTF-8'}
+
def get_config(config=None):
if config:
@@ -53,9 +54,9 @@ def get_config(config=None):
else:
conf = Config()
base = ['system', 'option']
- options = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
+ options = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
+ )
if 'performance' in options:
# Update IPv4/IPv6 and sysctl options after tuned applied it's settings
@@ -64,6 +65,7 @@ def get_config(config=None):
return options
+
def verify(options):
if 'http_client' in options:
config = options['http_client']
@@ -71,7 +73,9 @@ def verify(options):
verify_interface_exists(options, config['source_interface'])
if {'source_address', 'source_interface'} <= set(config):
- raise ConfigError('Can not define both HTTP source-interface and source-address')
+ raise ConfigError(
+ 'Can not define both HTTP source-interface and source-address'
+ )
if 'source_address' in config:
if not is_addr_assigned(config['source_address']):
@@ -92,10 +96,20 @@ def verify(options):
address = config['source_address']
interface = config['source_interface']
if not is_intf_addr_assigned(interface, address):
- raise ConfigError(f'Address "{address}" not assigned on interface "{interface}"!')
+ raise ConfigError(
+ f'Address "{address}" not assigned on interface "{interface}"!'
+ )
+
+ if 'kernel' in options:
+ cpu_vendor = get_cpus()[0]['vendor_id']
+ if 'amd_pstate_driver' in options['kernel'] and cpu_vendor != 'AuthenticAMD':
+ raise ConfigError(
+ f'AMD pstate driver cannot be used with "{cpu_vendor}" CPU!'
+ )
return None
+
def generate(options):
render(curlrc_config, 'system/curlrc.j2', options)
render(ssh_config, 'system/ssh_config.j2', options)
@@ -107,10 +121,16 @@ def generate(options):
cmdline_options.append('mitigations=off')
if 'disable_power_saving' in options['kernel']:
cmdline_options.append('intel_idle.max_cstate=0 processor.max_cstate=1')
+ if 'amd_pstate_driver' in options['kernel']:
+ mode = options['kernel']['amd_pstate_driver']
+ cmdline_options.append(
+ f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}'
+ )
grub_util.update_kernel_cmdline_options(' '.join(cmdline_options))
return None
+
def apply(options):
# System bootup beep
beep_service = 'vyos-beep.service'
@@ -149,7 +169,7 @@ def apply(options):
if 'performance' in options:
cmd('systemctl restart tuned.service')
# wait until daemon has started before sending configuration
- while (not is_systemd_service_running('tuned.service')):
+ while not is_systemd_service_running('tuned.service'):
sleep(0.250)
cmd('tuned-adm profile network-{performance}'.format(**options))
else:
@@ -164,9 +184,9 @@ def apply(options):
# Enable/diable root-partition-auto-resize SystemD service
if 'root_partition_auto_resize' in options:
- cmd('systemctl enable root-partition-auto-resize.service')
+ cmd('systemctl enable root-partition-auto-resize.service')
else:
- cmd('systemctl disable root-partition-auto-resize.service')
+ cmd('systemctl disable root-partition-auto-resize.service')
# Time format 12|24-hour
if 'time_format' in options:
@@ -186,6 +206,7 @@ def apply(options):
else:
write_file(kernel_dynamic_debug, f'module {module} -p')
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py
index 07fbb0734..eb2f02eb3 100755
--- a/src/conf_mode/system_syslog.py
+++ b/src/conf_mode/system_syslog.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2023 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
@@ -52,12 +53,29 @@ def get_config(config=None):
if syslog.from_defaults(['global']):
del syslog['global']
+ if (
+ 'global' in syslog
+ and 'preserve_fqdn' in syslog['global']
+ and conf.exists(['system', 'host-name'])
+ and conf.exists(['system', 'domain-name'])
+ ):
+ hostname = conf.return_value(['system', 'host-name'])
+ domain = conf.return_value(['system', 'domain-name'])
+ fqdn = f'{hostname}.{domain}'
+ syslog['global']['local_host_name'] = fqdn
+
return syslog
def verify(syslog):
if not syslog:
return None
+ if 'host' in syslog:
+ for host, host_options in syslog['host'].items():
+ if 'protocol' in host_options and host_options['protocol'] == 'udp':
+ if 'format' in host_options and 'octet_counted' in host_options['format']:
+ Warning(f'Syslog UDP transport for "{host}" should not use octet-counted format!')
+
verify_vrf(syslog)
def generate(syslog):
diff --git a/src/etc/sudoers.d/vyos b/src/etc/sudoers.d/vyos
index 63a944f41..67d7babc4 100644
--- a/src/etc/sudoers.d/vyos
+++ b/src/etc/sudoers.d/vyos
@@ -57,4 +57,7 @@ Cmnd_Alias KEA_IP6_ROUTES = /sbin/ip -6 route replace *,\
# Allow members of group sudo to execute any command
%sudo ALL=NOPASSWD: ALL
+# Allow any user to query Machine Owner Key status
+%sudo ALL=NOPASSWD: /usr/bin/mokutil
+
_kea ALL=NOPASSWD: KEA_IP6_ROUTES
diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/execute_bandwidth_test.sh
index a6ad0b42c..a6ad0b42c 100755
--- a/src/op_mode/monitor_bandwidth_test.sh
+++ b/src/op_mode/execute_bandwidth_test.sh
diff --git a/src/op_mode/execute_port-scan.py b/src/op_mode/execute_port-scan.py
new file mode 100644
index 000000000..bf17d0379
--- /dev/null
+++ b/src/op_mode/execute_port-scan.py
@@ -0,0 +1,155 @@
+#! /usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from vyos.utils.process import call
+
+
+options = {
+ 'port': {
+ 'cmd': '{command} -p {value}',
+ 'type': '<1-65535> <list>',
+ 'help': 'Scan specified ports.'
+ },
+ 'tcp': {
+ 'cmd': '{command} -sT',
+ 'type': 'noarg',
+ 'help': 'Use TCP scan.'
+ },
+ 'udp': {
+ 'cmd': '{command} -sU',
+ 'type': 'noarg',
+ 'help': 'Use UDP scan.'
+ },
+ 'skip-ping': {
+ 'cmd': '{command} -Pn',
+ 'type': 'noarg',
+ 'help': 'Skip the Nmap discovery stage altogether.'
+ },
+ 'ipv6': {
+ 'cmd': '{command} -6',
+ 'type': 'noarg',
+ 'help': 'Enable IPv6 scanning.'
+ },
+}
+
+nmap = 'sudo /usr/bin/nmap'
+
+
+class List(list):
+ def first(self):
+ return self.pop(0) if self else ''
+
+ def last(self):
+ return self.pop() if self else ''
+
+ def prepend(self, value):
+ self.insert(0, value)
+
+
+def completion_failure(option: str) -> None:
+ """
+ Shows failure message after TAB when option is wrong
+ :param option: failure option
+ :type str:
+ """
+ sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option))
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def expansion_failure(option, completions):
+ reason = 'Ambiguous' if completions else 'Invalid'
+ sys.stderr.write(
+ '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv),
+ option))
+ if completions:
+ sys.stderr.write(' Possible completions:\n ')
+ sys.stderr.write('\n '.join(completions))
+ sys.stderr.write('\n')
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def complete(prefix):
+ return [o for o in options if o.startswith(prefix)]
+
+
+def convert(command, args):
+ while args:
+ shortname = args.first()
+ longnames = complete(shortname)
+ if len(longnames) != 1:
+ expansion_failure(shortname, longnames)
+ longname = longnames[0]
+ if options[longname]['type'] == 'noarg':
+ command = options[longname]['cmd'].format(
+ command=command, value='')
+ elif not args:
+ sys.exit(f'port-scan: missing argument for {longname} option')
+ else:
+ command = options[longname]['cmd'].format(
+ command=command, value=args.first())
+ return command
+
+
+if __name__ == '__main__':
+ args = List(sys.argv[1:])
+ host = args.first()
+
+ if host == '--get-options-nested':
+ args.first() # pop execute
+ args.first() # pop port-scan
+ args.first() # pop host
+ args.first() # pop <host>
+ usedoptionslist = []
+ while args:
+ option = args.first() # pop option
+ matched = complete(option) # get option parameters
+ usedoptionslist.append(option) # list of used options
+ # Select options
+ if not args:
+ # remove from Possible completions used options
+ for o in usedoptionslist:
+ if o in matched:
+ matched.remove(o)
+ if not matched:
+ sys.stdout.write('<nocomps>')
+ else:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+
+ if len(matched) > 1:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+ # If option doesn't have value
+ if matched:
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+ else:
+ # Unexpected option
+ completion_failure(option)
+
+ value = args.first() # pop option's value
+ if not args:
+ matched = complete(option)
+ helplines = options[matched[0]]['type']
+ sys.stdout.write(helplines)
+ sys.exit(0)
+
+ command = convert(nmap, args)
+ call(f'{command} -T4 {host}')
diff --git a/src/op_mode/interfaces_wireguard.py b/src/op_mode/interfaces_wireguard.py
new file mode 100644
index 000000000..627af0579
--- /dev/null
+++ b/src/op_mode/interfaces_wireguard.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import vyos.opmode
+
+from vyos.ifconfig import WireGuardIf
+from vyos.configquery import ConfigTreeQuery
+
+
+def _verify(func):
+ """Decorator checks if WireGuard interface config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ interface = kwargs.get('intf_name')
+ if not config.exists(['interfaces', 'wireguard', interface]):
+ unconf_message = f'WireGuard interface {interface} is not configured'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+
+@_verify
+def show_summary(raw: bool, intf_name: str):
+ intf = WireGuardIf(intf_name, create=False, debug=False)
+ return intf.operational.show_interface()
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index 813d3a2b7..a83c8b9d8 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -25,11 +25,11 @@ from vyos.utils.commit import commit_in_progress
config = ConfigTreeQuery()
service_map = {
- 'dhcp' : {
+ 'dhcp': {
'systemd_service': 'kea-dhcp4-server',
'path': ['service', 'dhcp-server'],
},
- 'dhcpv6' : {
+ 'dhcpv6': {
'systemd_service': 'kea-dhcp6-server',
'path': ['service', 'dhcpv6-server'],
},
@@ -61,24 +61,40 @@ service_map = {
'systemd_service': 'radvd',
'path': ['service', 'router-advert'],
},
- 'snmp' : {
+ 'snmp': {
'systemd_service': 'snmpd',
},
- 'ssh' : {
+ 'ssh': {
'systemd_service': 'ssh',
},
- 'suricata' : {
+ 'suricata': {
'systemd_service': 'suricata',
},
- 'vrrp' : {
+ 'vrrp': {
'systemd_service': 'keepalived',
'path': ['high-availability', 'vrrp'],
},
- 'webproxy' : {
+ 'webproxy': {
'systemd_service': 'squid',
},
}
-services = typing.Literal['dhcp', 'dhcpv6', 'dns_dynamic', 'dns_forwarding', 'igmp_proxy', 'ipsec', 'mdns_repeater', 'reverse_proxy', 'router_advert', 'snmp', 'ssh', 'suricata' 'vrrp', 'webproxy']
+services = typing.Literal[
+ 'dhcp',
+ 'dhcpv6',
+ 'dns_dynamic',
+ 'dns_forwarding',
+ 'igmp_proxy',
+ 'ipsec',
+ 'mdns_repeater',
+ 'reverse_proxy',
+ 'router_advert',
+ 'snmp',
+ 'ssh',
+ 'suricata',
+ 'vrrp',
+ 'webproxy',
+]
+
def _verify(func):
"""Decorator checks if DHCP(v6) config exists"""
@@ -102,13 +118,18 @@ def _verify(func):
# Check if config does not exist
if not config.exists(path):
- raise vyos.opmode.UnconfiguredSubsystem(f'Service {human_name} is not configured!')
+ raise vyos.opmode.UnconfiguredSubsystem(
+ f'Service {human_name} is not configured!'
+ )
if config.exists(path + ['disable']):
- raise vyos.opmode.UnconfiguredSubsystem(f'Service {human_name} is disabled!')
+ raise vyos.opmode.UnconfiguredSubsystem(
+ f'Service {human_name} is disabled!'
+ )
return func(*args, **kwargs)
return _wrapper
+
@_verify
def restart_service(raw: bool, name: services, vrf: typing.Optional[str]):
systemd_service = service_map[name]['systemd_service']
@@ -117,6 +138,7 @@ def restart_service(raw: bool, name: services, vrf: typing.Optional[str]):
else:
call(f'systemctl restart "{systemd_service}.service"')
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index 8841b0eca..83146f5ec 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -139,7 +139,7 @@ def _reload_config(daemon):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd', 'fabricd'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/secure_boot.py b/src/op_mode/secure_boot.py
new file mode 100755
index 000000000..5f6390a15
--- /dev/null
+++ b/src/op_mode/secure_boot.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import vyos.opmode
+
+from vyos.utils.boot import is_uefi_system
+from vyos.utils.system import get_secure_boot_state
+
+def _get_raw_data(name=None):
+ sb_data = {
+ 'state' : get_secure_boot_state(),
+ 'uefi' : is_uefi_system()
+ }
+ return sb_data
+
+def _get_formatted_output(raw_data):
+ if not raw_data['uefi']:
+ print('System run in legacy BIOS mode!')
+ state = 'enabled' if raw_data['state'] else 'disabled'
+ return f'SecureBoot {state}'
+
+def show(raw: bool):
+ sb_data = _get_raw_data()
+ if raw:
+ return sb_data
+ else:
+ return _get_formatted_output(sb_data)
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/version.py b/src/op_mode/version.py
index 09d69ad1d..71a40dd50 100755
--- a/src/op_mode/version.py
+++ b/src/op_mode/version.py
@@ -25,6 +25,9 @@ import vyos.opmode
import vyos.version
import vyos.limericks
+from vyos.utils.boot import is_uefi_system
+from vyos.utils.system import get_secure_boot_state
+
from jinja2 import Template
version_output_tmpl = """
@@ -43,6 +46,7 @@ Build comment: {{build_comment}}
Architecture: {{system_arch}}
Boot via: {{boot_via}}
System type: {{system_type}}
+Secure Boot: {{secure_boot}}
Hardware vendor: {{hardware_vendor}}
Hardware model: {{hardware_model}}
@@ -57,6 +61,11 @@ Copyright: VyOS maintainers and contributors
def _get_raw_data(funny=False):
version_data = vyos.version.get_full_version_data()
+ version_data["secure_boot"] = "n/a (BIOS)"
+ if is_uefi_system():
+ version_data["secure_boot"] = "disabled"
+ if get_secure_boot_state():
+ version_data["secure_boot"] = "enabled"
if funny:
version_data["limerick"] = vyos.limericks.get_random()
diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py
index 5e2aaae6b..9385bcd0c 100755
--- a/src/op_mode/vpn_ike_sa.py
+++ b/src/op_mode/vpn_ike_sa.py
@@ -38,6 +38,8 @@ def ike_sa(peer, nat):
peers = []
for conn in sas:
for name, sa in conn.items():
+ if peer and s(sa['remote-host']) != peer:
+ continue
if name.startswith('peer_') and name in peers:
continue
if nat and 'nat-local' not in sa:
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 3674d9627..cb23642dc 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# pylint: disable=redefined-outer-name
+
import os
import sys
import grp
@@ -22,9 +24,12 @@ import json
import typing
import logging
import signal
+import traceback
import importlib.util
+import io
+from contextlib import redirect_stdout
+
import zmq
-from contextlib import contextmanager
from vyos.defaults import directories
from vyos.utils.boot import boot_configuration_complete
@@ -49,7 +54,8 @@ if debug:
else:
logger.setLevel(logging.INFO)
-SOCKET_PATH = "ipc:///run/vyos-configd.sock"
+SOCKET_PATH = 'ipc:///run/vyos-configd.sock'
+MAX_MSG_SIZE = 65535
# Response error codes
R_SUCCESS = 1
@@ -64,9 +70,6 @@ configd_env_unset_file = os.path.join(directories['data'], 'vyos-configd-env-uns
# sourced on entering config session
configd_env_file = '/etc/default/vyos-configd-env'
-session_out = None
-session_mode = None
-
def key_name_from_file_name(f):
return os.path.splitext(f)[0]
@@ -76,17 +79,19 @@ def module_name_from_key(k):
def path_from_file_name(f):
return os.path.join(vyos_conf_scripts_dir, f)
+
# opt-in to be run by daemon
with open(configd_include_file) as f:
try:
include = json.load(f)
except OSError as e:
- logger.critical(f"configd include file error: {e}")
+ logger.critical(f'configd include file error: {e}')
sys.exit(1)
except json.JSONDecodeError as e:
- logger.critical(f"JSON load error: {e}")
+ logger.critical(f'JSON load error: {e}')
sys.exit(1)
+
# import conf_mode scripts
(_, _, filenames) = next(iter(os.walk(vyos_conf_scripts_dir)))
filenames.sort()
@@ -110,31 +115,17 @@ conf_mode_scripts = dict(zip(imports, modules))
exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include}
include_set = {key_name_from_file_name(f) for f in filenames if f in include}
-@contextmanager
-def stdout_redirected(filename, mode):
- saved_stdout_fd = None
- destination_file = None
- try:
- sys.stdout.flush()
- saved_stdout_fd = os.dup(sys.stdout.fileno())
- destination_file = open(filename, mode)
- os.dup2(destination_file.fileno(), sys.stdout.fileno())
- yield
- finally:
- if saved_stdout_fd is not None:
- os.dup2(saved_stdout_fd, sys.stdout.fileno())
- os.close(saved_stdout_fd)
- if destination_file is not None:
- destination_file.close()
-
-def explicit_print(path, mode, msg):
- try:
- with open(path, mode) as f:
- f.write(f"\n{msg}\n\n")
- except OSError:
- logger.critical("error explicit_print")
-def run_script(script_name, config, args) -> int:
+def write_stdout_log(file_name, msg):
+ if boot_configuration_complete():
+ return
+ with open(file_name, 'a') as f:
+ f.write(msg)
+
+
+def run_script(script_name, config, args) -> tuple[int, str]:
+ # pylint: disable=broad-exception-caught
+
script = conf_mode_scripts[script_name]
script.argv = args
config.set_level([])
@@ -145,64 +136,54 @@ def run_script(script_name, config, args) -> int:
script.apply(c)
except ConfigError as e:
logger.error(e)
- explicit_print(session_out, session_mode, str(e))
- return R_ERROR_COMMIT
- except Exception as e:
- logger.critical(e)
- return R_ERROR_DAEMON
+ return R_ERROR_COMMIT, str(e)
+ except Exception:
+ tb = traceback.format_exc()
+ logger.error(tb)
+ return R_ERROR_COMMIT, tb
+
+ return R_SUCCESS, ''
- return R_SUCCESS
def initialization(socket):
- global session_out
- global session_mode
+ # pylint: disable=broad-exception-caught,too-many-locals
+
# Reset config strings:
active_string = ''
session_string = ''
# check first for resent init msg, in case of client timeout
while True:
- msg = socket.recv().decode("utf-8", "ignore")
+ msg = socket.recv().decode('utf-8', 'ignore')
try:
message = json.loads(msg)
- if message["type"] == "init":
- resp = "init"
+ if message['type'] == 'init':
+ resp = 'init'
socket.send(resp.encode())
- except:
+ except Exception:
break
# zmq synchronous for ipc from single client:
active_string = msg
- resp = "active"
+ resp = 'active'
socket.send(resp.encode())
- session_string = socket.recv().decode("utf-8", "ignore")
- resp = "session"
+ session_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'session'
socket.send(resp.encode())
- pid_string = socket.recv().decode("utf-8", "ignore")
- resp = "pid"
+ pid_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'pid'
socket.send(resp.encode())
- sudo_user_string = socket.recv().decode("utf-8", "ignore")
- resp = "sudo_user"
+ sudo_user_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'sudo_user'
socket.send(resp.encode())
- temp_config_dir_string = socket.recv().decode("utf-8", "ignore")
- resp = "temp_config_dir"
+ temp_config_dir_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'temp_config_dir'
socket.send(resp.encode())
- changes_only_dir_string = socket.recv().decode("utf-8", "ignore")
- resp = "changes_only_dir"
+ changes_only_dir_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'changes_only_dir'
socket.send(resp.encode())
- logger.debug(f"config session pid is {pid_string}")
- logger.debug(f"config session sudo_user is {sudo_user_string}")
-
- try:
- session_out = os.readlink(f"/proc/{pid_string}/fd/1")
- session_mode = 'w'
- except FileNotFoundError:
- session_out = None
-
- # if not a 'live' session, for example on boot, write to file
- if not session_out or not boot_configuration_complete():
- session_out = script_stdout_log
- session_mode = 'a'
+ logger.debug(f'config session pid is {pid_string}')
+ logger.debug(f'config session sudo_user is {sudo_user_string}')
os.environ['SUDO_USER'] = sudo_user_string
if temp_config_dir_string:
@@ -229,10 +210,12 @@ def initialization(socket):
return config
-def process_node_data(config, data, last: bool = False) -> int:
+
+def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
if not config:
- logger.critical(f"Empty config")
- return R_ERROR_DAEMON
+ out = 'Empty config'
+ logger.critical(out)
+ return R_ERROR_DAEMON, out
script_name = None
os.environ['VYOS_TAGNODE_VALUE'] = ''
@@ -246,8 +229,9 @@ def process_node_data(config, data, last: bool = False) -> int:
if res.group(2):
script_name = res.group(2)
if not script_name:
- logger.critical(f"Missing script_name")
- return R_ERROR_DAEMON
+ out = 'Missing script_name'
+ logger.critical(out)
+ return R_ERROR_DAEMON, out
if res.group(3):
args = res.group(3).split()
args.insert(0, f'{script_name}.py')
@@ -259,26 +243,55 @@ def process_node_data(config, data, last: bool = False) -> int:
scripts_called.append(script_record)
if script_name not in include_set:
- return R_PASS
+ return R_PASS, ''
+
+ with redirect_stdout(io.StringIO()) as o:
+ result, err_out = run_script(script_name, config, args)
+ amb_out = o.getvalue()
+ o.close()
+
+ out = amb_out + err_out
+
+ return result, out
+
- with stdout_redirected(session_out, session_mode):
- result = run_script(script_name, config, args)
+def send_result(sock, err, msg):
+ msg_size = min(MAX_MSG_SIZE, len(msg)) if msg else 0
+
+ err_rep = err.to_bytes(1, byteorder=sys.byteorder)
+ logger.debug(f'Sending reply: {err}')
+ sock.send(err_rep)
+
+ # size req from vyshim client
+ size_req = sock.recv().decode()
+ logger.debug(f'Received request: {size_req}')
+ msg_size_rep = hex(msg_size).encode()
+ sock.send(msg_size_rep)
+ logger.debug(f'Sending reply: {msg_size}')
+
+ if msg_size > 0:
+ # send req is sent from vyshim client only if msg_size > 0
+ send_req = sock.recv().decode()
+ logger.debug(f'Received request: {send_req}')
+ sock.send(msg.encode())
+ logger.debug('Sending reply with output')
+
+ write_stdout_log(script_stdout_log, msg)
- return result
def remove_if_file(f: str):
try:
os.remove(f)
except FileNotFoundError:
pass
- except OSError:
- raise
+
def shutdown():
remove_if_file(configd_env_file)
os.symlink(configd_env_unset_file, configd_env_file)
sys.exit(0)
+
if __name__ == '__main__':
context = zmq.Context()
socket = context.socket(zmq.REP)
@@ -294,6 +307,7 @@ if __name__ == '__main__':
os.environ['VYOS_CONFIGD'] = 't'
def sig_handler(signum, frame):
+ # pylint: disable=unused-argument
shutdown()
signal.signal(signal.SIGTERM, sig_handler)
@@ -308,20 +322,19 @@ if __name__ == '__main__':
while True:
# Wait for next request from client
msg = socket.recv().decode()
- logger.debug(f"Received message: {msg}")
+ logger.debug(f'Received message: {msg}')
message = json.loads(msg)
- if message["type"] == "init":
- resp = "init"
+ if message['type'] == 'init':
+ resp = 'init'
socket.send(resp.encode())
config = initialization(socket)
- elif message["type"] == "node":
- res = process_node_data(config, message["data"], message["last"])
- response = res.to_bytes(1, byteorder=sys.byteorder)
- logger.debug(f"Sending response {res}")
- socket.send(response)
- if message["last"] and config:
+ elif message['type'] == 'node':
+ res, out = process_node_data(config, message['data'], message['last'])
+ send_result(socket, res, out)
+
+ if message['last'] and config:
scripts_called = getattr(config, 'scripts_called', [])
logger.debug(f'scripts_called: {scripts_called}')
else:
- logger.critical(f"Unexpected message: {message}")
+ logger.critical(f'Unexpected message: {message}')
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 97633577d..91100410c 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -577,7 +577,9 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
background_tasks.add_task(call_commit, session)
msg = self_ref_msg
else:
- session.commit()
+ # capture non-fatal warnings
+ out = session.commit()
+ msg = out if out else msg
logger.info(f"Configuration modified via HTTP API using key '{app.state.vyos_id}'")
except ConfigSessionError as e:
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index a78f62a7b..68e6c4015 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -67,6 +67,8 @@ void timer_handler(int);
double get_posix_clock_time(void);
+static char * s_recv_string (void *, int);
+
int main(int argc, char* argv[])
{
// string for node data: conf_mode script and tagnode, if applicable
@@ -119,31 +121,44 @@ int main(int argc, char* argv[])
zmq_recv(requester, error_code, 1, 0);
debug_print("Received node data receipt\n");
- int err = (int)error_code[0];
+ char msg_size_str[7];
+ zmq_send(requester, "msg_size", 8, 0);
+ zmq_recv(requester, msg_size_str, 6, 0);
+ msg_size_str[6] = '\0';
+ int msg_size = (int)strtol(msg_size_str, NULL, 16);
+ debug_print("msg_size: %d\n", msg_size);
+
+ if (msg_size > 0) {
+ zmq_send(requester, "send", 4, 0);
+ char *msg = s_recv_string(requester, msg_size);
+ printf("%s", msg);
+ free(msg);
+ }
free(string_node_data_msg);
- zmq_close(requester);
- zmq_ctx_destroy(context);
+ int err = (int)error_code[0];
+ int ret = 0;
if (err & PASS) {
debug_print("Received PASS\n");
- int ret = pass_through(argv, ex_index);
- return ret;
+ ret = pass_through(argv, ex_index);
}
if (err & ERROR_DAEMON) {
debug_print("Received ERROR_DAEMON\n");
- int ret = pass_through(argv, ex_index);
- return ret;
+ ret = pass_through(argv, ex_index);
}
if (err & ERROR_COMMIT) {
debug_print("Received ERROR_COMMIT\n");
- return -1;
+ ret = -1;
}
- return 0;
+ zmq_close(requester);
+ zmq_ctx_destroy(context);
+
+ return ret;
}
int initialization(void* Requester)
@@ -342,3 +357,15 @@ double get_posix_clock_time(void)
double get_posix_clock_time(void)
{return (double)0;}
#endif
+
+// Receive string from socket and convert into C string
+static char * s_recv_string (void *socket, int bufsize) {
+ char * buffer = (char *)malloc(bufsize+1);
+ int size = zmq_recv(socket, buffer, bufsize, 0);
+ if (size == -1)
+ return NULL;
+ if (size > bufsize)
+ size = bufsize;
+ buffer[size] = '\0';
+ return buffer;
+}
diff --git a/src/systemd/podman.service b/src/systemd/podman.service
new file mode 100644
index 000000000..20a16304b
--- /dev/null
+++ b/src/systemd/podman.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Podman API Service
+Requires=podman.socket
+After=podman.socket
+Documentation=man:podman-system-service(1)
+StartLimitIntervalSec=0
+
+[Service]
+Delegate=true
+Type=exec
+KillMode=process
+Environment=LOGGING="--log-level=info"
+ExecStart=/usr/bin/podman $LOGGING system service
+
+[Install]
+WantedBy=default.target
diff --git a/src/systemd/podman.socket b/src/systemd/podman.socket
new file mode 100644
index 000000000..397058ee4
--- /dev/null
+++ b/src/systemd/podman.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=Podman API Socket
+Documentation=man:podman-system-service(1)
+
+[Socket]
+ListenStream=%t/podman/podman.sock
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/validators/interface-address b/src/validators/interface-address
index 4c203956b..2a2583fc3 100755
--- a/src/validators/interface-address
+++ b/src/validators/interface-address
@@ -1,3 +1,3 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-host $1 || ipaddrcheck --is-ipv6-host $1
+ipaddrcheck --is-any-host "$1"
diff --git a/src/validators/ip-address b/src/validators/ip-address
index 11d6df09e..351f728a6 100755
--- a/src/validators/ip-address
+++ b/src/validators/ip-address
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-single $1
+ipaddrcheck --is-any-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ip-cidr b/src/validators/ip-cidr
index 60d2ac295..8a01e7ad9 100755
--- a/src/validators/ip-cidr
+++ b/src/validators/ip-cidr
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-cidr $1
+ipaddrcheck --is-any-cidr "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP CIDR"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ip-host b/src/validators/ip-host
index 77c578fa2..7c5ad2612 100755
--- a/src/validators/ip-host
+++ b/src/validators/ip-host
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-host $1
+ipaddrcheck --is-any-host "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP host"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ip-prefix b/src/validators/ip-prefix
index e5a64fea8..25204ace5 100755
--- a/src/validators/ip-prefix
+++ b/src/validators/ip-prefix
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-net $1
+ipaddrcheck --is-any-net "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP prefix"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4 b/src/validators/ipv4
index 8676d5800..11f854cf1 100755
--- a/src/validators/ipv4
+++ b/src/validators/ipv4
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4 $1
+ipaddrcheck --is-ipv4 "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not IPv4"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-address b/src/validators/ipv4-address
index 058db088b..1cfd961ba 100755
--- a/src/validators/ipv4-address
+++ b/src/validators/ipv4-address
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-single $1
+ipaddrcheck --is-ipv4-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-host b/src/validators/ipv4-host
index 74b8c36a7..eb8faaa2a 100755
--- a/src/validators/ipv4-host
+++ b/src/validators/ipv4-host
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-host $1
+ipaddrcheck --is-ipv4-host "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 host"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast
index 3f28c51db..cf871bd59 100755
--- a/src/validators/ipv4-multicast
+++ b/src/validators/ipv4-multicast
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1
+ipaddrcheck --is-ipv4-multicast "$1" && ipaddrcheck --is-ipv4-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 multicast address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-prefix b/src/validators/ipv4-prefix
index 7e1e0e8dd..f8d46c69c 100755
--- a/src/validators/ipv4-prefix
+++ b/src/validators/ipv4-prefix
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-net $1
+ipaddrcheck --is-ipv4-net "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 prefix"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6 b/src/validators/ipv6
index 4ae130eb5..57696add7 100755
--- a/src/validators/ipv6
+++ b/src/validators/ipv6
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6 $1
+ipaddrcheck --is-ipv6 "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not IPv6"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-address b/src/validators/ipv6-address
index 1fca77668..460639090 100755
--- a/src/validators/ipv6-address
+++ b/src/validators/ipv6-address
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-single $1
+ipaddrcheck --is-ipv6-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-host b/src/validators/ipv6-host
index 7085809a9..1eb4d8e35 100755
--- a/src/validators/ipv6-host
+++ b/src/validators/ipv6-host
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-host $1
+ipaddrcheck --is-ipv6-host "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 host"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast
index 5aa7d734a..746ff7edf 100755
--- a/src/validators/ipv6-multicast
+++ b/src/validators/ipv6-multicast
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1
+ipaddrcheck --is-ipv6-multicast "$1" && ipaddrcheck --is-ipv6-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 multicast address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-prefix b/src/validators/ipv6-prefix
index 890dda723..1bb9b42fe 100755
--- a/src/validators/ipv6-prefix
+++ b/src/validators/ipv6-prefix
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-net $1
+ipaddrcheck --is-ipv6-net "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 prefix"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0