summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/firewall.py21
-rwxr-xr-xsrc/conf_mode/nat.py20
-rwxr-xr-xsrc/conf_mode/service_monitoring_node-exporter.py101
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py13
-rwxr-xr-xsrc/helpers/vyos-domain-resolver.py107
-rw-r--r--src/systemd/vyos-domain-resolver.service1
-rwxr-xr-xsrc/utils/vyos-show-config57
-rwxr-xr-xsrc/validators/ipv4-range42
-rwxr-xr-xsrc/validators/ipv6-range26
9 files changed, 278 insertions, 110 deletions
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 5638a9668..ffbd915a2 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -36,11 +36,15 @@ from vyos.utils.process import cmd
from vyos.utils.process import rc_cmd
from vyos import ConfigError
from vyos import airbag
+from pathlib import Path
from subprocess import run as subp_run
airbag.enable()
nftables_conf = '/run/nftables.conf'
+domain_resolver_usage = '/run/use-vyos-domain-resolver-firewall'
+domain_resolver_usage_nat = '/run/use-vyos-domain-resolver-nat'
+
sysctl_file = r'/run/sysctl/10-vyos-firewall.conf'
valid_groups = [
@@ -128,7 +132,7 @@ def get_config(config=None):
firewall['geoip_updated'] = geoip_updated(conf, firewall)
- fqdn_config_parse(firewall)
+ fqdn_config_parse(firewall, 'firewall')
set_dependents('conntrack', conf)
@@ -570,12 +574,15 @@ def apply(firewall):
call_dependents()
- # T970 Enable a resolver (systemd daemon) that checks
- # domain-group/fqdn addresses and update entries for domains by timeout
- # If router loaded without internet connection or for synchronization
- domain_action = 'stop'
- if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']:
- domain_action = 'restart'
+ ## DOMAIN RESOLVER
+ domain_action = 'restart'
+ if 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:
+ Path(domain_resolver_usage).unlink(missing_ok=True)
+ if not Path('/run').glob('use-vyos-domain-resolver*'):
+ domain_action = 'stop'
call(f'systemctl {domain_action} vyos-domain-resolver.service')
if firewall['geoip_updated']:
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 39803fa02..98b2f3f29 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -26,10 +26,13 @@ from vyos.template import is_ip_network
from vyos.utils.kernel import check_kmod
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
+from vyos.utils.file import write_file
from vyos.utils.process import cmd
from vyos.utils.process import run
+from vyos.utils.process import call
from vyos.utils.network import is_addr_assigned
from vyos.utils.network import interface_exists
+from vyos.firewall import fqdn_config_parse
from vyos import ConfigError
from vyos import airbag
@@ -39,6 +42,8 @@ k_mod = ['nft_nat', 'nft_chain_nat']
nftables_nat_config = '/run/nftables_nat.conf'
nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
+domain_resolver_usage = '/run/use-vyos-domain-resolver-nat'
+domain_resolver_usage_firewall = '/run/use-vyos-domain-resolver-firewall'
valid_groups = [
'address_group',
@@ -71,6 +76,8 @@ def get_config(config=None):
if 'dynamic_group' in nat['firewall_group']:
del nat['firewall_group']['dynamic_group']
+ fqdn_config_parse(nat, 'nat')
+
return nat
def verify_rule(config, err_msg, groups_dict):
@@ -251,6 +258,19 @@ def apply(nat):
call_dependents()
+ # DOMAIN RESOLVER
+ if nat and 'deleted' not in nat:
+ domain_action = 'restart'
+ if nat['ip_fqdn'].items():
+ text = f'# Automatically generated by nat.py\nThis file indicates that vyos-domain-resolver service is used by nat.\n'
+ write_file(domain_resolver_usage, text)
+ elif os.path.exists(domain_resolver_usage):
+ os.unlink(domain_resolver_usage)
+ if not os.path.exists(domain_resolver_usage_firewall):
+ # Firewall not using domain resolver
+ domain_action = 'stop'
+ call(f'systemctl {domain_action} vyos-domain-resolver.service')
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/service_monitoring_node-exporter.py b/src/conf_mode/service_monitoring_node-exporter.py
new file mode 100755
index 000000000..db34bb5d0
--- /dev/null
+++ b/src/conf_mode/service_monitoring_node-exporter.py
@@ -0,0 +1,101 @@
+#!/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 os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
+from vyos.template import render
+from vyos.utils.process import call
+from vyos import ConfigError
+from vyos import airbag
+
+
+airbag.enable()
+
+service_file = '/etc/systemd/system/node_exporter.service'
+systemd_service = 'node_exporter.service'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'monitoring', 'node-exporter']
+ if not conf.exists(base):
+ return None
+
+ config_data = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True
+ )
+ config_data = conf.merge_defaults(config_data, recursive=True)
+
+ tmp = is_node_changed(conf, base + ['vrf'])
+ if tmp:
+ config_data.update({'restart_required': {}})
+
+ return config_data
+
+
+def verify(config_data):
+ # bail out early - looks like removal from running config
+ if not config_data:
+ return None
+
+ verify_vrf(config_data)
+ return None
+
+
+def generate(config_data):
+ if not config_data:
+ # Delete systemd files
+ if os.path.isfile(service_file):
+ os.unlink(service_file)
+ return None
+
+ # Render node_exporter service_file
+ render(service_file, 'node_exporter/node_exporter.service.j2', config_data)
+ return None
+
+
+def apply(config_data):
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+ if not config_data:
+ call(f'systemctl stop {systemd_service}')
+ return
+
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in config_data:
+ systemd_action = 'restart'
+
+ call(f'systemctl {systemd_action} {systemd_service}')
+
+
+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/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index ca0c3657f..e22b7550c 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -214,6 +214,19 @@ def verify(ipsec):
else:
verify_interface_exists(ipsec, interface)
+ # need to use a pseudo-random function (PRF) with an authenticated encryption algorithm.
+ # If a hash algorithm is defined then it will be mapped to an equivalent PRF
+ if 'ike_group' in ipsec:
+ for _, ike_config in ipsec['ike_group'].items():
+ for proposal, proposal_config in ike_config.get('proposal', {}).items():
+ if 'encryption' in proposal_config and 'prf' not in proposal_config:
+ # list of hash algorithms that cannot be mapped to an equivalent PRF
+ algs = ['aes128gmac', 'aes192gmac', 'aes256gmac', 'sha256_96']
+ if 'hash' in proposal_config and proposal_config['hash'] in algs:
+ raise ConfigError(
+ f"A PRF algorithm is mandatory in IKE proposal {proposal}"
+ )
+
if 'l2tp' in ipsec:
if 'esp_group' in ipsec['l2tp']:
if 'esp_group' not in ipsec or ipsec['l2tp']['esp_group'] not in ipsec['esp_group']:
diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py
index 57cfcabd7..f5a1d9297 100755
--- a/src/helpers/vyos-domain-resolver.py
+++ b/src/helpers/vyos-domain-resolver.py
@@ -30,6 +30,8 @@ from vyos.xml_ref import get_defaults
base = ['firewall']
timeout = 300
cache = False
+base_firewall = ['firewall']
+base_nat = ['nat']
domain_state = {}
@@ -46,25 +48,25 @@ ipv6_tables = {
'ip6 raw'
}
-def get_config(conf):
- firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+def get_config(conf, node):
+ node_config = conf.get_config_dict(node, key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
- default_values = get_defaults(base, get_first_key=True)
+ default_values = get_defaults(node, get_first_key=True)
- firewall = dict_merge(default_values, firewall)
+ node_config = dict_merge(default_values, node_config)
global timeout, cache
- if 'resolver_interval' in firewall:
- timeout = int(firewall['resolver_interval'])
+ if 'resolver_interval' in node_config:
+ timeout = int(node_config['resolver_interval'])
- if 'resolver_cache' in firewall:
+ if 'resolver_cache' in node_config:
cache = True
- fqdn_config_parse(firewall)
+ fqdn_config_parse(node_config, node[0])
- return firewall
+ return node_config
def resolve(domains, ipv6=False):
global domain_state
@@ -108,55 +110,60 @@ def nft_valid_sets():
except:
return []
-def update(firewall):
+def update_fqdn(config, node):
conf_lines = []
count = 0
-
valid_sets = nft_valid_sets()
- domain_groups = dict_search_args(firewall, 'group', 'domain_group')
- if domain_groups:
- for set_name, domain_config in domain_groups.items():
- if 'address' not in domain_config:
- continue
-
- nft_set_name = f'D_{set_name}'
- domains = domain_config['address']
-
- ip_list = resolve(domains, ipv6=False)
- for table in ipv4_tables:
- if (table, nft_set_name) in valid_sets:
- conf_lines += nft_output(table, nft_set_name, ip_list)
-
- ip6_list = resolve(domains, ipv6=True)
- for table in ipv6_tables:
- if (table, nft_set_name) in valid_sets:
- conf_lines += nft_output(table, nft_set_name, ip6_list)
+ if node == 'firewall':
+ domain_groups = dict_search_args(config, 'group', 'domain_group')
+ if domain_groups:
+ for set_name, domain_config in domain_groups.items():
+ if 'address' not in domain_config:
+ continue
+ nft_set_name = f'D_{set_name}'
+ domains = domain_config['address']
+
+ ip_list = resolve(domains, ipv6=False)
+ for table in ipv4_tables:
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip_list)
+ ip6_list = resolve(domains, ipv6=True)
+ for table in ipv6_tables:
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip6_list)
+ count += 1
+
+ for set_name, domain in config['ip_fqdn'].items():
+ table = 'ip vyos_filter'
+ nft_set_name = f'FQDN_{set_name}'
+ ip_list = resolve([domain], ipv6=False)
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip_list)
count += 1
- for set_name, domain in firewall['ip_fqdn'].items():
- table = 'ip vyos_filter'
- nft_set_name = f'FQDN_{set_name}'
-
- ip_list = resolve([domain], ipv6=False)
-
- if (table, nft_set_name) in valid_sets:
- conf_lines += nft_output(table, nft_set_name, ip_list)
- count += 1
-
- for set_name, domain in firewall['ip6_fqdn'].items():
- table = 'ip6 vyos_filter'
- nft_set_name = f'FQDN_{set_name}'
+ for set_name, domain in config['ip6_fqdn'].items():
+ table = 'ip6 vyos_filter'
+ nft_set_name = f'FQDN_{set_name}'
+ ip_list = resolve([domain], ipv6=True)
+ if (table, nft_set_name) in valid_sets:
+ conf_lines += nft_output(table, nft_set_name, ip_list)
+ count += 1
- ip_list = resolve([domain], ipv6=True)
- if (table, nft_set_name) in valid_sets:
- conf_lines += nft_output(table, nft_set_name, ip_list)
- count += 1
+ else:
+ # It's NAT
+ for set_name, domain in config['ip_fqdn'].items():
+ table = 'ip vyos_nat'
+ nft_set_name = f'FQDN_nat_{set_name}'
+ ip_list = resolve([domain], ipv6=False)
+ 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)
- print(f'Updated {count} sets - result: {code}')
+ print(f'Updated {count} sets in {node} - result: {code}')
if __name__ == '__main__':
print(f'VyOS domain resolver')
@@ -169,10 +176,12 @@ if __name__ == '__main__':
time.sleep(1)
conf = ConfigTreeQuery()
- firewall = get_config(conf)
+ firewall = get_config(conf, base_firewall)
+ nat = get_config(conf, base_nat)
print(f'interval: {timeout}s - cache: {cache}')
while True:
- update(firewall)
+ update_fqdn(firewall, 'firewall')
+ update_fqdn(nat, 'nat')
time.sleep(timeout)
diff --git a/src/systemd/vyos-domain-resolver.service b/src/systemd/vyos-domain-resolver.service
index c56b51f0c..e63ae5e34 100644
--- a/src/systemd/vyos-domain-resolver.service
+++ b/src/systemd/vyos-domain-resolver.service
@@ -1,6 +1,7 @@
[Unit]
Description=VyOS firewall domain resolver
After=vyos-router.service
+ConditionPathExistsGlob=/run/use-vyos-domain-resolver*
[Service]
Type=simple
diff --git a/src/utils/vyos-show-config b/src/utils/vyos-show-config
new file mode 100755
index 000000000..152322fc1
--- /dev/null
+++ b/src/utils/vyos-show-config
@@ -0,0 +1,57 @@
+#!/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 os
+import sys
+import argparse
+
+from signal import signal, SIGPIPE, SIG_DFL
+
+def get_config(path):
+ from vyos.utils.process import rc_cmd
+ res, out = rc_cmd(f"cli-shell-api showCfg {path}")
+ if res > 0:
+ print("Error: failed to retrieve the config", file=sys.stderr)
+ sys.exit(1)
+ else:
+ return out
+
+def strip_config(config):
+ from vyos.utils.strip_config import strip_config_source
+ return strip_config_source(config)
+
+if __name__ == '__main__':
+ signal(SIGPIPE,SIG_DFL)
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--strip-private",
+ help="Strip private information from the config",
+ action="store_true")
+
+ args, path_args = parser.parse_known_args()
+
+ config = get_config(" ".join(path_args))
+
+ if args.strip_private:
+ edit_level = os.getenv("VYATTA_EDIT_LEVEL")
+ if (edit_level != "/") or (len(path_args) > 0):
+ print("Error: show --strip-private only works at the top level",
+ file=sys.stderr)
+ sys.exit(1)
+ else:
+ print(strip_config(config))
+ else:
+ print(config)
diff --git a/src/validators/ipv4-range b/src/validators/ipv4-range
index 6492bfc52..7bf271bbb 100755
--- a/src/validators/ipv4-range
+++ b/src/validators/ipv4-range
@@ -1,40 +1,10 @@
-#!/bin/bash
+#!/bin/sh
-# snippet from https://stackoverflow.com/questions/10768160/ip-address-converter
-ip2dec () {
- local a b c d ip=$@
- IFS=. read -r a b c d <<< "$ip"
- printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
-}
+ipaddrcheck --verbose --is-ipv4-range "$1"
-error_exit() {
- echo "Error: $1 is not a valid IPv4 address range"
- exit 1
-}
-
-# Only run this if there is a hypen present in $1
-if [[ "$1" =~ "-" ]]; then
- # This only works with real bash (<<<) - split IP addresses into array with
- # hyphen as delimiter
- readarray -d - -t strarr <<< $1
-
- ipaddrcheck --is-ipv4-single ${strarr[0]}
- if [ $? -gt 0 ]; then
- error_exit $1
- fi
-
- ipaddrcheck --is-ipv4-single ${strarr[1]}
- if [ $? -gt 0 ]; then
- error_exit $1
- fi
-
- start=$(ip2dec ${strarr[0]})
- stop=$(ip2dec ${strarr[1]})
- if [ $start -ge $stop ]; then
- error_exit $1
- fi
-
- exit 0
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 address range"
+ exit 1
fi
-error_exit $1
+exit 0
diff --git a/src/validators/ipv6-range b/src/validators/ipv6-range
index 7080860c4..0d2eb6384 100755
--- a/src/validators/ipv6-range
+++ b/src/validators/ipv6-range
@@ -1,20 +1,10 @@
-#!/usr/bin/env python3
+#!/bin/sh
-from ipaddress import IPv6Address
-from sys import argv, exit
+ipaddrcheck --verbose --is-ipv6-range "$1"
-if __name__ == '__main__':
- if len(argv) > 1:
- # try to pass validation and raise an error if failed
- try:
- ipv6_range = argv[1]
- range_left = ipv6_range.split('-')[0]
- range_right = ipv6_range.split('-')[1]
- if not IPv6Address(range_left) < IPv6Address(range_right):
- raise ValueError(f'left element {range_left} must be less than right element {range_right}')
- except Exception as err:
- print(f'Error: {ipv6_range} is not a valid IPv6 range: {err}')
- exit(1)
- else:
- print('Error: an IPv6 range argument must be provided')
- exit(1)
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 address range"
+ exit 1
+fi
+
+exit 0