summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j217
-rw-r--r--interface-definitions/include/haproxy/logging.xml.i10
-rw-r--r--interface-definitions/load-balancing_reverse-proxy.xml.in3
-rw-r--r--op-mode-definitions/generate_firewall_rule-resequence.xml.in29
-rw-r--r--op-mode-definitions/generate_nat64_rule-resequence.xml.in15
-rw-r--r--op-mode-definitions/generate_nat66_rule-resequence.xml.in15
-rw-r--r--op-mode-definitions/generate_nat_rule-resequence.xml.in15
-rw-r--r--op-mode-definitions/include/rule-resequence.xml.i30
-rw-r--r--op-mode-definitions/restart-serial.xml.in31
-rw-r--r--python/vyos/ipsec.py136
-rw-r--r--python/vyos/utils/serial.py118
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_l2tpv3.py3
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_reverse-proxy.py41
-rw-r--r--src/completion/list_login_ttys.py25
-rwxr-xr-xsrc/conf_mode/system_console.py15
-rwxr-xr-xsrc/op_mode/generate_service_rule-resequence.py (renamed from src/op_mode/generate_firewall_rule-resequence.py)29
-rwxr-xr-xsrc/op_mode/ipsec.py489
-rw-r--r--src/op_mode/serial.py38
18 files changed, 835 insertions, 224 deletions
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index c18a998b8..5137966c1 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -1,8 +1,6 @@
### Autogenerated by load-balancing_reverse-proxy.py ###
global
- log /dev/log local0
- log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
@@ -11,6 +9,11 @@ global
daemon
{% if global_parameters is vyos_defined %}
+{% if global_parameters.logging is vyos_defined %}
+{% for facility, facility_config in global_parameters.logging.facility.items() %}
+ log /dev/log {{ facility }} {{ facility_config.level }}
+{% endfor %}
+{% endif %}
{% if global_parameters.max_connections is vyos_defined %}
maxconn {{ global_parameters.max_connections }}
{% endif %}
@@ -67,6 +70,11 @@ frontend {{ front }}
{% if front_config.redirect_http_to_https is vyos_defined %}
http-request redirect scheme https unless { ssl_fc }
{% endif %}
+{% if front_config.logging is vyos_defined %}
+{% for facility, facility_config in front_config.logging.facility.items() %}
+ log /dev/log {{ facility }} {{ facility_config.level }}
+{% endfor %}
+{% endif %}
mode {{ front_config.mode }}
{% if front_config.tcp_request.inspect_delay is vyos_defined %}
tcp-request inspect-delay {{ front_config.tcp_request.inspect_delay }}
@@ -166,6 +174,11 @@ backend {{ back }}
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
{% endif %}
+{% if back_config.logging is vyos_defined %}
+{% for facility, facility_config in back_config.logging.facility.items() %}
+ log /dev/log {{ facility }} {{ facility_config.level }}
+{% endfor %}
+{% endif %}
mode {{ back_config.mode }}
{% if back_config.http_response_headers is vyos_defined %}
{% for header, header_config in back_config.http_response_headers.items() %}
diff --git a/interface-definitions/include/haproxy/logging.xml.i b/interface-definitions/include/haproxy/logging.xml.i
new file mode 100644
index 000000000..e0af54fa4
--- /dev/null
+++ b/interface-definitions/include/haproxy/logging.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from haproxy/logging.xml.i -->
+<node name="logging">
+ <properties>
+ <help>Logging parameters</help>
+ </properties>
+ <children>
+ #include <include/syslog-facility.xml.i>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_reverse-proxy.xml.in
index 1a432be6d..18274622c 100644
--- a/interface-definitions/load-balancing_reverse-proxy.xml.in
+++ b/interface-definitions/load-balancing_reverse-proxy.xml.in
@@ -36,6 +36,7 @@
</leafNode>
#include <include/generic-description.xml.i>
#include <include/listen-address.xml.i>
+ #include <include/haproxy/logging.xml.i>
#include <include/haproxy/mode.xml.i>
#include <include/port-number.xml.i>
#include <include/haproxy/rule-frontend.xml.i>
@@ -91,6 +92,7 @@
<defaultValue>round-robin</defaultValue>
</leafNode>
#include <include/generic-description.xml.i>
+ #include <include/haproxy/logging.xml.i>
#include <include/haproxy/mode.xml.i>
#include <include/haproxy/http-response-headers.xml.i>
<node name="http-check">
@@ -254,6 +256,7 @@
<help>Global perfomance parameters and limits</help>
</properties>
<children>
+ #include <include/haproxy/logging.xml.i>
<leafNode name="max-connections">
<properties>
<help>Maximum allowed connections</help>
diff --git a/op-mode-definitions/generate_firewall_rule-resequence.xml.in b/op-mode-definitions/generate_firewall_rule-resequence.xml.in
index 66078deb9..ef81579fa 100644
--- a/op-mode-definitions/generate_firewall_rule-resequence.xml.in
+++ b/op-mode-definitions/generate_firewall_rule-resequence.xml.in
@@ -7,34 +7,7 @@
<help>Firewall</help>
</properties>
<children>
- <node name="rule-resequence">
- <properties>
- <help>Resequence the firewall rules</help>
- </properties>
- <command>${vyos_op_scripts_dir}/generate_firewall_rule-resequence.py</command>
- <children>
- <tagNode name="start">
- <properties>
- <help>Set the first sequence number</help>
- <completionHelp>
- <list>1-1000</list>
- </completionHelp>
- </properties>
- <command>${vyos_op_scripts_dir}/generate_firewall_rule-resequence.py --start $5</command>
- <children>
- <tagNode name="step">
- <properties>
- <help>Step between rules</help>
- <completionHelp>
- <list>1-1000</list>
- </completionHelp>
- </properties>
- <command>${vyos_op_scripts_dir}/generate_firewall_rule-resequence.py --start $5 --step $7</command>
- </tagNode>
- </children>
- </tagNode>
- </children>
- </node>
+ #include <include/rule-resequence.xml.i>
</children>
</node>
</children>
diff --git a/op-mode-definitions/generate_nat64_rule-resequence.xml.in b/op-mode-definitions/generate_nat64_rule-resequence.xml.in
new file mode 100644
index 000000000..399253b37
--- /dev/null
+++ b/op-mode-definitions/generate_nat64_rule-resequence.xml.in
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="generate">
+ <children>
+ <node name="nat64">
+ <properties>
+ <help>Network Address Translation (NAT64)</help>
+ </properties>
+ <children>
+ #include <include/rule-resequence.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/generate_nat66_rule-resequence.xml.in b/op-mode-definitions/generate_nat66_rule-resequence.xml.in
new file mode 100644
index 000000000..d7159cf60
--- /dev/null
+++ b/op-mode-definitions/generate_nat66_rule-resequence.xml.in
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="generate">
+ <children>
+ <node name="nat66">
+ <properties>
+ <help>Network Prefix Translation (NAT66/NPTv6)</help>
+ </properties>
+ <children>
+ #include <include/rule-resequence.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/generate_nat_rule-resequence.xml.in b/op-mode-definitions/generate_nat_rule-resequence.xml.in
new file mode 100644
index 000000000..e32a89e08
--- /dev/null
+++ b/op-mode-definitions/generate_nat_rule-resequence.xml.in
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="generate">
+ <children>
+ <node name="nat">
+ <properties>
+ <help>Network Address Translation (NAT)</help>
+ </properties>
+ <children>
+ #include <include/rule-resequence.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/include/rule-resequence.xml.i b/op-mode-definitions/include/rule-resequence.xml.i
new file mode 100644
index 000000000..987bf634e
--- /dev/null
+++ b/op-mode-definitions/include/rule-resequence.xml.i
@@ -0,0 +1,30 @@
+<!-- included start from show-nht.xml.i -->
+<node name="rule-resequence">
+ <properties>
+ <help>Resequence rules</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/generate_service_rule-resequence.py --service $2</command>
+ <children>
+ <tagNode name="start">
+ <properties>
+ <help>Set the first sequence number</help>
+ <completionHelp>
+ <list>1-1000</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/generate_service_rule-resequence.py --service $2 --start $5</command>
+ <children>
+ <tagNode name="step">
+ <properties>
+ <help>Step between rules</help>
+ <completionHelp>
+ <list>1-1000</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/generate_service_rule-resequence.py --service $2 --start $5 --step $7</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+</node>
+<!-- included end -->
diff --git a/op-mode-definitions/restart-serial.xml.in b/op-mode-definitions/restart-serial.xml.in
new file mode 100644
index 000000000..4d8a03633
--- /dev/null
+++ b/op-mode-definitions/restart-serial.xml.in
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="restart">
+ <children>
+ <node name="serial">
+ <properties>
+ <help>Restart services on serial ports</help>
+ </properties>
+ <children>
+ <node name="console">
+ <properties>
+ <help>Restart serial console service for login TTYs</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/serial.py restart_console</command>
+ <children>
+ <tagNode name="device">
+ <properties>
+ <help>Restart specific TTY device</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_login_ttys.py</script>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/serial.py restart_console --device-name "$5"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py
index 4603aab22..28f77565a 100644
--- a/python/vyos/ipsec.py
+++ b/python/vyos/ipsec.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2024 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
@@ -13,31 +13,38 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-#Package to communicate with Strongswan VICI
+# Package to communicate with Strongswan VICI
+
class ViciInitiateError(Exception):
"""
- VICI can't initiate a session.
+ VICI can't initiate a session.
"""
+
pass
+
+
class ViciCommandError(Exception):
"""
- VICI can't execute a command by any reason.
+ VICI can't execute a command by any reason.
"""
+
pass
+
def get_vici_sas():
from vici import Session as vici_session
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec not initialized')
try:
sas = list(session.list_sas())
return sas
except Exception:
- raise ViciCommandError(f'Failed to get SAs')
+ raise ViciCommandError('Failed to get SAs')
+
def get_vici_connections():
from vici import Session as vici_session
@@ -45,18 +52,19 @@ def get_vici_connections():
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec not initialized')
try:
connections = list(session.list_conns())
return connections
except Exception:
- raise ViciCommandError(f'Failed to get connections')
+ raise ViciCommandError('Failed to get connections')
+
def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
"""
- Find sas by IKE_SA name and/or CHILD_SA name
- and return list of OrdinaryDicts with SASs info
- If tunnel is not None return value is list of OrdenaryDicts contained only
+ Find installed SAs by IKE_SA name and/or CHILD_SA name
+ and return list with SASs info.
+ If tunnel is not None return a list contained only
CHILD_SAs wich names equal tunnel value.
:param ike_name: IKE SA name
:type ike_name: str
@@ -70,7 +78,7 @@ def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec not initialized')
vici_dict = {}
if ike_name:
vici_dict['ike'] = ike_name
@@ -80,7 +88,31 @@ def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
sas = list(session.list_sas(vici_dict))
return sas
except Exception:
- raise ViciCommandError(f'Failed to get SAs')
+ raise ViciCommandError('Failed to get SAs')
+
+
+def get_vici_connection_by_name(ike_name: str) -> list:
+ """
+ Find loaded SAs by IKE_SA name and return list with SASs info
+ :param ike_name: IKE SA name
+ :type ike_name: str
+ :return: list of Ordinary Dicts with SASs
+ :rtype: list
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError('IPsec is not initialized')
+ vici_dict = {}
+ if ike_name:
+ vici_dict['ike'] = ike_name
+ try:
+ sas = list(session.list_conns(vici_dict))
+ return sas
+ except Exception:
+ raise ViciCommandError('Failed to get SAs')
def terminate_vici_ikeid_list(ike_id_list: list) -> None:
@@ -94,19 +126,17 @@ def terminate_vici_ikeid_list(ike_id_list: list) -> None:
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec is not initialized')
try:
for ikeid in ike_id_list:
- session_generator = session.terminate(
- {'ike-id': ikeid, 'timeout': '-1'})
+ session_generator = session.terminate({'ike-id': ikeid, 'timeout': '-1'})
# a dummy `for` loop is required because of requirements
# from vici. Without a full iteration on the output, the
# command to vici may not be executed completely
for _ in session_generator:
pass
except Exception:
- raise ViciCommandError(
- f'Failed to terminate SA for IKE ids {ike_id_list}')
+ raise ViciCommandError(f'Failed to terminate SA for IKE ids {ike_id_list}')
def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
@@ -123,9 +153,9 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec is not initialized')
try:
- vici_dict: dict= {}
+ vici_dict: dict = {}
if ike_name:
vici_dict['ike'] = ike_name
if child_name:
@@ -138,16 +168,48 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
pass
except Exception:
if child_name:
- raise ViciCommandError(
- f'Failed to terminate SA for IPSEC {child_name}')
+ raise ViciCommandError(f'Failed to terminate SA for IPSEC {child_name}')
else:
- raise ViciCommandError(
- f'Failed to terminate SA for IKE {ike_name}')
+ raise ViciCommandError(f'Failed to terminate SA for IKE {ike_name}')
+
+
+def vici_initiate_all_child_sa_by_ike(ike_sa_name: str, child_sa_list: list) -> bool:
+ """
+ Initiate IKE SA with scpecified CHILD_SAs in list
+
+ Args:
+ ike_sa_name (str): an IKE SA connection name
+ child_sa_list (list): a list of child SA names
+
+ Returns:
+ bool: a result of initiation command
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError('IPsec is not initialized')
+
+ try:
+ for child_sa_name in child_sa_list:
+ session_generator = session.initiate(
+ {'ike': ike_sa_name, 'child': child_sa_name, 'timeout': '-1'}
+ )
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
+ return True
+ except Exception:
+ raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}')
-def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
- dst_addr: str) -> bool:
- """Initiate IKE SA connection with specific peer
+def vici_initiate(
+ ike_sa_name: str, child_sa_name: str, src_addr: str, dst_addr: str
+) -> bool:
+ """Initiate IKE SA with one child_sa connection with specific peer
Args:
ike_sa_name (str): an IKE SA connection name
@@ -163,16 +225,18 @@ def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec is not initialized')
try:
- session_generator = session.initiate({
- 'ike': ike_sa_name,
- 'child': child_sa_name,
- 'timeout': '-1',
- 'my-host': src_addr,
- 'other-host': dst_addr
- })
+ session_generator = session.initiate(
+ {
+ 'ike': ike_sa_name,
+ 'child': child_sa_name,
+ 'timeout': '-1',
+ 'my-host': src_addr,
+ 'other-host': dst_addr,
+ }
+ )
# a dummy `for` loop is required because of requirements
# from vici. Without a full iteration on the output, the
# command to vici may not be executed completely
@@ -180,4 +244,4 @@ def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
pass
return True
except Exception:
- raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') \ No newline at end of file
+ raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}')
diff --git a/python/vyos/utils/serial.py b/python/vyos/utils/serial.py
new file mode 100644
index 000000000..b646f881e
--- /dev/null
+++ b/python/vyos/utils/serial.py
@@ -0,0 +1,118 @@
+# Copyright 2024 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import os, re, json
+from typing import List
+
+from vyos.base import Warning
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import cmd
+
+GLOB_GETTY_UNITS = 'serial-getty@*.service'
+RE_GETTY_DEVICES = re.compile(r'.+@(.+).service$')
+
+SD_UNIT_PATH = '/run/systemd/system'
+UTMP_PATH = '/run/utmp'
+
+def get_serial_units(include_devices=[]):
+ # Since we cannot depend on the current config for decommissioned ports,
+ # we just grab everything that systemd knows about.
+ tmp = cmd(f'systemctl list-units {GLOB_GETTY_UNITS} --all --output json --no-pager')
+ getty_units = json.loads(tmp)
+ for sdunit in getty_units:
+ m = RE_GETTY_DEVICES.search(sdunit['unit'])
+ if m is None:
+ Warning(f'Serial console unit name "{sdunit["unit"]}" is malformed and cannot be checked for activity!')
+ continue
+
+ getty_device = m.group(1)
+ if include_devices and getty_device not in include_devices:
+ continue
+
+ sdunit['device'] = getty_device
+
+ return getty_units
+
+def get_authenticated_ports(units):
+ connected = []
+ ports = [ x['device'] for x in units if 'device' in x ]
+ #
+ # utmpdump just gives us an easily parseable dump of currently logged-in sessions, for eg:
+ # $ utmpdump /run/utmp
+ # Utmp dump of /run/utmp
+ # [2] [00000] [~~ ] [reboot ] [~ ] [6.6.31-amd64-vyos ] [0.0.0.0 ] [2024-06-18T13:56:53,958484+00:00]
+ # [1] [00051] [~~ ] [runlevel] [~ ] [6.6.31-amd64-vyos ] [0.0.0.0 ] [2024-06-18T13:57:01,790808+00:00]
+ # [6] [03178] [tty1] [LOGIN ] [tty1 ] [ ] [0.0.0.0 ] [2024-06-18T13:57:31,015392+00:00]
+ # [7] [37151] [ts/0] [vyos ] [pts/0 ] [10.9.8.7 ] [10.9.8.7 ] [2024-07-04T13:42:08,760892+00:00]
+ # [8] [24812] [ts/1] [ ] [pts/1 ] [10.9.8.7 ] [10.9.8.7 ] [2024-06-20T18:10:07,309365+00:00]
+ #
+ # We can safely skip blank or LOGIN sessions with valid device names.
+ #
+ for line in cmd(f'utmpdump {UTMP_PATH}').splitlines():
+ row = line.split('] [')
+ user_name = row[3].strip()
+ user_term = row[4].strip()
+ if user_name and user_name != 'LOGIN' and user_term in ports:
+ connected.append(user_term)
+
+ return connected
+
+def restart_login_consoles(prompt_user=False, quiet=True, devices: List[str]=[]):
+ # restart_login_consoles() is called from both conf- and op-mode scripts, including
+ # the warning messages and user prompts common to both.
+ #
+ # The default case, called with no arguments, is a simple serial-getty restart &
+ # cleanup wrapper with no output or prompts that can be used from anywhere.
+ #
+ # quiet and prompt_user args have been split from an original "no_prompt", in
+ # order to support the completely silent default use case. "no_prompt" would
+ # only suppress the user interactive prompt.
+ #
+ # quiet intentionally does not suppress a vyos.base.Warning() for malformed
+ # device names in _get_serial_units().
+ #
+ cmd('systemctl daemon-reload')
+
+ units = get_serial_units(devices)
+ connected = get_authenticated_ports(units)
+
+ if connected:
+ if not quiet:
+ Warning('There are user sessions connected via serial console that '\
+ 'will be terminated when serial console settings are changed!')
+ if not prompt_user:
+ # This flag is used by conf_mode/system_console.py to reset things, if there's
+ # a problem, the user should issue a manual restart for serial-getty.
+ Warning('Please ensure all settings are committed and saved before issuing a ' \
+ '"restart serial console" command to apply new configuration!')
+ if not prompt_user:
+ return False
+ if not ask_yes_no('Any uncommitted changes from these sessions will be lost\n' \
+ 'and in-progress actions may be left in an inconsistent state.\n'\
+ '\nContinue?'):
+ return False
+
+ for unit in units:
+ if 'device' not in unit:
+ continue # malformed or filtered.
+ unit_name = unit['unit']
+ unit_device = unit['device']
+ if os.path.exists(os.path.join(SD_UNIT_PATH, unit_name)):
+ cmd(f'systemctl restart {unit_name}')
+ else:
+ # Deleted stubs don't need to be restarted, just shut them down.
+ cmd(f'systemctl stop {unit_name}')
+
+ return True
diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py
index abc55e6d2..28165736b 100755
--- a/smoketest/scripts/cli/test_interfaces_l2tpv3.py
+++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -14,7 +14,6 @@
# 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 json
import unittest
diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
index aa796f59f..db43a78ec 100755
--- a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
+++ b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
@@ -458,6 +458,47 @@ class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase):
config = read_file(HAPROXY_CONF)
self.assertIn(f'option smtpchk', config)
+ def test_09_lb_reverse_proxy_logging(self):
+ # Setup base
+ self.base_config()
+ self.cli_commit()
+
+ # Ensure default logging configuration is present
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local0', config)
+ self.assertIn('log /dev/log local1 notice', config)
+
+ # Test global-parameters logging options
+ self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local1', 'level', 'err'])
+ self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local2', 'level', 'warning'])
+ self.cli_commit()
+
+ # Test global logging parameters are generated in configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local1 err', config)
+ self.assertIn('log /dev/log local2 warning', config)
+
+ # Test backend logging options
+ backend_path = base_path + ['backend', 'bk-01']
+ self.cli_set(backend_path + ['logging', 'facility', 'local3', 'level', 'debug'])
+ self.cli_set(backend_path + ['logging', 'facility', 'local4', 'level', 'info'])
+ self.cli_commit()
+
+ # Test backend logging parameters are generated in configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local3 debug', config)
+ self.assertIn('log /dev/log local4 info', config)
+
+ # Test service logging options
+ service_path = base_path + ['service', 'https_front']
+ self.cli_set(service_path + ['logging', 'facility', 'local5', 'level', 'notice'])
+ self.cli_set(service_path + ['logging', 'facility', 'local6', 'level', 'crit'])
+ self.cli_commit()
+
+ # Test service logging parameters are generated in configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local5 notice', config)
+ self.assertIn('log /dev/log local6 crit', config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/completion/list_login_ttys.py b/src/completion/list_login_ttys.py
new file mode 100644
index 000000000..4d77a1b8b
--- /dev/null
+++ b/src/completion/list_login_ttys.py
@@ -0,0 +1,25 @@
+#!/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/>.
+
+from vyos.utils.serial import get_serial_units
+
+if __name__ == '__main__':
+ # Autocomplete uses runtime state rather than the config tree, as a manual
+ # restart/cleanup may be needed for deleted devices.
+ tty_completions = [ '<text>' ] + [ x['device'] for x in get_serial_units() if 'device' in x ]
+ print(' '.join(tty_completions))
+
+
diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py
index 19bbb8875..27bf92e0b 100755
--- a/src/conf_mode/system_console.py
+++ b/src/conf_mode/system_console.py
@@ -19,8 +19,10 @@ from pathlib import Path
from vyos.config import Config
from vyos.utils.process import call
+from vyos.utils.serial import restart_login_consoles
from vyos.system import grub_util
from vyos.template import render
+from vyos.defaults import directories
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -74,7 +76,6 @@ def generate(console):
for root, dirs, files in os.walk(base_dir):
for basename in files:
if 'serial-getty' in basename:
- call(f'systemctl stop {basename}')
os.unlink(os.path.join(root, basename))
if not console or 'device' not in console:
@@ -122,6 +123,11 @@ def apply(console):
# Reload systemd manager configuration
call('systemctl daemon-reload')
+ # Service control moved to vyos.utils.serial to unify checks and prompts.
+ # If users are connected, we want to show an informational message on completing
+ # the process, but not halt configuration processing with an interactive prompt.
+ restart_login_consoles(prompt_user=False, quiet=False)
+
if not console:
return None
@@ -129,13 +135,6 @@ def apply(console):
# Configure screen blank powersaving on VGA console
call('/usr/bin/setterm -blank 15 -powersave powerdown -powerdown 60 -term linux </dev/tty1 >/dev/tty1 2>&1')
- # Start getty process on configured serial interfaces
- for device in console['device']:
- # Only start console if it exists on the running system. If a user
- # detaches a USB serial console and reboots - it should not fail!
- if os.path.exists(f'/dev/{device}'):
- call(f'systemctl restart serial-getty@{device}.service')
-
return None
if __name__ == '__main__':
diff --git a/src/op_mode/generate_firewall_rule-resequence.py b/src/op_mode/generate_service_rule-resequence.py
index 21441f689..9333d6353 100755
--- a/src/op_mode/generate_firewall_rule-resequence.py
+++ b/src/op_mode/generate_service_rule-resequence.py
@@ -77,7 +77,7 @@ def change_rule_numbers(config_dict, start, step):
change_rule_numbers(config_dict[key], start, step)
-def convert_rule_keys_to_int(config_dict):
+def convert_rule_keys_to_int(config_dict, prev_key=None):
"""
Converts rule keys in the configuration dictionary to integers.
@@ -91,11 +91,11 @@ def convert_rule_keys_to_int(config_dict):
new_dict = {}
for key, value in config_dict.items():
# Convert key to integer if possible
- new_key = int(key) if key.isdigit() else key
+ new_key = int(key) if key.isdigit() and prev_key == 'rule' else key
# Recur for nested dictionaries
if isinstance(value, dict):
- new_value = convert_rule_keys_to_int(value)
+ new_value = convert_rule_keys_to_int(value, key)
else:
new_value = value
@@ -111,27 +111,24 @@ def convert_rule_keys_to_int(config_dict):
if __name__ == "__main__":
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Convert dictionary to set commands with rule number modifications.')
- parser.add_argument('--start', type=int, default=100, help='Start rule number')
+ parser.add_argument('--service', type=str, help='Name of service')
+ parser.add_argument('--start', type=int, default=100, help='Start rule number (default: 100)')
parser.add_argument('--step', type=int, default=10, help='Step for rule numbers (default: 10)')
args = parser.parse_args()
config = ConfigTreeQuery()
- if not config.exists('firewall'):
- print('Firewall is not configured')
+ if not config.exists(args.service):
+ print(f'{args.service} is not configured')
exit(1)
- config_dict = config.get_config_dict('firewall')
+ config_dict = config.get_config_dict(args.service)
- # Remove global-options, group and flowtable as they don't need sequencing
- if 'global-options' in config_dict['firewall']:
- del config_dict['firewall']['global-options']
+ if 'firewall' in config_dict:
+ # Remove global-options, group and flowtable as they don't need sequencing
+ for item in ['global-options', 'group', 'flowtable']:
+ if item in config_dict['firewall']:
+ del config_dict['firewall'][item]
- if 'group' in config_dict['firewall']:
- del config_dict['firewall']['group']
-
- if 'flowtable' in config_dict['firewall']:
- del config_dict['firewall']['flowtable']
-
# Convert rule keys to integers, rule "10" -> rule 10
# This is necessary for sorting the rules
config_dict = convert_rule_keys_to_int(config_dict)
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index 44d41219e..02ba126b4 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022-2023 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -25,6 +25,7 @@ from vyos.utils.convert import convert_data
from vyos.utils.convert import seconds_to_human
from vyos.utils.process import cmd
from vyos.configquery import ConfigTreeQuery
+from vyos.base import Warning
import vyos.opmode
import vyos.ipsec
@@ -43,7 +44,7 @@ def _get_raw_data_sas():
get_sas = vyos.ipsec.get_vici_sas()
sas = convert_data(get_sas)
return sas
- except (vyos.ipsec.ViciInitiateError) as err:
+ except vyos.ipsec.ViciInitiateError as err:
raise vyos.opmode.UnconfiguredSubsystem(err)
@@ -56,11 +57,10 @@ def _get_output_swanctl_sas_from_list(ra_output_list: list) -> str:
:return: formatted string
:rtype: str
"""
- output = '';
+ output = ''
for sa_val in ra_output_list:
for sa in sa_val.values():
- swanctl_output: str = cmd(
- f'sudo swanctl -l --ike-id {sa["uniqueid"]}')
+ swanctl_output: str = cmd(f'sudo swanctl -l --ike-id {sa["uniqueid"]}')
output = f'{output}{swanctl_output}\n\n'
return output
@@ -72,7 +72,9 @@ def _get_formatted_output_sas(sas):
# create an item for each child-sa
for child_sa in parent_sa.get('child-sas', {}).values():
# prepare a list for output data
- sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A'
+ sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = (
+ sa_out_packets
+ ) = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A'
# collect raw data
sa_name = child_sa.get('name')
@@ -104,10 +106,8 @@ def _get_formatted_output_sas(sas):
bytes_out = filesize.size(int(sa_bytes_out))
sa_out_bytes = f'{bytes_in}/{bytes_out}'
if sa_packets_in and sa_packets_out:
- packets_in = filesize.size(int(sa_packets_in),
- system=filesize.si)
- packets_out = filesize.size(int(sa_packets_out),
- system=filesize.si)
+ packets_in = filesize.size(int(sa_packets_in), system=filesize.si)
+ packets_out = filesize.size(int(sa_packets_out), system=filesize.si)
packets_str = f'{packets_in}/{packets_out}'
sa_out_packets = re.sub(r'B', r'', packets_str)
if sa_remote_addr:
@@ -119,7 +119,9 @@ def _get_formatted_output_sas(sas):
sa_out_proposal = sa_proposal_encr_alg
if sa_proposal_encr_keysize:
sa_proposal_encr_keysize_str = sa_proposal_encr_keysize
- sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}'
+ sa_out_proposal = (
+ f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}'
+ )
if sa_proposal_integ_alg:
sa_proposal_integ_alg_str = sa_proposal_integ_alg
sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}'
@@ -128,15 +130,28 @@ def _get_formatted_output_sas(sas):
sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}'
# add a new item to output data
- sa_data.append([
- sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes,
- sa_out_packets, sa_out_remote_addr, sa_out_remote_id,
- sa_out_proposal
- ])
+ sa_data.append(
+ [
+ sa_out_name,
+ sa_out_state,
+ sa_out_uptime,
+ sa_out_bytes,
+ sa_out_packets,
+ sa_out_remote_addr,
+ sa_out_remote_id,
+ sa_out_proposal,
+ ]
+ )
headers = [
- "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out",
- "Remote address", "Remote ID", "Proposal"
+ 'Connection',
+ 'State',
+ 'Uptime',
+ 'Bytes In/Out',
+ 'Packets In/Out',
+ 'Remote address',
+ 'Remote ID',
+ 'Proposal',
]
sa_data = sorted(sa_data, key=_alphanum_key)
output = tabulate(sa_data, headers)
@@ -145,14 +160,16 @@ def _get_formatted_output_sas(sas):
# Connections block
+
def _get_convert_data_connections():
try:
get_connections = vyos.ipsec.get_vici_connections()
connections = convert_data(get_connections)
return connections
- except (vyos.ipsec.ViciInitiateError) as err:
+ except vyos.ipsec.ViciInitiateError as err:
raise vyos.opmode.UnconfiguredSubsystem(err)
+
def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
"""Get parent SA proposals by connection name
if connections not in the 'down' state
@@ -184,7 +201,7 @@ def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
'mode': mode,
'key_size': encr_keysize,
'hash': integ_alg,
- 'dh': dh_group
+ 'dh': dh_group,
}
return proposal
return {}
@@ -213,8 +230,7 @@ def _get_parent_sa_state(connection_name: str, data: list) -> str:
return ike_state
-def _get_child_sa_state(connection_name: str, tunnel_name: str,
- data: list) -> str:
+def _get_child_sa_state(connection_name: str, tunnel_name: str, data: list) -> str:
"""Get child SA state by connection and tunnel name
Args:
@@ -236,14 +252,12 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str,
# Get all child SA states
# there can be multiple SAs per tunnel
child_sa_states = [
- v['state'] for k, v in child_sas.items() if
- v['name'] == tunnel_name
+ v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name
]
return 'up' if 'INSTALLED' in child_sa_states else child_sa
-def _get_child_sa_info(connection_name: str, tunnel_name: str,
- data: list) -> dict:
+def _get_child_sa_info(connection_name: str, tunnel_name: str, data: list) -> dict:
"""Get child SA installed info by connection and tunnel name
Args:
@@ -264,8 +278,9 @@ def _get_child_sa_info(connection_name: str, tunnel_name: str,
# {'OFFICE-B-tunnel-0-46': {'name': 'OFFICE-B-tunnel-0'}...}
# i.e get all data after 'OFFICE-B-tunnel-0-46'
child_sa_info = [
- v for k, v in child_sas.items() if 'name' in v and
- v['name'] == tunnel_name and v['state'] == 'INSTALLED'
+ v
+ for k, v in child_sas.items()
+ if 'name' in v and v['name'] == tunnel_name and v['state'] == 'INSTALLED'
]
return child_sa_info[-1] if child_sa_info else {}
@@ -283,7 +298,7 @@ def _get_child_sa_proposal(child_sa_data: dict) -> dict:
'mode': mode,
'key_size': key_size,
'hash': integ_alg,
- 'dh': dh_group
+ 'dh': dh_group,
}
return proposal
return {}
@@ -305,10 +320,10 @@ def _get_raw_data_connections(list_connections: list, list_sas: list) -> list:
for connection, conn_conf in connections.items():
base_list['ike_connection_name'] = connection
base_list['ike_connection_state'] = _get_parent_sa_state(
- connection, list_sas)
+ connection, list_sas
+ )
base_list['ike_remote_address'] = conn_conf['remote_addrs']
- base_list['ike_proposal'] = _get_parent_sa_proposal(
- connection, list_sas)
+ base_list['ike_proposal'] = _get_parent_sa_proposal(connection, list_sas)
base_list['local_id'] = conn_conf.get('local-1', '').get('id')
base_list['remote_id'] = conn_conf.get('remote-1', '').get('id')
base_list['version'] = conn_conf.get('version', 'IKE')
@@ -322,22 +337,25 @@ def _get_raw_data_connections(list_connections: list, list_sas: list) -> list:
close_action = tun_options.get('close_action')
sa_info = _get_child_sa_info(connection, tunnel, list_sas)
esp_proposal = _get_child_sa_proposal(sa_info)
- base_list['children'].append({
- 'name': tunnel,
- 'state': state,
- 'local_ts': local_ts,
- 'remote_ts': remote_ts,
- 'dpd_action': dpd_action,
- 'close_action': close_action,
- 'sa': sa_info,
- 'esp_proposal': esp_proposal
- })
+ base_list['children'].append(
+ {
+ 'name': tunnel,
+ 'state': state,
+ 'local_ts': local_ts,
+ 'remote_ts': remote_ts,
+ 'dpd_action': dpd_action,
+ 'close_action': close_action,
+ 'sa': sa_info,
+ 'esp_proposal': esp_proposal,
+ }
+ )
base_dict.append(base_list)
return base_dict
def _get_raw_connections_summary(list_conn, list_sas):
import jmespath
+
data = _get_raw_data_connections(list_conn, list_sas)
match = '[*].children[]'
child = jmespath.search(match, data)
@@ -347,17 +365,16 @@ def _get_raw_connections_summary(list_conn, list_sas):
'tunnels': child,
'total': len(child),
'down': tunnels_down,
- 'up': tunnels_up
+ 'up': tunnels_up,
}
return tun_dict
def _get_formatted_output_conections(data):
from tabulate import tabulate
- data_entries = ''
+
connections = []
for entry in data:
- tunnels = []
ike_name = entry['ike_connection_name']
ike_state = entry['ike_connection_state']
conn_type = entry.get('version', 'IKE')
@@ -367,15 +384,26 @@ def _get_formatted_output_conections(data):
remote_id = entry['remote_id']
proposal = '-'
if entry.get('ike_proposal'):
- proposal = (f'{entry["ike_proposal"]["cipher"]}_'
- f'{entry["ike_proposal"]["mode"]}/'
- f'{entry["ike_proposal"]["key_size"]}/'
- f'{entry["ike_proposal"]["hash"]}/'
- f'{entry["ike_proposal"]["dh"]}')
- connections.append([
- ike_name, ike_state, conn_type, remote_addrs, local_ts, remote_ts,
- local_id, remote_id, proposal
- ])
+ proposal = (
+ f'{entry["ike_proposal"]["cipher"]}_'
+ f'{entry["ike_proposal"]["mode"]}/'
+ f'{entry["ike_proposal"]["key_size"]}/'
+ f'{entry["ike_proposal"]["hash"]}/'
+ f'{entry["ike_proposal"]["dh"]}'
+ )
+ connections.append(
+ [
+ ike_name,
+ ike_state,
+ conn_type,
+ remote_addrs,
+ local_ts,
+ remote_ts,
+ local_id,
+ remote_id,
+ proposal,
+ ]
+ )
for tun in entry['children']:
tun_name = tun.get('name')
tun_state = tun.get('state')
@@ -384,18 +412,36 @@ def _get_formatted_output_conections(data):
remote_ts = '\n'.join(tun.get('remote_ts'))
proposal = '-'
if tun.get('esp_proposal'):
- proposal = (f'{tun["esp_proposal"]["cipher"]}_'
- f'{tun["esp_proposal"]["mode"]}/'
- f'{tun["esp_proposal"]["key_size"]}/'
- f'{tun["esp_proposal"]["hash"]}/'
- f'{tun["esp_proposal"]["dh"]}')
- connections.append([
- tun_name, tun_state, conn_type, remote_addrs, local_ts,
- remote_ts, local_id, remote_id, proposal
- ])
+ proposal = (
+ f'{tun["esp_proposal"]["cipher"]}_'
+ f'{tun["esp_proposal"]["mode"]}/'
+ f'{tun["esp_proposal"]["key_size"]}/'
+ f'{tun["esp_proposal"]["hash"]}/'
+ f'{tun["esp_proposal"]["dh"]}'
+ )
+ connections.append(
+ [
+ tun_name,
+ tun_state,
+ conn_type,
+ remote_addrs,
+ local_ts,
+ remote_ts,
+ local_id,
+ remote_id,
+ proposal,
+ ]
+ )
connection_headers = [
- 'Connection', 'State', 'Type', 'Remote address', 'Local TS',
- 'Remote TS', 'Local id', 'Remote id', 'Proposal'
+ 'Connection',
+ 'State',
+ 'Type',
+ 'Remote address',
+ 'Local TS',
+ 'Remote TS',
+ 'Local id',
+ 'Remote id',
+ 'Proposal',
]
output = tabulate(connections, connection_headers, numalign='left')
return output
@@ -421,6 +467,31 @@ def _get_childsa_id_list(ike_sas: list) -> list:
return list_childsa_id
+def _get_con_childsa_name_list(
+ ike_sas: list, filter_dict: typing.Optional[dict] = None
+) -> list:
+ """
+ Generate list of CHILD SA ids based on list of OrderingDict
+ wich is returned by vici
+ :param ike_sas: list of IKE SAs connections generated by vici
+ :type ike_sas: list
+ :param filter_dict: dict of filter options
+ :type filter_dict: dict
+ :return: list of IKE SAs name
+ :rtype: list
+ """
+ list_childsa_name: list = []
+ for ike in ike_sas:
+ for ike_name, ike_values in ike.items():
+ for sa, sa_values in ike_values['children'].items():
+ if filter_dict:
+ if filter_dict.items() <= sa_values.items():
+ list_childsa_name.append(sa)
+ else:
+ list_childsa_name.append(sa)
+ return list_childsa_name
+
+
def _get_all_sitetosite_peers_name_list() -> list:
"""
Return site-to-site peers configuration
@@ -429,53 +500,142 @@ def _get_all_sitetosite_peers_name_list() -> list:
"""
conf: ConfigTreeQuery = ConfigTreeQuery()
config_path = ['vpn', 'ipsec', 'site-to-site', 'peer']
- peers_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ peers_config = conf.get_config_dict(
+ config_path,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ )
peers_list: list = []
for name in peers_config:
peers_list.append(name)
return peers_list
-def reset_peer(peer: str, tunnel: typing.Optional[str] = None):
- # Convert tunnel to Strongwan format of CHILD_SA
+def _get_tunnel_sw_format(peer: str, tunnel: str) -> str:
+ """
+ Convert tunnel to Strongwan format of CHILD_SA
+ :param peer: Peer name (IKE_SA)
+ :type peer: str
+ :param tunnel: tunnel number (CHILD_SA)
+ :type tunnel: str
+ :return: Converted tunnel name (CHILD_SA)
+ :rtype: str
+ """
tunnel_sw = None
if tunnel:
if tunnel.isnumeric():
tunnel_sw = f'{peer}-tunnel-{tunnel}'
elif tunnel == 'vti':
tunnel_sw = f'{peer}-vti'
+ return tunnel_sw
+
+
+def _initiate_peer_with_childsas(
+ peer: str, tunnel: typing.Optional[str] = None
+) -> None:
+ """
+ Initiate IPSEC peer SAs by vici.
+ If tunnel is None it initiates all peers tunnels
+ :param peer: Peer name (IKE_SA)
+ :type peer: str
+ :param tunnel: tunnel number (CHILD_SA)
+ :type tunnel: str
+ """
+ tunnel_sw = _get_tunnel_sw_format(peer, tunnel)
try:
- sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw)
- if not sa_list:
+ con_list: list = vyos.ipsec.get_vici_connection_by_name(peer)
+ if not con_list:
raise vyos.opmode.IncorrectValue(
- f'Peer\'s {peer} SA(s) not found, aborting')
- if tunnel and sa_list:
- childsa_id_list: list = _get_childsa_id_list(sa_list)
- if not childsa_id_list:
- raise vyos.opmode.IncorrectValue(
- f'Peer {peer} tunnel {tunnel} SA(s) not found, aborting')
- vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw)
- print(f'Peer {peer} reset result: success')
- except (vyos.ipsec.ViciInitiateError) as err:
+ f"Peer's {peer} SA(s) not loaded. Initiation was failed"
+ )
+ childsa_name_list: list = _get_con_childsa_name_list(con_list)
+
+ if not tunnel_sw:
+ vyos.ipsec.vici_initiate_all_child_sa_by_ike(peer, childsa_name_list)
+ print(f'Peer {peer} initiate result: success')
+ return
+
+ if tunnel_sw in childsa_name_list:
+ vyos.ipsec.vici_initiate_all_child_sa_by_ike(peer, [tunnel_sw])
+ print(f'Peer {peer} tunnel {tunnel} initiate result: success')
+ return
+
+ raise vyos.opmode.IncorrectValue(f'Peer {peer} SA {tunnel} not found, aborting')
+
+ except vyos.ipsec.ViciInitiateError as err:
raise vyos.opmode.UnconfiguredSubsystem(err)
- except (vyos.ipsec.ViciCommandError) as err:
+ except vyos.ipsec.ViciCommandError as err:
raise vyos.opmode.IncorrectValue(err)
-def reset_all_peers():
+def _terminate_peer(peer: str, tunnel: typing.Optional[str] = None) -> None:
+ """
+ Terminate IPSEC peer SAs by vici.
+ If tunnel is None it terminates all peers tunnels
+ :param peer: Peer name (IKE_SA)
+ :type peer: str
+ :param tunnel: tunnel number (CHILD_SA)
+ :type tunnel: str
+ """
+ # Convert tunnel to Strongwan format of CHILD_SA
+ tunnel_sw = _get_tunnel_sw_format(peer, tunnel)
+ try:
+ sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw)
+ if sa_list:
+ if tunnel:
+ childsa_id_list: list = _get_childsa_id_list(sa_list)
+ if childsa_id_list:
+ vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw)
+ print(f'Peer {peer} tunnel {tunnel} terminate result: success')
+ else:
+ Warning(
+ f'Peer {peer} tunnel {tunnel} SA is not initiated. Nothing to terminate'
+ )
+ else:
+ vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw)
+ print(f'Peer {peer} terminate result: success')
+ else:
+ Warning(f"Peer's {peer} SAs are not initiated. Nothing to terminate")
+
+ except vyos.ipsec.ViciInitiateError as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except vyos.ipsec.ViciCommandError as err:
+ raise vyos.opmode.IncorrectValue(err)
+
+
+def reset_peer(peer: str, tunnel: typing.Optional[str] = None) -> None:
+ """
+ Reset IPSEC peer SAs.
+ If tunnel is None it resets all peers tunnels
+ :param peer: Peer name (IKE_SA)
+ :type peer: str
+ :param tunnel: tunnel number (CHILD_SA)
+ :type tunnel: str
+ """
+ _terminate_peer(peer, tunnel)
+ peer_config = _get_sitetosite_peer_config(peer)
+ # initiate SAs only if 'connection-type=initiate'
+ if (
+ 'connection_type' in peer_config
+ and peer_config['connection_type'] == 'initiate'
+ ):
+ _initiate_peer_with_childsas(peer, tunnel)
+
+
+def reset_all_peers() -> None:
sitetosite_list = _get_all_sitetosite_peers_name_list()
if sitetosite_list:
for peer_name in sitetosite_list:
try:
reset_peer(peer_name)
- except (vyos.opmode.IncorrectValue) as err:
+ except vyos.opmode.IncorrectValue as err:
print(err)
print('Peers reset result: success')
else:
raise vyos.opmode.UnconfiguredSubsystem(
- 'VPN IPSec site-to-site is not configured, aborting')
+ 'VPN IPSec site-to-site is not configured, aborting'
+ )
def _get_ra_session_list_by_username(username: typing.Optional[str] = None):
@@ -500,7 +660,7 @@ def _get_ra_session_list_by_username(username: typing.Optional[str] = None):
def reset_ra(username: typing.Optional[str] = None):
- #Reset remote-access ipsec sessions
+ # Reset remote-access ipsec sessions
if username:
list_sa_id = _get_ra_session_list_by_username(username)
else:
@@ -514,32 +674,47 @@ def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str):
ike_sa_name = f'dmvpn-{profile}-{tunnel}'
try:
# Get IKE SAs
- sa_list = convert_data(
- vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ sa_list = convert_data(vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
if not sa_list:
raise vyos.opmode.IncorrectValue(
- f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
- sa_nbma_list = list([x for x in sa_list if
- ike_sa_name in x and x[ike_sa_name][
- 'remote-host'] == nbma_dst])
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting'
+ )
+ sa_nbma_list = list(
+ [
+ x
+ for x in sa_list
+ if ike_sa_name in x and x[ike_sa_name]['remote-host'] == nbma_dst
+ ]
+ )
if not sa_nbma_list:
raise vyos.opmode.IncorrectValue(
- f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting')
+ f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting'
+ )
# terminate IKE SAs
- vyos.ipsec.terminate_vici_ikeid_list(list(
- [x[ike_sa_name]['uniqueid'] for x in sa_nbma_list if
- ike_sa_name in x]))
+ vyos.ipsec.terminate_vici_ikeid_list(
+ list(
+ [
+ x[ike_sa_name]['uniqueid']
+ for x in sa_nbma_list
+ if ike_sa_name in x
+ ]
+ )
+ )
# initiate IKE SAs
for ike in sa_nbma_list:
if ike_sa_name in ike:
- vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
- ike[ike_sa_name]['local-host'],
- ike[ike_sa_name]['remote-host'])
+ vyos.ipsec.vici_initiate(
+ ike_sa_name,
+ 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'],
+ )
print(
- f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success')
- except (vyos.ipsec.ViciInitiateError) as err:
+ f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success'
+ )
+ except vyos.ipsec.ViciInitiateError as err:
raise vyos.opmode.UnconfiguredSubsystem(err)
- except (vyos.ipsec.ViciCommandError) as err:
+ except vyos.ipsec.ViciCommandError as err:
raise vyos.opmode.IncorrectValue(err)
@@ -549,24 +724,30 @@ def reset_profile_all(profile: str, tunnel: str):
try:
# Get IKE SAs
sa_list: list = convert_data(
- vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None)
+ )
if not sa_list:
raise vyos.opmode.IncorrectValue(
- f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting'
+ )
# terminate IKE SAs
vyos.ipsec.terminate_vici_by_name(ike_sa_name, None)
# initiate IKE SAs
for ike in sa_list:
if ike_sa_name in ike:
- vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
- ike[ike_sa_name]['local-host'],
- ike[ike_sa_name]['remote-host'])
+ vyos.ipsec.vici_initiate(
+ ike_sa_name,
+ 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'],
+ )
print(
- f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success')
+ f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success'
+ )
print(f'Profile {profile} tunnel {tunnel} reset result: success')
- except (vyos.ipsec.ViciInitiateError) as err:
+ except vyos.ipsec.ViciInitiateError as err:
raise vyos.opmode.UnconfiguredSubsystem(err)
- except (vyos.ipsec.ViciCommandError) as err:
+ except vyos.ipsec.ViciCommandError as err:
raise vyos.opmode.IncorrectValue(err)
@@ -734,36 +915,56 @@ def _get_formatted_output_ra_summary(ra_output_list: list):
if child_sa_key:
child_sa = sa['child-sas'][child_sa_key]
sa_ipsec_proposal = _get_formatted_ipsec_proposal(child_sa)
- sa_state = "UP"
+ sa_state = 'UP'
sa_uptime = seconds_to_human(sa['established'])
else:
sa_ipsec_proposal = ''
- sa_state = "DOWN"
+ sa_state = 'DOWN'
sa_uptime = ''
sa_data.append(
- [sa_id, sa_username, sa_protocol, sa_state, sa_uptime,
- sa_tunnel_ip,
- sa_remotehost, sa_remoteid, sa_ike_proposal,
- sa_ipsec_proposal])
-
- headers = ["Connection ID", "Username", "Protocol", "State", "Uptime",
- "Tunnel IP", "Remote Host", "Remote ID", "IKE Proposal",
- "IPSec Proposal"]
+ [
+ sa_id,
+ sa_username,
+ sa_protocol,
+ sa_state,
+ sa_uptime,
+ sa_tunnel_ip,
+ sa_remotehost,
+ sa_remoteid,
+ sa_ike_proposal,
+ sa_ipsec_proposal,
+ ]
+ )
+
+ headers = [
+ 'Connection ID',
+ 'Username',
+ 'Protocol',
+ 'State',
+ 'Uptime',
+ 'Tunnel IP',
+ 'Remote Host',
+ 'Remote ID',
+ 'IKE Proposal',
+ 'IPSec Proposal',
+ ]
sa_data = sorted(sa_data, key=_alphanum_key)
output = tabulate(sa_data, headers)
return output
-def show_ra_detail(raw: bool, username: typing.Optional[str] = None,
- conn_id: typing.Optional[str] = None):
+def show_ra_detail(
+ raw: bool,
+ username: typing.Optional[str] = None,
+ conn_id: typing.Optional[str] = None,
+):
list_sa: list = _get_ra_sessions()
if username:
list_sa = _filter_ikesas(list_sa, 'remote-eap-id', username)
elif conn_id:
list_sa = _filter_ikesas(list_sa, 'uniqueid', conn_id)
if not list_sa:
- raise vyos.opmode.IncorrectValue(
- f'No active connections found, aborting')
+ raise vyos.opmode.IncorrectValue('No active connections found, aborting')
if raw:
return list_sa
return _get_output_ra_sas_detail(list_sa)
@@ -772,8 +973,7 @@ def show_ra_detail(raw: bool, username: typing.Optional[str] = None,
def show_ra_summary(raw: bool):
list_sa: list = _get_ra_sessions()
if not list_sa:
- raise vyos.opmode.IncorrectValue(
- f'No active connections found, aborting')
+ raise vyos.opmode.IncorrectValue('No active connections found, aborting')
if raw:
return list_sa
return _get_formatted_output_ra_summary(list_sa)
@@ -783,9 +983,12 @@ def show_ra_summary(raw: bool):
def _get_raw_psk():
conf: ConfigTreeQuery = ConfigTreeQuery()
config_path = ['vpn', 'ipsec', 'authentication', 'psk']
- psk_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ psk_config = conf.get_config_dict(
+ config_path,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ )
psk_list = []
for psk, psk_data in psk_config.items():
@@ -796,11 +999,13 @@ def _get_raw_psk():
def _get_formatted_psk(psk_list):
- headers = ["PSK", "Id", "Secret"]
+ headers = ['PSK', 'Id', 'Secret']
formatted_data = []
for psk_data in psk_list:
- formatted_data.append([psk_data["psk"], "\n".join(psk_data["id"]), psk_data["secret"]])
+ formatted_data.append(
+ [psk_data['psk'], '\n'.join(psk_data['id']), psk_data['secret']]
+ )
return tabulate(formatted_data, headers=headers)
@@ -808,16 +1013,36 @@ def _get_formatted_psk(psk_list):
def show_psk(raw: bool):
config = ConfigTreeQuery()
if not config.exists('vpn ipsec authentication psk'):
- raise vyos.opmode.UnconfiguredSubsystem('VPN ipsec psk authentication is not configured')
+ raise vyos.opmode.UnconfiguredSubsystem(
+ 'VPN ipsec psk authentication is not configured'
+ )
psk = _get_raw_psk()
if raw:
return psk
return _get_formatted_psk(psk)
+
# PSK block end
+def _get_sitetosite_peer_config(peer: str):
+ """
+ Return site-to-site peers configuration
+ :return: site-to-site peers configuration
+ :rtype: list
+ """
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ config_path = ['vpn', 'ipsec', 'site-to-site', 'peer', peer]
+ peers_config = conf.get_config_dict(
+ config_path,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ )
+ return peers_config
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/serial.py b/src/op_mode/serial.py
new file mode 100644
index 000000000..a5864872b
--- /dev/null
+++ b/src/op_mode/serial.py
@@ -0,0 +1,38 @@
+#!/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, typing
+
+import vyos.opmode
+from vyos.utils.serial import restart_login_consoles as _restart_login_consoles
+
+def restart_console(device_name: typing.Optional[str]):
+ # Service control moved to vyos.utils.serial to unify checks and prompts.
+ # If users are connected, we want to show an informational message and a prompt
+ # to continue, verifying that the user acknowledges possible interruptions.
+ if device_name:
+ _restart_login_consoles(prompt_user=True, quiet=False, devices=[device_name])
+ else:
+ _restart_login_consoles(prompt_user=True, quiet=False)
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)