summaryrefslogtreecommitdiff
path: root/src/conf_mode/service_ssh.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode/service_ssh.py')
-rwxr-xr-xsrc/conf_mode/service_ssh.py65
1 files changed, 53 insertions, 12 deletions
diff --git a/src/conf_mode/service_ssh.py b/src/conf_mode/service_ssh.py
index 9abdd33dc..bf8afe8b7 100755
--- a/src/conf_mode/service_ssh.py
+++ b/src/conf_mode/service_ssh.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# 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
@@ -23,10 +23,17 @@ from syslog import LOG_INFO
from vyos.config import Config
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_pki_openssh_key
+from vyos.defaults import config_files
from vyos.utils.process import call
from vyos.template import render
from vyos import ConfigError
from vyos import airbag
+from vyos.pki import encode_public_key
+from vyos.pki import load_openssh_public_key
+from vyos.utils.dict import dict_search_recursive
+from vyos.utils.file import write_file
+
airbag.enable()
config_file = r'/run/sshd/sshd_config'
@@ -38,6 +45,8 @@ key_rsa = '/etc/ssh/ssh_host_rsa_key'
key_dsa = '/etc/ssh/ssh_host_dsa_key'
key_ed25519 = '/etc/ssh/ssh_host_ed25519_key'
+trusted_user_ca = config_files['sshd_user_ca']
+
def get_config(config=None):
if config:
conf = config
@@ -46,36 +55,55 @@ def get_config(config=None):
base = ['service', 'ssh']
if not conf.exists(base):
return None
-
- ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ ssh = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, with_pki=True)
tmp = is_node_changed(conf, base + ['vrf'])
- if tmp: ssh.update({'restart_required': {}})
+ if tmp:
+ ssh.update({'restart_required': {}})
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
ssh = conf.merge_defaults(ssh, recursive=True)
- # pass config file path - used in override template
- ssh['config_file'] = config_file
-
# Ignore default XML values if config doesn't exists
# Delete key from dict
if not conf.exists(base + ['dynamic-protection']):
- del ssh['dynamic_protection']
+ del ssh['dynamic_protection']
+
+ # See if any user has specified a list of principal names that are accepted
+ # for certificate authentication.
+ tmp = conf.get_config_dict(['system', 'login', 'user'],
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
+
+ for value, _ in dict_search_recursive(tmp, 'principal'):
+ # Only enable principal handling if SSH trusted-user-ca is set
+ if 'trusted_user_ca' in ssh:
+ ssh['has_principals'] = {}
+ # We do only need to execute this code path once as we need to know
+ # if any one of the local users has a principal set or not - this
+ # accounts for the entire system.
+ break
return ssh
+
def verify(ssh):
if not ssh:
return None
if 'rekey' in ssh and 'data' not in ssh['rekey']:
- raise ConfigError(f'Rekey data is required!')
+ raise ConfigError('Rekey data is required!')
+
+ if 'trusted_user_ca' in ssh:
+ verify_pki_openssh_key(ssh, ssh['trusted_user_ca'])
verify_vrf(ssh)
return None
+
def generate(ssh):
if not ssh:
if os.path.isfile(config_file):
@@ -95,6 +123,18 @@ def generate(ssh):
syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!')
call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}')
+ if 'trusted_user_ca' in ssh:
+ key_name = ssh['trusted_user_ca']
+ openssh_cert = ssh['pki']['openssh'][key_name]
+ loaded_ca_cert = load_openssh_public_key(openssh_cert['public']['key'],
+ openssh_cert['public']['type'])
+ tmp = encode_public_key(loaded_ca_cert, encoding='OpenSSH',
+ key_format='OpenSSH')
+ write_file(trusted_user_ca, tmp, trailing_newline=True)
+ else:
+ if os.path.exists(trusted_user_ca):
+ os.unlink(trusted_user_ca)
+
render(config_file, 'ssh/sshd_config.j2', ssh)
if 'dynamic_protection' in ssh:
@@ -103,12 +143,12 @@ def generate(ssh):
return None
+
def apply(ssh):
- systemd_service_ssh = 'ssh.service'
systemd_service_sshguard = 'sshguard.service'
if not ssh:
# SSH access is removed in the commit
- call(f'systemctl stop ssh@*.service')
+ call('systemctl stop ssh@*.service')
call(f'systemctl stop {systemd_service_sshguard}')
return None
@@ -122,13 +162,14 @@ def apply(ssh):
if 'restart_required' in ssh:
# this is only true if something for the VRFs changed, thus we
# stop all VRF services and only restart then new ones
- call(f'systemctl stop ssh@*.service')
+ call('systemctl stop ssh@*.service')
systemd_action = 'restart'
for vrf in ssh['vrf']:
call(f'systemctl {systemd_action} ssh@{vrf}.service')
return None
+
if __name__ == '__main__':
try:
c = get_config()