diff options
| author | Christian Poessinger <christian@poessinger.com> | 2022-09-14 07:55:54 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-14 07:55:54 +0200 | 
| commit | e5c9f290b70c700fbec5acdb3a90bf0c67edd091 (patch) | |
| tree | 531a8c025b5115b443e780c659a1e4973659a4ec /src | |
| parent | 24fc5a832dbdc3cb592674afd89bc72a22496713 (diff) | |
| parent | 30945f39d6d1f0fdba34ce1c2d887a1a6823ecbe (diff) | |
| download | vyos-1x-e5c9f290b70c700fbec5acdb3a90bf0c67edd091.tar.gz vyos-1x-e5c9f290b70c700fbec5acdb3a90bf0c67edd091.zip | |
Merge pull request #1534 from sarthurdev/firewall_interfaces
firewall: zone-policy: T2199: T4605: Refactor firewall, migrate zone-policy
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/firewall-interface.py | 186 | ||||
| -rwxr-xr-x | src/conf_mode/firewall.py | 268 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_nhrp.py | 8 | ||||
| -rwxr-xr-x | src/conf_mode/service_monitoring_telegraf.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/zone_policy.py | 213 | ||||
| -rwxr-xr-x | src/etc/telegraf/custom_scripts/show_firewall_input_filter.py | 6 | ||||
| -rwxr-xr-x | src/migration-scripts/firewall/7-to-8 | 98 | 
7 files changed, 191 insertions, 590 deletions
| diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py deleted file mode 100755 index ab1c69259..000000000 --- a/src/conf_mode/firewall-interface.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 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 os -import re - -from sys import argv -from sys import exit - -from vyos.config import Config -from vyos.configdict import leaf_node_changed -from vyos.ifconfig import Section -from vyos.template import render -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import run -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -NAME_PREFIX = 'NAME_' -NAME6_PREFIX = 'NAME6_' - -NFT_CHAINS = { -    'in': 'VYOS_FW_FORWARD', -    'out': 'VYOS_FW_FORWARD', -    'local': 'VYOS_FW_LOCAL' -} -NFT6_CHAINS = { -    'in': 'VYOS_FW6_FORWARD', -    'out': 'VYOS_FW6_FORWARD', -    'local': 'VYOS_FW6_LOCAL' -} - -def get_config(config=None): -    if config: -        conf = config -    else: -        conf = Config() - -    ifname = argv[1] -    ifpath = Section.get_config_path(ifname) -    if_firewall_path = f'interfaces {ifpath} firewall' - -    if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True, -                                    no_tag_node_value_mangle=True) - -    if_firewall['ifname'] = ifname -    if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True, -                                    no_tag_node_value_mangle=True) - -    return if_firewall - -def verify_chain(table, chain): -    # Verify firewall applied -    code = run(f'nft list chain {table} {chain}') -    return code == 0 - -def verify(if_firewall): -    # bail out early - looks like removal from running config -    if not if_firewall: -        return None - -    for direction in ['in', 'out', 'local']: -        if direction in if_firewall: -            if 'name' in if_firewall[direction]: -                name = if_firewall[direction]['name'] - -                if 'name' not in if_firewall['firewall']: -                    raise ConfigError('Firewall name not configured') - -                if name not in if_firewall['firewall']['name']: -                    raise ConfigError(f'Invalid firewall name "{name}"') - -                if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'): -                    raise ConfigError('Firewall did not apply') - -            if 'ipv6_name' in if_firewall[direction]: -                name = if_firewall[direction]['ipv6_name'] - -                if 'ipv6_name' not in if_firewall['firewall']: -                    raise ConfigError('Firewall ipv6-name not configured') - -                if name not in if_firewall['firewall']['ipv6_name']: -                    raise ConfigError(f'Invalid firewall ipv6-name "{name}"') - -                if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'): -                    raise ConfigError('Firewall did not apply') - -    return None - -def generate(if_firewall): -    return None - -def cleanup_rule(table, chain, prefix, ifname, new_name=None): -    results = cmd(f'nft -a list chain {table} {chain}').split("\n") -    retval = None -    for line in results: -        if f'{prefix}ifname "{ifname}"' in line: -            if new_name and f'jump {new_name}' in line: -                # new_name is used to clear rules for any previously referenced chains -                # returns true when rule exists and doesn't need to be created -                retval = True -                continue - -            handle_search = re.search('handle (\d+)', line) -            if handle_search: -                run(f'nft delete rule {table} {chain} handle {handle_search[1]}') -    return retval - -def state_policy_handle(table, chain): -    # Find any state-policy rule to ensure interface rules are only inserted afterwards -    results = cmd(f'nft -a list chain {table} {chain}').split("\n") -    for line in results: -        if 'jump VYOS_STATE_POLICY' in line: -            handle_search = re.search('handle (\d+)', line) -            if handle_search: -                return handle_search[1] -    return None - -def apply(if_firewall): -    ifname = if_firewall['ifname'] - -    for direction in ['in', 'out', 'local']: -        chain = NFT_CHAINS[direction] -        ipv6_chain = NFT6_CHAINS[direction] -        if_prefix = 'i' if direction in ['in', 'local'] else 'o' - -        name = dict_search_args(if_firewall, direction, 'name') -        if name: -            rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}') - -            if not rule_exists: -                rule_action = 'insert' -                rule_prefix = '' - -                handle = state_policy_handle('ip filter', chain) -                if handle: -                    rule_action = 'add' -                    rule_prefix = f'position {handle}' - -                run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}') -        else: -            cleanup_rule('ip filter', chain, if_prefix, ifname) - -        ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') -        if ipv6_name: -            rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}') - -            if not rule_exists: -                rule_action = 'insert' -                rule_prefix = '' - -                handle = state_policy_handle('ip6 filter', ipv6_chain) -                if handle: -                    rule_action = 'add' -                    rule_prefix = f'position {handle}' - -                run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}') -        else: -            cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname) - -    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/firewall.py b/src/conf_mode/firewall.py index f0ea1a1e5..eeb57bd30 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -26,6 +26,7 @@ from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.configdict import node_changed  from vyos.configdiff import get_config_diff, Diff +# from vyos.configverify import verify_interface_exists  from vyos.firewall import geoip_update  from vyos.firewall import get_ips_domains_dict  from vyos.firewall import nft_add_set_elements @@ -38,7 +39,7 @@ from vyos.util import cmd  from vyos.util import dict_search_args  from vyos.util import dict_search_recursive  from vyos.util import process_named_running -from vyos.util import run +from vyos.util import rc_cmd  from vyos.xml import defaults  from vyos import ConfigError  from vyos import airbag @@ -47,7 +48,6 @@ airbag.enable()  policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'  nftables_conf = '/run/nftables.conf' -nftables_defines_conf = '/run/nftables_defines.conf'  sysfs_config = {      'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'}, @@ -63,28 +63,6 @@ sysfs_config = {      'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}  } -NAME_PREFIX = 'NAME_' -NAME6_PREFIX = 'NAME6_' - -preserve_chains = [ -    'INPUT', -    'FORWARD', -    'OUTPUT', -    'VYOS_FW_FORWARD', -    'VYOS_FW_LOCAL', -    'VYOS_FW_OUTPUT', -    'VYOS_POST_FW', -    'VYOS_FRAG_MARK', -    'VYOS_FW6_FORWARD', -    'VYOS_FW6_LOCAL', -    'VYOS_FW6_OUTPUT', -    'VYOS_POST_FW6', -    'VYOS_FRAG6_MARK' -] - -nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] -nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] -  valid_groups = [      'address_group',      'domain_group', @@ -97,16 +75,6 @@ nested_group_types = [      'port_group', 'ipv6_address_group', 'ipv6_network_group'  ] -group_set_prefix = { -    'A_': 'address_group', -    'A6_': 'ipv6_address_group', -    'D_': 'domain_group', -    'M_': 'mac_group', -    'N_': 'network_group', -    'N6_': 'ipv6_network_group', -    'P_': 'port_group' -} -  snmp_change_type = {      'unknown': 0,      'add': 1, @@ -117,51 +85,6 @@ snmp_event_source = 1  snmp_trap_mib = 'VYATTA-TRAP-MIB'  snmp_trap_name = 'mgmtEventTrap' -def get_firewall_interfaces(conf): -    out = {} -    interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, -                                    no_tag_node_value_mangle=True) -    def find_interfaces(iftype_conf, output={}, prefix=''): -        for ifname, if_conf in iftype_conf.items(): -            if 'firewall' in if_conf: -                output[prefix + ifname] = if_conf['firewall'] -            for vif in ['vif', 'vif_s', 'vif_c']: -                if vif in if_conf: -                    output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.')) -        return output -    for iftype, iftype_conf in interfaces.items(): -        out.update(find_interfaces(iftype_conf)) -    return out - -def get_firewall_zones(conf): -    used_v4 = [] -    used_v6 = [] -    zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True, -                                    no_tag_node_value_mangle=True) - -    if 'zone' in zone_policy: -        for zone, zone_conf in zone_policy['zone'].items(): -            if 'from' in zone_conf: -                for from_zone, from_conf in zone_conf['from'].items(): -                    name = dict_search_args(from_conf, 'firewall', 'name') -                    if name: -                        used_v4.append(name) - -                    ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') -                    if ipv6_name: -                        used_v6.append(ipv6_name) - -            if 'intra_zone_filtering' in zone_conf: -                name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name') -                if name: -                    used_v4.append(name) - -                ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name') -                if ipv6_name: -                    used_v6.append(ipv6_name) - -    return {'name': used_v4, 'ipv6_name': used_v6} -  def geoip_updated(conf, firewall):      diff = get_config_diff(conf)      node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True) @@ -215,6 +138,9 @@ def get_config(config=None):          if tmp in default_values:              del default_values[tmp] +    if 'zone' in default_values: +        del default_values['zone'] +      firewall = dict_merge(default_values, firewall)      # Merge in defaults for IPv4 ruleset @@ -231,9 +157,12 @@ def get_config(config=None):              firewall['ipv6_name'][ipv6_name] = dict_merge(default_values,                                                            firewall['ipv6_name'][ipv6_name]) +    if 'zone' in firewall: +        default_values = defaults(base + ['zone']) +        for zone in firewall['zone']: +            firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone]) +      firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) -    firewall['interfaces'] = get_firewall_interfaces(conf) -    firewall['zone_policy'] = get_firewall_zones(conf)      if 'config_trap' in firewall and firewall['config_trap'] == 'enable':          diff = get_config_diff(conf) @@ -358,109 +287,99 @@ def verify(firewall):      for name in ['name', 'ipv6_name']:          if name in firewall:              for name_id, name_conf in firewall[name].items(): -                if name_id in preserve_chains: -                    raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS') - -                if name_id.startswith("VZONE"): -                    raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix') -                  if 'rule' in name_conf:                      for rule_id, rule_conf in name_conf['rule'].items():                          verify_rule(firewall, rule_conf, name == 'ipv6_name') -    for ifname, if_firewall in firewall['interfaces'].items(): -        for direction in ['in', 'out', 'local']: -            name = dict_search_args(if_firewall, direction, 'name') -            ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') +    if 'interface' in firewall: +        for ifname, if_firewall in firewall['interface'].items(): +            # verify ifname needs to be disabled, dynamic devices come up later +            # verify_interface_exists(ifname) -            if name and dict_search_args(firewall, 'name', name) == None: -                raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}') +            for direction in ['in', 'out', 'local']: +                name = dict_search_args(if_firewall, direction, 'name') +                ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') -            if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: -                raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}') +                if name and dict_search_args(firewall, 'name', name) == None: +                    raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}') -    for fw_name, used_names in firewall['zone_policy'].items(): -        for name in used_names: -            if dict_search_args(firewall, fw_name, name) == None: -                raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') +                if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: +                    raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}') -    return None +    local_zone = False +    zone_interfaces = [] -def cleanup_commands(firewall): -    commands = [] -    commands_chains = [] -    commands_sets = [] -    for table in ['ip filter', 'ip6 filter']: -        name_node = 'name' if table == 'ip filter' else 'ipv6_name' -        chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX -        state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6' -        iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains - -        geoip_list = [] -        if firewall['geoip_updated']: -            geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name' -            geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or [] - -        json_str = cmd(f'nft -t -j list table {table}') -        obj = loads(json_str) - -        if 'nftables' not in obj: -            continue - -        for item in obj['nftables']: -            if 'chain' in item: -                chain = item['chain']['name'] -                if chain in preserve_chains or chain.startswith("VZONE"): -                    continue +    if 'zone' in firewall: +        for zone, zone_conf in firewall['zone'].items(): +            if 'local_zone' not in zone_conf and 'interface' not in zone_conf: +                raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') -                if chain == state_chain: -                    command = 'delete' if 'state_policy' not in firewall else 'flush' -                    commands_chains.append(f'{command} chain {table} {chain}') -                elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None: -                    commands.append(f'flush chain {table} {chain}') -                else: -                    commands_chains.append(f'delete chain {table} {chain}') +            if 'local_zone' in zone_conf: +                if local_zone: +                    raise ConfigError('There cannot be multiple local zones') +                if 'interface' in zone_conf: +                    raise ConfigError('Local zone cannot have interfaces assigned') +                if 'intra_zone_filtering' in zone_conf: +                    raise ConfigError('Local zone cannot use intra-zone-filtering') +                local_zone = True -            if 'rule' in item: -                rule = item['rule'] -                chain = rule['chain'] -                handle = rule['handle'] +            if 'interface' in zone_conf: +                found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] -                if chain in iface_chains: -                    target, _ = next(dict_search_recursive(rule['expr'], 'target')) +                if found_duplicates: +                    raise ConfigError(f'Interfaces cannot be assigned to multiple zones') -                    if target == state_chain and 'state_policy' not in firewall: -                        commands.append(f'delete rule {table} {chain} handle {handle}') +                zone_interfaces += zone_conf['interface'] -                    if target.startswith(chain_prefix): -                        if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None: -                            commands.append(f'delete rule {table} {chain} handle {handle}') +            if 'intra_zone_filtering' in zone_conf: +                intra_zone = zone_conf['intra_zone_filtering'] -            if 'set' in item: -                set_name = item['set']['name'] +                if len(intra_zone) > 1: +                    raise ConfigError('Only one intra-zone-filtering action must be specified') -                if set_name.startswith('GEOIP_CC_') and set_name in geoip_list: -                    commands_sets.append(f'delete set {table} {set_name}') -                    continue +                if 'firewall' in intra_zone: +                    v4_name = dict_search_args(intra_zone, 'firewall', 'name') +                    if v4_name and not dict_search_args(firewall, 'name', v4_name): +                        raise ConfigError(f'Firewall name "{v4_name}" does not exist') -                if set_name.startswith("RECENT_"): -                    commands_sets.append(f'delete set {table} {set_name}') -                    continue +                    v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') +                    if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name): +                        raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + +                    if not v4_name and not v6_name: +                        raise ConfigError('No firewall names specified for intra-zone-filtering') -                for prefix, group_type in group_set_prefix.items(): -                    if set_name.startswith(prefix): -                        group_name = set_name.replace(prefix, "", 1) -                        if dict_search_args(firewall, 'group', group_type, group_name) != None: -                            commands_sets.append(f'flush set {table} {set_name}') -                        else: -                            commands_sets.append(f'delete set {table} {set_name}') -    return commands + commands_chains + commands_sets +            if 'from' in zone_conf: +                for from_zone, from_conf in zone_conf['from'].items(): +                    if from_zone not in firewall['zone']: +                        raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') + +                    v4_name = dict_search_args(from_conf, 'firewall', 'name') +                    if v4_name and not dict_search_args(firewall, 'name', v4_name): +                        raise ConfigError(f'Firewall name "{v4_name}" does not exist') + +                    v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') +                    if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name): +                        raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + +    return None  def generate(firewall):      if not os.path.exists(nftables_conf):          firewall['first_install'] = True -    else: -        firewall['cleanup_commands'] = cleanup_commands(firewall) + +    if 'zone' in firewall: +        for local_zone, local_zone_conf in firewall['zone'].items(): +            if 'local_zone' not in local_zone_conf: +                continue + +            local_zone_conf['from_local'] = {} + +            for zone, zone_conf in firewall['zone'].items(): +                if zone == local_zone or 'from' not in zone_conf: +                    continue +                if local_zone in zone_conf['from']: +                    local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]      render(nftables_conf, 'firewall/nftables.j2', firewall)      return None @@ -521,26 +440,16 @@ def post_apply_trap(firewall):                  cmd(base_cmd + ' '.join(objects)) -def state_policy_rule_exists(): -    # Determine if state policy rules already exist in nft -    search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD') -    return 'VYOS_STATE_POLICY' in search_str -  def resync_policy_route():      # Update policy route as firewall groups were updated -    tmp = run(policy_route_conf_script) +    tmp, out = rc_cmd(policy_route_conf_script)      if tmp > 0: -        Warning('Failed to re-apply policy route configuration!') +        Warning(f'Failed to re-apply policy route configuration! {out}')  def apply(firewall): -    if 'first_install' in firewall: -        run('nfct helper add rpc inet tcp') -        run('nfct helper add rpc inet udp') -        run('nfct helper add tns inet tcp') - -    install_result = run(f'nft -f {nftables_conf}') +    install_result, output = rc_cmd(f'nft -f {nftables_conf}')      if install_result == 1: -        raise ConfigError('Failed to apply firewall') +        raise ConfigError(f'Failed to apply firewall: {output}')      # set firewall group domain-group xxx      if 'group' in firewall: @@ -563,13 +472,6 @@ def apply(firewall):          else:              call('systemctl stop vyos-domain-group-resolve.service') -    if 'state_policy' in firewall and not state_policy_rule_exists(): -        for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']: -            cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY') - -        for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']: -            cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6') -      apply_sysfs(firewall)      if firewall['policy_resync']: diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py index b247ce2ab..991fcc7eb 100755 --- a/src/conf_mode/protocols_nhrp.py +++ b/src/conf_mode/protocols_nhrp.py @@ -94,15 +94,15 @@ def apply(nhrp):                  comment = f'VYOS_NHRP_{tunnel}'                  source_address = nhrp['if_tunnel'][tunnel]['source_address'] -                rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4']) +                rule_handle = find_nftables_rule('ip vyos_filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4'])                  if not rule_handle: -                    run(f'sudo nft insert rule ip filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"') +                    run(f'sudo nft insert rule ip vyos_filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"')      for tunnel in nhrp['del_tunnels']:          comment = f'VYOS_NHRP_{tunnel}' -        rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"']) +        rule_handle = find_nftables_rule('ip vyos_filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"'])          if rule_handle: -            remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle) +            remove_nftables_rule('ip vyos_filter', 'VYOS_FW_OUTPUT', rule_handle)      action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'      run(f'systemctl {action} opennhrp.service') diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 53df006a4..427cb6911 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -42,7 +42,7 @@ systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf'  def get_nft_filter_chains():      """ Get nft chains for table filter """ -    nft = cmd('nft --json list table ip filter') +    nft = cmd('nft --json list table ip vyos_filter')      nft = json.loads(nft)      chain_list = [] diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py deleted file mode 100755 index a52c52706..000000000 --- a/src/conf_mode/zone_policy.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021-2022 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 os - -from json import loads -from sys import exit - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.template import render -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import run -from vyos.xml import defaults -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -nftables_conf = '/run/nftables_zone.conf' - -def get_config(config=None): -    if config: -        conf = config -    else: -        conf = Config() -    base = ['zone-policy'] -    zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'), -                                       get_first_key=True, -                                       no_tag_node_value_mangle=True) - -    zone_policy['firewall'] = conf.get_config_dict(['firewall'], -                                                   key_mangling=('-', '_'), -                                                   get_first_key=True, -                                                   no_tag_node_value_mangle=True) - -    if 'zone' in zone_policy: -        # 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 = defaults(base + ['zone']) -        for zone in zone_policy['zone']: -            zone_policy['zone'][zone] = dict_merge(default_values, -                                                   zone_policy['zone'][zone]) - -    return zone_policy - -def verify(zone_policy): -    # bail out early - looks like removal from running config -    if not zone_policy: -        return None - -    local_zone = False -    interfaces = [] - -    if 'zone' in zone_policy: -        for zone, zone_conf in zone_policy['zone'].items(): -            if 'local_zone' not in zone_conf and 'interface' not in zone_conf: -                raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') - -            if 'local_zone' in zone_conf: -                if local_zone: -                    raise ConfigError('There cannot be multiple local zones') -                if 'interface' in zone_conf: -                    raise ConfigError('Local zone cannot have interfaces assigned') -                if 'intra_zone_filtering' in zone_conf: -                    raise ConfigError('Local zone cannot use intra-zone-filtering') -                local_zone = True - -            if 'interface' in zone_conf: -                found_duplicates = [intf for intf in zone_conf['interface'] if intf in interfaces] - -                if found_duplicates: -                    raise ConfigError(f'Interfaces cannot be assigned to multiple zones') - -                interfaces += zone_conf['interface'] - -            if 'intra_zone_filtering' in zone_conf: -                intra_zone = zone_conf['intra_zone_filtering'] - -                if len(intra_zone) > 1: -                    raise ConfigError('Only one intra-zone-filtering action must be specified') - -                if 'firewall' in intra_zone: -                    v4_name = dict_search_args(intra_zone, 'firewall', 'name') -                    if v4_name and not dict_search_args(zone_policy, 'firewall', 'name', v4_name): -                        raise ConfigError(f'Firewall name "{v4_name}" does not exist') - -                    v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6-name') -                    if v6_name and not dict_search_args(zone_policy, 'firewall', 'ipv6-name', v6_name): -                        raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - -                    if not v4_name and not v6_name: -                        raise ConfigError('No firewall names specified for intra-zone-filtering') - -            if 'from' in zone_conf: -                for from_zone, from_conf in zone_conf['from'].items(): -                    if from_zone not in zone_policy['zone']: -                        raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') - -                    v4_name = dict_search_args(from_conf, 'firewall', 'name') -                    if v4_name: -                        if 'name' not in zone_policy['firewall']: -                            raise ConfigError(f'Firewall name "{v4_name}" does not exist') - -                        if not dict_search_args(zone_policy, 'firewall', 'name', v4_name): -                            raise ConfigError(f'Firewall name "{v4_name}" does not exist') - -                    v6_name = dict_search_args(from_conf, 'firewall', 'v6_name') -                    if v6_name: -                        if 'ipv6_name' not in zone_policy['firewall']: -                            raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - -                        if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name): -                            raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - -    return None - -def has_ipv4_fw(zone_conf): -    if 'from' not in zone_conf: -        return False -    zone_from = zone_conf['from'] -    return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'name')]) - -def has_ipv6_fw(zone_conf): -    if 'from' not in zone_conf: -        return False -    zone_from = zone_conf['from'] -    return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'ipv6_name')]) - -def get_local_from(zone_policy, local_zone_name): -    # Get all zone firewall names from the local zone -    out = {} -    for zone, zone_conf in zone_policy['zone'].items(): -        if zone == local_zone_name: -            continue -        if 'from' not in zone_conf: -            continue -        if local_zone_name in zone_conf['from']: -            out[zone] = zone_conf['from'][local_zone_name] -    return out - -def cleanup_commands(): -    commands = [] -    for table in ['ip filter', 'ip6 filter']: -        json_str = cmd(f'nft -t -j list table {table}') -        obj = loads(json_str) -        if 'nftables' not in obj: -            continue -        for item in obj['nftables']: -            if 'rule' in item: -                chain = item['rule']['chain'] -                handle = item['rule']['handle'] -                if 'expr' not in item['rule']: -                    continue -                for expr in item['rule']['expr']: -                    target = dict_search_args(expr, 'jump', 'target') -                    if not target: -                        continue -                    if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"): -                        commands.append(f'delete rule {table} {chain} handle {handle}') -        for item in obj['nftables']: -            if 'chain' in item: -                if item['chain']['name'].startswith("VZONE"): -                    chain = item['chain']['name'] -                    commands.append(f'delete chain {table} {chain}') -    return commands - -def generate(zone_policy): -    data = zone_policy or {} - -    if os.path.exists(nftables_conf): # Check to see if we've run before -        data['cleanup_commands'] = cleanup_commands() - -    if 'zone' in data: -        for zone, zone_conf in data['zone'].items(): -            zone_conf['ipv4'] = has_ipv4_fw(zone_conf) -            zone_conf['ipv6'] = has_ipv6_fw(zone_conf) - -            if 'local_zone' in zone_conf: -                zone_conf['from_local'] = get_local_from(data, zone) - -    render(nftables_conf, 'zone_policy/nftables.j2', data) -    return None - -def apply(zone_policy): -    install_result = run(f'nft -f {nftables_conf}') -    if install_result != 0: -        raise ConfigError('Failed to apply zone-policy') - -    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/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py index bf4bfd05d..cbc2bfe6b 100755 --- a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -11,7 +11,7 @@ def get_nft_filter_chains():      """      Get list of nft chains for table filter      """ -    nft = cmd('/usr/sbin/nft --json list table ip filter') +    nft = cmd('/usr/sbin/nft --json list table ip vyos_filter')      nft = json.loads(nft)      chain_list = [] @@ -27,7 +27,7 @@ def get_nftables_details(name):      """      Get dict, counters packets and bytes for chain      """ -    command = f'/usr/sbin/nft list chain ip filter {name}' +    command = f'/usr/sbin/nft list chain ip vyos_filter {name}'      try:          results = cmd(command)      except: @@ -60,7 +60,7 @@ def get_nft_telegraf(name):      Get data for telegraf in influxDB format      """      for rule, rule_config in get_nftables_details(name).items(): -        print(f'nftables,table=filter,chain={name},' +        print(f'nftables,table=vyos_filter,chain={name},'                f'ruleid={rule} '                f'pkts={rule_config["packets"]}i,'                f'bytes={rule_config["bytes"]}i ' diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8 new file mode 100755 index 000000000..ce527acf5 --- /dev/null +++ b/src/migration-scripts/firewall/7-to-8 @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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/>. + +# T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name> +# T2199: Migrate zone-policy to firewall node + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['firewall'] +zone_base = ['zone-policy'] +config = ConfigTree(config_file) + +if not config.exists(base) and not config.exists(zone_base): +    # Nothing to do +    exit(0) + +def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): +    if_path = ['interfaces', iftype, ifname] +    ifname_full = ifname + +    if vif: +        if_path += ['vif', vif] +        ifname_full = f'{ifname}.{vif}' +    elif vifs: +        if_path += ['vif-s', vifs] +        ifname_full = f'{ifname}.{vifs}' +        if vifc: +            if_path += ['vif-c', vifc] +            ifname_full = f'{ifname}.{vifs}.{vifc}' + +    if not config.exists(if_path + ['firewall']): +        return + +    if not config.exists(['firewall', 'interface']): +        config.set(['firewall', 'interface']) +        config.set_tag(['firewall', 'interface']) + +    config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full]) +    config.delete(if_path + ['firewall']) + +for iftype in config.list_nodes(['interfaces']): +    for ifname in config.list_nodes(['interfaces', iftype]): +        migrate_interface(config, iftype, ifname) + +        if config.exists(['interfaces', iftype, ifname, 'vif']): +            for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): +                migrate_interface(config, iftype, ifname, vif=vif) + +        if config.exists(['interfaces', iftype, ifname, 'vif-s']): +            for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): +                migrate_interface(config, iftype, ifname, vifs=vifs) + +                if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): +                    for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): +                        migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + +if config.exists(zone_base + ['zone']): +    config.set(['firewall', 'zone']) +    config.set_tag(['firewall', 'zone']) + +    for zone in config.list_nodes(zone_base + ['zone']): +        config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone]) +    config.delete(zone_base) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) | 
