diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/pki.py | 33 | ||||
| -rwxr-xr-x | src/conf_mode/policy_local-route.py | 45 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_static.py | 2 | ||||
| -rw-r--r-- | src/op_mode/mtr.py | 14 | ||||
| -rwxr-xr-x | src/op_mode/pki.py | 17 | ||||
| -rwxr-xr-x | src/utils/vyos-commands-to-config | 53 | 
6 files changed, 146 insertions, 18 deletions
| diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index 215b22b37..233d73ba8 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -27,6 +27,7 @@ from vyos.configdict import node_changed  from vyos.configdiff import Diff  from vyos.configdiff import get_config_diff  from vyos.defaults import directories +from vyos.pki import encode_certificate  from vyos.pki import is_ca_certificate  from vyos.pki import load_certificate  from vyos.pki import load_public_key @@ -36,9 +37,11 @@ from vyos.pki import load_private_key  from vyos.pki import load_crl  from vyos.pki import load_dh_parameters  from vyos.utils.boot import boot_configuration_complete +from vyos.utils.configfs import add_cli_node  from vyos.utils.dict import dict_search  from vyos.utils.dict import dict_search_args  from vyos.utils.dict import dict_search_recursive +from vyos.utils.file import read_file  from vyos.utils.process import call  from vyos.utils.process import cmd  from vyos.utils.process import is_systemd_service_active @@ -446,9 +449,37 @@ def generate(pki):      # Get foldernames under vyos_certbot_dir which each represent a certbot cert      if os.path.exists(f'{vyos_certbot_dir}/live'):          for cert in certbot_list_on_disk: +            # ACME certificate is no longer in use by CLI remove it              if cert not in certbot_list: -                # certificate is no longer active on the CLI - remove it                  certbot_delete(cert) +                continue +            # ACME not enabled for individual certificate - bail out early +            if 'acme' not in pki['certificate'][cert]: +                continue + +            # Read in ACME certificate chain information +            tmp = read_file(f'{vyos_certbot_dir}/live/{cert}/chain.pem') +            tmp = load_certificate(tmp, wrap_tags=False) +            cert_chain_base64 = "".join(encode_certificate(tmp).strip().split("\n")[1:-1]) + +            # Check if CA chain certificate is already present on CLI to avoid adding +            # a duplicate. This only checks for manual added CA certificates and not +            # auto added ones with the AUTOCHAIN_ prefix +            autochain_prefix = 'AUTOCHAIN_' +            ca_cert_present = False +            if 'ca' in pki: +                for ca_base64, cli_path in dict_search_recursive(pki['ca'], 'certificate'): +                    # Ignore automatic added CA certificates +                    if any(item.startswith(autochain_prefix) for item in cli_path): +                        continue +                    if cert_chain_base64 == ca_base64: +                        ca_cert_present = True + +            if not ca_cert_present: +                tmp = dict_search_args(pki, 'ca', f'{autochain_prefix}{cert}', 'certificate') +                if not bool(tmp) or tmp != cert_chain_base64: +                    print(f'Adding/replacing automatically imported CA certificate for "{cert}" ...') +                    add_cli_node(['pki', 'ca', f'{autochain_prefix}{cert}', 'certificate'], value=cert_chain_base64)      return None diff --git a/src/conf_mode/policy_local-route.py b/src/conf_mode/policy_local-route.py index 331fd972d..9be2bc227 100755 --- a/src/conf_mode/policy_local-route.py +++ b/src/conf_mode/policy_local-route.py @@ -54,6 +54,7 @@ def get_config(config=None):                  dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address'])                  dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port'])                  table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) +                vrf = leaf_node_changed(conf, base_rule + [rule, 'set', 'vrf'])                  proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])                  rule_def = {}                  if src: @@ -70,6 +71,8 @@ def get_config(config=None):                      rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def)                  if table:                      rule_def = dict_merge({'table' : table}, rule_def) +                if vrf: +                    rule_def = dict_merge({'vrf' : vrf}, rule_def)                  if proto:                      rule_def = dict_merge({'protocol' : proto}, rule_def)                  dict = dict_merge({dict_id : {rule : rule_def}}, dict) @@ -90,6 +93,7 @@ def get_config(config=None):                  dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address'])                  dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port'])                  table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) +                vrf = leaf_node_changed(conf, base_rule + [rule, 'set', 'vrf'])                  proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])                  # keep track of changes in configuration                  # otherwise we might remove an existing node although nothing else has changed @@ -179,6 +183,15 @@ def get_config(config=None):                      if len(table) > 0:                          rule_def = dict_merge({'table' : table}, rule_def) +                # vrf +                if vrf is None: +                    if 'set' in rule_config and 'vrf' in rule_config['set']: +                        rule_def = dict_merge({'vrf': [rule_config['set']['vrf']]}, rule_def) +                else: +                    changed = True +                    if len(vrf) > 0: +                        rule_def = dict_merge({'vrf' : vrf}, rule_def) +                  # protocol                  if proto is None:                      if 'protocol' in rule_config: @@ -218,8 +231,15 @@ def verify(pbr):                  ):                      raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!') -                if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: -                    raise ConfigError('Table set is required!') +                if 'set' not in pbr_route['rule'][rule]: +                    raise ConfigError('Either set table or set vrf is required!') + +                set_tgts = pbr_route['rule'][rule]['set'] +                if 'table' not in set_tgts and 'vrf' not in set_tgts: +                    raise ConfigError('Either set table or set vrf is required!') + +                if 'table' in set_tgts and 'vrf' in set_tgts: +                    raise ConfigError('set table and set vrf cannot both be set!')                  if 'inbound_interface' in pbr_route['rule'][rule]:                      interface = pbr_route['rule'][rule]['inbound_interface'] @@ -250,11 +270,14 @@ def apply(pbr):                  fwmark = rule_config.get('fwmark', [''])                  inbound_interface = rule_config.get('inbound_interface', [''])                  protocol = rule_config.get('protocol', ['']) -                table = rule_config.get('table', ['']) +                # VRF 'default' is actually table 'main' for RIB rules +                vrf = [ 'main' if x == 'default' else x for x in rule_config.get('vrf', ['']) ] +                # See generate section below for table/vrf overlap explanation  +                table_or_vrf = rule_config.get('table', vrf) -                for src, dst, src_port, dst_port, fwmk, iif, proto, table in product( +                for src, dst, src_port, dst_port, fwmk, iif, proto, table_or_vrf in product(                          source, destination, source_port, destination_port, -                        fwmark, inbound_interface, protocol, table): +                        fwmark, inbound_interface, protocol, table_or_vrf):                      f_src = '' if src == '' else f' from {src} '                      f_src_port = '' if src_port == '' else f' sport {src_port} '                      f_dst = '' if dst == '' else f' to {dst} ' @@ -262,7 +285,7 @@ def apply(pbr):                      f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '                      f_iif = '' if iif == '' else f' iif {iif} '                      f_proto = '' if proto == '' else f' ipproto {proto} ' -                    f_table = '' if table == '' else f' lookup {table} ' +                    f_table = '' if table_or_vrf == '' else f' lookup {table_or_vrf} '                      call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif}{f_table}') @@ -276,7 +299,13 @@ def apply(pbr):          if 'rule' in pbr_route:              for rule, rule_config in pbr_route['rule'].items(): -                table = rule_config['set'].get('table', '') +                # VRFs get configred as route table alias names for iproute2 and only  +                # one 'set' can get past validation. Either can be fed to lookup.  +                vrf = rule_config['set'].get('vrf', '') +                if vrf == 'default': +                    table_or_vrf = 'main' +                else: +                    table_or_vrf = rule_config['set'].get('table', vrf)                  source = rule_config.get('source', {}).get('address', ['all'])                  source_port = rule_config.get('source', {}).get('port', '')                  destination = rule_config.get('destination', {}).get('address', ['all']) @@ -295,7 +324,7 @@ def apply(pbr):                          f_iif = f' iif {inbound_interface} ' if inbound_interface else ''                          f_proto = f' ipproto {protocol} ' if protocol else '' -                        call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table}') +                        call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table_or_vrf}')      return None diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index a2373218a..430cc69d4 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -88,7 +88,7 @@ def verify(static):              if {'blackhole', 'reject'} <= set(prefix_options):                  raise ConfigError(f'Can not use both blackhole and reject for '\ -                                  'prefix "{prefix}"!') +                                  f'prefix "{prefix}"!')      return None diff --git a/src/op_mode/mtr.py b/src/op_mode/mtr.py index de139f2fa..baf9672a1 100644 --- a/src/op_mode/mtr.py +++ b/src/op_mode/mtr.py @@ -178,6 +178,7 @@ mtr = {      6: '/bin/mtr -6',  } +  class List(list):      def first(self):          return self.pop(0) if self else '' @@ -218,12 +219,15 @@ def complete(prefix):  def convert(command, args): +    to_json = False      while args:          shortname = args.first()          longnames = complete(shortname)          if len(longnames) != 1:              expension_failure(shortname, longnames)          longname = longnames[0] +        if longname == 'json': +            to_json = True          if options[longname]['type'] == 'noarg':              command = options[longname]['mtr'].format(                  command=command, value='') @@ -232,7 +236,7 @@ def convert(command, args):          else:              command = options[longname]['mtr'].format(                  command=command, value=args.first()) -    return command +    return command, to_json  if __name__ == '__main__': @@ -242,7 +246,6 @@ if __name__ == '__main__':      if not host:          sys.exit("mtr: Missing host") -      if host == '--get-options' or host == '--get-options-nested':          if host == '--get-options-nested':              args.first()  # pop monitor @@ -302,5 +305,8 @@ if __name__ == '__main__':      except ValueError:          sys.exit(f'mtr: Unknown host: {host}') -    command = convert(mtr[version], args) -    call(f'{command} --curses --displaymode 0 {host}') +    command, to_json = convert(mtr[version], args) +    if to_json: +        call(f'{command} {host}') +    else: +        call(f'{command} --curses --displaymode 0 {host}') diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index ab613e5c4..5652a5d74 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -26,13 +26,22 @@ from cryptography.x509.oid import ExtendedKeyUsageOID  from vyos.config import Config  from vyos.config import config_dict_mangle_acme -from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters +from vyos.pki import encode_certificate +from vyos.pki import encode_public_key +from vyos.pki import encode_private_key +from vyos.pki import encode_dh_parameters  from vyos.pki import get_certificate_fingerprint -from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list +from vyos.pki import create_certificate +from vyos.pki import create_certificate_request +from vyos.pki import create_certificate_revocation_list  from vyos.pki import create_private_key  from vyos.pki import create_dh_parameters -from vyos.pki import load_certificate, load_certificate_request, load_private_key -from vyos.pki import load_crl, load_dh_parameters, load_public_key +from vyos.pki import load_certificate +from vyos.pki import load_certificate_request +from vyos.pki import load_private_key +from vyos.pki import load_crl +from vyos.pki import load_dh_parameters +from vyos.pki import load_public_key  from vyos.pki import verify_certificate  from vyos.utils.io import ask_input  from vyos.utils.io import ask_yes_no diff --git a/src/utils/vyos-commands-to-config b/src/utils/vyos-commands-to-config new file mode 100755 index 000000000..927d9bd70 --- /dev/null +++ b/src/utils/vyos-commands-to-config @@ -0,0 +1,53 @@ +#! /usr/bin/python3 +# +# Copyright (C) 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +import sys +import json + +from vyos.configtree import ConfigTree +from vyos.utils.config import parse_commands +from vyos.utils.config import set_tags + +def commands_to_config(cmds): +    ct = ConfigTree('') +    cmds = parse_commands(cmds) + +    for c in cmds: +        if c["op"] == "set": +            if c["is_leaf"]: +                replace = False if c["is_multi"] else True +                ct.set(c["path"], value=c["value"], replace=replace) +                set_tags(ct, c["path"]) +            else: +                ct.create_node(c["path"]) +                set_tags(ct, c["path"]) +        else: +            raise ValueError( +              f"\"{c['op']}\" is not a supported config operation") + +    return ct + + +if __name__ == '__main__': +    try: +        cmds = sys.stdin.read() +        ct = commands_to_config(cmds) +        out = ConfigTree(ct.to_string()) +        print(str(out)) +    except Exception as e: +        print(e) +        sys.exit(1) | 
