diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/container.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/nat66.py | 28 | ||||
-rwxr-xr-x | src/conf_mode/service_dns_forwarding.py | 20 | ||||
-rw-r--r-- | src/op_mode/execute_port-scan.py | 155 |
4 files changed, 203 insertions, 4 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index ded370a7a..14387cbbf 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -421,6 +421,10 @@ def generate(container): 'driver': 'host-local' } } + + if 'no_name_server' in network_config: + tmp['dns_enabled'] = False + for prefix in network_config['prefix']: net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)} tmp['subnets'].append(net) diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index c44320f36..95dfae3a5 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -26,6 +26,7 @@ from vyos.utils.dict import dict_search from vyos.utils.kernel import check_kmod from vyos.utils.network import interface_exists from vyos.utils.process import cmd +from vyos.utils.process import run from vyos.template import is_ipv6 from vyos import ConfigError from vyos import airbag @@ -48,6 +49,14 @@ def get_config(config=None): if not conf.exists(base): nat['deleted'] = '' + return nat + + nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove dynamic firewall groups if present: + if 'dynamic_group' in nat['firewall_group']: + del nat['firewall_group']['dynamic_group'] return nat @@ -99,22 +108,33 @@ def verify(nat): if not interface_exists(interface_name): Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!') + if 'destination' in config and 'group' in config['destination']: + if len({'address_group', 'network_group', 'domain_group'} & set(config['destination']['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + return None def generate(nat): if not os.path.exists(nftables_nat66_config): nat['first_install'] = True - render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755) + render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat) + + # dry-run newly generated configuration + tmp = run(f'nft --check --file {nftables_nat66_config}') + if tmp > 0: + raise ConfigError('Configuration file errors encountered!') + return None def apply(nat): - if not nat: - return None - check_kmod(k_mod) cmd(f'nft --file {nftables_nat66_config}') + + if not nat or 'deleted' in nat: + os.unlink(nftables_nat66_config) + call_dependents() return None diff --git a/src/conf_mode/service_dns_forwarding.py b/src/conf_mode/service_dns_forwarding.py index 70686534f..e3bdbc9f8 100755 --- a/src/conf_mode/service_dns_forwarding.py +++ b/src/conf_mode/service_dns_forwarding.py @@ -224,6 +224,18 @@ def get_config(config=None): dns['authoritative_zones'].append(zone) + if 'zone_cache' in dns: + # convert refresh interval to sec: + for _, zone_conf in dns['zone_cache'].items(): + if 'options' in zone_conf \ + and 'refresh' in zone_conf['options']: + + if 'on_reload' in zone_conf['options']['refresh']: + interval = 0 + else: + interval = zone_conf['options']['refresh']['interval'] + zone_conf['options']['refresh']['interval'] = interval + return dns def verify(dns): @@ -259,8 +271,16 @@ def verify(dns): if not 'system_name_server' in dns: print('Warning: No "system name-server" configured') + if 'zone_cache' in dns: + for name, conf in dns['zone_cache'].items(): + if ('source' not in conf) \ + or ('url' in conf['source'] and 'axfr' in conf['source']): + raise ConfigError(f'Invalid configuration for zone "{name}": ' + f'Please select one source type "url" or "axfr".') + return None + def generate(dns): # bail out early - looks like removal from running config if not dns: diff --git a/src/op_mode/execute_port-scan.py b/src/op_mode/execute_port-scan.py new file mode 100644 index 000000000..bf17d0379 --- /dev/null +++ b/src/op_mode/execute_port-scan.py @@ -0,0 +1,155 @@ +#! /usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from vyos.utils.process import call + + +options = { + 'port': { + 'cmd': '{command} -p {value}', + 'type': '<1-65535> <list>', + 'help': 'Scan specified ports.' + }, + 'tcp': { + 'cmd': '{command} -sT', + 'type': 'noarg', + 'help': 'Use TCP scan.' + }, + 'udp': { + 'cmd': '{command} -sU', + 'type': 'noarg', + 'help': 'Use UDP scan.' + }, + 'skip-ping': { + 'cmd': '{command} -Pn', + 'type': 'noarg', + 'help': 'Skip the Nmap discovery stage altogether.' + }, + 'ipv6': { + 'cmd': '{command} -6', + 'type': 'noarg', + 'help': 'Enable IPv6 scanning.' + }, +} + +nmap = 'sudo /usr/bin/nmap' + + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expansion_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expansion_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['cmd'].format( + command=command, value='') + elif not args: + sys.exit(f'port-scan: missing argument for {longname} option') + else: + command = options[longname]['cmd'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if host == '--get-options-nested': + args.first() # pop execute + args.first() # pop port-scan + args.first() # pop host + args.first() # pop <host> + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + if not matched: + sys.stdout.write('<nocomps>') + else: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + sys.stdout.write(helplines) + sys.exit(0) + + command = convert(nmap, args) + call(f'{command} -T4 {host}') |