diff options
23 files changed, 246 insertions, 68 deletions
@@ -27,7 +27,7 @@ libvyosconfig: rm -rf /tmp/libvyosconfig && \ git clone https://github.com/vyos/libvyosconfig.git /tmp/libvyosconfig || exit 1 cd /tmp/libvyosconfig && \ - git checkout 5f15d8095efd11756a867e552a3f8fe6c77e57cc || exit 1 + git checkout 27e4b0a5eaf77d9a1f5e1f6dcaa109e5d73c51d1 || exit 1 eval $$(opam env --root=/opt/opam --set-root) && ./build.sh fi diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index fa6cd74c0..3147b4c37 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -35,6 +35,15 @@ } {% endfor %} {% endif %} +{% if group.remote_group is vyos_defined and is_l3 and not is_ipv6 %} +{% for name, name_config in group.remote_group.items() %} + set R_{{ name }} { + type {{ ip_type }} + flags interval + auto-merge + } +{% endfor %} +{% endif %} {% if group.mac_group is vyos_defined %} {% for group_name, group_conf in group.mac_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index ba97f37f6..fde58651a 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -195,10 +195,6 @@ if [ ! -x $PRECONFIG_SCRIPT ]; then EOF fi -# cracklib-runtime default database location -CRACKLIB_DIR=/var/cache/cracklib -CRACKLIB_DB=cracklib_dict - # create /opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script POSTCONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script if [ ! -x $POSTCONFIG_SCRIPT ]; then @@ -210,15 +206,7 @@ if [ ! -x $POSTCONFIG_SCRIPT ]; then # This script is executed at boot time after VyOS configuration is fully applied. # Any modifications required to work around unfixed bugs # or use services not available through the VyOS CLI system can be placed here. -# -# T6353 - Just in case, check if cracklib was installed properly -# If the database file is missing, re-install the runtime package -# -if [ ! -f "${CRACKLIB_DIR}/${CRACKLIB_DB}.pwd" ]; then - mkdir -p $CRACKLIB_DIR - /usr/sbin/create-cracklib-dict -o $CRACKLIB_DIR/$CRACKLIB_DB \ - /usr/share/dict/cracklib-small -fi + EOF fi diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index e4fe9a508..7538c3cc5 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -138,6 +138,19 @@ </tagNode> </children> </node> + <tagNode name="remote-group"> + <properties> + <help>Firewall remote-group</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> + </properties> + <children> + #include <include/url-http-https.xml.i> + #include <include/generic-description.xml.i> + </children> + </tagNode> <tagNode name="interface-group"> <properties> <help>Firewall interface-group</help> diff --git a/interface-definitions/include/constraint/wireguard-keys.xml.i b/interface-definitions/include/constraint/wireguard-keys.xml.i new file mode 100644 index 000000000..f59c86087 --- /dev/null +++ b/interface-definitions/include/constraint/wireguard-keys.xml.i @@ -0,0 +1,6 @@ +<!-- include start from constraint/wireguard-keys.xml.i --> +<constraint> + <validator name="base64" argument="--decoded-len 32"/> +</constraint> +<constraintErrorMessage>Key must be Base64-encoded with 32 bytes in length</constraintErrorMessage> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-ipv4.xml.i b/interface-definitions/include/firewall/common-rule-ipv4.xml.i index 803b94b06..b67ef25dc 100644 --- a/interface-definitions/include/firewall/common-rule-ipv4.xml.i +++ b/interface-definitions/include/firewall/common-rule-ipv4.xml.i @@ -16,6 +16,7 @@ #include <include/firewall/port.xml.i> #include <include/firewall/source-destination-group.xml.i> #include <include/firewall/source-destination-dynamic-group.xml.i> + #include <include/firewall/source-destination-remote-group.xml.i> </children> </node> <leafNode name="jump-target"> @@ -39,6 +40,7 @@ #include <include/firewall/port.xml.i> #include <include/firewall/source-destination-group.xml.i> #include <include/firewall/source-destination-dynamic-group.xml.i> + #include <include/firewall/source-destination-remote-group.xml.i> </children> </node> <!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/source-destination-remote-group.xml.i b/interface-definitions/include/firewall/source-destination-remote-group.xml.i new file mode 100644 index 000000000..16463c8eb --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-remote-group.xml.i @@ -0,0 +1,17 @@ +<!-- include start from firewall/source-destination-remote-group.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="remote-group"> + <properties> + <help>Group of remote addresses</help> + <completionHelp> + <path>firewall group remote-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/interfaces_wireguard.xml.in b/interface-definitions/interfaces_wireguard.xml.in index 4f8b6c751..33cb5864a 100644 --- a/interface-definitions/interfaces_wireguard.xml.in +++ b/interface-definitions/interfaces_wireguard.xml.in @@ -56,10 +56,7 @@ <leafNode name="private-key"> <properties> <help>Base64 encoded private key</help> - <constraint> - <validator name="base64"/> - </constraint> - <constraintErrorMessage>Key is not base64-encoded</constraintErrorMessage> + #include <include/constraint/wireguard-keys.xml.i> </properties> </leafNode> <tagNode name="peer"> @@ -75,20 +72,14 @@ #include <include/generic-description.xml.i> <leafNode name="public-key"> <properties> - <help>base64 encoded public key</help> - <constraint> - <validator name="base64"/> - </constraint> - <constraintErrorMessage>Key is not base64-encoded</constraintErrorMessage> + <help>Base64 encoded public key</help> + #include <include/constraint/wireguard-keys.xml.i> </properties> </leafNode> <leafNode name="preshared-key"> <properties> - <help>base64 encoded preshared key</help> - <constraint> - <validator name="base64"/> - </constraint> - <constraintErrorMessage>Key is not base64-encoded</constraintErrorMessage> + <help>Base64 encoded preshared key</help> + #include <include/constraint/wireguard-keys.xml.i> </properties> </leafNode> <leafNode name="allowed-ips"> diff --git a/op-mode-definitions/install-mok.xml.in b/op-mode-definitions/install-mok.xml.in index 18526a354..c7e62349a 100644 --- a/op-mode-definitions/install-mok.xml.in +++ b/op-mode-definitions/install-mok.xml.in @@ -6,7 +6,7 @@ <properties> <help>Install Secure Boot MOK (Machine Owner Key)</help> </properties> - <command>if test -f /var/lib/shim-signed/mok/MOK.der; then sudo mokutil --ignore-keyring --import /var/lib/shim-signed/mok/MOK.der; else echo "Secure Boot Machine Owner Key not found"; fi</command> + <command>if test -f /var/lib/shim-signed/mok/vyos-dev-2025-shim.der; then sudo mokutil --ignore-keyring --import /var/lib/shim-signed/mok/vyos-dev-2025-shim.der; else echo "Secure Boot Machine Owner Key not found"; fi</command> </leafNode> </children> </node> diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 83954327c..dade852c7 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -50,7 +50,7 @@ def unescape_backslash(string: str) -> str: def extract_version(s): """Extract the version string from the config string""" t = re.split('(^//)', s, maxsplit=1, flags=re.MULTILINE) - return (s, ''.join(t[1:])) + return (t[0], ''.join(t[1:])) def check_path(path): diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 314e8dfe3..9f01f8be1 100755 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -310,6 +310,13 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): operator = '!=' group_name = group_name[1:] output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}') + elif 'remote_group' in group: + group_name = group['remote_group'] + operator = '' + if group_name[0] == '!': + operator = '!=' + group_name = group_name[1:] + output.append(f'{ip_name} {prefix}addr {operator} @R_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] operator = '' diff --git a/python/vyos/utils/auth.py b/python/vyos/utils/auth.py index a27d8a28a..5d0e3464a 100644 --- a/python/vyos/utils/auth.py +++ b/python/vyos/utils/auth.py @@ -23,15 +23,18 @@ from decimal import Decimal from vyos.utils.process import cmd -DEFAULT_PASSWORD = 'vyos' -LOW_ENTROPY_MSG = 'should be at least 8 characters long;' -WEAK_PASSWORD_MSG= 'The password complexity is too low - @MSG@' - +DEFAULT_PASSWORD: str = 'vyos' +LOW_ENTROPY_MSG: str = 'should be at least 8 characters long;' +WEAK_PASSWORD_MSG: str = 'The password complexity is too low - @MSG@' +CRACKLIB_ERROR_MSG: str = 'A following error occurred: @MSG@\n' \ + 'Possibly the cracklib database is corrupted or is missing. ' \ + 'Try reinstalling the python3-cracklib package.' class EPasswdStrength(StrEnum): WEAK = 'Weak' DECENT = 'Decent' STRONG = 'Strong' + ERROR = 'Cracklib Error' def calculate_entropy(charset: str, passwd: str) -> float: @@ -63,6 +66,9 @@ def evaluate_strength(passwd: str) -> dict[str, str]: msg = f'should not be {e}' result.update(strength=EPasswdStrength.WEAK) result.update(error=WEAK_PASSWORD_MSG.replace('@MSG@', msg)) + except Exception as e: + result.update(strength=EPasswdStrength.ERROR) + result.update(error=CRACKLIB_ERROR_MSG.replace('@MSG@', str(e))) else: # Now check the password's entropy # Cast to Decimal for more precise rounding diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index dc0c0a6d6..2f666f0ee 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -599,3 +599,19 @@ def get_nft_vrf_zone_mapping() -> dict: for (vrf_name, vrf_id) in vrf_list: output.append({'interface' : vrf_name, 'vrf_tableid' : vrf_id}) return output + +def is_valid_ipv4_address_or_range(addr: str) -> bool: + """ + Validates if the provided address is a valid IPv4, CIDR or IPv4 range + :param addr: address to test + :return: bool: True if provided address is valid + """ + from ipaddress import ip_network + try: + if '-' in addr: # If we are checking a range, validate both address's individually + split = addr.split('-') + return is_valid_ipv4_address_or_range(split[0]) and is_valid_ipv4_address_or_range(split[1]) + else: + return ip_network(addr).version == 4 + except: + return False diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 33144c7fa..2829edbfb 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -1273,5 +1273,39 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): with self.assertRaises(ConfigSessionError): self.cli_commit() + def test_ipv4_remote_group(self): + # Setup base config for test + self.cli_set(['firewall', 'group', 'remote-group', 'group01', 'url', 'http://127.0.0.1:80/list.txt']) + self.cli_set(['firewall', 'group', 'remote-group', 'group01', 'description', 'Example Group 01']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'group', 'remote-group', 'group01']) + + self.cli_commit() + + # Test remote-group had been loaded correctly in nft + nftables_search = [ + ['R_group01'], + ['type ipv4_addr'], + ['flags interval'], + ['meta l4proto', 'daddr @R_group01', "ipv4-INP-filter-10"] + ] + self.verify_nftables(nftables_search, 'ip vyos_filter') + + # Test remote-group cannot be configured without a URL + self.cli_delete(['firewall', 'group', 'remote-group', 'group01', 'url']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_discard() + + # Test remote-group cannot be set alongside address in rules + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'address', '127.0.0.1']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_discard() + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 768bb127d..cebe57092 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -44,6 +44,7 @@ airbag.enable() nftables_conf = '/run/nftables.conf' domain_resolver_usage = '/run/use-vyos-domain-resolver-firewall' +firewall_config_dir = "/config/firewall" sysctl_file = r'/run/sysctl/10-vyos-firewall.conf' @@ -53,7 +54,8 @@ valid_groups = [ 'network_group', 'port_group', 'interface_group', - ## Added for group ussage in bridge firewall + 'remote_group', + ## Added for group usage in bridge firewall 'ipv4_address_group', 'ipv6_address_group', 'ipv4_network_group', @@ -311,8 +313,8 @@ def verify_rule(firewall, family, hook, priority, rule_id, rule_conf): raise ConfigError('Only one of address, fqdn or geoip can be specified') if 'group' in side_conf: - if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: - raise ConfigError('Only one address-group, network-group or domain-group can be specified') + if len({'address_group', 'network_group', 'domain_group', 'remote_group'} & set(side_conf['group'])) > 1: + raise ConfigError('Only one address-group, network-group, remote-group or domain-group can be specified') for group in valid_groups: if group in side_conf['group']: @@ -332,7 +334,7 @@ def verify_rule(firewall, family, hook, priority, rule_id, rule_conf): error_group = fw_group.replace("_", "-") - if group in ['address_group', 'network_group', 'domain_group']: + if group in ['address_group', 'network_group', 'domain_group', 'remote_group']: types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf] if types: raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') @@ -442,6 +444,11 @@ def verify(firewall): for group_name, group in groups.items(): verify_nested_group(group_name, group, groups, []) + if 'remote_group' in firewall['group']: + 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') + for family in ['ipv4', 'ipv6', 'bridge']: if family in firewall: for chain in ['name','forward','input','output', 'prerouting']: @@ -539,6 +546,15 @@ def verify(firewall): def generate(firewall): render(nftables_conf, 'firewall/nftables.j2', firewall) render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall) + + # Cleanup remote-group cache files + if os.path.exists(firewall_config_dir): + for fw_file in os.listdir(firewall_config_dir): + # Delete matching files in 'config/firewall' that no longer exist as a remote-group in config + if fw_file.startswith("R_") and fw_file.endswith(".txt"): + if 'group' not in firewall or 'remote_group' not in firewall['group'] or fw_file[2:-4] not in firewall['group']['remote_group'].keys(): + os.unlink(os.path.join(firewall_config_dir, fw_file)) + return None def parse_firewall_error(output): @@ -598,7 +614,7 @@ def apply(firewall): ## DOMAIN RESOLVER domain_action = 'restart' - if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'].items() or firewall['ip6_fqdn'].items(): + if dict_search_args(firewall, 'group', 'remote_group') or dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'].items() or firewall['ip6_fqdn'].items(): text = f'# Automatically generated by firewall.py\nThis file indicates that vyos-domain-resolver service is used by the firewall.\n' Path(domain_resolver_usage).write_text(text) else: diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py index 1e6061ecf..3fed6d273 100755 --- a/src/conf_mode/system_login.py +++ b/src/conf_mode/system_login.py @@ -160,9 +160,10 @@ def verify(login): dict_object=user_config ) or None + failed_check_status = [EPasswdStrength.WEAK, EPasswdStrength.ERROR] if plaintext_password is not None: result = evaluate_strength(plaintext_password) - if result['strength'] == EPasswdStrength.WEAK: + if result['strength'] in failed_check_status: Warning(result['error']) for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items(): diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index c197ca434..7a3ab921d 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -253,15 +253,17 @@ def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule 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') + source_addr = dict_search_args(rule_conf, 'source', 'group', 'remote_group') 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) + source_addr = dict_search_args(rule_conf, 'source', 'fqdn') if not source_addr: - source_addr = 'any' + 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') @@ -272,15 +274,17 @@ def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule 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') + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'remote_group') 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) + dest_addr = dict_search_args(rule_conf, 'destination', 'fqdn') if not dest_addr: - dest_addr = 'any' + 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', 'name') @@ -571,6 +575,8 @@ def show_firewall_group(name=None): row.append("\n".join(sorted(group_conf['port']))) elif 'interface' in group_conf: row.append("\n".join(sorted(group_conf['interface']))) + elif 'url' in group_conf: + row.append(group_conf['url']) else: row.append('N/D') rows.append(row) diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index c6e9c7f6f..82756daec 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -783,6 +783,7 @@ def install_image() -> None: break print(MSG_WARN_IMAGE_NAME_WRONG) + failed_check_status = [EPasswdStrength.WEAK, EPasswdStrength.ERROR] # ask for password while True: user_password: str = ask_input(MSG_INPUT_PASSWORD, no_echo=True, @@ -792,7 +793,7 @@ def install_image() -> None: Warning(MSG_WARN_CHANGE_PASSWORD) else: result = evaluate_strength(user_password) - if result['strength'] == EPasswdStrength.WEAK: + if result['strength'] in failed_check_status: Warning(result['error']) confirm: str = ask_input(MSG_INPUT_PASSWORD_CONFIRM, no_echo=True, diff --git a/src/op_mode/qos.py b/src/op_mode/qos.py index b8ca149a0..464b552ee 100755 --- a/src/op_mode/qos.py +++ b/src/op_mode/qos.py @@ -38,7 +38,7 @@ def get_tc_info(interface_dict, interface_name, policy_type): if not policy_name: return None, None - class_dict = op_mode_config_dict(['qos', 'policy', policy_type, policy_name], key_mangling=('-', '_'), + class_dict = op_mode_config_dict(['qos', 'policy', policy_type, policy_name], get_first_key=True) if not class_dict: return None, None diff --git a/src/services/vyos-domain-resolver b/src/services/vyos-domain-resolver index 48c6b86d8..aba5ba9db 100755 --- a/src/services/vyos-domain-resolver +++ b/src/services/vyos-domain-resolver @@ -13,19 +13,22 @@ # # 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 json import time import logging +import os from vyos.configdict import dict_merge from vyos.configquery import ConfigTreeQuery from vyos.firewall import fqdn_config_parse 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.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 from vyos.utils.process import cmd from vyos.utils.process import run from vyos.xml_ref import get_defaults @@ -37,6 +40,8 @@ base_firewall = ['firewall'] base_nat = ['nat'] base_interfaces = ['interfaces'] +firewall_config_dir = "/config/firewall" + domain_state = {} ipv4_tables = { @@ -121,6 +126,56 @@ def nft_valid_sets(): except: return [] +def update_remote_group(config): + conf_lines = [] + count = 0 + valid_sets = nft_valid_sets() + + remote_groups = dict_search_args(config, 'group', 'remote_group') + if remote_groups: + # Create directory for list files if necessary + if not os.path.isdir(firewall_config_dir): + makedir(firewall_config_dir, group='vyattacfg') + chmod_775(firewall_config_dir) + + for set_name, remote_config in remote_groups.items(): + if 'url' not in remote_config: + continue + nft_set_name = f'R_{set_name}' + + # Create list file if necessary + list_file = os.path.join(firewall_config_dir, f"{nft_set_name}.txt") + if not os.path.exists(list_file): + write_file(list_file, '', user="root", group="vyattacfg", mode=0o644) + + # Attempt to download file, use cached version if download fails + try: + download(list_file, remote_config['url'], raise_error=True) + except: + logger.error(f'Failed to download list-file for {set_name} remote group') + logger.info(f'Using cached list-file for {set_name} remote group') + + # Read list file + ip_list = [] + for line in read_file(list_file).splitlines(): + line_first_word = line.strip().partition(' ')[0] + + if is_valid_ipv4_address_or_range(line_first_word): + ip_list.append(line_first_word) + + # Load tables + for table in ipv4_tables: + if (table, nft_set_name) in valid_sets: + conf_lines += nft_output(table, nft_set_name, ip_list) + + count += 1 + + 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}') + + def update_fqdn(config, node): conf_lines = [] count = 0 @@ -234,5 +289,6 @@ if __name__ == '__main__': while True: update_fqdn(firewall, 'firewall') update_fqdn(nat, 'nat') + update_remote_group(firewall) update_interfaces(interfaces, 'interfaces') time.sleep(timeout) diff --git a/src/tests/test_config_parser.py b/src/tests/test_config_parser.py index 9a4f02859..1b4a57311 100644 --- a/src/tests/test_config_parser.py +++ b/src/tests/test_config_parser.py @@ -51,3 +51,7 @@ class TestConfigParser(TestCase): def test_rename_duplicate(self): with self.assertRaises(vyos.configtree.ConfigTreeError): self.config.rename(["top-level-tag-node", "foo"], "bar") + + def test_leading_slashes(self): + self.assertTrue(self.config.exists(["normal-node", "value-with-leading-slashes"])) + self.assertEqual(self.config.return_value(["normal-node", "value-with-leading-slashes"]), "//other-value") diff --git a/src/validators/base64 b/src/validators/base64 index e2b1e730d..a54168ef7 100755 --- a/src/validators/base64 +++ b/src/validators/base64 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2025 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 @@ -15,13 +15,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import base64 -from sys import argv +import argparse -if __name__ == '__main__': - if len(argv) != 2: - exit(1) - try: - base64.b64decode(argv[1]) - except: +parser = argparse.ArgumentParser(description="Validate base64 input.") +parser.add_argument("base64", help="Base64 encoded string to validate") +parser.add_argument("--decoded-len", type=int, help="Optional list of valid lengths for the decoded input") +args = parser.parse_args() + +try: + decoded = base64.b64decode(args.base64) + if args.decoded_len and len(decoded) != args.decoded_len: exit(1) - exit(0) +except: + exit(1) +exit(0) diff --git a/tests/data/config.valid b/tests/data/config.valid index 1fbdd1505..024e5e05c 100644 --- a/tests/data/config.valid +++ b/tests/data/config.valid @@ -26,6 +26,7 @@ normal-node { } } option-with-quoted-value "some-value" + value-with-leading-slashes "//other-value" } trailing-leaf-node-option some-value @@ -35,5 +36,5 @@ empty-node { trailing-leaf-node-without-value -// Trailing comment -// Another trailing comment +// some version string info +// continued |