diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/config_mgmt.py | 12 | ||||
-rw-r--r-- | python/vyos/configsession.py | 33 | ||||
-rw-r--r-- | python/vyos/configverify.py | 19 | ||||
-rw-r--r-- | python/vyos/defaults.py | 14 | ||||
-rwxr-xr-x | python/vyos/template.py | 20 | ||||
-rw-r--r-- | python/vyos/utils/commit.py | 27 | ||||
-rw-r--r-- | python/vyos/utils/file.py | 21 | ||||
-rw-r--r-- | python/vyos/vyconf_session.py | 7 |
8 files changed, 138 insertions, 15 deletions
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py index dd8910afb..23eb3666e 100644 --- a/python/vyos/config_mgmt.py +++ b/python/vyos/config_mgmt.py @@ -44,6 +44,7 @@ from vyos.utils.io import ask_yes_no from vyos.utils.boot import boot_configuration_complete from vyos.utils.process import is_systemd_service_active from vyos.utils.process import rc_cmd +from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES SAVE_CONFIG = '/usr/libexec/vyos/vyos-save-config.py' config_json = '/run/vyatta/config/config.json' @@ -56,7 +57,6 @@ commit_hooks = { 'commit_archive': '02vyos-commit-archive', } -DEFAULT_TIME_MINUTES = 10 timer_name = 'commit-confirm' config_file = os.path.join(directories['config'], 'config.boot') @@ -144,14 +144,16 @@ class ConfigMgmt: ['system', 'config-management'], key_mangling=('-', '_'), get_first_key=True, - with_defaults=True, + with_recursive_defaults=True, ) self.max_revisions = int(d.get('commit_revisions', 0)) self.num_revisions = 0 self.locations = d.get('commit_archive', {}).get('location', []) self.source_address = d.get('commit_archive', {}).get('source_address', '') - self.reboot_unconfirmed = bool(d.get('commit_confirm') == 'reboot') + self.reboot_unconfirmed = bool( + d.get('commit_confirm', {}).get('action') == 'reboot' + ) self.config_dict = d if config.exists(['system', 'host-name']): @@ -181,7 +183,7 @@ class ConfigMgmt: # Console script functions # def commit_confirm( - self, minutes: int = DEFAULT_TIME_MINUTES, no_prompt: bool = False + self, minutes: int = DEFAULT_COMMIT_CONFIRM_MINUTES, no_prompt: bool = False ) -> Tuple[str, int]: """Commit with reload/reboot to saved config in 'minutes' minutes if 'confirm' call is not issued. @@ -805,7 +807,7 @@ def run(): '-t', dest='minutes', type=int, - default=DEFAULT_TIME_MINUTES, + default=DEFAULT_COMMIT_CONFIRM_MINUTES, help="Minutes until reboot, unless 'confirm'", ) commit_confirm.add_argument( diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 1b19c68b4..f0d636b89 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -25,6 +25,7 @@ from vyos.utils.boot import boot_configuration_complete from vyos.utils.backend import vyconf_backend from vyos.vyconf_session import VyconfSession from vyos.base import Warning as Warn +from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES CLI_SHELL_API = '/bin/cli-shell-api' @@ -32,10 +33,13 @@ SET = '/opt/vyatta/sbin/my_set' DELETE = '/opt/vyatta/sbin/my_delete' COMMENT = '/opt/vyatta/sbin/my_comment' COMMIT = '/opt/vyatta/sbin/my_commit' +COMMIT_CONFIRM = ['/usr/bin/config-mgmt', 'commit_confirm', '-y'] +CONFIRM = ['/usr/bin/config-mgmt', 'confirm'] DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] +MERGE_CONFIG = ['/usr/libexec/vyos/vyos-merge-config.py'] SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] INSTALL_IMAGE = [ '/usr/libexec/vyos/op_mode/image_installer.py', @@ -68,6 +72,7 @@ GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset'] REBOOT = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reboot'] +RENEW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'renew'] POWEROFF = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'poweroff'] OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add'] OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete'] @@ -298,6 +303,22 @@ class ConfigSession(object): return out + def commit_confirm(self, minutes: int = DEFAULT_COMMIT_CONFIRM_MINUTES): + if self._vyconf_session is None: + out = self.__run_command(COMMIT_CONFIRM + [f'-t {minutes}']) + else: + out = 'unimplemented' + + return out + + def confirm(self): + if self._vyconf_session is None: + out = self.__run_command(CONFIRM) + else: + out = 'unimplemented' + + return out + def discard(self): if self._vyconf_session is None: self.__run_command([DISCARD]) @@ -338,6 +359,14 @@ class ConfigSession(object): return out + def merge_config(self, file_path): + if self._vyconf_session is None: + out = self.__run_command(MERGE_CONFIG + [file_path]) + else: + out = 'unimplemented' + + return out + def save_config(self, file_path): if self._vyconf_session is None: out = self.__run_command(SAVE_CONFIG + [file_path]) @@ -384,6 +413,10 @@ class ConfigSession(object): out = self.__run_command(RESET + path) return out + def renew(self, path): + out = self.__run_command(RENEW + path) + return out + def poweroff(self, path): out = self.__run_command(POWEROFF + path) return out diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index d5f443f15..07eb29a68 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -527,6 +527,25 @@ def verify_pki_dh_parameters(config: dict, dh_name: str, min_key_size: int=0): if dh_bits < min_key_size: raise ConfigError(f'Minimum DH key-size is {min_key_size} bits!') +def verify_pki_openssh_key(config: dict, key_name: str): + """ + Common helper function user by PKI consumers to perform recurring + validation functions on OpenSSH keys + """ + if 'pki' not in config: + raise ConfigError('PKI is not configured!') + + if 'openssh' not in config['pki']: + raise ConfigError('PKI does not contain any OpenSSH keys!') + + if key_name not in config['pki']['openssh']: + raise ConfigError(f'OpenSSH key "{key_name}" not found in configuration!') + + if 'public' in config['pki']['openssh'][key_name]: + if not {'key', 'type'} <= set(config['pki']['openssh'][key_name]['public']): + raise ConfigError('Both public key and type must be defined for '\ + f'OpenSSH public key "{key_name}"!') + def verify_eapol(config: dict): """ Common helper function used by interface implementations to perform diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index fbde0298b..f84b14040 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -53,6 +53,10 @@ internal_ports = { 'certbot_haproxy' : 65080, # Certbot running behing haproxy } +config_files = { + 'sshd_user_ca' : '/run/sshd/trusted_user_ca', +} + config_status = '/tmp/vyos-config-status' api_config_state = '/run/http-api-state' frr_debug_enable = '/tmp/vyos.frr.debug' @@ -69,8 +73,8 @@ config_default = os.path.join(directories['data'], 'config.boot.default') rt_symbolic_names = { # Standard routing tables for Linux & reserved IDs for VyOS - 'default': 253, # Confusingly, a final fallthru, not the default. - 'main': 254, # The actual global table used by iproute2 unless told otherwise. + 'default': 253, # Confusingly, a final fallthru, not the default. + 'main': 254, # The actual global table used by iproute2 unless told otherwise. 'local': 255, # Special kernel loopback table. } @@ -78,3 +82,9 @@ rt_global_vrf = rt_symbolic_names['main'] rt_global_table = rt_symbolic_names['main'] vyconfd_conf = '/etc/vyos/vyconfd.conf' + +DEFAULT_COMMIT_CONFIRM_MINUTES = 10 + +commit_hooks = {'pre': '/etc/commit/pre-hooks.d', + 'post': '/etc/commit/post-hooks.d' + } diff --git a/python/vyos/template.py b/python/vyos/template.py index aa215db95..bf7928914 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -1079,7 +1079,7 @@ def vyos_defined(value, test_value=None, var_type=None): def get_default_port(service): """ Jinja2 plugin to retrieve common service port number from vyos.defaults - class form a Jinja2 template. This removes the need to hardcode, or pass in + class from a Jinja2 template. This removes the need to hardcode, or pass in the data using the general dictionary. Added to remove code complexity and make it easier to read. @@ -1092,3 +1092,21 @@ def get_default_port(service): raise RuntimeError(f'Service "{service}" not found in internal ' \ 'vyos.defaults.internal_ports dict!') return internal_ports[service] + +@register_clever_function('get_default_config_file') +def get_default_config_file(filename): + """ + Jinja2 plugin to retrieve a common configuration file path from + vyos.defaults class from a Jinja2 template. This removes the need to + hardcode, or pass in the data using the general dictionary. + + Added to remove code complexity and make it easier to read. + + Example: + {{ get_default_config_file('certbot_haproxy') }} + """ + from vyos.defaults import config_files + if filename not in config_files: + raise RuntimeError(f'Configuration file "{filename}" not found in '\ + 'internal vyos.defaults.config_files dict!') + return config_files[filename] diff --git a/python/vyos/utils/commit.py b/python/vyos/utils/commit.py index 9167c78d2..fc259dadb 100644 --- a/python/vyos/utils/commit.py +++ b/python/vyos/utils/commit.py @@ -101,3 +101,30 @@ def release_commit_lock_file(file_descr): return fcntl.lockf(file_descr, fcntl.LOCK_UN) file_descr.close() + + +def call_commit_hooks(which: str): + import re + import os + from pathlib import Path + from vyos.defaults import commit_hooks + from vyos.utils.process import rc_cmd + + if which not in list(commit_hooks): + raise ValueError(f'no entry {which} in commit_hooks') + + hook_dir = commit_hooks[which] + file_list = list(Path(hook_dir).glob('*')) + regex = re.compile('^[a-zA-Z0-9._-]+$') + hook_list = sorted([str(f) for f in file_list if regex.match(f.name)]) + err = False + out = '' + for runf in hook_list: + try: + e, o = rc_cmd(runf) + except FileNotFoundError: + continue + err = err | bool(e) + out = out + o + + return out, int(err) diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py index eaebb57a3..cc46d77d1 100644 --- a/python/vyos/utils/file.py +++ b/python/vyos/utils/file.py @@ -28,22 +28,28 @@ def file_is_persistent(path): absolute = os.path.abspath(os.path.dirname(path)) return re.match(location,absolute) -def read_file(fname, defaultonfailure=None): +def read_file(fname, defaultonfailure=None, sudo=False): """ read the content of a file, stripping any end characters (space, newlines) should defaultonfailure be not None, it is returned on failure to read """ try: - """ Read a file to string """ - with open(fname, 'r') as f: - data = f.read().strip() - return data + # Some files can only be read by root - emulate sudo cat call + if sudo: + from vyos.utils.process import cmd + data = cmd(['sudo', 'cat', fname]) + else: + # If not sudo, just read the file + with open(fname, 'r') as f: + data = f.read() + return data.strip() except Exception as e: if defaultonfailure is not None: return defaultonfailure raise e -def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False): +def write_file(fname, data, defaultonfailure=None, user=None, group=None, + mode=None, append=False, trailing_newline=False): """ Write content of data to given fname, should defaultonfailure be not None, it is returned on failure to read. @@ -60,6 +66,9 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N bytes = 0 with open(fname, 'w' if not append else 'a') as f: bytes = f.write(data) + if trailing_newline and not data.endswith('\n'): + f.write('\n') + bytes += 1 chown(fname, user, group) chmod(fname, mode) return bytes diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py index 4250f0cfb..3cf847b6c 100644 --- a/python/vyos/vyconf_session.py +++ b/python/vyos/vyconf_session.py @@ -29,6 +29,7 @@ from vyos.utils.session import in_config_session from vyos.proto.vyconf_proto import Errnum from vyos.utils.commit import acquire_commit_lock_file from vyos.utils.commit import release_commit_lock_file +from vyos.utils.commit import call_commit_hooks class VyconfSessionError(Exception): @@ -145,10 +146,14 @@ class VyconfSession: if lock_fd is None: return out, Errnum.COMMIT_IN_PROGRESS + pre_out, _ = call_commit_hooks('pre') out = vyconf_client.send_request('commit', token=self.__token) + os.environ['COMMIT_STATUS'] = 'FAILURE' if out.status else 'SUCCESS' + post_out, _ = call_commit_hooks('post') + release_commit_lock_file(lock_fd) - return self.output(out), out.status + return pre_out + self.output(out) + post_out, out.status @raise_exception @config_mode |