diff options
-rw-r--r-- | data/templates/accel-ppp/ipoe.config.j2 | 4 | ||||
-rw-r--r-- | data/templates/frr/igmp.frr.j2 | 6 | ||||
-rw-r--r-- | data/templates/wifi/hostapd.conf.j2 | 14 | ||||
-rw-r--r-- | debian/vyos-1x.postinst | 8 | ||||
-rw-r--r-- | interface-definitions/container.xml.in | 34 | ||||
-rw-r--r-- | interface-definitions/include/interface/eapol.xml.i | 2 | ||||
-rw-r--r-- | op-mode-definitions/firewall.xml.in | 1 | ||||
-rw-r--r-- | op-mode-definitions/monitor-command.xml.in | 28 | ||||
-rw-r--r-- | op-mode-definitions/show-hardware.xml.in | 2 | ||||
-rw-r--r-- | python/vyos/configverify.py | 13 | ||||
-rw-r--r-- | python/vyos/utils/network.py | 31 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_ethernet.py | 9 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_wireless.py | 3 | ||||
-rwxr-xr-x | src/conf_mode/container.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/high-availability.py | 16 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-ethernet.py | 13 | ||||
-rwxr-xr-x | src/conf_mode/protocols_igmp.py | 2 | ||||
-rwxr-xr-x | src/op_mode/dhcp.py | 4 | ||||
-rwxr-xr-x | src/op_mode/firewall.py | 136 | ||||
-rwxr-xr-x | src/op_mode/vyos-op-cmd-wrapper.sh | 6 |
20 files changed, 283 insertions, 60 deletions
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2 index add3dc7e4..f59428509 100644 --- a/data/templates/accel-ppp/ipoe.config.j2 +++ b/data/templates/accel-ppp/ipoe.config.j2 @@ -36,7 +36,9 @@ verbose=1 {% set shared = 'shared=0,' %} {% endif %} {% set range = 'range=' ~ iface_config.client_subnet ~ ',' if iface_config.client_subnet is vyos_defined else '' %} -{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1 +{% set relay = ',' ~ 'relay=' ~ iface_config.external_dhcp.dhcp_relay if iface_config.external_dhcp.dhcp_relay is vyos_defined else '' %} +{% set giaddr = ',' ~ 'giaddr=' ~ iface_config.external_dhcp.giaddr if iface_config.external_dhcp.giaddr is vyos_defined else '' %} +{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1{{ relay }}{{ giaddr }} {% if iface_config.vlan is vyos_defined %} vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }} {% endif %} diff --git a/data/templates/frr/igmp.frr.j2 b/data/templates/frr/igmp.frr.j2 index ce1f8fdda..b75884484 100644 --- a/data/templates/frr/igmp.frr.j2 +++ b/data/templates/frr/igmp.frr.j2 @@ -27,9 +27,9 @@ interface {{ interface }} {% if interface_config.query_max_resp_time %} ip igmp query-max-response-time {{ interface_config.query_max_resp_time }} {% endif %} -{% for group in interface_config.gr_join %} -{% if ifaces[iface].gr_join[group] %} -{% for source in ifaces[iface].gr_join[group] %} +{% for group, sources in interface_config.gr_join.items() %} +{% if sources is vyos_defined %} +{% for source in sources %} ip igmp join {{ group }} {{ source }} {% endfor %} {% else %} diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2 index 613038597..c3f32da72 100644 --- a/data/templates/wifi/hostapd.conf.j2 +++ b/data/templates/wifi/hostapd.conf.j2 @@ -340,6 +340,11 @@ vht_oper_chwidth={{ capabilities.vht.channel_set_width }} {% endif %} {% set output = namespace(value='') %} +{% if capabilities.vht.channel_set_width is vyos_defined('2') %} +{% set output.value = output.value ~ '[VHT160]' %} +{% elif capabilities.vht.channel_set_width is vyos_defined('3') %} +{% set output.value = output.value ~ '[VHT160-80PLUS80]' %} +{% endif %} {% if capabilities.vht.stbc.tx is vyos_defined %} {% set output.value = output.value ~ '[TX-STBC-2BY1]' %} {% endif %} @@ -363,30 +368,21 @@ vht_oper_chwidth={{ capabilities.vht.channel_set_width }} {% endif %} {% if capabilities.vht.max_mpdu_exp is vyos_defined %} {% set output.value = output.value ~ '[MAX-A-MPDU-LEN-EXP-' ~ capabilities.vht.max_mpdu_exp ~ ']' %} -{% if capabilities.vht.max_mpdu_exp is vyos_defined('2') %} -{% set output.value = output.value ~ '[VHT160]' %} -{% endif %} -{% if capabilities.vht.max_mpdu_exp is vyos_defined('3') %} -{% set output.value = output.value ~ '[VHT160-80PLUS80]' %} -{% endif %} {% endif %} {% if capabilities.vht.link_adaptation is vyos_defined('unsolicited') %} {% set output.value = output.value ~ '[VHT-LINK-ADAPT2]' %} {% elif capabilities.vht.link_adaptation is vyos_defined('both') %} {% set output.value = output.value ~ '[VHT-LINK-ADAPT3]' %} {% endif %} - {% for short_gi in capabilities.vht.short_gi if capabilities.vht.short_gi is vyos_defined %} {% set output.value = output.value ~ '[SHORT-GI-' ~ short_gi | upper ~ ']' %} {% endfor %} - {% for beamform in capabilities.vht.beamform if capabilities.vht.beamform is vyos_defined %} {% set output.value = output.value ~ '[SU-BEAMFORMER]' if beamform is vyos_defined('single-user-beamformer') else '' %} {% set output.value = output.value ~ '[SU-BEAMFORMEE]' if beamform is vyos_defined('single-user-beamformee') else '' %} {% set output.value = output.value ~ '[MU-BEAMFORMER]' if beamform is vyos_defined('multi-user-beamformer') else '' %} {% set output.value = output.value ~ '[MU-BEAMFORMEE]' if beamform is vyos_defined('multi-user-beamformee') else '' %} {% endfor %} - {% if capabilities.vht.antenna_count is vyos_defined and capabilities.vht.antenna_count | int > 1 %} {% if capabilities.vht.beamform is vyos_defined %} {% if capabilities.vht.beamform == 'single-user-beamformer' %} diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 5b5eaf015..b43416152 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/bash # Turn off Debian default for %sudo sed -i -e '/^%sudo/d' /etc/sudoers || true @@ -29,6 +29,11 @@ do sed -i "/^# Standard Un\*x authentication\./i${PAM_CONFIG}" $file done +# We do not make use of a TACACS UNIX group - drop it +if grep -q '^tacacs' /etc/group; then + delgroup tacacs +fi + # Both RADIUS and TACACS users belong to aaa group - this must be added first if ! grep -q '^aaa' /etc/group; then addgroup --firstgid 1000 --quiet aaa @@ -42,6 +47,7 @@ if grep -q '^tacacs' /etc/passwd; then vyos_group=vyattaop while [ $level -lt 16 ]; do userdel tacacs${level} || true + rm -rf /home/tacacs${level} || true level=$(( level+1 )) done 2>&1 fi diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index baab6104f..b35ba8d1c 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -25,7 +25,7 @@ <properties> <help>Container capabilities/permissions</help> <completionHelp> - <list>net-admin net-bind-service net-raw setpcap sys-admin sys-time</list> + <list>net-admin net-bind-service net-raw setpcap sys-admin sys-module sys-time</list> </completionHelp> <valueHelp> <format>net-admin</format> @@ -48,11 +48,15 @@ <description>Administation operations (quotactl, mount, sethostname, setdomainame)</description> </valueHelp> <valueHelp> + <format>sys-module</format> + <description>Load, unload and delete kernel modules</description> + </valueHelp> + <valueHelp> <format>sys-time</format> <description>Permission to set system clock</description> </valueHelp> <constraint> - <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-time)</regex> + <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-module|sys-time)</regex> </constraint> <multi/> </properties> @@ -110,7 +114,7 @@ <constraint> <regex>[ !#-%&(-~]+</regex> </constraint> - <constraintErrorMessage>Entrypoint must be ascii characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + <constraintErrorMessage>Entrypoint must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> </properties> </leafNode> <leafNode name="host-name"> @@ -133,7 +137,7 @@ <constraint> <regex>[ !#-%&(-~]+</regex> </constraint> - <constraintErrorMessage>Command must be ascii characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + <constraintErrorMessage>Command must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> </properties> </leafNode> <leafNode name="arguments"> @@ -142,9 +146,29 @@ <constraint> <regex>[ !#-%&(-~]+</regex> </constraint> - <constraintErrorMessage>The command's arguments must be ascii characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> + <constraintErrorMessage>The command's arguments must be ASCII characters, use &quot; and &apos for double and single quotes respectively</constraintErrorMessage> </properties> </leafNode> + <tagNode name="label"> + <properties> + <help>Add label variables</help> + <constraint> + <regex>[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?</regex> + </constraint> + <constraintErrorMessage>Label variable name must be alphanumeric and can contain hyphen, dots and underscores</constraintErrorMessage> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Set label option value</help> + <valueHelp> + <format>txt</format> + <description>Set label option value</description> + </valueHelp> + </properties> + </leafNode> + </children> + </tagNode> <leafNode name="memory"> <properties> <help>Memory (RAM) available to this container</help> diff --git a/interface-definitions/include/interface/eapol.xml.i b/interface-definitions/include/interface/eapol.xml.i index c4cdeae0c..a3206f2c7 100644 --- a/interface-definitions/include/interface/eapol.xml.i +++ b/interface-definitions/include/interface/eapol.xml.i @@ -4,7 +4,7 @@ <help>Extensible Authentication Protocol over Local Area Network</help> </properties> <children> - #include <include/pki/ca-certificate.xml.i> + #include <include/pki/ca-certificate-multi.xml.i> #include <include/pki/certificate-key.xml.i> </children> </node> diff --git a/op-mode-definitions/firewall.xml.in b/op-mode-definitions/firewall.xml.in index 164ce6b60..0f296c272 100644 --- a/op-mode-definitions/firewall.xml.in +++ b/op-mode-definitions/firewall.xml.in @@ -119,6 +119,7 @@ <path>firewall group address-group</path> <path>firewall group network-group</path> <path>firewall group port-group</path> + <path>firewall group interface-group</path> <path>firewall group ipv6-address-group</path> <path>firewall group ipv6-network-group</path> </completionHelp> diff --git a/op-mode-definitions/monitor-command.xml.in b/op-mode-definitions/monitor-command.xml.in new file mode 100644 index 000000000..31c68f029 --- /dev/null +++ b/op-mode-definitions/monitor-command.xml.in @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="monitor"> + <children> + <tagNode name="command"> + <properties> + <help>Monitor operational mode command (refreshes every 2 seconds)</help> + </properties> + <command>watch --no-title ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:3}</command> + </tagNode> + <node name="command"> + <children> + <node name="diff"> + <properties> + <help>Show differences during each run</help> + </properties> + </node> + <tagNode name="diff"> + <properties> + <help>Monitor operational mode command (refreshes every 2 seconds)</help> + </properties> + <command>watch --no-title --differences ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:4}</command> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-hardware.xml.in b/op-mode-definitions/show-hardware.xml.in index ebd806ba5..21079765a 100644 --- a/op-mode-definitions/show-hardware.xml.in +++ b/op-mode-definitions/show-hardware.xml.in @@ -31,7 +31,7 @@ <properties> <help>Show system DMI details</help> </properties> - <command>${vyatta_bindir}/vyatta-show-dmi</command> + <command>sudo dmidecode</command> </node> <node name="mem"> <properties> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 5b94bd98b..52f9238b8 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -187,15 +187,14 @@ def verify_eapol(config): if 'ca' not in config['pki']: raise ConfigError('Invalid CA certificate specified for EAPoL') - ca_cert_name = config['eapol']['ca_certificate'] + for ca_cert_name in config['eapol']['ca_certificate']: + if ca_cert_name not in config['pki']['ca']: + raise ConfigError('Invalid CA certificate specified for EAPoL') - if ca_cert_name not in config['pki']['ca']: - raise ConfigError('Invalid CA certificate specified for EAPoL') - - ca_cert = config['pki']['ca'][ca_cert_name] + ca_cert = config['pki']['ca'][ca_cert_name] - if 'certificate' not in ca_cert: - raise ConfigError('Invalid CA certificate specified for EAPoL') + if 'certificate' not in ca_cert: + raise ConfigError('Invalid CA certificate specified for EAPoL') def verify_mirror_redirect(config): """ diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 2f181d8d9..bc6899e45 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -117,6 +117,37 @@ def get_interface_namespace(iface): if iface == tmp["ifname"]: return netns +def is_ipv6_tentative(iface: str, ipv6_address: str) -> bool: + """Check if IPv6 address is in tentative state. + + This function checks if an IPv6 address on a specific network interface is + in the tentative state. IPv6 tentative addresses are not fully configured + and are undergoing Duplicate Address Detection (DAD) to ensure they are + unique on the network. + + Args: + iface (str): The name of the network interface. + ipv6_address (str): The IPv6 address to check. + + Returns: + bool: True if the IPv6 address is tentative, False otherwise. + """ + import json + from vyos.utils.process import rc_cmd + + rc, out = rc_cmd(f'ip -6 --json address show dev {iface} scope global') + if rc: + return False + + data = json.loads(out) + for addr_info in data[0]['addr_info']: + if ( + addr_info.get('local') == ipv6_address and + addr_info.get('tentative', False) + ): + return True + return False + def is_wwan_connected(interface): """ Determine if a given WWAN interface, e.g. wwan0 is connected to the carrier network or not """ diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 5ea21fea8..a39b81348 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -250,10 +250,19 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: # Enable EAPoL self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate']) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() + # Test multiple CA chains + self.assertEqual(get_certificate_count(interface, 'ca'), 4) + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) + + self.cli_commit() + # Check for running process self.assertTrue(process_named_running('wpa_supplicant')) diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index f8686edd8..95246a7b9 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -97,6 +97,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): vht_opt = { # VyOS CLI option hostapd - ht_capab setting + 'channel-set-width 3' : '[VHT160-80PLUS80]', 'stbc tx' : '[TX-STBC-2BY1]', 'stbc rx 12' : '[RX-STBC-12]', 'ldpc' : '[RXLDPC]', @@ -104,7 +105,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): 'vht-cf' : '[HTC-VHT]', 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]', 'max-mpdu 11454' : '[MAX-MPDU-11454]', - 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2][VHT160]', + 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]', 'link-adaptation both' : '[VHT-LINK-ADAPT3]', 'short-gi 80' : '[SHORT-GI-80]', 'short-gi 160' : '[SHORT-GI-160]', diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 79b605ffb..46eb10714 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -178,6 +178,11 @@ def verify(container): if 'value' not in cfg: raise ConfigError(f'Environment variable {var} has no value assigned!') + if 'label' in container_config: + for var, cfg in container_config['label'].items(): + if 'value' not in cfg: + raise ConfigError(f'Label variable {var} has no value assigned!') + if 'volume' in container_config: for volume, volume_config in container_config['volume'].items(): if 'source' not in volume_config: @@ -268,6 +273,12 @@ def generate_run_arguments(name, container_config): for k, v in container_config['environment'].items(): env_opt += f" --env \"{k}={v['value']}\"" + # Check/set label options "--label foo=bar" + env_opt = '' + if 'label' in container_config: + for k, v in container_config['label'].items(): + env_opt += f" --label \"{k}={v['value']}\"" + hostname = '' if 'host_name' in container_config: hostname = container_config['host_name'] diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 626a3757e..0121df11c 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -15,6 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +import time + from sys import exit from ipaddress import ip_interface from ipaddress import IPv4Interface @@ -26,11 +28,13 @@ from vyos.ifconfig.vrrp import VRRP from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 +from vyos.utils.network import is_ipv6_tentative from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() + def get_config(config=None): if config: conf = config @@ -171,6 +175,18 @@ def apply(ha): call(f'systemctl stop {service_name}') return None + # Check if IPv6 address is tentative T5533 + for group, group_config in ha['vrrp']['group'].items(): + if 'hello_source_address' in group_config: + if is_ipv6(group_config['hello_source_address']): + ipv6_address = group_config['hello_source_address'] + interface = group_config['interface'] + checks = 20 + interval = 0.1 + for _ in range(checks): + if is_ipv6_tentative(interface, ipv6_address): + time.sleep(interval) + call(f'systemctl reload-or-restart {service_name}') return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index b015bba88..f3e65ad5e 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -186,14 +186,15 @@ def generate(ethernet): if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') - ca_cert_name = ethernet['eapol']['ca_certificate'] - pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + ca_chains = [] - loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) - ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + 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(encode_certificate(c) for c in ca_full_chain)) + write_file(ca_cert_file_path, '\n'.join(ca_chains)) return None diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index f6097e282..435189025 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -102,7 +102,7 @@ def verify(igmp): # Check, is this multicast group for intfc in igmp['ifaces']: for gr_addr in igmp['ifaces'][intfc]['gr_join']: - if IPv4Address(gr_addr) < IPv4Address('224.0.0.0'): + if not IPv4Address(gr_addr).is_multicast: raise ConfigError(gr_addr + " not a multicast group") def generate(igmp): diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index f558c18b7..77f38992b 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -338,10 +338,12 @@ def _get_formatted_client_leases(lease_data, family): from time import localtime from time import strftime - from vyos.validate import is_intf_addr_assigned + from vyos.utils.network import is_intf_addr_assigned data_entries = [] for lease in lease_data: + if not lease.get('new_ip_address'): + continue data_entries.append(["Interface", lease['interface']]) if 'new_ip_address' in lease: tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]' diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 852a7248a..23b4b8459 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -24,7 +24,7 @@ from vyos.config import Config from vyos.utils.process import cmd from vyos.utils.dict import dict_search_args -def get_config_firewall(conf, hook=None, priority=None, ipv6=False, interfaces=True): +def get_config_firewall(conf, hook=None, priority=None, ipv6=False): config_path = ['firewall'] if hook: config_path += ['ipv6' if ipv6 else 'ipv4', hook] @@ -38,12 +38,13 @@ def get_config_firewall(conf, hook=None, priority=None, ipv6=False, interfaces=T def get_nftables_details(hook, priority, ipv6=False): suffix = '6' if ipv6 else '' + aux = 'IPV6_' if ipv6 else '' name_prefix = 'NAME6_' if ipv6 else 'NAME_' if hook == 'name' or hook == 'ipv6-name': command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{priority}' else: up_hook = hook.upper() - command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{up_hook}_{priority}' + command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}' try: results = cmd(command) @@ -106,7 +107,7 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ ip_str = 'IPv6' if ipv6 else 'IPv4' print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {prior}"\n') - details = get_nftables_details(prior, ipv6) + details = get_nftables_details(hook, prior, ipv6) rows = [] if 'rule' in prior_conf: @@ -117,8 +118,57 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ if 'disable' in rule_conf: continue - source_addr = dict_search_args(rule_conf, 'source', 'address') or '0.0.0.0/0' - dest_addr = dict_search_args(rule_conf, 'destination', 'address') or '0.0.0.0/0' + # Get source + source_addr = dict_search_args(rule_conf, 'source', 'address') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'address_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'network_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'domain_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'fqdn') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'geoip', 'country_code') + if source_addr: + source_addr = str(source_addr)[1:-1].replace('\'','') + if 'inverse_match' in dict_search_args(rule_conf, 'source', 'geoip'): + source_addr = 'NOT ' + str(source_addr) + if not source_addr: + source_addr = 'any' + + # Get destination + dest_addr = dict_search_args(rule_conf, 'destination', 'address') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'address_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'network_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'domain_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'fqdn') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'geoip', 'country_code') + if dest_addr: + dest_addr = str(dest_addr)[1:-1].replace('\'','') + if 'inverse_match' in dict_search_args(rule_conf, 'destination', 'geoip'): + dest_addr = 'NOT ' + str(dest_addr) + if not dest_addr: + dest_addr = 'any' + + # Get inbound interface + iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_name') + if not iiface: + iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + if not iiface: + iiface = 'any' + + # Get outbound interface + oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_name') + if not oiface: + oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + if not oiface: + oiface = 'any' row = [rule_id] if rule_id in details: @@ -131,9 +181,26 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ row.append(rule_conf['action']) row.append(source_addr) row.append(dest_addr) + row.append(iiface) + row.append(oiface) rows.append(row) - if 'default_action' in prior_conf and not single_rule_id: + + if hook in ['input', 'forward', 'output']: + row = ['default'] + row.append('N/A') + row.append('N/A') + if 'default_action' in prior_conf: + row.append(prior_conf['default_action']) + else: + row.append('accept') + row.append('any') + row.append('any') + row.append('any') + row.append('any') + rows.append(row) + + elif 'default_action' in prior_conf and not single_rule_id: row = ['default'] if 'default-action' in details: rule_details = details['default-action'] @@ -143,12 +210,14 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_ row.append('0') row.append('0') row.append(prior_conf['default_action']) - row.append('0.0.0.0/0') # Source - row.append('0.0.0.0/0') # Dest + row.append('any') # Source + row.append('any') # Dest + row.append('any') # inbound-interface + row.append('any') # outbound-interface rows.append(row) if rows: - header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination'] + header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination', 'Inbound-Interface', 'Outbound-interface'] print(tabulate.tabulate(rows, header) + '\n') def show_firewall(): @@ -204,7 +273,7 @@ def show_firewall_rule(hook, priority, rule_id, ipv6=False): def show_firewall_group(name=None): conf = Config() - firewall = get_config_firewall(conf, interfaces=False) + firewall = get_config_firewall(conf) if 'group' not in firewall: return @@ -225,18 +294,37 @@ def show_firewall_group(name=None): for item in family: for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']: - if name_type not in firewall[item]: - continue - for name, name_conf in firewall[item][name_type].items(): - if 'rule' not in name_conf: + if item in firewall: + if name_type not in firewall[item]: continue - for rule_id, rule_conf in name_conf['rule'].items(): - source_group = dict_search_args(rule_conf, 'source', 'group', group_type) - dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) - if source_group and group_name == source_group: - out.append(f'{name}-{rule_id}') - elif dest_group and group_name == dest_group: - out.append(f'{name}-{rule_id}') + for priority, priority_conf in firewall[item][name_type].items(): + if priority not in firewall[item][name_type]: + continue + for rule_id, rule_conf in priority_conf['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') return out header = ['Name', 'Type', 'References', 'Members'] @@ -248,7 +336,7 @@ def show_firewall_group(name=None): continue references = find_references(group_type, group_name) - row = [group_name, group_type, '\n'.join(references) or 'N/A'] + row = [group_name, group_type, '\n'.join(references) or 'N/D'] if 'address' in group_conf: row.append("\n".join(sorted(group_conf['address']))) elif 'network' in group_conf: @@ -257,8 +345,10 @@ def show_firewall_group(name=None): row.append("\n".join(sorted(group_conf['mac_address']))) elif 'port' in group_conf: row.append("\n".join(sorted(group_conf['port']))) + elif 'interface' in group_conf: + row.append("\n".join(sorted(group_conf['interface']))) else: - row.append('N/A') + row.append('N/D') rows.append(row) if rows: diff --git a/src/op_mode/vyos-op-cmd-wrapper.sh b/src/op_mode/vyos-op-cmd-wrapper.sh new file mode 100755 index 000000000..a89211b2b --- /dev/null +++ b/src/op_mode/vyos-op-cmd-wrapper.sh @@ -0,0 +1,6 @@ +#!/bin/vbash +shopt -s expand_aliases +source /etc/default/vyatta +source /etc/bash_completion.d/vyatta-op +_vyatta_op_init +_vyatta_op_run "$@" |