summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/pki.py33
-rwxr-xr-xsrc/conf_mode/policy_local-route.py45
-rwxr-xr-xsrc/conf_mode/protocols_static.py2
-rw-r--r--src/op_mode/mtr.py14
-rwxr-xr-xsrc/op_mode/pki.py17
-rwxr-xr-xsrc/utils/vyos-commands-to-config53
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)