summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/config_mgmt.py96
-rwxr-xr-xsrc/conf_mode/container.py16
-rwxr-xr-xsrc/conf_mode/high-availability.py22
-rwxr-xr-xsrc/conf_mode/interfaces-input.py70
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py3
-rwxr-xr-xsrc/conf_mode/snmp.py4
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py97
7 files changed, 286 insertions, 22 deletions
diff --git a/src/conf_mode/config_mgmt.py b/src/conf_mode/config_mgmt.py
new file mode 100755
index 000000000..c681a8405
--- /dev/null
+++ b/src/conf_mode/config_mgmt.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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
+
+from vyos import ConfigError
+from vyos.config import Config
+from vyos.config_mgmt import ConfigMgmt
+from vyos.config_mgmt import commit_post_hook_dir, commit_hooks
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['system', 'config-management']
+ if not conf.exists(base):
+ return None
+
+ mgmt = ConfigMgmt(config=conf)
+
+ return mgmt
+
+def verify(_mgmt):
+ return
+
+def generate(mgmt):
+ if mgmt is None:
+ return
+
+ mgmt.initialize_revision()
+
+def apply(mgmt):
+ if mgmt is None:
+ return
+
+ locations = mgmt.locations
+ archive_target = os.path.join(commit_post_hook_dir,
+ commit_hooks['commit_archive'])
+ if locations:
+ try:
+ os.symlink('/usr/bin/config-mgmt', archive_target)
+ except FileExistsError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+ else:
+ try:
+ os.unlink(archive_target)
+ except FileNotFoundError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+
+ revisions = mgmt.max_revisions
+ revision_target = os.path.join(commit_post_hook_dir,
+ commit_hooks['commit_revision'])
+ if revisions > 0:
+ try:
+ os.symlink('/usr/bin/config-mgmt', revision_target)
+ except FileExistsError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+ else:
+ try:
+ os.unlink(revision_target)
+ except FileNotFoundError:
+ pass
+ except OSError as exc:
+ raise ConfigError from exc
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 7567444db..08861053d 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -75,6 +75,8 @@ def get_config(config=None):
default_values = defaults(base + ['name'])
if 'port' in default_values:
del default_values['port']
+ if 'volume' in default_values:
+ del default_values['volume']
for name in container['name']:
container['name'][name] = dict_merge(default_values, container['name'][name])
@@ -85,6 +87,13 @@ def get_config(config=None):
default_values = defaults(base + ['name', 'port'])
container['name'][name]['port'][port] = dict_merge(
default_values, container['name'][name]['port'][port])
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'volume' in container['name'][name]:
+ for volume in container['name'][name]['volume']:
+ default_values = defaults(base + ['name', 'volume'])
+ container['name'][name]['volume'][volume] = dict_merge(
+ default_values, container['name'][name]['volume'][volume])
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
@@ -245,7 +254,7 @@ def generate_run_arguments(name, container_config):
env_opt = ''
if 'environment' in container_config:
for k, v in container_config['environment'].items():
- env_opt += f" -e \"{k}={v['value']}\""
+ env_opt += f" --env \"{k}={v['value']}\""
# Publish ports
port = ''
@@ -255,7 +264,7 @@ def generate_run_arguments(name, container_config):
protocol = container_config['port'][portmap]['protocol']
sport = container_config['port'][portmap]['source']
dport = container_config['port'][portmap]['destination']
- port += f' -p {sport}:{dport}/{protocol}'
+ port += f' --publish {sport}:{dport}/{protocol}'
# Bind volume
volume = ''
@@ -263,7 +272,8 @@ def generate_run_arguments(name, container_config):
for vol, vol_config in container_config['volume'].items():
svol = vol_config['source']
dvol = vol_config['destination']
- volume += f' -v {svol}:{dvol}'
+ mode = vol_config['mode']
+ volume += f' --volume {svol}:{dvol}:{mode}'
container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 4ed16d0d7..79e407efd 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -28,6 +28,7 @@ from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.util import call
+from vyos.util import dict_search
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -49,10 +50,27 @@ def get_config(config=None):
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
if 'vrrp' in ha:
+ if dict_search('vrrp.global_parameters.garp', ha) != None:
+ default_values = defaults(base_vrrp + ['global-parameters', 'garp'])
+ ha['vrrp']['global_parameters']['garp'] = dict_merge(
+ default_values, ha['vrrp']['global_parameters']['garp'])
+
if 'group' in ha['vrrp']:
- default_values_vrrp = defaults(base_vrrp + ['group'])
+ default_values = defaults(base_vrrp + ['group'])
+ default_values_garp = defaults(base_vrrp + ['group', 'garp'])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'garp' in default_values:
+ del default_values['garp']
for group in ha['vrrp']['group']:
- ha['vrrp']['group'][group] = dict_merge(default_values_vrrp, ha['vrrp']['group'][group])
+ ha['vrrp']['group'][group] = dict_merge(default_values, ha['vrrp']['group'][group])
+
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'garp' in ha['vrrp']['group'][group]:
+ ha['vrrp']['group'][group]['garp'] = dict_merge(
+ default_values_garp, ha['vrrp']['group'][group]['garp'])
# Merge per virtual-server default values
if 'virtual_server' in ha:
diff --git a/src/conf_mode/interfaces-input.py b/src/conf_mode/interfaces-input.py
new file mode 100755
index 000000000..ad248843d
--- /dev/null
+++ b/src/conf_mode/interfaces-input.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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/>.
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_mirror_redirect
+from vyos.ifconfig import InputIf
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ """
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at
+ least the interface name will be added or a deleted flag
+ """
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'input']
+ _, ifb = get_interface_dict(conf, base)
+
+ return ifb
+
+def verify(ifb):
+ if 'deleted' in ifb:
+ return None
+
+ verify_mirror_redirect(ifb)
+ return None
+
+def generate(ifb):
+ return None
+
+def apply(ifb):
+ d = InputIf(ifb['ifname'])
+
+ # Remove input interface
+ if 'deleted' in ifb:
+ d.remove()
+ else:
+ d.update(ifb)
+
+ 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/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index ee4defa0d..5f0b76f90 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -54,7 +54,8 @@ def get_config(config=None):
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
for options in ['access-concentrator', 'connect-on-demand', 'service-name',
- 'source-interface', 'vrf', 'no-default-route', 'authentication']:
+ 'source-interface', 'vrf', 'no-default-route',
+ 'authentication', 'host_uniq']:
if is_node_changed(conf, base + [ifname, options]):
pppoe.update({'shutdown_required': {}})
# bail out early - no need to further process other nodes
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 914ec245c..ab2ccf99e 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -166,6 +166,10 @@ def verify(snmp):
if 'community' not in trap_config:
raise ConfigError(f'Trap target "{trap}" requires a community to be set!')
+ if 'oid_enable' in snmp:
+ Warning(f'Custom OIDs are enabled and may lead to system instability and high resource consumption')
+
+
verify_vrf(snmp)
# bail out early if SNMP v3 is not configured
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index af3c51efc..63ffe2a41 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -46,6 +46,65 @@ radius_servers = cfg_dir + '/radius_servers'
def get_hash(password):
return crypt(password, mksalt(METHOD_SHA512))
+
+def T2665_default_dict_cleanup(origin: dict, default_values: dict) -> dict:
+ """
+ https://phabricator.vyos.net/T2665
+ Clear unnecessary key values in merged config by dict_merge function
+ :param origin: config
+ :type origin: dict
+ :param default_values: default values
+ :type default_values: dict
+ :return: merged dict
+ :rtype: dict
+ """
+ if 'mode' in origin["authentication"] and "local" in \
+ origin["authentication"]["mode"]:
+ del origin['authentication']['local_users']['username']['otp']
+ if not origin["authentication"]["local_users"]["username"]:
+ raise ConfigError(
+ 'Openconnect mode local required at least one user')
+ default_ocserv_usr_values = \
+ default_values['authentication']['local_users']['username']['otp']
+ for user, params in origin['authentication']['local_users'][
+ 'username'].items():
+ # Not every configuration requires OTP settings
+ if origin['authentication']['local_users']['username'][user].get(
+ 'otp'):
+ origin['authentication']['local_users']['username'][user][
+ 'otp'] = dict_merge(default_ocserv_usr_values,
+ origin['authentication'][
+ 'local_users']['username'][user][
+ 'otp'])
+
+ if 'mode' in origin["authentication"] and "radius" in \
+ origin["authentication"]["mode"]:
+ del origin['authentication']['radius']['server']['port']
+ if not origin["authentication"]['radius']['server']:
+ raise ConfigError(
+ 'Openconnect authentication mode radius required at least one radius server')
+ default_values_radius_port = \
+ default_values['authentication']['radius']['server']['port']
+ for server, params in origin['authentication']['radius'][
+ 'server'].items():
+ if 'port' not in params:
+ params['port'] = default_values_radius_port
+
+ if 'mode' in origin["accounting"] and "radius" in \
+ origin["accounting"]["mode"]:
+ del origin['accounting']['radius']['server']['port']
+ if not origin["accounting"]['radius']['server']:
+ raise ConfigError(
+ 'Openconnect accounting mode radius required at least one radius server')
+ default_values_radius_port = \
+ default_values['accounting']['radius']['server']['port']
+ for server, params in origin['accounting']['radius'][
+ 'server'].items():
+ if 'port' not in params:
+ params['port'] = default_values_radius_port
+ return origin
+
+
def get_config():
conf = Config()
base = ['vpn', 'openconnect']
@@ -57,18 +116,8 @@ def get_config():
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
ocserv = dict_merge(default_values, ocserv)
-
- if 'mode' in ocserv["authentication"] and "local" in ocserv["authentication"]["mode"]:
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
- del ocserv['authentication']['local_users']['username']['otp']
- if not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('openconnect mode local required at least one user')
- default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
- for user, params in ocserv['authentication']['local_users']['username'].items():
- # Not every configuration requires OTP settings
- if ocserv['authentication']['local_users']['username'][user].get('otp'):
- ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
-
+ # workaround a "know limitation" - https://phabricator.vyos.net/T2665
+ ocserv = T2665_default_dict_cleanup(ocserv, default_values)
if ocserv:
ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
@@ -85,6 +134,14 @@ def verify(ocserv):
not is_listen_port_bind_service(int(port), 'ocserv-main'):
raise ConfigError(f'"{proto}" port "{port}" is used by another service')
+ # Check accounting
+ if "accounting" in ocserv:
+ if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]:
+ if "authentication" not in ocserv or "mode" not in ocserv["authentication"]:
+ raise ConfigError('Accounting depends on OpenConnect authentication configuration')
+ elif "radius" not in ocserv["authentication"]["mode"]:
+ raise ConfigError('RADIUS accounting must be used with RADIUS authentication')
+
# Check authentication
if "authentication" in ocserv:
if "mode" in ocserv["authentication"]:
@@ -166,10 +223,18 @@ def generate(ocserv):
return None
if "radius" in ocserv["authentication"]["mode"]:
- # Render radius client configuration
- render(radius_cfg, 'ocserv/radius_conf.j2', ocserv["authentication"]["radius"])
- # Render radius servers
- render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"])
+ if dict_search(ocserv, 'accounting.mode.radius'):
+ # Render radius client configuration
+ render(radius_cfg, 'ocserv/radius_conf.j2', ocserv)
+ merged_servers = ocserv["accounting"]["radius"]["server"] | ocserv["authentication"]["radius"]["server"]
+ # Render radius servers
+ # Merge the accounting and authentication servers into a single dictionary
+ render(radius_servers, 'ocserv/radius_servers.j2', {'server': merged_servers})
+ else:
+ # Render radius client configuration
+ render(radius_cfg, 'ocserv/radius_conf.j2', ocserv)
+ # Render radius servers
+ render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"])
elif "local" in ocserv["authentication"]["mode"]:
# if mode "OTP", generate OTP users file parameters
if "otp" in ocserv["authentication"]["mode"]["local"]: