# Copyright VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . # T5160: Firewall re-writing # cli changes from: # set firewall name ... # set firewall ipv6-name ... # To # set firewall ipv4 name # set firewall ipv6 name ## Also from 'firewall interface' removed. ## in and out: # set firewall interface [in|out] [name | ipv6-name] # To # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target ## local: # set firewall interface local [name | ipv6-name] # To # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target # T8281: Normalize firewall rule network prefixes for source and destination address # # Fixes invalid/non-canonical prefixes like: # 10.10.10.1/30 -> 10.10.10.0/30 # 2001:db8::1/64 -> 2001:db8::/64 # # Only "source address" and "destination address" are rewritten. from ipaddress import ip_network from vyos.configtree import ConfigTree base = ['firewall'] def normalize_address(config: ConfigTree, path: list[str]): if not config.exists(path): return def normalize(value: str) -> str | None: if '/' not in value: return None is_except = value.startswith('!') if is_except: value = value.lstrip('!') try: new_value = str(ip_network(value, strict=False)) except ValueError: return None return f'!{new_value}' if is_except else new_value value = config.return_value(path) if value: normalized = normalize(value) if normalized and normalized != value: config.set(path, value=normalized) def migrate_ruleset(config: ConfigTree, ruleset_base: list[str]): if not config.exists(ruleset_base + ['rule']): return for rule_id in config.list_nodes(ruleset_base + ['rule']): rule_base = ruleset_base + ['rule', rule_id] for direction in ['source', 'destination']: normalize_address(config, rule_base + [direction, 'address']) def migrate(config: ConfigTree) -> None: if not config.exists(base): # Nothing to do return ### Migration of state policies if config.exists(base + ['state-policy']): for state in config.list_nodes(base + ['state-policy']): action = config.return_value(base + ['state-policy', state, 'action']) config.set(base + ['global-options', 'state-policy', state, 'action'], value=action) if config.exists(base + ['state-policy', state, 'log']): config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable') config.delete(base + ['state-policy']) ## migration of global options: for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians', 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']: if config.exists(base + [option]): if option != 'config-trap': val = config.return_value(base + [option]) config.set(base + ['global-options', option], value=val) config.delete(base + [option]) ### Migration of firewall name and ipv6-name ### Also migrate legacy 'accept' behaviour if config.exists(base + ['name']): config.set(['firewall', 'ipv4', 'name']) config.set_tag(['firewall', 'ipv4', 'name']) for ipv4name in config.list_nodes(base + ['name']): config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name]) if config.exists(base + ['ipv4', 'name', ipv4name, 'default-action']): action = config.return_value(base + ['ipv4', 'name', ipv4name, 'default-action']) if action == 'accept': config.set(base + ['ipv4', 'name', ipv4name, 'default-action'], value='return') if config.exists(base + ['ipv4', 'name', ipv4name, 'rule']): for rule_id in config.list_nodes(base + ['ipv4', 'name', ipv4name, 'rule']): action = config.return_value(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action']) if action == 'accept': config.set(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'], value='return') config.delete(base + ['name']) if config.exists(base + ['ipv6-name']): config.set(['firewall', 'ipv6', 'name']) config.set_tag(['firewall', 'ipv6', 'name']) for ipv6name in config.list_nodes(base + ['ipv6-name']): config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name]) if config.exists(base + ['ipv6', 'name', ipv6name, 'default-action']): action = config.return_value(base + ['ipv6', 'name', ipv6name, 'default-action']) if action == 'accept': config.set(base + ['ipv6', 'name', ipv6name, 'default-action'], value='return') if config.exists(base + ['ipv6', 'name', ipv6name, 'rule']): for rule_id in config.list_nodes(base + ['ipv6', 'name', ipv6name, 'rule']): action = config.return_value(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action']) if action == 'accept': config.set(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'], value='return') config.delete(base + ['ipv6-name']) ### Migration of firewall interface if config.exists(base + ['interface']): fwd_ipv4_rule = 5 inp_ipv4_rule = 5 fwd_ipv6_rule = 5 inp_ipv6_rule = 5 for direction in ['in', 'out', 'local']: for iface in config.list_nodes(base + ['interface']): if config.exists(base + ['interface', iface, direction]): if config.exists(base + ['interface', iface, direction, 'name']): target = config.return_value(base + ['interface', iface, direction, 'name']) if direction == 'in': # Add default-action== accept for compatibility reasons: config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') new_base = base + ['ipv4', 'forward', 'filter', 'rule'] config.set(new_base) config.set_tag(new_base) config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) fwd_ipv4_rule = fwd_ipv4_rule + 5 elif direction == 'out': # Add default-action== accept for compatibility reasons: config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') new_base = base + ['ipv4', 'forward', 'filter', 'rule'] config.set(new_base) config.set_tag(new_base) config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface) config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) fwd_ipv4_rule = fwd_ipv4_rule + 5 else: # Add default-action== accept for compatibility reasons: config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') new_base = base + ['ipv4', 'input', 'filter', 'rule'] config.set(new_base) config.set_tag(new_base) config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) config.set(new_base + [inp_ipv4_rule, 'action'], value='jump') config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target) inp_ipv4_rule = inp_ipv4_rule + 5 if config.exists(base + ['interface', iface, direction, 'ipv6-name']): target = config.return_value(base + ['interface', iface, direction, 'ipv6-name']) if direction == 'in': # Add default-action== accept for compatibility reasons: config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') new_base = base + ['ipv6', 'forward', 'filter', 'rule'] config.set(new_base) config.set_tag(new_base) config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) fwd_ipv6_rule = fwd_ipv6_rule + 5 elif direction == 'out': # Add default-action== accept for compatibility reasons: config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') new_base = base + ['ipv6', 'forward', 'filter', 'rule'] config.set(new_base) config.set_tag(new_base) config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface) config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) fwd_ipv6_rule = fwd_ipv6_rule + 5 else: new_base = base + ['ipv6', 'input', 'filter', 'rule'] # Add default-action== accept for compatibility reasons: config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') config.set(new_base) config.set_tag(new_base) config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) config.set(new_base + [inp_ipv6_rule, 'action'], value='jump') config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target) inp_ipv6_rule = inp_ipv6_rule + 5 config.delete(base + ['interface']) ### Normalize/fix firewall rule network prefixes for source and destination address (T8281) for family in ['ipv4', 'ipv6', 'bridge']: # 1) Named rulesets: # set firewall ipv4|ipv6|bridge name rule ... ruleset_base = base + [family, 'name'] if config.exists(ruleset_base): for ruleset in config.list_nodes(ruleset_base): migrate_ruleset(config, ruleset_base + [ruleset]) # 2) Hook-based rulesets: # set firewall ipv4|ipv6|bridge input|output|forward filter|raw rule ... # set firewall ipv4|ipv6 prerouting raw rule ... hook_rulesets = { 'input': ['filter', 'raw'], 'output': ['filter', 'raw'], 'forward': ['filter', 'raw'], 'prerouting': ['raw'], } for hook, rulesets in hook_rulesets.items(): for ruleset in rulesets: migrate_ruleset(config, base + [family, hook, ruleset])