summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorNataliia S. <81954790+natali-rs1985@users.noreply.github.com>2024-10-21 10:30:38 +0300
committerGitHub <noreply@github.com>2024-10-21 10:30:38 +0300
commit18a9cec3deb6cc2dc49020a89208dc70defe9822 (patch)
treebe0bcfa93d52d559da03bc97eb928fc508bacd7d /python
parent5c76607a9faef1fb5dc07459a38d37261ce988c1 (diff)
parentfa272f5297177354714ee7867104f43e4e322df6 (diff)
downloadvyos-1x-18a9cec3deb6cc2dc49020a89208dc70defe9822.tar.gz
vyos-1x-18a9cec3deb6cc2dc49020a89208dc70defe9822.zip
Merge branch 'current' into T6695
Diffstat (limited to 'python')
-rw-r--r--python/vyos/config_mgmt.py351
-rw-r--r--python/vyos/configsession.py86
-rw-r--r--python/vyos/opmode.py2
-rw-r--r--python/vyos/pki.py37
4 files changed, 325 insertions, 151 deletions
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index d518737ca..1c2b70fdf 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -33,6 +33,8 @@ from urllib.parse import urlunsplit
from vyos.config import Config
from vyos.configtree import ConfigTree
from vyos.configtree import ConfigTreeError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
from vyos.configtree import show_diff
from vyos.load_config import load
from vyos.load_config import LoadConfigError
@@ -49,8 +51,10 @@ config_json = '/run/vyatta/config/config.json'
# created by vyatta-cfg-postinst
commit_post_hook_dir = '/etc/commit/post-hooks.d'
-commit_hooks = {'commit_revision': '01vyos-commit-revision',
- 'commit_archive': '02vyos-commit-archive'}
+commit_hooks = {
+ 'commit_revision': '01vyos-commit-revision',
+ 'commit_archive': '02vyos-commit-archive',
+}
DEFAULT_TIME_MINUTES = 10
timer_name = 'commit-confirm'
@@ -72,6 +76,7 @@ formatter = logging.Formatter('%(funcName)s: %(levelname)s:%(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
+
def save_config(target, json_out=None):
if json_out is None:
cmd = f'{SAVE_CONFIG} {target}'
@@ -81,6 +86,7 @@ def save_config(target, json_out=None):
if rc != 0:
logger.critical(f'save config failed: {out}')
+
def unsaved_commits(allow_missing_config=False) -> bool:
if get_full_version_data()['boot_via'] == 'livecd':
return False
@@ -92,6 +98,7 @@ def unsaved_commits(allow_missing_config=False) -> bool:
os.unlink(tmp_save)
return ret
+
def get_file_revision(rev: int):
revision = os.path.join(archive_dir, f'config.boot.{rev}.gz')
try:
@@ -102,12 +109,15 @@ def get_file_revision(rev: int):
return ''
return r
+
def get_config_tree_revision(rev: int):
c = get_file_revision(rev)
return ConfigTree(c)
+
def is_node_revised(path: list = [], rev1: int = 1, rev2: int = 0) -> bool:
from vyos.configtree import DiffTree
+
left = get_config_tree_revision(rev1)
right = get_config_tree_revision(rev2)
diff_tree = DiffTree(left, right)
@@ -115,9 +125,11 @@ def is_node_revised(path: list = [], rev1: int = 1, rev2: int = 0) -> bool:
return True
return False
+
class ConfigMgmtError(Exception):
pass
+
class ConfigMgmt:
def __init__(self, session_env=None, config=None):
if session_env:
@@ -128,15 +140,20 @@ class ConfigMgmt:
if config is None:
config = Config()
- d = config.get_config_dict(['system', 'config-management'],
- key_mangling=('-', '_'),
- get_first_key=True)
+ d = config.get_config_dict(
+ ['system', 'config-management'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_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.source_address = d.get('commit_archive', {}).get('source_address', '')
+ self.reboot_unconfirmed = bool(d.get('commit_confirm') == 'reboot')
+ self.config_dict = d
+
if config.exists(['system', 'host-name']):
self.hostname = config.return_value(['system', 'host-name'])
if config.exists(['system', 'domain-name']):
@@ -156,51 +173,73 @@ class ConfigMgmt:
# a call to compare without args is edit_level aware
edit_level = os.getenv('VYATTA_EDIT_LEVEL', '')
- self.edit_path = [l for l in edit_level.split('/') if l]
+ self.edit_path = [l for l in edit_level.split('/') if l] # noqa: E741
self.active_config = config._running_config
self.working_config = config._session_config
# Console script functions
#
- def commit_confirm(self, minutes: int=DEFAULT_TIME_MINUTES,
- no_prompt: bool=False) -> Tuple[str,int]:
- """Commit with reboot to saved config in 'minutes' minutes if
+ def commit_confirm(
+ self, minutes: int = DEFAULT_TIME_MINUTES, no_prompt: bool = False
+ ) -> Tuple[str, int]:
+ """Commit with reload/reboot to saved config in 'minutes' minutes if
'confirm' call is not issued.
"""
if is_systemd_service_active(f'{timer_name}.timer'):
msg = 'Another confirm is pending'
return msg, 1
- if unsaved_commits():
+ if self.reboot_unconfirmed and unsaved_commits():
W = '\nYou should save previous commits before commit-confirm !\n'
else:
W = ''
- prompt_str = f'''
+ if self.reboot_unconfirmed:
+ prompt_str = f"""
commit-confirm will automatically reboot in {minutes} minutes unless changes
-are confirmed.\n
-Proceed ?'''
+are confirmed.
+Proceed ?"""
+ else:
+ prompt_str = f"""
+commit-confirm will automatically reload previous config in {minutes} minutes
+unless changes are confirmed.
+Proceed ?"""
+
prompt_str = W + prompt_str
if not no_prompt and not ask_yes_no(prompt_str, default=True):
msg = 'commit-confirm canceled'
return msg, 1
- action = 'sg vyattacfg "/usr/bin/config-mgmt revert"'
+ if self.reboot_unconfirmed:
+ action = 'sg vyattacfg "/usr/bin/config-mgmt revert"'
+ else:
+ action = 'sg vyattacfg "/usr/bin/config-mgmt revert_soft"'
+
cmd = f'sudo systemd-run --quiet --on-active={minutes}m --unit={timer_name} {action}'
rc, out = rc_cmd(cmd)
if rc != 0:
raise ConfigMgmtError(out)
# start notify
- cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}'
+ if self.reboot_unconfirmed:
+ cmd = (
+ f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py --reboot {minutes}'
+ )
+ else:
+ cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}'
+
os.system(cmd)
- msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot'
+ if self.reboot_unconfirmed:
+ msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot'
+ else:
+ msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reload'
+
return msg, 0
- def confirm(self) -> Tuple[str,int]:
- """Do not reboot to saved config following 'commit-confirm'.
+ def confirm(self) -> Tuple[str, int]:
+ """Do not reboot/reload to saved/completed config following 'commit-confirm'.
Update commit log and archive.
"""
if not is_systemd_service_active(f'{timer_name}.timer'):
@@ -224,12 +263,15 @@ Proceed ?'''
self._add_log_entry(**entry)
self._update_archive()
- msg = 'Reboot timer stopped'
+ if self.reboot_unconfirmed:
+ msg = 'Reboot timer stopped'
+ else:
+ msg = 'Reload timer stopped'
+
return msg, 0
- def revert(self) -> Tuple[str,int]:
- """Reboot to saved config, dropping commits from 'commit-confirm'.
- """
+ def revert(self) -> Tuple[str, int]:
+ """Reboot to saved config, dropping commits from 'commit-confirm'."""
_ = self._read_tmp_log_entry()
# archived config will be reverted on boot
@@ -239,13 +281,39 @@ Proceed ?'''
return '', 0
- def rollback(self, rev: int, no_prompt: bool=False) -> Tuple[str,int]:
- """Reboot to config revision 'rev'.
- """
+ def revert_soft(self) -> Tuple[str, int]:
+ """Reload last revision, dropping commits from 'commit-confirm'."""
+ _ = self._read_tmp_log_entry()
+
+ # commits under commit-confirm are not added to revision list unless
+ # confirmed, hence a soft revert is to revision 0
+ revert_ct = self._get_config_tree_revision(0)
+
+ message = '[commit-confirm] Reverting to previous config now'
+ os.system('wall -n ' + message)
+
+ mask = os.umask(0o002)
+ session = ConfigSession(os.getpid(), app='config-mgmt')
+
+ try:
+ session.load_explicit(revert_ct)
+ session.commit()
+ except ConfigSessionError as e:
+ raise ConfigMgmtError(e) from e
+ finally:
+ os.umask(mask)
+ del session
+
+ return '', 0
+
+ def rollback(self, rev: int, no_prompt: bool = False) -> Tuple[str, int]:
+ """Reboot to config revision 'rev'."""
msg = ''
if not self._check_revision_number(rev):
- msg = f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}'
+ msg = (
+ f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}'
+ )
return msg, 1
prompt_str = 'Proceed with reboot ?'
@@ -274,12 +342,13 @@ Proceed ?'''
return msg, 0
def rollback_soft(self, rev: int):
- """Rollback without reboot (rollback-soft)
- """
+ """Rollback without reboot (rollback-soft)"""
msg = ''
if not self._check_revision_number(rev):
- msg = f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}'
+ msg = (
+ f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}'
+ )
return msg, 1
rollback_ct = self._get_config_tree_revision(rev)
@@ -292,9 +361,13 @@ Proceed ?'''
return msg, 0
- def compare(self, saved: bool=False, commands: bool=False,
- rev1: Optional[int]=None,
- rev2: Optional[int]=None) -> Tuple[str,int]:
+ def compare(
+ self,
+ saved: bool = False,
+ commands: bool = False,
+ rev1: Optional[int] = None,
+ rev2: Optional[int] = None,
+ ) -> Tuple[str, int]:
"""General compare function for config file revisions:
revision n vs. revision m; working version vs. active version;
or working version vs. saved version.
@@ -335,7 +408,7 @@ Proceed ?'''
return msg, 0
- def wrap_compare(self, options) -> Tuple[str,int]:
+ def wrap_compare(self, options) -> Tuple[str, int]:
"""Interface to vyatta-cfg-run: args collected as 'options' to parse
for compare.
"""
@@ -343,7 +416,7 @@ Proceed ?'''
r1 = None
r2 = None
if 'commands' in options:
- cmnds=True
+ cmnds = True
options.remove('commands')
for i in options:
if not i.isnumeric():
@@ -358,8 +431,7 @@ Proceed ?'''
# Initialization and post-commit hooks for conf-mode
#
def initialize_revision(self):
- """Initialize config archive, logrotate conf, and commit log.
- """
+ """Initialize config archive, logrotate conf, and commit log."""
mask = os.umask(0o002)
os.makedirs(archive_dir, exist_ok=True)
json_dir = os.path.dirname(config_json)
@@ -371,8 +443,7 @@ Proceed ?'''
self._add_logrotate_conf()
- if (not os.path.exists(commit_log_file) or
- self._get_number_of_revisions() == 0):
+ if not os.path.exists(commit_log_file) or self._get_number_of_revisions() == 0:
user = self._get_user()
via = 'init'
comment = ''
@@ -399,8 +470,7 @@ Proceed ?'''
self._update_archive()
def commit_archive(self):
- """Upload config to remote archive.
- """
+ """Upload config to remote archive."""
from vyos.remote import upload
hostname = self.hostname
@@ -410,20 +480,23 @@ Proceed ?'''
source_address = self.source_address
if self.effective_locations:
- print("Archiving config...")
+ print('Archiving config...')
for location in self.effective_locations:
url = urlsplit(location)
- _, _, netloc = url.netloc.rpartition("@")
+ _, _, netloc = url.netloc.rpartition('@')
redacted_location = urlunsplit(url._replace(netloc=netloc))
- print(f" {redacted_location}", end=" ", flush=True)
- upload(archive_config_file, f'{location}/{remote_file}',
- source_host=source_address)
+ print(f' {redacted_location}', end=' ', flush=True)
+ upload(
+ archive_config_file,
+ f'{location}/{remote_file}',
+ source_host=source_address,
+ )
# op-mode functions
#
def get_raw_log_data(self) -> list:
"""Return list of dicts of log data:
- keys: [timestamp, user, commit_via, commit_comment]
+ keys: [timestamp, user, commit_via, commit_comment]
"""
log = self._get_log_entries()
res_l = []
@@ -435,20 +508,20 @@ Proceed ?'''
@staticmethod
def format_log_data(data: list) -> str:
- """Return formatted log data as str.
- """
+ """Return formatted log data as str."""
res_l = []
- for l_no, l in enumerate(data):
- time_d = datetime.fromtimestamp(int(l['timestamp']))
- time_str = time_d.strftime("%Y-%m-%d %H:%M:%S")
+ for l_no, l_val in enumerate(data):
+ time_d = datetime.fromtimestamp(int(l_val['timestamp']))
+ time_str = time_d.strftime('%Y-%m-%d %H:%M:%S')
- res_l.append([l_no, time_str,
- f"by {l['user']}", f"via {l['commit_via']}"])
+ res_l.append(
+ [l_no, time_str, f"by {l_val['user']}", f"via {l_val['commit_via']}"]
+ )
- if l['commit_comment'] != 'commit': # default comment
- res_l.append([None, l['commit_comment']])
+ if l_val['commit_comment'] != 'commit': # default comment
+ res_l.append([None, l_val['commit_comment']])
- ret = tabulate(res_l, tablefmt="plain")
+ ret = tabulate(res_l, tablefmt='plain')
return ret
@staticmethod
@@ -459,23 +532,25 @@ Proceed ?'''
'rollback'.
"""
res_l = []
- for l_no, l in enumerate(data):
- time_d = datetime.fromtimestamp(int(l['timestamp']))
- time_str = time_d.strftime("%Y-%m-%d %H:%M:%S")
+ for l_no, l_val in enumerate(data):
+ time_d = datetime.fromtimestamp(int(l_val['timestamp']))
+ time_str = time_d.strftime('%Y-%m-%d %H:%M:%S')
- res_l.append(['\t', l_no, time_str,
- f"{l['user']}", f"by {l['commit_via']}"])
+ res_l.append(
+ ['\t', l_no, time_str, f"{l_val['user']}", f"by {l_val['commit_via']}"]
+ )
- ret = tabulate(res_l, tablefmt="plain")
+ ret = tabulate(res_l, tablefmt='plain')
return ret
- def show_commit_diff(self, rev: int, rev2: Optional[int]=None,
- commands: bool=False) -> str:
+ def show_commit_diff(
+ self, rev: int, rev2: Optional[int] = None, commands: bool = False
+ ) -> str:
"""Show commit diff at revision number, compared to previous
revision, or to another revision.
"""
if rev2 is None:
- out, _ = self.compare(commands=commands, rev1=rev, rev2=(rev+1))
+ out, _ = self.compare(commands=commands, rev1=rev, rev2=(rev + 1))
return out
out, _ = self.compare(commands=commands, rev1=rev, rev2=rev2)
@@ -519,8 +594,9 @@ Proceed ?'''
conf_file.chmod(0o644)
def _archive_active_config(self) -> bool:
- save_to_tmp = (boot_configuration_complete() or not
- os.path.isfile(archive_config_file))
+ save_to_tmp = boot_configuration_complete() or not os.path.isfile(
+ archive_config_file
+ )
mask = os.umask(0o113)
ext = os.getpid()
@@ -560,15 +636,14 @@ Proceed ?'''
@staticmethod
def _update_archive():
- cmd = f"sudo logrotate -f -s {logrotate_state} {logrotate_conf}"
+ cmd = f'sudo logrotate -f -s {logrotate_state} {logrotate_conf}'
rc, out = rc_cmd(cmd)
if rc != 0:
logger.critical(f'logrotate failure: {out}')
@staticmethod
def _get_log_entries() -> list:
- """Return lines of commit log as list of strings
- """
+ """Return lines of commit log as list of strings"""
entries = []
if os.path.exists(commit_log_file):
with open(commit_log_file) as f:
@@ -577,8 +652,8 @@ Proceed ?'''
return entries
def _get_number_of_revisions(self) -> int:
- l = self._get_log_entries()
- return len(l)
+ log_entries = self._get_log_entries()
+ return len(log_entries)
def _check_revision_number(self, rev: int) -> bool:
self.num_revisions = self._get_number_of_revisions()
@@ -599,9 +674,14 @@ Proceed ?'''
user = 'unknown'
return user
- def _new_log_entry(self, user: str='', commit_via: str='',
- commit_comment: str='', timestamp: Optional[int]=None,
- tmp_file: str=None) -> Optional[str]:
+ def _new_log_entry(
+ self,
+ user: str = '',
+ commit_via: str = '',
+ commit_comment: str = '',
+ timestamp: Optional[int] = None,
+ tmp_file: str = None,
+ ) -> Optional[str]:
# Format log entry and return str or write to file.
#
# Usage is within a post-commit hook, using env values. In case of
@@ -647,12 +727,12 @@ Proceed ?'''
logger.critical(f'Invalid log format {line}')
return {}
- timestamp, user, commit_via, commit_comment = (
- tuple(line.strip().strip('|').split('|')))
+ timestamp, user, commit_via, commit_comment = tuple(
+ line.strip().strip('|').split('|')
+ )
commit_comment = commit_comment.replace('%%', '|')
- d = dict(zip(keys, [user, commit_via,
- commit_comment, timestamp]))
+ d = dict(zip(keys, [user, commit_via, commit_comment, timestamp]))
return d
@@ -662,17 +742,28 @@ Proceed ?'''
entry = f.read()
os.unlink(tmp_log_entry)
except OSError as e:
- logger.critical(f'error on file {tmp_log_entry}: {e}')
+ logger.info(f'error on file {tmp_log_entry}: {e}')
+ # fail gracefully in corner case:
+ # delete commit-revisions; commit-confirm
+ return {}
return self._get_log_entry(entry)
- def _add_log_entry(self, user: str='', commit_via: str='',
- commit_comment: str='', timestamp: Optional[int]=None):
+ def _add_log_entry(
+ self,
+ user: str = '',
+ commit_via: str = '',
+ commit_comment: str = '',
+ timestamp: Optional[int] = None,
+ ):
mask = os.umask(0o113)
- entry = self._new_log_entry(user=user, commit_via=commit_via,
- commit_comment=commit_comment,
- timestamp=timestamp)
+ entry = self._new_log_entry(
+ user=user,
+ commit_via=commit_via,
+ commit_comment=commit_comment,
+ timestamp=timestamp,
+ )
log_entries = self._get_log_entries()
log_entries.insert(0, entry)
@@ -687,6 +778,7 @@ Proceed ?'''
os.umask(mask)
+
# entry_point for console script
#
def run():
@@ -706,43 +798,54 @@ def run():
parser = ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand')
- commit_confirm = subparsers.add_parser('commit_confirm',
- help="Commit with opt-out reboot to saved config")
- commit_confirm.add_argument('-t', dest='minutes', type=int,
- default=DEFAULT_TIME_MINUTES,
- help="Minutes until reboot, unless 'confirm'")
- commit_confirm.add_argument('-y', dest='no_prompt', action='store_true',
- help="Execute without prompt")
-
- subparsers.add_parser('confirm', help="Confirm commit")
- subparsers.add_parser('revert', help="Revert commit-confirm")
-
- rollback = subparsers.add_parser('rollback',
- help="Rollback to earlier config")
- rollback.add_argument('--rev', type=int,
- help="Revision number for rollback")
- rollback.add_argument('-y', dest='no_prompt', action='store_true',
- help="Excute without prompt")
-
- rollback_soft = subparsers.add_parser('rollback_soft',
- help="Rollback to earlier config")
- rollback_soft.add_argument('--rev', type=int,
- help="Revision number for rollback")
-
- compare = subparsers.add_parser('compare',
- help="Compare config files")
-
- compare.add_argument('--saved', action='store_true',
- help="Compare session config with saved config")
- compare.add_argument('--commands', action='store_true',
- help="Show difference between commands")
- compare.add_argument('--rev1', type=int, default=None,
- help="Compare revision with session config or other revision")
- compare.add_argument('--rev2', type=int, default=None,
- help="Compare revisions")
-
- wrap_compare = subparsers.add_parser('wrap_compare',
- help="Wrapper interface for vyatta-cfg-run")
+ commit_confirm = subparsers.add_parser(
+ 'commit_confirm', help='Commit with opt-out reboot to saved config'
+ )
+ commit_confirm.add_argument(
+ '-t',
+ dest='minutes',
+ type=int,
+ default=DEFAULT_TIME_MINUTES,
+ help="Minutes until reboot, unless 'confirm'",
+ )
+ commit_confirm.add_argument(
+ '-y', dest='no_prompt', action='store_true', help='Execute without prompt'
+ )
+
+ subparsers.add_parser('confirm', help='Confirm commit')
+ subparsers.add_parser('revert', help='Revert commit-confirm with reboot')
+ subparsers.add_parser('revert_soft', help='Revert commit-confirm with reload')
+
+ rollback = subparsers.add_parser('rollback', help='Rollback to earlier config')
+ rollback.add_argument('--rev', type=int, help='Revision number for rollback')
+ rollback.add_argument(
+ '-y', dest='no_prompt', action='store_true', help='Excute without prompt'
+ )
+
+ rollback_soft = subparsers.add_parser(
+ 'rollback_soft', help='Rollback to earlier config'
+ )
+ rollback_soft.add_argument('--rev', type=int, help='Revision number for rollback')
+
+ compare = subparsers.add_parser('compare', help='Compare config files')
+
+ compare.add_argument(
+ '--saved', action='store_true', help='Compare session config with saved config'
+ )
+ compare.add_argument(
+ '--commands', action='store_true', help='Show difference between commands'
+ )
+ compare.add_argument(
+ '--rev1',
+ type=int,
+ default=None,
+ help='Compare revision with session config or other revision',
+ )
+ compare.add_argument('--rev2', type=int, default=None, help='Compare revisions')
+
+ wrap_compare = subparsers.add_parser(
+ 'wrap_compare', help='Wrapper interface for vyatta-cfg-run'
+ )
wrap_compare.add_argument('--options', nargs=REMAINDER)
args = vars(parser.parse_args())
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 6bfca5200..90b96b88c 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -32,15 +32,33 @@ SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile']
MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py']
SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py']
-INSTALL_IMAGE = ['/usr/libexec/vyos/op_mode/image_installer.py',
- '--action', 'add', '--no-prompt', '--image-path']
+INSTALL_IMAGE = [
+ '/usr/libexec/vyos/op_mode/image_installer.py',
+ '--action',
+ 'add',
+ '--no-prompt',
+ '--image-path',
+]
IMPORT_PKI = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'import']
-IMPORT_PKI_NO_PROMPT = ['/usr/libexec/vyos/op_mode/pki.py',
- '--action', 'import', '--no-prompt']
-REMOVE_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py',
- '--action', 'delete', '--no-prompt', '--image-name']
-SET_DEFAULT_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py',
- '--action', 'set', '--no-prompt', '--image-name']
+IMPORT_PKI_NO_PROMPT = [
+ '/usr/libexec/vyos/op_mode/pki.py',
+ 'import_pki',
+ '--no-prompt',
+]
+REMOVE_IMAGE = [
+ '/usr/libexec/vyos/op_mode/image_manager.py',
+ '--action',
+ 'delete',
+ '--no-prompt',
+ '--image-name',
+]
+SET_DEFAULT_IMAGE = [
+ '/usr/libexec/vyos/op_mode/image_manager.py',
+ '--action',
+ 'set',
+ '--no-prompt',
+ '--image-name',
+]
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']
@@ -60,7 +78,8 @@ TRACEROUTE = [
]
# Default "commit via" string
-APP = "vyos-http-api"
+APP = 'vyos-http-api'
+
# When started as a service rather than from a user shell,
# the process lacks the VyOS-specific environment that comes
@@ -71,7 +90,7 @@ def inject_vyos_env(env):
env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin'
env['VYATTA_PROCESS_CLIENT'] = 'gui2_rest'
env['VYOS_HEADLESS_CLIENT'] = 'vyos_http_api'
- env['vyatta_bindir']= '/opt/vyatta/bin'
+ env['vyatta_bindir'] = '/opt/vyatta/bin'
env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
env['vyatta_configdir'] = directories['vyos_configdir']
env['vyatta_datadir'] = '/opt/vyatta/share'
@@ -88,7 +107,7 @@ def inject_vyos_env(env):
env['vyos_configdir'] = directories['vyos_configdir']
env['vyos_conf_scripts_dir'] = '/usr/libexec/vyos/conf_mode'
env['vyos_datadir'] = '/opt/vyatta/share'
- env['vyos_datarootdir']= '/opt/vyatta/share'
+ env['vyos_datarootdir'] = '/opt/vyatta/share'
env['vyos_libdir'] = '/opt/vyatta/lib'
env['vyos_libexec_dir'] = '/usr/libexec/vyos'
env['vyos_op_scripts_dir'] = '/usr/libexec/vyos/op_mode'
@@ -112,6 +131,7 @@ class ConfigSession(object):
"""
The write API of VyOS.
"""
+
def __init__(self, session_id, app=APP):
"""
Creates a new config session.
@@ -126,7 +146,9 @@ class ConfigSession(object):
and used the PID for the session identifier.
"""
- env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)])
+ env_str = subprocess.check_output(
+ [CLI_SHELL_API, 'getSessionEnv', str(session_id)]
+ )
self.__session_id = session_id
# Extract actual variables from the chunk of shell it outputs
@@ -139,20 +161,39 @@ class ConfigSession(object):
session_env[k] = v
self.__session_env = session_env
- self.__session_env["COMMIT_VIA"] = app
+ self.__session_env['COMMIT_VIA'] = app
self.__run_command([CLI_SHELL_API, 'setupSession'])
def __del__(self):
try:
- output = subprocess.check_output([CLI_SHELL_API, 'teardownSession'], env=self.__session_env).decode().strip()
+ output = (
+ subprocess.check_output(
+ [CLI_SHELL_API, 'teardownSession'], env=self.__session_env
+ )
+ .decode()
+ .strip()
+ )
if output:
- print("cli-shell-api teardownSession output for sesion {0}: {1}".format(self.__session_id, output), file=sys.stderr)
+ print(
+ 'cli-shell-api teardownSession output for sesion {0}: {1}'.format(
+ self.__session_id, output
+ ),
+ file=sys.stderr,
+ )
except Exception as e:
- print("Could not tear down session {0}: {1}".format(self.__session_id, e), file=sys.stderr)
+ print(
+ 'Could not tear down session {0}: {1}'.format(self.__session_id, e),
+ file=sys.stderr,
+ )
def __run_command(self, cmd_list):
- p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env)
+ p = subprocess.Popen(
+ cmd_list,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ env=self.__session_env,
+ )
(stdout_data, stderr_data) = p.communicate()
output = stdout_data.decode()
result = p.wait()
@@ -214,7 +255,7 @@ class ConfigSession(object):
def comment(self, path, value=None):
if not value:
- value = [""]
+ value = ['']
else:
value = [value]
self.__run_command([COMMENT] + path + value)
@@ -236,6 +277,15 @@ class ConfigSession(object):
out = self.__run_command(LOAD_CONFIG + [file_path])
return out
+ def load_explicit(self, file_path):
+ from vyos.load_config import load
+ from vyos.load_config import LoadConfigError
+
+ try:
+ load(file_path, switch='explicit')
+ except LoadConfigError as e:
+ raise ConfigSessionError(e) from e
+
def migrate_and_load_config(self, file_path):
out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path])
return out
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 58c1e2c9d..7b11d36dd 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -109,7 +109,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
if re.match(
- r'^(show|clear|reset|restart|add|update|delete|generate|set|renew|release|execute|mtr)',
+ r'^(show|clear|reset|restart|add|update|delete|generate|set|renew|release|execute|import|mtr)',
name,
):
return True
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index 5a0e2ddda..55dc02631 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -33,6 +33,8 @@ CERT_BEGIN='-----BEGIN CERTIFICATE-----\n'
CERT_END='\n-----END CERTIFICATE-----'
KEY_BEGIN='-----BEGIN PRIVATE KEY-----\n'
KEY_END='\n-----END PRIVATE KEY-----'
+KEY_EC_BEGIN='-----BEGIN EC PRIVATE KEY-----\n'
+KEY_EC_END='\n-----END EC PRIVATE KEY-----'
KEY_ENC_BEGIN='-----BEGIN ENCRYPTED PRIVATE KEY-----\n'
KEY_ENC_END='\n-----END ENCRYPTED PRIVATE KEY-----'
KEY_PUB_BEGIN='-----BEGIN PUBLIC KEY-----\n'
@@ -228,8 +230,18 @@ def create_dh_parameters(bits=2048):
def wrap_public_key(raw_data):
return KEY_PUB_BEGIN + raw_data + KEY_PUB_END
-def wrap_private_key(raw_data, passphrase=None):
- return (KEY_ENC_BEGIN if passphrase else KEY_BEGIN) + raw_data + (KEY_ENC_END if passphrase else KEY_END)
+def wrap_private_key(raw_data, passphrase=None, ec=False):
+ begin = KEY_BEGIN
+ end = KEY_END
+
+ if passphrase:
+ begin = KEY_ENC_BEGIN
+ end = KEY_ENC_END
+ elif ec:
+ begin = KEY_EC_BEGIN
+ end = KEY_EC_END
+
+ return begin + raw_data + end
def wrap_openssh_public_key(raw_data, type):
return f'{type} {raw_data}'
@@ -262,17 +274,26 @@ def load_public_key(raw_data, wrap_tags=True):
except ValueError:
return False
-def load_private_key(raw_data, passphrase=None, wrap_tags=True):
- if wrap_tags:
- raw_data = wrap_private_key(raw_data, passphrase)
+def _load_private_key(raw_data, passphrase):
+ try:
+ return serialization.load_pem_private_key(bytes(raw_data, 'utf-8'), password=passphrase)
+ except (ValueError, TypeError):
+ return False
+def load_private_key(raw_data, passphrase=None, wrap_tags=True):
if passphrase is not None:
passphrase = bytes(passphrase, 'utf-8')
- try:
- return serialization.load_pem_private_key(bytes(raw_data, 'utf-8'), password=passphrase)
- except (ValueError, TypeError):
+ result = False
+
+ if wrap_tags:
+ for ec_test in [False, True]:
+ wrapped_data = wrap_private_key(raw_data, passphrase, ec_test)
+ if result := _load_private_key(wrapped_data, passphrase):
+ return result
return False
+ else:
+ return _load_private_key(raw_data, passphrase)
def load_openssh_public_key(raw_data, type):
try: