summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.j22
-rw-r--r--data/templates/ocserv/ocserv_config.j213
-rw-r--r--data/templates/rsyslog/rsyslog.conf.j22
-rw-r--r--interface-definitions/load-balancing-wan.xml.in1
-rw-r--r--interface-definitions/vpn-openconnect.xml.in52
-rw-r--r--python/vyos/base.py3
-rw-r--r--python/vyos/ifconfig/ethernet.py9
-rw-r--r--python/vyos/util.py7
-rw-r--r--python/vyos/utils/dict.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py18
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py3
-rwxr-xr-xsrc/conf_mode/high-availability.py10
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py9
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py14
-rwxr-xr-xsrc/op_mode/interfaces.py34
-rw-r--r--src/tests/test_util.py16
16 files changed, 145 insertions, 50 deletions
diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2
index c2c9b1dd6..e8ef5ac90 100644
--- a/data/templates/dynamic-dns/ddclient.conf.j2
+++ b/data/templates/dynamic-dns/ddclient.conf.j2
@@ -34,7 +34,9 @@ zone={{ config.zone }}
# DynDNS provider configuration for {{ service }}, {{ dns_record }}
protocol={{ config.protocol }},
max-interval=28d,
+{% if config.login is vyos_defined %}
login={{ config.login }},
+{% endif %}
password='{{ config.password }}',
{% if config.server is vyos_defined %}
server={{ config.server }},
diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index aa1073bca..1401b8b26 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -16,6 +16,12 @@ acct = "radius [config=/run/ocserv/radiusclient.conf]"
{% if "radius" in authentication.mode %}
auth = "radius [config=/run/ocserv/radiusclient.conf{{ ',groupconfig=true' if authentication.radius.groupconfig is vyos_defined else '' }}]"
+{% if authentication.identity_based_config.disabled is not vyos_defined %}
+{% if "group" in authentication.identity_based_config.mode %}
+config-per-group = {{ authentication.identity_based_config.directory }}
+default-group-config = {{ authentication.identity_based_config.default_config }}
+{% endif %}
+{% endif %}
{% elif "local" in authentication.mode %}
{% if authentication.mode.local == "password-otp" %}
auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"
@@ -28,6 +34,13 @@ auth = "plain[/run/ocserv/ocpasswd]"
auth = "plain[/run/ocserv/ocpasswd]"
{% endif %}
+{% if "identity_based_config" in authentication %}
+{% if "user" in authentication.identity_based_config.mode %}
+config-per-user = {{ authentication.identity_based_config.directory }}
+default-user-config = {{ authentication.identity_based_config.default_config }}
+{% endif %}
+{% endif %}
+
{% if ssl.certificate is vyos_defined %}
server-cert = /run/ocserv/cert.pem
server-key = /run/ocserv/cert.key
diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
index 0460ae5f0..5352fc367 100644
--- a/data/templates/rsyslog/rsyslog.conf.j2
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -49,7 +49,7 @@ $outchannel {{ file_name }},/var/log/user/{{ file_name }},{{ file_options.archiv
{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
{% endfor %}
{% if host_options.protocol is vyos_defined('tcp') %}
-{% if host_options.oct_count is vyos_defined %}
+{% if host_options.format.octet_counted is vyos_defined %}
{{ tmp | join(';') }} @@(o){{ host_name | bracketize_ipv6 }}:{{ host_options.port }};RSYSLOG_SyslogProtocol23Format
{% else %}
{{ tmp | join(';') }} @@{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}
diff --git a/interface-definitions/load-balancing-wan.xml.in b/interface-definitions/load-balancing-wan.xml.in
index 3a2c111ac..c12cab22a 100644
--- a/interface-definitions/load-balancing-wan.xml.in
+++ b/interface-definitions/load-balancing-wan.xml.in
@@ -179,6 +179,7 @@
<regex>(ping|ttl|user-defined)</regex>
</constraint>
</properties>
+ <defaultValue>ping</defaultValue>
</leafNode>
</children>
</tagNode>
diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in
index a426f604d..75c64a99a 100644
--- a/interface-definitions/vpn-openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -71,6 +71,58 @@
</leafNode>
</children>
</node>
+ <node name="identity-based-config">
+ <properties>
+ <help>Include configuration file by username or RADIUS group attribute</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <leafNode name="mode">
+ <properties>
+ <help>Select per user or per group configuration file - ignored if authentication group is configured</help>
+ <completionHelp>
+ <list>user group</list>
+ </completionHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Match configuration file on username</description>
+ </valueHelp>
+ <valueHelp>
+ <format>group</format>
+ <description>Match RADIUS response class attribute as file name</description>
+ </valueHelp>
+ <constraint>
+ <regex>(user|group)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid mode, must be either user or group</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="directory">
+ <properties>
+ <help>Directory to containing configuration files</help>
+ <valueHelp>
+ <format>path</format>
+ <description>Path to configuration directory, must be under /config/auth</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-path" argument="--directory --parent-dir /config/auth --strict"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="default-config">
+ <properties>
+ <help>Default configuration if discrete config could not be found</help>
+ <valueHelp>
+ <format>filename</format>
+ <description>Default configuration filename, must be under /config/auth</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-path" argument="--file --parent-dir /config/auth --strict"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="group">
<properties>
<help>Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute.</help>
diff --git a/python/vyos/base.py b/python/vyos/base.py
index c1acfd060..9b93cb2f2 100644
--- a/python/vyos/base.py
+++ b/python/vyos/base.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -41,6 +41,7 @@ class BaseWarning:
isfirstmessage = False
initial_indent = self.standardindent
print(f'{mes}')
+ print('')
class Warning():
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 6a49c022a..30bea3b86 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -374,10 +374,11 @@ class EthernetIf(Interface):
self.set_tso(dict_search('offload.tso', config) != None)
# Set physical interface speed and duplex
- if {'speed', 'duplex'} <= set(config):
- speed = config.get('speed')
- duplex = config.get('duplex')
- self.set_speed_duplex(speed, duplex)
+ if 'speed_duplex_changed' in config:
+ if {'speed', 'duplex'} <= set(config):
+ speed = config.get('speed')
+ duplex = config.get('duplex')
+ self.set_speed_duplex(speed, duplex)
# Set interface ring buffer
if 'ring_buffer' in config:
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 0593184cc..d5330db13 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -1146,13 +1146,6 @@ def sysctl_write(name, value):
return True
return False
-# approach follows a discussion in:
-# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
-def camel_to_snake_case(name: str) -> str:
- pattern = r'\d+|[A-Z]?[a-z]+|\W|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)'
- words = re.findall(pattern, name)
- return '_'.join(map(str.lower, words))
-
def load_as_module(name: str, path: str):
import importlib.util
diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py
index 4afc9f54e..7c93deef6 100644
--- a/python/vyos/utils/dict.py
+++ b/python/vyos/utils/dict.py
@@ -253,4 +253,4 @@ def check_mutually_exclusive_options(d, keys, required=False):
raise ValueError(f"Options {orig_keys} are mutually-exclusive but more than one of them is present: {orig_present_keys}")
if required and (len(present_keys) < 1):
- raise ValueError(f"At least one of the following options is required: {orig_present_keys}")
+ raise ValueError(f"At least one of the following options is required: {orig_keys}")
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index 57705e26f..a3aa41f94 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -45,22 +45,32 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_dyndns_service(self):
+ from itertools import product
ddns = ['interface', interface, 'service']
+ users = [None, 'vyos_user']
services = ['cloudflare', 'afraid', 'dyndns', 'zoneedit']
- for service in services:
- user = 'vyos_user'
+ for user, service in product(users, services):
password = 'vyos_pass'
zone = 'vyos.io'
self.cli_delete(base_path)
self.cli_set(base_path + ddns + [service, 'host-name', hostname])
- self.cli_set(base_path + ddns + [service, 'login', user])
+ if user is not None:
+ self.cli_set(base_path + ddns + [service, 'login', user])
self.cli_set(base_path + ddns + [service, 'password', password])
self.cli_set(base_path + ddns + [service, 'zone', zone])
# commit changes
if service == 'cloudflare':
self.cli_commit()
+ elif user is None:
+ # not set user is only allowed for cloudflare
+ with self.assertRaises(ConfigSessionError):
+ # remove zone to test not set user
+ self.cli_delete(base_path + ddns + [service, 'zone', 'vyos.io'])
+ self.cli_commit()
+ # this case is fininshed, user not set is not allowed when service isn't cloudflare
+ continue
else:
# zone option only works on cloudflare, an exception is raised
# for all others
@@ -72,7 +82,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
# we can only read the configuration file when we operate as 'root'
protocol = get_config_value('protocol')
- login = get_config_value('login')
+ login = None if user is None else get_config_value('login')
pwd = get_config_value('password')
# some services need special treatment
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index 06a2f7e15..426e3d693 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -108,7 +108,8 @@ def verify(dyndns):
raise ConfigError(f'"host-name" {error_msg}')
if 'login' not in config:
- raise ConfigError(f'"login" (username) {error_msg}')
+ if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'):
+ raise ConfigError(f'"login" (username) {error_msg}, unless using CloudFlare')
if 'password' not in config:
raise ConfigError(f'"password" {error_msg}')
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 7a63f5b4b..e18b426b1 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -21,6 +21,7 @@ from ipaddress import ip_interface
from ipaddress import IPv4Interface
from ipaddress import IPv6Interface
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.ifconfig.vrrp import VRRP
@@ -107,11 +108,16 @@ def verify(ha):
raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"')
if 'health_check' in group_config:
+ health_check_types = ["script", "ping"]
from vyos.utils.dict import check_mutually_exclusive_options
try:
- check_mutually_exclusive_options(group_config["health_check"], ["script", "ping"], required=True)
+ check_mutually_exclusive_options(group_config["health_check"], health_check_types, required=True)
except ValueError as e:
- raise ConfigError(f'Health check config is incorrect in VRRP group "{group}": {e}')
+ Warning(f'Health check configuration for VRRP group "{group}" will remain unused ' \
+ f'until it has one of the following options: {health_check_types}')
+ # XXX: health check has default options so we need to remove it
+ # to avoid generating useless config statements in keepalived.conf
+ del group_config["health_check"]
# Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# We also need to make sure VRID is not used twice on the same interface with the
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index b49c945cd..31cfab368 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -22,6 +22,7 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_eapol
@@ -66,11 +67,17 @@ def get_config(config=None):
get_first_key=True, no_tag_node_value_mangle=True)
base = ['interfaces', 'ethernet']
- _, ethernet = get_interface_dict(conf, base)
+ ifname, ethernet = get_interface_dict(conf, base)
if 'deleted' not in ethernet:
if pki: ethernet['pki'] = pki
+ tmp = is_node_changed(conf, base + [ifname, 'speed'])
+ if tmp: ethernet.update({'speed_duplex_changed': {}})
+
+ tmp = is_node_changed(conf, base + [ifname, 'duplex'])
+ if tmp: ethernet.update({'speed_duplex_changed': {}})
+
return ethernet
def verify(ethernet):
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 68da70d7d..83021a3e6 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.pki import wrap_certificate
@@ -173,6 +174,19 @@ def verify(ocserv):
users_wo_pswd.append(user)
if users_wo_pswd:
raise ConfigError(f'password required for users:\n{users_wo_pswd}')
+
+ # Validate that if identity-based-config is configured all child config nodes are set
+ if 'identity_based_config' in ocserv["authentication"]:
+ if 'disabled' not in ocserv["authentication"]["identity_based_config"]:
+ Warning("Identity based configuration files is a 3rd party addition. Use at your own risk, this might break the ocserv daemon!")
+ if 'mode' not in ocserv["authentication"]["identity_based_config"]:
+ raise ConfigError('OpenConnect radius identity-based-config enabled but mode not selected')
+ elif 'group' in ocserv["authentication"]["identity_based_config"]["mode"] and "radius" not in ocserv["authentication"]["mode"]:
+ raise ConfigError('OpenConnect config-per-group must be used with radius authentication')
+ if 'directory' not in ocserv["authentication"]["identity_based_config"]:
+ raise ConfigError('OpenConnect identity-based-config enabled but directory not set')
+ if 'default_config' not in ocserv["authentication"]["identity_based_config"]:
+ raise ConfigError('OpenConnect identity-based-config enabled but default-config not set')
else:
raise ConfigError('openconnect authentication mode required')
else:
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
index dd87b5901..f38b95a71 100755
--- a/src/op_mode/interfaces.py
+++ b/src/op_mode/interfaces.py
@@ -277,6 +277,10 @@ def _get_counter_data(ifname: typing.Optional[str],
res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes'])
res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets'])
res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes'])
+ res_intf['rx_dropped'] = _get_counter_val(cache['rx_dropped'], stats['rx_dropped'])
+ res_intf['tx_dropped'] = _get_counter_val(cache['tx_dropped'], stats['tx_dropped'])
+ res_intf['rx_over_errors'] = _get_counter_val(cache['rx_over_errors'], stats['rx_over_errors'])
+ res_intf['tx_carrier_errors'] = _get_counter_val(cache['tx_carrier_errors'], stats['tx_carrier_errors'])
ret.append(res_intf)
@@ -368,19 +372,23 @@ def _format_show_summary(data):
@catch_broken_pipe
def _format_show_counters(data: list):
- formatting = '%-12s %10s %10s %10s %10s'
- print(formatting % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
-
- for intf in data:
- print(formatting % (
- intf['ifname'],
- intf['rx_packets'],
- intf['rx_bytes'],
- intf['tx_packets'],
- intf['tx_bytes']
- ))
-
- return 0
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ rx_packets = entry.get('rx_packets')
+ rx_bytes = entry.get('rx_bytes')
+ tx_packets = entry.get('tx_packets')
+ tx_bytes = entry.get('tx_bytes')
+ rx_dropped = entry.get('rx_dropped')
+ tx_dropped = entry.get('tx_dropped')
+ rx_errors = entry.get('rx_over_errors')
+ tx_errors = entry.get('tx_carrier_errors')
+ data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors])
+
+ headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors']
+ output = tabulate(data_entries, headers, numalign="left")
+ print (output)
+ return output
def show(raw: bool, intf_name: typing.Optional[str],
intf_type: typing.Optional[str],
diff --git a/src/tests/test_util.py b/src/tests/test_util.py
index d8b2b7940..473052bef 100644
--- a/src/tests/test_util.py
+++ b/src/tests/test_util.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 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
@@ -26,17 +26,3 @@ class TestVyOSUtil(TestCase):
def test_sysctl_read(self):
self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1')
-
- def test_camel_to_snake_case(self):
- self.assertEqual(camel_to_snake_case('ConnectionTimeout'),
- 'connection_timeout')
- self.assertEqual(camel_to_snake_case('connectionTimeout'),
- 'connection_timeout')
- self.assertEqual(camel_to_snake_case('TCPConnectionTimeout'),
- 'tcp_connection_timeout')
- self.assertEqual(camel_to_snake_case('TCPPort'),
- 'tcp_port')
- self.assertEqual(camel_to_snake_case('UseHTTPProxy'),
- 'use_http_proxy')
- self.assertEqual(camel_to_snake_case('CustomerID'),
- 'customer_id')