diff options
| author | Christian Breunig <christian@breunig.cc> | 2024-02-13 05:32:36 +0100 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-13 05:32:36 +0100 | 
| commit | 0732e89d561ff9606fa1b91e718d3243bdfa3ff7 (patch) | |
| tree | 561a7324e7d2d6f59a19a661f631f586c771168a /src | |
| parent | 87ddb8c5e89a81959e56829dedc6b9f1bb253388 (diff) | |
| parent | 3bfbbef22954488541abd3cad262b1e196d4c240 (diff) | |
| download | vyos-1x-0732e89d561ff9606fa1b91e718d3243bdfa3ff7.tar.gz vyos-1x-0732e89d561ff9606fa1b91e718d3243bdfa3ff7.zip | |
Merge pull request #2988 from c-po/pki-rpki-t6034
rpki: T6034: move file based SSH keys for authentication to PKI subsystem
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/pki.py | 40 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_rpki.py | 47 | ||||
| -rwxr-xr-x | src/migration-scripts/rpki/1-to-2 | 22 | 
3 files changed, 96 insertions, 13 deletions
| diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index 4be40e99e..3ab6ac5c3 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -24,11 +24,12 @@ from vyos.config import config_dict_merge  from vyos.configdep import set_dependents  from vyos.configdep import call_dependents  from vyos.configdict import node_changed -from vyos.configdiff import Diff  from vyos.defaults import directories  from vyos.pki import is_ca_certificate  from vyos.pki import load_certificate  from vyos.pki import load_public_key +from vyos.pki import load_openssh_public_key +from vyos.pki import load_openssh_private_key  from vyos.pki import load_private_key  from vyos.pki import load_crl  from vyos.pki import load_dh_parameters @@ -64,6 +65,10 @@ sync_search = [          'path': ['interfaces', 'sstpc'],      },      { +        'keys': ['key'], +        'path': ['protocols', 'rpki', 'cache'], +    }, +    {          'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'],          'path': ['vpn', 'ipsec'],      }, @@ -86,7 +91,8 @@ sync_translate = {      'remote_key': 'key_pair',      'shared_secret_key': 'openvpn',      'auth_key': 'openvpn', -    'crypt_key': 'openvpn' +    'crypt_key': 'openvpn', +    'key': 'openssh',  }  def certbot_delete(certificate): @@ -150,6 +156,11 @@ def get_config(config=None):          if 'changed' not in pki: pki.update({'changed':{}})          pki['changed'].update({'key_pair' : tmp}) +    tmp = node_changed(conf, base + ['openssh'], recursive=True) +    if tmp: +        if 'changed' not in pki: pki.update({'changed':{}}) +        pki['changed'].update({'openssh' : tmp}) +      tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True)      if tmp:          if 'changed' not in pki: pki.update({'changed':{}}) @@ -241,6 +252,17 @@ def is_valid_private_key(raw_data, protected=False):          return True      return load_private_key(raw_data, passphrase=None, wrap_tags=True) +def is_valid_openssh_public_key(raw_data, type): +    # If it loads correctly we're good, or return False +    return load_openssh_public_key(raw_data, type) + +def is_valid_openssh_private_key(raw_data, protected=False): +    # If it loads correctly we're good, or return False +    # With encrypted private keys, we always return true as we cannot ask for password to verify +    if protected: +        return True +    return load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True) +  def is_valid_crl(raw_data):      # If it loads correctly we're good, or return False      return load_crl(raw_data, wrap_tags=True) @@ -322,6 +344,20 @@ def verify(pki):                  if not is_valid_private_key(private['key'], protected):                      raise ConfigError(f'Invalid private key on key-pair "{name}"') +    if 'openssh' in pki: +        for name, key_conf in pki['openssh'].items(): +            if 'public' in key_conf and 'key' in key_conf['public']: +                if 'type' not in key_conf['public']: +                    raise ConfigError(f'Must define OpenSSH public key type for "{name}"') +                if not is_valid_openssh_public_key(key_conf['public']['key'], key_conf['public']['type']): +                    raise ConfigError(f'Invalid OpenSSH public key "{name}"') + +            if 'private' in key_conf and 'key' in key_conf['private']: +                private = key_conf['private'] +                protected = 'password_protected' in private +                if not is_valid_openssh_private_key(private['key'], protected): +                    raise ConfigError(f'Invalid OpenSSH private key "{name}"') +      if 'x509' in pki:          if 'default' in pki['x509']:              default_values = pki['x509']['default'] diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 0fc14e868..a59ecf3e4 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -16,16 +16,22 @@  import os +from glob import glob  from sys import exit  from vyos.config import Config +from vyos.pki import wrap_openssh_public_key +from vyos.pki import wrap_openssh_private_key  from vyos.template import render_to_string -from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.file import write_file  from vyos import ConfigError  from vyos import frr  from vyos import airbag  airbag.enable() +rpki_ssh_key_base = '/run/frr/id_rpki' +  def get_config(config=None):      if config:          conf = config @@ -33,7 +39,8 @@ def get_config(config=None):          conf = Config()      base = ['protocols', 'rpki'] -    rpki = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) +    rpki = conf.get_config_dict(base, key_mangling=('-', '_'), +                                get_first_key=True, with_pki=True)      # Bail out early if configuration tree does not exist      if not conf.exists(base):          rpki.update({'deleted' : ''}) @@ -63,22 +70,40 @@ def verify(rpki):                  preferences.append(preference)              if 'ssh' in peer_config: -                files = ['private_key_file', 'public_key_file'] -                for file in files: -                    if file not in peer_config['ssh']: -                        raise ConfigError('RPKI+SSH requires username and public/private ' \ -                                          'key file to be defined!') +                if 'username' not in peer_config['ssh']: +                    raise ConfigError('RPKI+SSH requires username to be defined!') + +                if 'key' not in peer_config['ssh'] or 'openssh' not in rpki['pki']: +                    raise ConfigError('RPKI+SSH requires key to be defined!') -                    filename = peer_config['ssh'][file] -                    if not os.path.exists(filename): -                        raise ConfigError(f'RPKI SSH {file.replace("-","-")} "{filename}" does not exist!') +                if peer_config['ssh']['key'] not in rpki['pki']['openssh']: +                    raise ConfigError('RPKI+SSH key not found on PKI subsystem!')      return None  def generate(rpki): +    for key in glob(f'{rpki_ssh_key_base}*'): +        os.unlink(key) +      if not rpki:          return + +    if 'cache' in rpki: +        for cache, cache_config in rpki['cache'].items(): +            if 'ssh' in cache_config: +                key_name = cache_config['ssh']['key'] +                public_key_data = dict_search_args(rpki['pki'], 'openssh', key_name, 'public', 'key') +                public_key_type = dict_search_args(rpki['pki'], 'openssh', key_name, 'public', 'type') +                private_key_data = dict_search_args(rpki['pki'], 'openssh', key_name, 'private', 'key') + +                cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' +                cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' + +                write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) +                write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) +      rpki['new_frr_config'] = render_to_string('frr/rpki.frr.j2', rpki) +      return None  def apply(rpki): diff --git a/src/migration-scripts/rpki/1-to-2 b/src/migration-scripts/rpki/1-to-2 index 559440bba..50d4a3dfc 100755 --- a/src/migration-scripts/rpki/1-to-2 +++ b/src/migration-scripts/rpki/1-to-2 @@ -19,7 +19,11 @@  from sys import exit  from sys import argv +  from vyos.configtree import ConfigTree +from vyos.pki import OPENSSH_KEY_BEGIN +from vyos.pki import OPENSSH_KEY_END +from vyos.utils.file import read_file  if len(argv) < 2:      print("Must specify file name!") @@ -43,6 +47,24 @@ if config.exists(base + ['cache']):          if config.exists(ssh_node + ['known-hosts-file']):              config.delete(ssh_node + ['known-hosts-file']) +        if config.exists(base + ['cache', cache, 'ssh']): +            private_key_node = base + ['cache', cache, 'ssh', 'private-key-file'] +            private_key_file = config.return_value(private_key_node) +            private_key = read_file(private_key_file).replace(OPENSSH_KEY_BEGIN, '').replace(OPENSSH_KEY_END, '').replace('\n','') + +            public_key_node = base + ['cache', cache, 'ssh', 'public-key-file'] +            public_key_file = config.return_value(public_key_node) +            public_key = read_file(public_key_file).split() + +            config.set(['pki', 'openssh', f'rpki-{cache}', 'private', 'key'], value=private_key) +            config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'key'], value=public_key[1]) +            config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'type'], value=public_key[0]) +            config.set_tag(['pki', 'openssh']) +            config.set(ssh_node + ['key'], value=f'rpki-{cache}') + +            config.delete(private_key_node) +            config.delete(public_key_node) +  try:      with open(file_name, 'w') as f:          f.write(config.to_string()) | 
