summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/airbag.py24
-rw-r--r--python/vyos/configquery.py44
-rw-r--r--python/vyos/configsession.py7
-rw-r--r--python/vyos/configverify.py39
-rw-r--r--python/vyos/defaults.py5
-rwxr-xr-x[-rw-r--r--]python/vyos/ifconfig/interface.py23
-rw-r--r--python/vyos/ifconfig/l2tpv3.py24
-rw-r--r--python/vyos/ifconfig/vrrp.py9
-rw-r--r--python/vyos/ifconfig/vti.py7
-rw-r--r--python/vyos/ifconfig/wireguard.py12
-rw-r--r--python/vyos/pki.py5
-rw-r--r--python/vyos/template.py50
-rw-r--r--python/vyos/util.py10
13 files changed, 209 insertions, 50 deletions
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py
index 510ab7f46..a20f44207 100644
--- a/python/vyos/airbag.py
+++ b/python/vyos/airbag.py
@@ -18,7 +18,6 @@ from datetime import datetime
from vyos import debug
from vyos.logger import syslog
-from vyos.version import get_version
from vyos.version import get_full_version_data
@@ -78,7 +77,7 @@ def bug_report(dtype, value, trace):
information.update({
'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'trace': trace,
- 'instructions': COMMUNITY if 'rolling' in get_version() else SUPPORTED,
+ 'instructions': INSTRUCTIONS,
'note': note,
})
@@ -162,20 +161,13 @@ When reporting problems, please include as much information as possible:
"""
-COMMUNITY = """\
-- Make sure you are running the latest version of the code available at
- https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso
-- Consult the forum to see how to handle this issue
- https://forum.vyos.io
-- Join our community on slack where our users exchange help and advice
- https://vyos.slack.com
-""".strip()
-
-SUPPORTED = """\
-- Make sure you are running the latest stable version of VyOS
- the code is available at https://downloads.vyos.io/?dir=release/current
-- Contact us using the online help desk
+INSTRUCTIONS = """\
+- Contact us using the online help desk if you have a subscription:
https://support.vyos.io/
-- Join our community on slack where our users exchange help and advice
+- Make sure you are running the latest version of VyOS available at:
+ https://vyos.net/get/
+- Consult the community forum to see how to handle this issue:
+ https://forum.vyos.io
+- Join us on Slack where our users exchange help and advice:
https://vyos.slack.com
""".strip()
diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py
index ed7346f1f..1cdcbcf39 100644
--- a/python/vyos/configquery.py
+++ b/python/vyos/configquery.py
@@ -18,9 +18,16 @@ A small library that allows querying existence or value(s) of config
settings from op mode, and execution of arbitrary op mode commands.
'''
+import re
+import json
+from copy import deepcopy
from subprocess import STDOUT
-from vyos.util import popen
+import vyos.util
+import vyos.xml
+from vyos.config import Config
+from vyos.configtree import ConfigTree
+from vyos.configsource import ConfigSourceSession
class ConfigQueryError(Exception):
pass
@@ -51,32 +58,59 @@ class CliShellApiConfigQuery(GenericConfigQuery):
def exists(self, path: list):
cmd = ' '.join(path)
- (_, err) = popen(f'cli-shell-api existsActive {cmd}')
+ (_, err) = vyos.util.popen(f'cli-shell-api existsActive {cmd}')
if err:
return False
return True
def value(self, path: list):
cmd = ' '.join(path)
- (out, err) = popen(f'cli-shell-api returnActiveValue {cmd}')
+ (out, err) = vyos.util.popen(f'cli-shell-api returnActiveValue {cmd}')
if err:
raise ConfigQueryError('No value for given path')
return out
def values(self, path: list):
cmd = ' '.join(path)
- (out, err) = popen(f'cli-shell-api returnActiveValues {cmd}')
+ (out, err) = vyos.util.popen(f'cli-shell-api returnActiveValues {cmd}')
if err:
raise ConfigQueryError('No values for given path')
return out
+class ConfigTreeQuery(GenericConfigQuery):
+ def __init__(self):
+ super().__init__()
+
+ config_source = ConfigSourceSession()
+ self.configtree = Config(config_source=config_source)
+
+ def exists(self, path: list):
+ return self.configtree.exists(path)
+
+ def value(self, path: list):
+ return self.configtree.return_value(path)
+
+ def values(self, path: list):
+ return self.configtree.return_values(path)
+
+ def list_nodes(self, path: list):
+ return self.configtree.list_nodes(path)
+
+ def get_config_dict(self, path=[], effective=False, key_mangling=None,
+ get_first_key=False, no_multi_convert=False,
+ no_tag_node_value_mangle=False):
+ return self.configtree.get_config_dict(path, effective=effective,
+ key_mangling=key_mangling, get_first_key=get_first_key,
+ no_multi_convert=no_multi_convert,
+ no_tag_node_value_mangle=no_tag_node_value_mangle)
+
class VbashOpRun(GenericOpRun):
def __init__(self):
super().__init__()
def run(self, path: list, **kwargs):
cmd = ' '.join(path)
- (out, err) = popen(f'. /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run; _vyatta_op_run {cmd}', stderr=STDOUT, **kwargs)
+ (out, err) = vyos.util.popen(f'. /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run; _vyatta_op_run {cmd}', stderr=STDOUT, **kwargs)
if err:
raise ConfigQueryError(out)
return out
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 670e6c7fc..f28ad09c5 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -10,14 +10,14 @@
# 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os
import re
import sys
import subprocess
-from vyos.util import call
+from vyos.util import is_systemd_service_running
CLI_SHELL_API = '/bin/cli-shell-api'
SET = '/opt/vyatta/sbin/my_set'
@@ -73,8 +73,7 @@ def inject_vyos_env(env):
env['vyos_validators_dir'] = '/usr/libexec/vyos/validators'
# if running the vyos-configd daemon, inject the vyshim env var
- ret = call('systemctl is-active --quiet vyos-configd.service')
- if not ret:
+ if is_systemd_service_running('vyos-configd.service'):
env['vyshim'] = '/usr/sbin/vyshim'
return env
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 979e28b11..4279e6982 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -149,9 +149,38 @@ def verify_eapol(config):
recurring validation of EAPoL configuration.
"""
if 'eapol' in config:
- if not {'cert_file', 'key_file'} <= set(config['eapol']):
- raise ConfigError('Both cert and key-file must be specified '\
- 'when using EAPoL!')
+ if 'certificate' not in config['eapol']:
+ raise ConfigError('Certificate must be specified when using EAPoL!')
+
+ if 'certificate' not in config['pki']:
+ raise ConfigError('Invalid certificate specified for EAPoL')
+
+ cert_name = config['eapol']['certificate']
+
+ if cert_name not in config['pki']['certificate']:
+ raise ConfigError('Invalid certificate specified for EAPoL')
+
+ cert = config['pki']['certificate'][cert_name]
+
+ if 'certificate' not in cert or 'private' not in cert or 'key' not in cert['private']:
+ raise ConfigError('Invalid certificate/private key specified for EAPoL')
+
+ if 'password_protected' in cert['private']:
+ raise ConfigError('Encrypted private key cannot be used for EAPoL')
+
+ if 'ca_certificate' in config['eapol']:
+ if 'ca' not in config['pki']:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
+
+ ca_cert_name = config['eapol']['ca_certificate']
+
+ if ca_cert_name not in config['pki']['ca']:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
+
+ ca_cert = config['pki']['ca'][cert_name]
+
+ if 'certificate' not in ca_cert:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
def verify_mirror(config):
"""
@@ -315,7 +344,7 @@ def verify_accel_ppp_base_service(config):
# vertify auth settings
if dict_search('authentication.mode', config) == 'local':
if not dict_search('authentication.local_users', config):
- raise ConfigError('PPPoE local auth mode requires local users to be configured!')
+ raise ConfigError('Authentication mode local requires local users to be configured!')
for user in dict_search('authentication.local_users.username', config):
user_config = config['authentication']['local_users']['username'][user]
@@ -339,7 +368,7 @@ def verify_accel_ppp_base_service(config):
raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
if 'gateway_address' not in config:
- raise ConfigError('PPPoE server requires gateway-address to be configured!')
+ raise ConfigError('Server requires gateway-address to be configured!')
if 'name_server_ipv4' in config:
if len(config['name_server_ipv4']) > 2:
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 9921e3b5f..03006c383 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -22,7 +22,10 @@ directories = {
"migrate": "/opt/vyatta/etc/config-migrate/migrate",
"log": "/var/log/vyatta",
"templates": "/usr/share/vyos/templates/",
- "certbot": "/config/auth/letsencrypt"
+ "certbot": "/config/auth/letsencrypt",
+ "api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/",
+ "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/"
+
}
cfg_group = 'vyattacfg'
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 08b7af90b..a1928ba51 100644..100755
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -311,6 +311,28 @@ class Interface(Control):
cmd = 'ip link del dev {ifname}'.format(**self.config)
return self._cmd(cmd)
+ def _set_vrf_ct_zone(self, vrf):
+ """
+ Add/Remove rules in nftables to associate traffic in VRF to an
+ individual conntack zone
+ """
+ if vrf:
+ # Get routing table ID for VRF
+ vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get(
+ 'info_data', {}).get('table')
+ # Add map element with interface and zone ID
+ if vrf_table_id:
+ self._cmd(
+ f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}'
+ )
+ else:
+ nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
+ # Check if deleting is possible first to avoid raising errors
+ _, err = self._popen(f'nft -c {nft_del_element}')
+ if not err:
+ # Remove map element
+ self._cmd(f'nft {nft_del_element}')
+
def get_min_mtu(self):
"""
Get hardware minimum supported MTU
@@ -401,6 +423,7 @@ class Interface(Control):
>>> Interface('eth0').set_vrf()
"""
self.set_interface('vrf', vrf)
+ self._set_vrf_ct_zone(vrf)
def set_arp_cache_tmo(self, tmo):
"""
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index 7ff0fdd0e..fcd1fbf81 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -13,8 +13,28 @@
# 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/>.
+from time import sleep
+from time import time
+from vyos.util import run
from vyos.ifconfig.interface import Interface
+def wait_for_add_l2tpv3(timeout=10, sleep_interval=1, cmd=None):
+ '''
+ In some cases, we need to wait until local address is assigned.
+ And only then can the l2tpv3 tunnel be configured.
+ For example when ipv6 address in tentative state
+ or we wait for some routing daemon for remote address.
+ '''
+ start_time = time()
+ test_command = cmd
+ while True:
+ if (start_time + timeout) < time():
+ return None
+ result = run(test_command)
+ if result == 0:
+ return True
+ sleep(sleep_interval)
+
@Interface.register
class L2TPv3If(Interface):
"""
@@ -43,7 +63,9 @@ class L2TPv3If(Interface):
cmd += ' encap {encapsulation}'
cmd += ' local {source_address}'
cmd += ' remote {remote}'
- self._cmd(cmd.format(**self.config))
+ c = cmd.format(**self.config)
+ # wait until the local/remote address is available, but no more 10 sec.
+ wait_for_add_l2tpv3(cmd=c)
# setup session
cmd = 'ip l2tp add session name {ifname}'
diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py
index d3e9d5df2..b522cc1ab 100644
--- a/python/vyos/ifconfig/vrrp.py
+++ b/python/vyos/ifconfig/vrrp.py
@@ -92,11 +92,14 @@ class VRRP(object):
try:
# send signal to generate the configuration file
pid = util.read_file(cls.location['pid'])
- os.kill(int(pid), cls._signal[what])
+ util.wait_for_file_write_complete(fname,
+ pre_hook=(lambda: os.kill(int(pid), cls._signal[what])),
+ timeout=30)
- # should look for file size change?
- sleep(0.2)
return util.read_file(fname)
+ except OSError:
+ # raised by vyos.util.read_file
+ raise VRRPNoData("VRRP data is not available (wait time exceeded)")
except FileNotFoundError:
raise VRRPNoData("VRRP data is not available (process not running or no active groups)")
except Exception:
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index a217d28ea..470ebbff3 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -33,7 +33,7 @@ class VTIIf(Interface):
# - https://man7.org/linux/man-pages/man8/ip-link.8.html
# - https://man7.org/linux/man-pages/man8/ip-tunnel.8.html
mapping = {
- 'source_interface' : 'dev',
+ 'source_interface' : 'dev',
}
if_id = self.ifname.lstrip('vti')
@@ -50,8 +50,3 @@ class VTIIf(Interface):
self._cmd(cmd.format(**self.config))
self.set_interface('admin_state', 'down')
-
- def set_admin_state(self, state):
- # function is not implemented for VTI interfaces as this is entirely
- # handled by the ipsec up/down scripts
- pass
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index e5b9c4408..c4cf2fbbf 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -95,7 +95,7 @@ class WireGuardOperational(Operational):
for peer in c.list_effective_nodes(["peer"]):
if wgdump['peers']:
- pubkey = c.return_effective_value(["peer", peer, "pubkey"])
+ pubkey = c.return_effective_value(["peer", peer, "public_key"])
if pubkey in wgdump['peers']:
wgpeer = wgdump['peers'][pubkey]
@@ -194,11 +194,15 @@ class WireGuardIf(Interface):
peer = config['peer_remove'][tmp]
peer['ifname'] = config['ifname']
- cmd = 'wg set {ifname} peer {pubkey} remove'
+ cmd = 'wg set {ifname} peer {public_key} remove'
self._cmd(cmd.format(**peer))
+ config['private_key_file'] = '/tmp/tmp.wireguard.key'
+ with open(config['private_key_file'], 'w') as f:
+ f.write(config['private_key'])
+
# Wireguard base command is identical for every peer
- base_cmd = 'wg set {ifname} private-key {private_key}'
+ base_cmd = 'wg set {ifname} private-key {private_key_file}'
if 'port' in config:
base_cmd += ' listen-port {port}'
if 'fwmark' in config:
@@ -210,7 +214,7 @@ class WireGuardIf(Interface):
peer = config['peer'][tmp]
# start of with a fresh 'wg' command
- cmd = base_cmd + ' peer {pubkey}'
+ cmd = base_cmd + ' peer {public_key}'
# If no PSK is given remove it by using /dev/null - passing keys via
# the shell (usually bash) is considered insecure, thus we use a file
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index 1c6282d84..68ad73bf2 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -43,6 +43,8 @@ CSR_BEGIN='-----BEGIN CERTIFICATE REQUEST-----\n'
CSR_END='\n-----END CERTIFICATE REQUEST-----'
DH_BEGIN='-----BEGIN DH PARAMETERS-----\n'
DH_END='\n-----END DH PARAMETERS-----'
+OVPN_BEGIN = '-----BEGIN OpenVPN Static key V{0}-----\n'
+OVPN_END = '\n-----END OpenVPN Static key V{0}-----'
# Print functions
@@ -227,6 +229,9 @@ def wrap_crl(raw_data):
def wrap_dh_parameters(raw_data):
return DH_BEGIN + raw_data + DH_END
+def wrap_openvpn_key(raw_data, version='1'):
+ return OVPN_BEGIN.format(version) + raw_data + OVPN_END.format(version)
+
# Load functions
def load_public_key(raw_data, wrap_tags=True):
diff --git a/python/vyos/template.py b/python/vyos/template.py
index f03fd7ee7..08a5712af 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -29,13 +29,17 @@ _FILTERS = {}
# reuse Environments with identical settings to improve performance
@functools.lru_cache(maxsize=2)
-def _get_environment():
+def _get_environment(location=None):
+ if location is None:
+ loc_loader=FileSystemLoader(directories["templates"])
+ else:
+ loc_loader=FileSystemLoader(location)
env = Environment(
# Don't check if template files were modified upon re-rendering
auto_reload=False,
# Cache up to this number of templates for quick re-rendering
cache_size=100,
- loader=FileSystemLoader(directories["templates"]),
+ loader=loc_loader,
trim_blocks=True,
)
env.filters.update(_FILTERS)
@@ -63,7 +67,7 @@ def register_filter(name, func=None):
return func
-def render_to_string(template, content, formater=None):
+def render_to_string(template, content, formater=None, location=None):
"""Render a template from the template directory, raise on any errors.
:param template: the path to the template relative to the template folder
@@ -78,7 +82,7 @@ def render_to_string(template, content, formater=None):
package is build (recovering the load time and overhead caused by having the
file out of the code).
"""
- template = _get_environment().get_template(template)
+ template = _get_environment(location).get_template(template)
rendered = template.render(content)
if formater is not None:
rendered = formater(rendered)
@@ -93,6 +97,7 @@ def render(
permission=None,
user=None,
group=None,
+ location=None,
):
"""Render a template from the template directory to a file, raise on any errors.
@@ -109,7 +114,7 @@ def render(
# As we are opening the file with 'w', we are performing the rendering before
# calling open() to not accidentally erase the file if rendering fails
- rendered = render_to_string(template, content, formater)
+ rendered = render_to_string(template, content, formater, location)
# Write to file
with open(destination, "w") as file:
@@ -433,3 +438,38 @@ def get_esp_ike_cipher(group_config):
ciphers.append(tmp)
return ciphers
+
+@register_filter('get_uuid')
+def get_uuid(interface):
+ """ Get interface IP addresses"""
+ from uuid import uuid1
+ return uuid1()
+
+openvpn_translate = {
+ 'des': 'des-cbc',
+ '3des': 'des-ede3-cbc',
+ 'bf128': 'bf-cbc',
+ 'bf256': 'bf-cbc',
+ 'aes128gcm': 'aes-128-gcm',
+ 'aes128': 'aes-128-cbc',
+ 'aes192gcm': 'aes-192-gcm',
+ 'aes192': 'aes-192-cbc',
+ 'aes256gcm': 'aes-256-gcm',
+ 'aes256': 'aes-256-cbc'
+}
+
+@register_filter('openvpn_cipher')
+def get_openvpn_cipher(cipher):
+ if cipher in openvpn_translate:
+ return openvpn_translate[cipher].upper()
+ return cipher.upper()
+
+@register_filter('openvpn_ncp_ciphers')
+def get_openvpn_ncp_ciphers(ciphers):
+ out = []
+ for cipher in ciphers:
+ if cipher in openvpn_translate:
+ out.append(openvpn_translate[cipher])
+ else:
+ out.append(cipher)
+ return ':'.join(out).upper()
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 5a96013ea..59f9f1c44 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -515,6 +515,7 @@ def wait_for_inotify(file_path, pre_hook=None, event_type=None, timeout=None, sl
from inotify.adapters import Inotify
from time import time
+ from time import sleep
time_start = time()
@@ -530,11 +531,14 @@ def wait_for_inotify(file_path, pre_hook=None, event_type=None, timeout=None, sl
# the file failed to have been written to and closed within the timeout
raise OSError("Waiting for file {} to be written has failed".format(file_path))
+ # Most such events don't take much time, so it's better to check right away
+ # and sleep later.
if event is not None:
(_, type_names, path, filename) = event
if filename == os.path.basename(file_path):
if event_type in type_names:
return
+ sleep(sleep_interval)
def wait_for_file_write_complete(file_path, pre_hook=None, timeout=None, sleep_interval=0.1):
""" Waits for a process to close a file after opening it in write mode. """
@@ -800,3 +804,9 @@ def make_incremental_progressbar(increment: float):
# Ignore further calls.
while True:
yield
+
+def is_systemd_service_running(service):
+ """ Test is a specified systemd service is actually running.
+ Returns True if service is running, false otherwise. """
+ tmp = run(f'systemctl is-active --quiet {service}')
+ return bool((tmp == 0))