summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/repo-sync.yml1
-rw-r--r--data/config-mode-dependencies/vyos-1x.json2
-rw-r--r--data/op-mode-standardized.json2
-rw-r--r--data/templates/frr_exporter/frr_exporter.service.j220
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j22
-rw-r--r--data/templates/login/motd_vyos_nonproduction.j23
-rw-r--r--debian/control7
-rw-r--r--interface-definitions/include/version/reverseproxy-version.xml.i2
-rw-r--r--interface-definitions/load-balancing_haproxy.xml.in (renamed from interface-definitions/load-balancing_reverse-proxy.xml.in)8
-rw-r--r--interface-definitions/service_monitoring_frr_exporter.xml.in25
-rw-r--r--interface-definitions/system_config-management.xml.in27
-rw-r--r--op-mode-definitions/load-balacing_haproxy.in (renamed from op-mode-definitions/reverse-proxy.xml.in)12
-rw-r--r--op-mode-definitions/pki.xml.in108
-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
-rw-r--r--smoketest/configs/basic-vyos21
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_haproxy.py (renamed from smoketest/scripts/cli/test_load-balancing_reverse-proxy.py)2
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_frr-exporter.py64
-rw-r--r--[-rwxr-xr-x]src/conf_mode/load-balancing_haproxy.py (renamed from src/conf_mode/load-balancing_reverse-proxy.py)2
-rwxr-xr-xsrc/conf_mode/pki.py2
-rwxr-xr-xsrc/conf_mode/service_monitoring_frr-exporter.py101
-rwxr-xr-xsrc/conf_mode/system_config-management.py20
-rwxr-xr-xsrc/conf_mode/system_login_banner.py4
-rwxr-xr-xsrc/helpers/commit-confirm-notify.py48
-rwxr-xr-xsrc/migration-scripts/reverse-proxy/1-to-227
-rwxr-xr-xsrc/op_mode/load-balancing_haproxy.py (renamed from src/op_mode/reverseproxy.py)4
-rwxr-xr-xsrc/op_mode/pki.py820
-rwxr-xr-xsrc/op_mode/restart.py10
-rw-r--r--src/services/api/rest/routers.py4
31 files changed, 1297 insertions, 527 deletions
diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml
index 6da2fb40d..752cf947a 100644
--- a/.github/workflows/repo-sync.yml
+++ b/.github/workflows/repo-sync.yml
@@ -6,6 +6,7 @@ on:
- closed
branches:
- current
+ - equuleus
workflow_dispatch:
jobs:
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 2981a0851..cbd14f7c6 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -26,10 +26,10 @@
"pki": {
"ethernet": ["interfaces_ethernet"],
"openvpn": ["interfaces_openvpn"],
+ "haproxy": ["load-balancing_haproxy"],
"https": ["service_https"],
"ipsec": ["vpn_ipsec"],
"openconnect": ["vpn_openconnect"],
- "reverse_proxy": ["load-balancing_reverse-proxy"],
"rpki": ["protocols_rpki"],
"sstp": ["vpn_sstp"],
"sstpc": ["interfaces_sstpc"],
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index baa1e9110..35587b63c 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -25,7 +25,7 @@
"otp.py",
"qos.py",
"reset_vpn.py",
-"reverseproxy.py",
+"load-balancing_haproxy.py",
"route.py",
"storage.py",
"system.py",
diff --git a/data/templates/frr_exporter/frr_exporter.service.j2 b/data/templates/frr_exporter/frr_exporter.service.j2
new file mode 100644
index 000000000..c3892e42b
--- /dev/null
+++ b/data/templates/frr_exporter/frr_exporter.service.j2
@@ -0,0 +1,20 @@
+{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' runuser -u frr -- ' if vrf is vyos_defined else '' %}
+[Unit]
+Description=FRR Exporter
+Documentation=https://github.com/tynany/frr_exporter
+After=network.target
+
+[Service]
+{% if vrf is not vyos_defined %}
+User=frr
+{% endif %}
+ExecStart={{ vrf_command }}/usr/sbin/frr_exporter \
+{% if listen_address is vyos_defined %}
+{% for address in listen_address %}
+ --web.listen-address={{ address }}:{{ port }}
+{% endfor %}
+{% else %}
+ --web.listen-address=:{{ port }}
+{% endif %}
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index 5137966c1..786ebfb21 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -1,4 +1,4 @@
-### Autogenerated by load-balancing_reverse-proxy.py ###
+### Autogenerated by load-balancing_haproxy.py ###
global
chroot /var/lib/haproxy
diff --git a/data/templates/login/motd_vyos_nonproduction.j2 b/data/templates/login/motd_vyos_nonproduction.j2
new file mode 100644
index 000000000..32922f27f
--- /dev/null
+++ b/data/templates/login/motd_vyos_nonproduction.j2
@@ -0,0 +1,3 @@
+
+---
+Warning: This VyOS system is not a stable long-term support version and is not intended for production use.
diff --git a/debian/control b/debian/control
index 15fb5d72e..a19461412 100644
--- a/debian/control
+++ b/debian/control
@@ -202,9 +202,9 @@ Depends:
# For "service router-advert"
radvd,
# End "service route-advert"
-# For "load-balancing reverse-proxy"
+# For "load-balancing haproxy"
haproxy,
-# End "load-balancing reverse-proxy"
+# End "load-balancing haproxy"
# For "load-balancing wan"
vyatta-wanloadbalance,
# End "load-balancing wan"
@@ -238,6 +238,9 @@ Depends:
# For "service monitoring node-exporter"
node-exporter,
# End "service monitoring node-exporter"
+# For "service monitoring frr-exporter"
+ frr-exporter,
+# End "service monitoring frr-exporter"
# For "service monitoring telegraf"
telegraf (>= 1.20),
# End "service monitoring telegraf"
diff --git a/interface-definitions/include/version/reverseproxy-version.xml.i b/interface-definitions/include/version/reverseproxy-version.xml.i
index 907ea1e5e..4f09f2848 100644
--- a/interface-definitions/include/version/reverseproxy-version.xml.i
+++ b/interface-definitions/include/version/reverseproxy-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/reverseproxy-version.xml.i -->
-<syntaxVersion component='reverse-proxy' version='1'></syntaxVersion>
+<syntaxVersion component='reverse-proxy' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_haproxy.xml.in
index 18274622c..742272436 100644
--- a/interface-definitions/load-balancing_reverse-proxy.xml.in
+++ b/interface-definitions/load-balancing_haproxy.xml.in
@@ -2,9 +2,9 @@
<interfaceDefinition>
<node name="load-balancing">
<children>
- <node name="reverse-proxy" owner="${vyos_conf_scripts_dir}/load-balancing_reverse-proxy.py">
+ <node name="haproxy" owner="${vyos_conf_scripts_dir}/load-balancing_haproxy.py">
<properties>
- <help>Configure reverse-proxy</help>
+ <help>Configure haproxy</help>
<priority>900</priority>
</properties>
<children>
@@ -26,10 +26,10 @@
<constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
<valueHelp>
<format>txt</format>
- <description>Name of reverse-proxy backend system</description>
+ <description>Name of haproxy backend system</description>
</valueHelp>
<completionHelp>
- <path>load-balancing reverse-proxy backend</path>
+ <path>load-balancing haproxy backend</path>
</completionHelp>
<multi/>
</properties>
diff --git a/interface-definitions/service_monitoring_frr_exporter.xml.in b/interface-definitions/service_monitoring_frr_exporter.xml.in
new file mode 100644
index 000000000..96aee3ab4
--- /dev/null
+++ b/interface-definitions/service_monitoring_frr_exporter.xml.in
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="monitoring">
+ <children>
+ <node name="frr-exporter" owner="${vyos_conf_scripts_dir}/service_monitoring_frr-exporter.py">
+ <properties>
+ <help>Prometheus exporter for FRR metrics</help>
+ <priority>1280</priority>
+ </properties>
+ <children>
+ #include <include/listen-address.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>9342</defaultValue>
+ </leafNode>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system_config-management.xml.in b/interface-definitions/system_config-management.xml.in
index e666633b7..a23d44aea 100644
--- a/interface-definitions/system_config-management.xml.in
+++ b/interface-definitions/system_config-management.xml.in
@@ -67,6 +67,33 @@
<constraintErrorMessage>Number of revisions must be between 0 and 65535</constraintErrorMessage>
</properties>
</leafNode>
+ <node name="commit-confirm">
+ <properties>
+ <help>Commit confirm options</help>
+ </properties>
+ <children>
+ <leafNode name="action">
+ <properties>
+ <help>Commit confirm revert action</help>
+ <completionHelp>
+ <list>reload reboot</list>
+ </completionHelp>
+ <valueHelp>
+ <format>reload</format>
+ <description>Reload previous configuration if not confirmed</description>
+ </valueHelp>
+ <valueHelp>
+ <format>reboot</format>
+ <description>Reboot to saved configuration if not confirmed</description>
+ </valueHelp>
+ <constraint>
+ <regex>(reload|reboot)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>reboot</defaultValue>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/reverse-proxy.xml.in b/op-mode-definitions/load-balacing_haproxy.in
index b45ce107f..c3d6c799b 100644
--- a/op-mode-definitions/reverse-proxy.xml.in
+++ b/op-mode-definitions/load-balacing_haproxy.in
@@ -2,21 +2,21 @@
<interfaceDefinition>
<node name="restart">
<children>
- <node name="reverse-proxy">
+ <node name="haproxy">
<properties>
- <help>Restart reverse-proxy service</help>
+ <help>Restart haproxy service</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name reverse_proxy</command>
+ <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name haproxy</command>
</node>
</children>
</node>
<node name="show">
<children>
- <node name="reverse-proxy">
+ <node name="haproxy">
<properties>
- <help>Show load-balancing reverse-proxy</help>
+ <help>Show load-balancing haproxy</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/reverseproxy.py show</command>
+ <command>sudo ${vyos_op_scripts_dir}/load-balacing_haproxy.py show</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index 254ef08cc..866f482bf 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -27,7 +27,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ca --name "$7" --sign "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -36,10 +36,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ca --name "$7" --sign "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "noname" --sign "$5"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ca --sign "$5"</command>
</tagNode>
<tagNode name="file">
<properties>
@@ -48,7 +48,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ca --name "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -57,10 +57,10 @@
<list>&lt;CA name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ca --name "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ca</command>
</node>
<node name="certificate">
<properties>
@@ -79,7 +79,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --name "$6" --self-sign --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -88,10 +88,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --name "$6" --self-sign --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --self-sign</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --self-sign</command>
</node>
<tagNode name="sign">
<properties>
@@ -108,7 +108,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --name "$7" --sign "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -117,10 +117,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --name "$7" --sign "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --sign "$5"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --sign "$5"</command>
</tagNode>
<tagNode name="file">
<properties>
@@ -129,7 +129,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --name "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -138,10 +138,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate --name "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type certificate</command>
</node>
<tagNode name="crl">
<properties>
@@ -158,16 +158,16 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type crl --name "$4" --file</command>
</tagNode>
<leafNode name="install">
<properties>
<help>Commands for installing generated CRL into running configuration</help>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type crl --name "$4" --install</command>
</leafNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --crl "$4"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type crl --name "$4"</command>
</tagNode>
<node name="dh">
<properties>
@@ -181,7 +181,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type dh --name "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -190,10 +190,10 @@
<list>&lt;DH name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type dh --name "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --dh "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type dh</command>
</node>
<node name="key-pair">
<properties>
@@ -207,7 +207,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type key-pair --name "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -216,10 +216,10 @@
<list>&lt;key name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type key-pair --name "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --keypair "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type key-pair</command>
</node>
<node name="openvpn">
<properties>
@@ -238,7 +238,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type openvpn --name "$6" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -247,10 +247,10 @@
<list>&lt;key name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type openvpn --name "$6" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --openvpn "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type openvpn</command>
</node>
</children>
</node>
@@ -266,7 +266,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ssh --name "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -275,10 +275,10 @@
<list>&lt;key name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ssh --name "$5" --install</command>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --ssh "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type ssh</command>
</node>
<node name="wireguard">
<properties>
@@ -302,12 +302,12 @@
<path>interfaces wireguard</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key --interface "$7" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type wireguard --key --interface "$7" --install</command>
</tagNode>
</children>
</node>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type wireguard --key</command>
</node>
<node name="preshared-key">
<properties>
@@ -334,14 +334,14 @@
<path>interfaces wireguard ${COMP_WORDS[COMP_CWORD-2]} peer</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk --interface "$7" --peer "$9" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type wireguard --psk --interface "$7" --peer "$9" --install</command>
</tagNode>
</children>
</tagNode>
</children>
</node>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk</command>
+ <command>${vyos_op_scripts_dir}/pki.py generate_pki --pki-type wireguard --psk</command>
</node>
</children>
</node>
@@ -371,13 +371,13 @@
<properties>
<help>Path to CA certificate file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type ca --name "$4" --filename "$6"</command>
</tagNode>
<tagNode name="key-file">
<properties>
<help>Path to private key file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --key-filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type ca --name "$4" --key-filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -393,13 +393,13 @@
<properties>
<help>Path to certificate file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type certificate --name "$4" --filename "$6"</command>
</tagNode>
<tagNode name="key-file">
<properties>
<help>Path to private key file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --key-filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type certificate --name "$4" --key-filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -415,7 +415,7 @@
<properties>
<help>Path to CRL file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --crl "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type crl --name "$4" --filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -431,7 +431,7 @@
<properties>
<help>Path to DH parameters file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --dh "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type dh --name "$4" --filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -447,13 +447,13 @@
<properties>
<help>Path to public key file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type key-pair --name "$4" --filename "$6"</command>
</tagNode>
<tagNode name="private-file">
<properties>
<help>Path to private key file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --key-filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type key-pair --name "$4" --key-filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -474,7 +474,7 @@
<properties>
<help>Path to shared secret key file</help>
</properties>
- <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --openvpn "$5" --filename "$7"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py import_pki --pki-type openvpn --name "$5" --filename "$7"</command>
</tagNode>
</children>
</tagNode>
@@ -490,13 +490,13 @@
<properties>
<help>Show PKI x509 certificates</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_all</command>
<children>
<leafNode name="ca">
<properties>
<help>Show x509 CA certificates</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "all"</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate_authority</command>
</leafNode>
<tagNode name="ca">
<properties>
@@ -505,13 +505,13 @@
<path>pki ca</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4"</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate_authority --name "$4"</command>
<children>
<leafNode name="pem">
<properties>
<help>Show x509 CA certificate in PEM format</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4" --pem</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate_authority --name "$4" --pem</command>
</leafNode>
</children>
</tagNode>
@@ -519,7 +519,7 @@
<properties>
<help>Show x509 certificates</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "all"</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate</command>
</leafNode>
<tagNode name="certificate">
<properties>
@@ -528,13 +528,13 @@
<path>pki certificate</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4"</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate --name "$4"</command>
<children>
<leafNode name="pem">
<properties>
<help>Show x509 certificate in PEM format</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --pem</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate --name "$4" --pem</command>
</leafNode>
<tagNode name="fingerprint">
<properties>
@@ -543,7 +543,7 @@
<list>sha256 sha384 sha512</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --fingerprint "$6"</command>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py show_certificate --name "$4" --fingerprint "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -551,7 +551,7 @@
<properties>
<help>Show x509 certificate revocation lists</help>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action show --crl "all"</command>
+ <command>${vyos_op_scripts_dir}/pki.py show_crl</command>
</leafNode>
<tagNode name="crl">
<properties>
@@ -560,13 +560,13 @@
<path>pki ca</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action show --crl "$4"</command>
+ <command>${vyos_op_scripts_dir}/pki.py show_crl --name "$4"</command>
<children>
<leafNode name="pem">
<properties>
<help>Show x509 certificate revocation lists by CA name in PEM format</help>
</properties>
- <command>${vyos_op_scripts_dir}/pki.py --action show --crl "$4" --pem</command>
+ <command>${vyos_op_scripts_dir}/pki.py show_crl --name "$4" --pem</command>
</leafNode>
</children>
</tagNode>
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:
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index e95d7458f..242f3d1de 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -32,6 +32,27 @@ interfaces {
loopback lo {
}
}
+load-balancing {
+ reverse-proxy {
+ backend bk-01 {
+ balance "round-robin"
+ mode "tcp"
+ server srv01 {
+ address "192.0.2.11"
+ port "8881"
+ }
+ server srv02 {
+ address "192.0.2.12"
+ port "8882"
+ }
+ }
+ service my-tcp-api {
+ backend "bk-01"
+ mode "tcp"
+ port "8888"
+ }
+ }
+}
protocols {
static {
arp 192.168.0.20 {
diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_haproxy.py
index 34f77b95d..967eb3869 100755
--- a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
+++ b/smoketest/scripts/cli/test_load-balancing_haproxy.py
@@ -24,7 +24,7 @@ from vyos.utils.file import read_file
PROCESS_NAME = 'haproxy'
HAPROXY_CONF = '/run/haproxy/haproxy.cfg'
-base_path = ['load-balancing', 'reverse-proxy']
+base_path = ['load-balancing', 'haproxy']
proxy_interface = 'eth1'
valid_ca_cert = """
diff --git a/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py b/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
new file mode 100755
index 000000000..230171c11
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env 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 unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.utils.process import process_named_running
+from vyos.utils.file import read_file
+
+PROCESS_NAME = 'frr_exporter'
+base_path = ['service', 'monitoring', 'frr-exporter']
+service_file = '/etc/systemd/system/frr_exporter.service'
+listen_if = 'dum3421'
+listen_ip = '192.0.2.1'
+
+
+class TestMonitoringFrrExporter(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # call base-classes classmethod
+ super(TestMonitoringFrrExporter, cls).setUpClass()
+ # create a test interfaces
+ cls.cli_set(
+ cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']
+ )
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['interfaces', 'dummy', listen_if])
+ super(TestMonitoringFrrExporter, cls).tearDownClass()
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_01_basic_config(self):
+ self.cli_set(base_path + ['listen-address', listen_ip])
+
+ # commit changes
+ self.cli_commit()
+
+ file_content = read_file(service_file)
+ self.assertIn(f'{listen_ip}:9342', file_content)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_haproxy.py
index 17226efe9..45042dd52 100755..100644
--- a/src/conf_mode/load-balancing_reverse-proxy.py
+++ b/src/conf_mode/load-balancing_haproxy.py
@@ -48,7 +48,7 @@ def get_config(config=None):
else:
conf = Config()
- base = ['load-balancing', 'reverse-proxy']
+ base = ['load-balancing', 'haproxy']
if not conf.exists(base):
return None
lb = conf.get_config_dict(base,
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 233d73ba8..45e0129a3 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -71,7 +71,7 @@ sync_search = [
},
{
'keys': ['certificate', 'ca_certificate'],
- 'path': ['load_balancing', 'reverse_proxy'],
+ 'path': ['load_balancing', 'haproxy'],
},
{
'keys': ['key'],
diff --git a/src/conf_mode/service_monitoring_frr-exporter.py b/src/conf_mode/service_monitoring_frr-exporter.py
new file mode 100755
index 000000000..01527d579
--- /dev/null
+++ b/src/conf_mode/service_monitoring_frr-exporter.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env 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 os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
+from vyos.template import render
+from vyos.utils.process import call
+from vyos import ConfigError
+from vyos import airbag
+
+
+airbag.enable()
+
+service_file = '/etc/systemd/system/frr_exporter.service'
+systemd_service = 'frr_exporter.service'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'monitoring', 'frr-exporter']
+ if not conf.exists(base):
+ return None
+
+ config_data = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True
+ )
+ config_data = conf.merge_defaults(config_data, recursive=True)
+
+ tmp = is_node_changed(conf, base + ['vrf'])
+ if tmp:
+ config_data.update({'restart_required': {}})
+
+ return config_data
+
+
+def verify(config_data):
+ # bail out early - looks like removal from running config
+ if not config_data:
+ return None
+
+ verify_vrf(config_data)
+ return None
+
+
+def generate(config_data):
+ if not config_data:
+ # Delete systemd files
+ if os.path.isfile(service_file):
+ os.unlink(service_file)
+ return None
+
+ # Render frr_exporter service_file
+ render(service_file, 'frr_exporter/frr_exporter.service.j2', config_data)
+ return None
+
+
+def apply(config_data):
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+ if not config_data:
+ call(f'systemctl stop {systemd_service}')
+ return
+
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'restart_required' in config_data:
+ systemd_action = 'restart'
+
+ call(f'systemctl {systemd_action} {systemd_service}')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/system_config-management.py b/src/conf_mode/system_config-management.py
index c681a8405..8de4e5342 100755
--- a/src/conf_mode/system_config-management.py
+++ b/src/conf_mode/system_config-management.py
@@ -22,6 +22,7 @@ from vyos.config import Config
from vyos.config_mgmt import ConfigMgmt
from vyos.config_mgmt import commit_post_hook_dir, commit_hooks
+
def get_config(config=None):
if config:
conf = config
@@ -36,22 +37,29 @@ def get_config(config=None):
return mgmt
-def verify(_mgmt):
+
+def verify(mgmt):
+ d = mgmt.config_dict
+ confirm = d.get('commit_confirm', {})
+ if confirm.get('action', '') == 'reload' and 'commit_revisions' not in d:
+ raise ConfigError('commit-confirm reload requires non-zero commit-revisions')
+
return
+
def generate(mgmt):
if mgmt is None:
return
mgmt.initialize_revision()
+
def apply(mgmt):
if mgmt is None:
return
locations = mgmt.locations
- archive_target = os.path.join(commit_post_hook_dir,
- commit_hooks['commit_archive'])
+ archive_target = os.path.join(commit_post_hook_dir, commit_hooks['commit_archive'])
if locations:
try:
os.symlink('/usr/bin/config-mgmt', archive_target)
@@ -68,8 +76,9 @@ def apply(mgmt):
raise ConfigError from exc
revisions = mgmt.max_revisions
- revision_target = os.path.join(commit_post_hook_dir,
- commit_hooks['commit_revision'])
+ revision_target = os.path.join(
+ commit_post_hook_dir, commit_hooks['commit_revision']
+ )
if revisions > 0:
try:
os.symlink('/usr/bin/config-mgmt', revision_target)
@@ -85,6 +94,7 @@ def apply(mgmt):
except OSError as exc:
raise ConfigError from exc
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/system_login_banner.py b/src/conf_mode/system_login_banner.py
index 923e1bf57..5826d8042 100755
--- a/src/conf_mode/system_login_banner.py
+++ b/src/conf_mode/system_login_banner.py
@@ -28,6 +28,7 @@ airbag.enable()
PRELOGIN_FILE = r'/etc/issue'
PRELOGIN_NET_FILE = r'/etc/issue.net'
POSTLOGIN_FILE = r'/etc/motd'
+POSTLOGIN_VYOS_FILE = r'/run/motd.d/01-vyos-nonproduction'
default_config_data = {
'issue': 'Welcome to VyOS - \\n \\l\n\n',
@@ -94,6 +95,9 @@ def apply(banner):
render(POSTLOGIN_FILE, 'login/default_motd.j2', banner,
permission=0o644, user='root', group='root')
+ render(POSTLOGIN_VYOS_FILE, 'login/motd_vyos_nonproduction.j2', banner,
+ permission=0o644, user='root', group='root')
+
return None
if __name__ == '__main__':
diff --git a/src/helpers/commit-confirm-notify.py b/src/helpers/commit-confirm-notify.py
index 8d7626c78..af6167651 100755
--- a/src/helpers/commit-confirm-notify.py
+++ b/src/helpers/commit-confirm-notify.py
@@ -2,30 +2,56 @@
import os
import sys
import time
+from argparse import ArgumentParser
# Minutes before reboot to trigger notification.
intervals = [1, 5, 15, 60]
-def notify(interval):
- s = "" if interval == 1 else "s"
+parser = ArgumentParser()
+parser.add_argument(
+ 'minutes', type=int, help='minutes before rollback to trigger notification'
+)
+parser.add_argument(
+ '--reboot', action='store_true', help="use 'soft' rollback instead of reboot"
+)
+
+
+def notify(interval, reboot=False):
+ s = '' if interval == 1 else 's'
time.sleep((minutes - interval) * 60)
- message = ('"[commit-confirm] System is going to reboot in '
- f'{interval} minute{s} to rollback the last commit.\n'
- 'Confirm your changes to cancel the reboot."')
- os.system("wall -n " + message)
+ if reboot:
+ message = (
+ '"[commit-confirm] System will reboot in '
+ f'{interval} minute{s}\nto rollback the last commit.\n'
+ 'Confirm your changes to cancel the reboot."'
+ )
+ os.system('wall -n ' + message)
+ else:
+ message = (
+ '"[commit-confirm] System will reload previous config in '
+ f'{interval} minute{s}\nto rollback the last commit.\n'
+ 'Confirm your changes to cancel the reload."'
+ )
+ os.system('wall -n ' + message)
+
-if __name__ == "__main__":
+if __name__ == '__main__':
# Must be run as root to call wall(1) without a banner.
- if len(sys.argv) != 2 or os.getuid() != 0:
+ if os.getuid() != 0:
print('This script requires superuser privileges.', file=sys.stderr)
exit(1)
- minutes = int(sys.argv[1])
+
+ args = parser.parse_args()
+
+ minutes = args.minutes
+ reboot = args.reboot
+
# Drop the argument from the list so that the notification
# doesn't kick in immediately.
if minutes in intervals:
intervals.remove(minutes)
for interval in sorted(intervals, reverse=True):
if minutes >= interval:
- notify(interval)
- minutes -= (minutes - interval)
+ notify(interval, reboot=reboot)
+ minutes -= minutes - interval
exit(0)
diff --git a/src/migration-scripts/reverse-proxy/1-to-2 b/src/migration-scripts/reverse-proxy/1-to-2
new file mode 100755
index 000000000..61612bc36
--- /dev/null
+++ b/src/migration-scripts/reverse-proxy/1-to-2
@@ -0,0 +1,27 @@
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# T6745: Rename base node to haproxy
+
+from vyos.configtree import ConfigTree
+
+base = ['load-balancing', 'reverse-proxy']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+
+ config.rename(base, 'haproxy')
diff --git a/src/op_mode/reverseproxy.py b/src/op_mode/load-balancing_haproxy.py
index 19704182a..ae6734e16 100755
--- a/src/op_mode/reverseproxy.py
+++ b/src/op_mode/load-balancing_haproxy.py
@@ -217,8 +217,8 @@ def _get_formatted_output(data):
def show(raw: bool):
config = ConfigTreeQuery()
- if not config.exists('load-balancing reverse-proxy'):
- raise vyos.opmode.UnconfiguredSubsystem('Reverse-proxy is not configured')
+ if not config.exists('load-balancing haproxy'):
+ raise vyos.opmode.UnconfiguredSubsystem('Haproxy is not configured')
data = _get_raw_data()
if raw:
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 5652a5d74..49a461e9e 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -14,16 +14,18 @@
# 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 argparse
import ipaddress
import os
import re
import sys
import tabulate
+import typing
from cryptography import x509
from cryptography.x509.oid import ExtendedKeyUsageOID
+import vyos.opmode
+
from vyos.config import Config
from vyos.config import config_dict_mangle_acme
from vyos.pki import encode_certificate
@@ -51,18 +53,50 @@ from vyos.utils.process import cmd
CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
+ArgsPkiType = typing.Literal['ca', 'certificate', 'dh', 'key-pair', 'openvpn', 'crl']
+ArgsPkiTypeGen = typing.Literal[ArgsPkiType, typing.Literal['ssh', 'wireguard']]
+ArgsFingerprint = typing.Literal['sha256', 'sha384', 'sha512']
+
# Helper Functions
conf = Config()
+
+
+def _verify(target):
+ """Decorator checks if config for PKI exists"""
+ from functools import wraps
+
+ if target not in ['ca', 'certificate']:
+ raise ValueError('Invalid PKI')
+
+ def _verify_target(func):
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ name = kwargs.get('name')
+ unconf_message = f'PKI {target} "{name}" does not exist!'
+ if name:
+ if not conf.exists(['pki', target, name]):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+ return _verify_target
+
+
def get_default_values():
# Fetch default x509 values
base = ['pki', 'x509', 'default']
- x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'),
- no_tag_node_value_mangle=True,
- get_first_key=True,
- with_recursive_defaults=True)
+ x509_defaults = conf.get_config_dict(
+ base,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True,
+ )
return x509_defaults
+
def get_config_ca_certificate(name=None):
# Fetch ca certificates from config
base = ['pki', 'ca']
@@ -71,12 +105,15 @@ def get_config_ca_certificate(name=None):
if name:
base = base + [name]
- if not conf.exists(base + ['private', 'key']) or not conf.exists(base + ['certificate']):
+ if not conf.exists(base + ['private', 'key']) or not conf.exists(
+ base + ['certificate']
+ ):
return False
- return conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ return conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True
+ )
+
def get_config_certificate(name=None):
# Get certificates from config
@@ -86,18 +123,21 @@ def get_config_certificate(name=None):
if name:
base = base + [name]
- if not conf.exists(base + ['private', 'key']) or not conf.exists(base + ['certificate']):
+ if not conf.exists(base + ['private', 'key']) or not conf.exists(
+ base + ['certificate']
+ ):
return False
- pki = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ pki = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True
+ )
if pki:
for certificate in pki:
pki[certificate] = config_dict_mangle_acme(certificate, pki[certificate])
return pki
+
def get_certificate_ca(cert, ca_certs):
# Find CA certificate for given certificate
if not ca_certs:
@@ -116,6 +156,7 @@ def get_certificate_ca(cert, ca_certs):
return ca_name
return None
+
def get_config_revoked_certificates():
# Fetch revoked certificates from config
ca_base = ['pki', 'ca']
@@ -124,19 +165,26 @@ def get_config_revoked_certificates():
certs = []
if conf.exists(ca_base):
- ca_certificates = conf.get_config_dict(ca_base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ ca_certificates = conf.get_config_dict(
+ ca_base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ )
certs.extend(ca_certificates.values())
if conf.exists(cert_base):
- certificates = conf.get_config_dict(cert_base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ certificates = conf.get_config_dict(
+ cert_base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ )
certs.extend(certificates.values())
return [cert_dict for cert_dict in certs if 'revoke' in cert_dict]
+
def get_revoked_by_serial_numbers(serial_numbers=[]):
# Return serial numbers of revoked certificates
certs_out = []
@@ -160,113 +208,153 @@ def get_revoked_by_serial_numbers(serial_numbers=[]):
certs_out.append(cert_name)
return certs_out
-def install_certificate(name, cert='', private_key=None, key_type=None, key_passphrase=None, is_ca=False):
+
+def install_certificate(
+ name, cert='', private_key=None, key_type=None, key_passphrase=None, is_ca=False
+):
# Show/install conf commands for certificate
prefix = 'ca' if is_ca else 'certificate'
- base = f"pki {prefix} {name}"
+ base = f'pki {prefix} {name}'
config_paths = []
if cert:
- cert_pem = "".join(encode_certificate(cert).strip().split("\n")[1:-1])
+ cert_pem = ''.join(encode_certificate(cert).strip().split('\n')[1:-1])
config_paths.append(f"{base} certificate '{cert_pem}'")
if private_key:
- key_pem = "".join(encode_private_key(private_key, passphrase=key_passphrase).strip().split("\n")[1:-1])
+ key_pem = ''.join(
+ encode_private_key(private_key, passphrase=key_passphrase)
+ .strip()
+ .split('\n')[1:-1]
+ )
config_paths.append(f"{base} private key '{key_pem}'")
if key_passphrase:
- config_paths.append(f"{base} private password-protected")
+ config_paths.append(f'{base} private password-protected')
install_into_config(conf, config_paths)
+
def install_crl(ca_name, crl):
# Show/install conf commands for crl
- crl_pem = "".join(encode_certificate(crl).strip().split("\n")[1:-1])
+ crl_pem = ''.join(encode_certificate(crl).strip().split('\n')[1:-1])
install_into_config(conf, [f"pki ca {ca_name} crl '{crl_pem}'"])
+
def install_dh_parameters(name, params):
# Show/install conf commands for dh params
- dh_pem = "".join(encode_dh_parameters(params).strip().split("\n")[1:-1])
+ dh_pem = ''.join(encode_dh_parameters(params).strip().split('\n')[1:-1])
install_into_config(conf, [f"pki dh {name} parameters '{dh_pem}'"])
+
def install_ssh_key(name, public_key, private_key, passphrase=None):
# Show/install conf commands for ssh key
- key_openssh = encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH')
+ key_openssh = encode_public_key(
+ public_key, encoding='OpenSSH', key_format='OpenSSH'
+ )
username = os.getlogin()
- type_key_split = key_openssh.split(" ")
-
- base = f"system login user {username} authentication public-keys {name}"
- install_into_config(conf, [
- f"{base} key '{type_key_split[1]}'",
- f"{base} type '{type_key_split[0]}'"
- ])
- print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
-
-def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True):
+ type_key_split = key_openssh.split(' ')
+
+ base = f'system login user {username} authentication public-keys {name}'
+ install_into_config(
+ conf,
+ [f"{base} key '{type_key_split[1]}'", f"{base} type '{type_key_split[0]}'"],
+ )
+ print(
+ encode_private_key(
+ private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase
+ )
+ )
+
+
+def install_keypair(
+ name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True
+):
# Show/install conf commands for key-pair
config_paths = []
if public_key:
- install_public_key = not prompt or ask_yes_no('Do you want to install the public key?', default=True)
+ install_public_key = not prompt or ask_yes_no(
+ 'Do you want to install the public key?', default=True
+ )
public_key_pem = encode_public_key(public_key)
if install_public_key:
- install_public_pem = "".join(public_key_pem.strip().split("\n")[1:-1])
- config_paths.append(f"pki key-pair {name} public key '{install_public_pem}'")
+ install_public_pem = ''.join(public_key_pem.strip().split('\n')[1:-1])
+ config_paths.append(
+ f"pki key-pair {name} public key '{install_public_pem}'"
+ )
else:
- print("Public key:")
+ print('Public key:')
print(public_key_pem)
if private_key:
- install_private_key = not prompt or ask_yes_no('Do you want to install the private key?', default=True)
+ install_private_key = not prompt or ask_yes_no(
+ 'Do you want to install the private key?', default=True
+ )
private_key_pem = encode_private_key(private_key, passphrase=passphrase)
if install_private_key:
- install_private_pem = "".join(private_key_pem.strip().split("\n")[1:-1])
- config_paths.append(f"pki key-pair {name} private key '{install_private_pem}'")
+ install_private_pem = ''.join(private_key_pem.strip().split('\n')[1:-1])
+ config_paths.append(
+ f"pki key-pair {name} private key '{install_private_pem}'"
+ )
if passphrase:
- config_paths.append(f"pki key-pair {name} private password-protected")
+ config_paths.append(f'pki key-pair {name} private password-protected')
else:
- print("Private key:")
+ print('Private key:')
print(private_key_pem)
install_into_config(conf, config_paths)
+
def install_openvpn_key(name, key_data, key_version='1'):
config_paths = [
f"pki openvpn shared-secret {name} key '{key_data}'",
- f"pki openvpn shared-secret {name} version '{key_version}'"
+ f"pki openvpn shared-secret {name} version '{key_version}'",
]
install_into_config(conf, config_paths)
+
def install_wireguard_key(interface, private_key, public_key):
# Show conf commands for installing wireguard key pairs
from vyos.ifconfig import Section
+
if Section.section(interface) != 'wireguard':
print(f'"{interface}" is not a WireGuard interface name!')
exit(1)
# Check if we are running in a config session - if yes, we can directly write to the CLI
- install_into_config(conf, [f"interfaces wireguard {interface} private-key '{private_key}'"])
+ install_into_config(
+ conf, [f"interfaces wireguard {interface} private-key '{private_key}'"]
+ )
print(f"Corresponding public-key to use on peer system is: '{public_key}'")
+
def install_wireguard_psk(interface, peer, psk):
from vyos.ifconfig import Section
+
if Section.section(interface) != 'wireguard':
print(f'"{interface}" is not a WireGuard interface name!')
exit(1)
# Check if we are running in a config session - if yes, we can directly write to the CLI
- install_into_config(conf, [f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'"])
+ install_into_config(
+ conf, [f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'"]
+ )
+
def ask_passphrase():
passphrase = None
- print("Note: If you plan to use the generated key on this router, do not encrypt the private key.")
+ print(
+ 'Note: If you plan to use the generated key on this router, do not encrypt the private key.'
+ )
if ask_yes_no('Do you want to encrypt the private key with a passphrase?'):
passphrase = ask_input('Enter passphrase:')
return passphrase
+
def write_file(filename, contents):
full_path = os.path.join(auth_dir, filename)
directory = os.path.dirname(full_path)
@@ -275,7 +363,9 @@ def write_file(filename, contents):
print('Failed to write file: directory does not exist')
return False
- if os.path.exists(full_path) and not ask_yes_no('Do you want to overwrite the existing file?'):
+ if os.path.exists(full_path) and not ask_yes_no(
+ 'Do you want to overwrite the existing file?'
+ ):
return False
with open(full_path, 'w') as f:
@@ -283,10 +373,14 @@ def write_file(filename, contents):
print(f'File written to {full_path}')
-# Generation functions
+# Generation functions
def generate_private_key():
- key_type = ask_input('Enter private key type: [rsa, dsa, ec]', default='rsa', valid_responses=['rsa', 'dsa', 'ec'])
+ key_type = ask_input(
+ 'Enter private key type: [rsa, dsa, ec]',
+ default='rsa',
+ valid_responses=['rsa', 'dsa', 'ec'],
+ )
size_valid = []
size_default = 0
@@ -298,28 +392,43 @@ def generate_private_key():
size_default = 256
size_valid = [224, 256, 384, 521]
- size = ask_input('Enter private key bits:', default=size_default, numeric_only=True, valid_responses=size_valid)
+ size = ask_input(
+ 'Enter private key bits:',
+ default=size_default,
+ numeric_only=True,
+ valid_responses=size_valid,
+ )
return create_private_key(key_type, size), key_type
+
def parse_san_string(san_string):
if not san_string:
return None
output = []
- san_split = san_string.strip().split(",")
+ san_split = san_string.strip().split(',')
for pair_str in san_split:
- tag, value = pair_str.strip().split(":", 1)
+ tag, value = pair_str.strip().split(':', 1)
if tag == 'ipv4':
output.append(ipaddress.IPv4Address(value))
elif tag == 'ipv6':
output.append(ipaddress.IPv6Address(value))
elif tag == 'dns' or tag == 'rfc822':
output.append(value)
- return output
-
-def generate_certificate_request(private_key=None, key_type=None, return_request=False, name=None, install=False, file=False, ask_san=True):
+ return
+
+
+def generate_certificate_request(
+ private_key=None,
+ key_type=None,
+ return_request=False,
+ name=None,
+ install=False,
+ file=False,
+ ask_san=True,
+):
if not private_key:
private_key, key_type = generate_private_key()
@@ -328,18 +437,24 @@ def generate_certificate_request(private_key=None, key_type=None, return_request
while True:
country = ask_input('Enter country code:', default=default_values['country'])
if len(country) != 2:
- print("Country name must be a 2 character country code")
+ print('Country name must be a 2 character country code')
continue
subject['country'] = country
break
subject['state'] = ask_input('Enter state:', default=default_values['state'])
- subject['locality'] = ask_input('Enter locality:', default=default_values['locality'])
- subject['organization'] = ask_input('Enter organization name:', default=default_values['organization'])
+ subject['locality'] = ask_input(
+ 'Enter locality:', default=default_values['locality']
+ )
+ subject['organization'] = ask_input(
+ 'Enter organization name:', default=default_values['organization']
+ )
subject['common_name'] = ask_input('Enter common name:', default='vyos.io')
subject_alt_names = None
if ask_san and ask_yes_no('Do you want to configure Subject Alternative Names?'):
- print("Enter alternative names in a comma separate list, example: ipv4:1.1.1.1,ipv6:fe80::1,dns:vyos.net,rfc822:user@vyos.net")
+ print(
+ 'Enter alternative names in a comma separate list, example: ipv4:1.1.1.1,ipv6:fe80::1,dns:vyos.net,rfc822:user@vyos.net'
+ )
san_string = ask_input('Enter Subject Alternative Names:')
subject_alt_names = parse_san_string(san_string)
@@ -356,24 +471,48 @@ def generate_certificate_request(private_key=None, key_type=None, return_request
return None
if install:
- print("Certificate request:")
- print(encode_certificate(cert_req) + "\n")
- install_certificate(name, private_key=private_key, key_type=key_type, key_passphrase=passphrase, is_ca=False)
+ print('Certificate request:')
+ print(encode_certificate(cert_req) + '\n')
+ install_certificate(
+ name,
+ private_key=private_key,
+ key_type=key_type,
+ key_passphrase=passphrase,
+ is_ca=False,
+ )
if file:
write_file(f'{name}.csr', encode_certificate(cert_req))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
-
-def generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False, is_sub_ca=False):
- valid_days = ask_input('Enter how many days certificate will be valid:', default='365' if not is_ca else '1825', numeric_only=True)
+ write_file(
+ f'{name}.key', encode_private_key(private_key, passphrase=passphrase)
+ )
+
+
+def generate_certificate(
+ cert_req, ca_cert, ca_private_key, is_ca=False, is_sub_ca=False
+):
+ valid_days = ask_input(
+ 'Enter how many days certificate will be valid:',
+ default='365' if not is_ca else '1825',
+ numeric_only=True,
+ )
cert_type = None
if not is_ca:
- cert_type = ask_input('Enter certificate type: (client, server)', default='server', valid_responses=['client', 'server'])
- return create_certificate(cert_req, ca_cert, ca_private_key, valid_days, cert_type, is_ca, is_sub_ca)
+ cert_type = ask_input(
+ 'Enter certificate type: (client, server)',
+ default='server',
+ valid_responses=['client', 'server'],
+ )
+ return create_certificate(
+ cert_req, ca_cert, ca_private_key, valid_days, cert_type, is_ca, is_sub_ca
+ )
+
def generate_ca_certificate(name, install=False, file=False):
private_key, key_type = generate_private_key()
- cert_req = generate_certificate_request(private_key, key_type, return_request=True, ask_san=False)
+ cert_req = generate_certificate_request(
+ private_key, key_type, return_request=True, ask_san=False
+ )
cert = generate_certificate(cert_req, cert_req, private_key, is_ca=True)
passphrase = ask_passphrase()
@@ -383,11 +522,16 @@ def generate_ca_certificate(name, install=False, file=False):
return None
if install:
- install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True)
+ install_certificate(
+ name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True
+ )
if file:
write_file(f'{name}.pem', encode_certificate(cert))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ write_file(
+ f'{name}.key', encode_private_key(private_key, passphrase=passphrase)
+ )
+
def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
ca_dict = get_config_ca_certificate(ca_name)
@@ -399,17 +543,19 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
ca_cert = load_certificate(ca_dict['certificate'])
if not ca_cert:
- print("Failed to load signing CA certificate, aborting")
+ print('Failed to load signing CA certificate, aborting')
return None
ca_private = ca_dict['private']
ca_private_passphrase = None
if 'password_protected' in ca_private:
ca_private_passphrase = ask_input('Enter signing CA private key passphrase:')
- ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase)
+ ca_private_key = load_private_key(
+ ca_private['key'], passphrase=ca_private_passphrase
+ )
if not ca_private_key:
- print("Failed to load signing CA private key, aborting")
+ print('Failed to load signing CA private key, aborting')
return None
private_key = None
@@ -418,9 +564,11 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
cert_req = None
if not ask_yes_no('Do you already have a certificate request?'):
private_key, key_type = generate_private_key()
- cert_req = generate_certificate_request(private_key, key_type, return_request=True, ask_san=False)
+ cert_req = generate_certificate_request(
+ private_key, key_type, return_request=True, ask_san=False
+ )
else:
- print("Paste certificate request and press enter:")
+ print('Paste certificate request and press enter:')
lines = []
curr_line = ''
while True:
@@ -430,17 +578,21 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
lines.append(curr_line)
if not lines:
- print("Aborted")
+ print('Aborted')
return None
- wrap = lines[0].find('-----') < 0 # Only base64 pasted, add the CSR tags for parsing
- cert_req = load_certificate_request("\n".join(lines), wrap)
+ wrap = (
+ lines[0].find('-----') < 0
+ ) # Only base64 pasted, add the CSR tags for parsing
+ cert_req = load_certificate_request('\n'.join(lines), wrap)
if not cert_req:
- print("Invalid certificate request")
+ print('Invalid certificate request')
return None
- cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=True, is_sub_ca=True)
+ cert = generate_certificate(
+ cert_req, ca_cert, ca_private_key, is_ca=True, is_sub_ca=True
+ )
passphrase = None
if private_key is not None:
@@ -453,12 +605,17 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
return None
if install:
- install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True)
+ install_certificate(
+ name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=True
+ )
if file:
write_file(f'{name}.pem', encode_certificate(cert))
if private_key is not None:
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ write_file(
+ f'{name}.key', encode_private_key(private_key, passphrase=passphrase)
+ )
+
def generate_certificate_sign(name, ca_name, install=False, file=False):
ca_dict = get_config_ca_certificate(ca_name)
@@ -470,17 +627,19 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
ca_cert = load_certificate(ca_dict['certificate'])
if not ca_cert:
- print("Failed to load CA certificate, aborting")
+ print('Failed to load CA certificate, aborting')
return None
ca_private = ca_dict['private']
ca_private_passphrase = None
if 'password_protected' in ca_private:
ca_private_passphrase = ask_input('Enter CA private key passphrase:')
- ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase)
+ ca_private_key = load_private_key(
+ ca_private['key'], passphrase=ca_private_passphrase
+ )
if not ca_private_key:
- print("Failed to load CA private key, aborting")
+ print('Failed to load CA private key, aborting')
return None
private_key = None
@@ -489,9 +648,11 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
cert_req = None
if not ask_yes_no('Do you already have a certificate request?'):
private_key, key_type = generate_private_key()
- cert_req = generate_certificate_request(private_key, key_type, return_request=True)
+ cert_req = generate_certificate_request(
+ private_key, key_type, return_request=True
+ )
else:
- print("Paste certificate request and press enter:")
+ print('Paste certificate request and press enter:')
lines = []
curr_line = ''
while True:
@@ -501,18 +662,20 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
lines.append(curr_line)
if not lines:
- print("Aborted")
+ print('Aborted')
return None
- wrap = lines[0].find('-----') < 0 # Only base64 pasted, add the CSR tags for parsing
- cert_req = load_certificate_request("\n".join(lines), wrap)
+ wrap = (
+ lines[0].find('-----') < 0
+ ) # Only base64 pasted, add the CSR tags for parsing
+ cert_req = load_certificate_request('\n'.join(lines), wrap)
if not cert_req:
- print("Invalid certificate request")
+ print('Invalid certificate request')
return None
cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False)
-
+
passphrase = None
if private_key is not None:
passphrase = ask_passphrase()
@@ -524,12 +687,17 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
return None
if install:
- install_certificate(name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=False)
+ install_certificate(
+ name, cert, private_key, key_type, key_passphrase=passphrase, is_ca=False
+ )
if file:
write_file(f'{name}.pem', encode_certificate(cert))
if private_key is not None:
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ write_file(
+ f'{name}.key', encode_private_key(private_key, passphrase=passphrase)
+ )
+
def generate_certificate_selfsign(name, install=False, file=False):
private_key, key_type = generate_private_key()
@@ -543,11 +711,21 @@ def generate_certificate_selfsign(name, install=False, file=False):
return None
if install:
- install_certificate(name, cert, private_key=private_key, key_type=key_type, key_passphrase=passphrase, is_ca=False)
+ install_certificate(
+ name,
+ cert,
+ private_key=private_key,
+ key_type=key_type,
+ key_passphrase=passphrase,
+ is_ca=False,
+ )
if file:
write_file(f'{name}.pem', encode_certificate(cert))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ write_file(
+ f'{name}.key', encode_private_key(private_key, passphrase=passphrase)
+ )
+
def generate_certificate_revocation_list(ca_name, install=False, file=False):
ca_dict = get_config_ca_certificate(ca_name)
@@ -559,17 +737,19 @@ def generate_certificate_revocation_list(ca_name, install=False, file=False):
ca_cert = load_certificate(ca_dict['certificate'])
if not ca_cert:
- print("Failed to load CA certificate, aborting")
+ print('Failed to load CA certificate, aborting')
return None
ca_private = ca_dict['private']
ca_private_passphrase = None
if 'password_protected' in ca_private:
ca_private_passphrase = ask_input('Enter CA private key passphrase:')
- ca_private_key = load_private_key(ca_private['key'], passphrase=ca_private_passphrase)
+ ca_private_key = load_private_key(
+ ca_private['key'], passphrase=ca_private_passphrase
+ )
if not ca_private_key:
- print("Failed to load CA private key, aborting")
+ print('Failed to load CA private key, aborting')
return None
revoked_certs = get_config_revoked_certificates()
@@ -590,13 +770,13 @@ def generate_certificate_revocation_list(ca_name, install=False, file=False):
continue
if not to_revoke:
- print("No revoked certificates to add to the CRL")
+ print('No revoked certificates to add to the CRL')
return None
crl = create_certificate_revocation_list(ca_cert, ca_private_key, to_revoke)
if not crl:
- print("Failed to create CRL")
+ print('Failed to create CRL')
return None
if not install and not file:
@@ -607,7 +787,8 @@ def generate_certificate_revocation_list(ca_name, install=False, file=False):
install_crl(ca_name, crl)
if file:
- write_file(f'{name}.crl', encode_certificate(crl))
+ write_file(f'{ca_name}.crl', encode_certificate(crl))
+
def generate_ssh_keypair(name, install=False, file=False):
private_key, key_type = generate_private_key()
@@ -616,29 +797,42 @@ def generate_ssh_keypair(name, install=False, file=False):
if not install and not file:
print(encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH'))
- print("")
- print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
+ print('')
+ print(
+ encode_private_key(
+ private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase
+ )
+ )
return None
if install:
install_ssh_key(name, public_key, private_key, passphrase)
if file:
- write_file(f'{name}.pem', encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH'))
- write_file(f'{name}.key', encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
+ write_file(
+ f'{name}.pem',
+ encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH'),
+ )
+ write_file(
+ f'{name}.key',
+ encode_private_key(
+ private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase
+ ),
+ )
+
def generate_dh_parameters(name, install=False, file=False):
bits = ask_input('Enter DH parameters key size:', default=2048, numeric_only=True)
- print("Generating parameters...")
+ print('Generating parameters...')
dh_params = create_dh_parameters(bits)
if not dh_params:
- print("Failed to create DH parameters")
+ print('Failed to create DH parameters')
return None
if not install and not file:
- print("DH Parameters:")
+ print('DH Parameters:')
print(encode_dh_parameters(dh_params))
if install:
@@ -647,6 +841,7 @@ def generate_dh_parameters(name, install=False, file=False):
if file:
write_file(f'{name}.pem', encode_dh_parameters(dh_params))
+
def generate_keypair(name, install=False, file=False):
private_key, key_type = generate_private_key()
public_key = private_key.public_key()
@@ -654,7 +849,7 @@ def generate_keypair(name, install=False, file=False):
if not install and not file:
print(encode_public_key(public_key))
- print("")
+ print('')
print(encode_private_key(private_key, passphrase=passphrase))
return None
@@ -663,13 +858,16 @@ def generate_keypair(name, install=False, file=False):
if file:
write_file(f'{name}.pem', encode_public_key(public_key))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ write_file(
+ f'{name}.key', encode_private_key(private_key, passphrase=passphrase)
+ )
+
def generate_openvpn_key(name, install=False, file=False):
result = cmd('openvpn --genkey secret /dev/stdout | grep -o "^[^#]*"')
if not result:
- print("Failed to generate OpenVPN key")
+ print('Failed to generate OpenVPN key')
return None
if not install and not file:
@@ -677,11 +875,13 @@ def generate_openvpn_key(name, install=False, file=False):
return None
if install:
- key_lines = result.split("\n")
- key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
+ key_lines = result.split('\n')
+ key_data = ''.join(key_lines[1:-1]) # Remove wrapper tags and line endings
key_version = '1'
- version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', result) # Future-proofing (hopefully)
+ version_search = re.search(
+ r'BEGIN OpenVPN Static key V(\d+)', result
+ ) # Future-proofing (hopefully)
if version_search:
key_version = version_search[1]
@@ -690,6 +890,7 @@ def generate_openvpn_key(name, install=False, file=False):
if file:
write_file(f'{name}.key', result)
+
def generate_wireguard_key(interface=None, install=False):
private_key = cmd('wg genkey')
public_key = cmd('wg pubkey', input=private_key)
@@ -700,6 +901,7 @@ def generate_wireguard_key(interface=None, install=False):
print(f'Private key: {private_key}')
print(f'Public key: {public_key}', end='\n\n')
+
def generate_wireguard_psk(interface=None, peer=None, install=False):
psk = cmd('wg genpsk')
if interface and peer and install:
@@ -707,8 +909,11 @@ def generate_wireguard_psk(interface=None, peer=None, install=False):
else:
print(f'Pre-shared key: {psk}')
+
# Import functions
-def import_ca_certificate(name, path=None, key_path=None, no_prompt=False, passphrase=None):
+def import_ca_certificate(
+ name, path=None, key_path=None, no_prompt=False, passphrase=None
+):
if path:
if not os.path.exists(path):
print(f'File not found: {path}')
@@ -745,7 +950,10 @@ def import_ca_certificate(name, path=None, key_path=None, no_prompt=False, passp
install_certificate(name, private_key=key, is_ca=True)
-def import_certificate(name, path=None, key_path=None, no_prompt=False, passphrase=None):
+
+def import_certificate(
+ name, path=None, key_path=None, no_prompt=False, passphrase=None
+):
if path:
if not os.path.exists(path):
print(f'File not found: {path}')
@@ -782,6 +990,7 @@ def import_certificate(name, path=None, key_path=None, no_prompt=False, passphra
install_certificate(name, private_key=key, is_ca=False)
+
def import_crl(name, path):
if not os.path.exists(path):
print(f'File not found: {path}')
@@ -799,6 +1008,7 @@ def import_crl(name, path):
install_crl(name, crl)
+
def import_dh_parameters(name, path):
if not os.path.exists(path):
print(f'File not found: {path}')
@@ -816,6 +1026,7 @@ def import_dh_parameters(name, path):
install_dh_parameters(name, dh)
+
def import_keypair(name, path=None, key_path=None, no_prompt=False, passphrase=None):
if path:
if not os.path.exists(path):
@@ -853,6 +1064,7 @@ def import_keypair(name, path=None, key_path=None, no_prompt=False, passphrase=N
install_keypair(name, None, private_key=key, prompt=False)
+
def import_openvpn_secret(name, path):
if not os.path.exists(path):
print(f'File not found: {path}')
@@ -862,19 +1074,134 @@ def import_openvpn_secret(name, path):
key_version = '1'
with open(path) as f:
- key_lines = f.read().strip().split("\n")
- key_lines = list(filter(lambda line: not line.strip().startswith('#'), key_lines)) # Remove commented lines
- key_data = "".join(key_lines[1:-1]) # Remove wrapper tags and line endings
-
- version_search = re.search(r'BEGIN OpenVPN Static key V(\d+)', key_lines[0]) # Future-proofing (hopefully)
+ key_lines = f.read().strip().split('\n')
+ key_lines = list(
+ filter(lambda line: not line.strip().startswith('#'), key_lines)
+ ) # Remove commented lines
+ key_data = ''.join(key_lines[1:-1]) # Remove wrapper tags and line endings
+
+ version_search = re.search(
+ r'BEGIN OpenVPN Static key V(\d+)', key_lines[0]
+ ) # Future-proofing (hopefully)
if version_search:
key_version = version_search[1]
install_openvpn_key(name, key_data, key_version)
-# Show functions
-def show_certificate_authority(name=None, pem=False):
- headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent']
+
+def generate_pki(
+ raw: bool,
+ pki_type: ArgsPkiTypeGen,
+ name: typing.Optional[str],
+ file: typing.Optional[bool],
+ install: typing.Optional[bool],
+ sign: typing.Optional[str],
+ self_sign: typing.Optional[bool],
+ key: typing.Optional[bool],
+ psk: typing.Optional[bool],
+ interface: typing.Optional[str],
+ peer: typing.Optional[str],
+):
+ try:
+ if pki_type == 'ca':
+ if sign:
+ generate_ca_certificate_sign(name, sign, install=install, file=file)
+ else:
+ generate_ca_certificate(name, install=install, file=file)
+ elif pki_type == 'certificate':
+ if sign:
+ generate_certificate_sign(name, sign, install=install, file=file)
+ elif self_sign:
+ generate_certificate_selfsign(name, install=install, file=file)
+ else:
+ generate_certificate_request(name=name, install=install, file=file)
+
+ elif pki_type == 'crl':
+ generate_certificate_revocation_list(name, install=install, file=file)
+
+ elif pki_type == 'ssh':
+ generate_ssh_keypair(name, install=install, file=file)
+
+ elif pki_type == 'dh':
+ generate_dh_parameters(name, install=install, file=file)
+
+ elif pki_type == 'key-pair':
+ generate_keypair(name, install=install, file=file)
+
+ elif pki_type == 'openvpn':
+ generate_openvpn_key(name, install=install, file=file)
+
+ elif pki_type == 'wireguard':
+ # WireGuard supports writing key directly into the CLI, but this
+ # requires the vyos_libexec_dir environment variable to be set
+ os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos'
+
+ if key:
+ generate_wireguard_key(interface, install=install)
+ if psk:
+ generate_wireguard_psk(interface, peer=peer, install=install)
+ except KeyboardInterrupt:
+ print('Aborted')
+ sys.exit(0)
+
+
+def import_pki(
+ name: str,
+ pki_type: ArgsPkiType,
+ filename: typing.Optional[str],
+ key_filename: typing.Optional[str],
+ no_prompt: typing.Optional[bool],
+ passphrase: typing.Optional[str],
+):
+ try:
+ if pki_type == 'ca':
+ import_ca_certificate(
+ name,
+ path=filename,
+ key_path=key_filename,
+ no_prompt=no_prompt,
+ passphrase=passphrase,
+ )
+ elif pki_type == 'certificate':
+ import_certificate(
+ name,
+ path=filename,
+ key_path=key_filename,
+ no_prompt=no_prompt,
+ passphrase=passphrase,
+ )
+ elif pki_type == 'crl':
+ import_crl(name, filename)
+ elif pki_type == 'dh':
+ import_dh_parameters(name, filename)
+ elif pki_type == 'key-pair':
+ import_keypair(
+ name,
+ path=filename,
+ key_path=key_filename,
+ no_prompt=no_prompt,
+ passphrase=passphrase,
+ )
+ elif pki_type == 'openvpn':
+ import_openvpn_secret(name, filename)
+ except KeyboardInterrupt:
+ print('Aborted')
+ sys.exit(0)
+
+
+@_verify('ca')
+def show_certificate_authority(
+ raw: bool, name: typing.Optional[str] = None, pem: typing.Optional[bool] = False
+):
+ headers = [
+ 'Name',
+ 'Subject',
+ 'Issuer CN',
+ 'Issued',
+ 'Expiry',
+ 'Private Key',
+ 'Parent',
+ ]
data = []
certs = get_config_ca_certificate()
if certs:
@@ -891,7 +1218,7 @@ def show_certificate_authority(name=None, pem=False):
return
parent_ca_name = get_certificate_ca(cert, certs)
- cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0]
+ cert_issuer_cn = cert.issuer.rfc4514_string().split(',')[0]
if not parent_ca_name or parent_ca_name == cert_name:
parent_ca_name = 'N/A'
@@ -899,14 +1226,45 @@ def show_certificate_authority(name=None, pem=False):
if not cert:
continue
- have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No'
- data.append([cert_name, cert.subject.rfc4514_string(), cert_issuer_cn, cert.not_valid_before, cert.not_valid_after, have_private, parent_ca_name])
-
- print("Certificate Authorities:")
+ have_private = (
+ 'Yes'
+ if 'private' in cert_dict and 'key' in cert_dict['private']
+ else 'No'
+ )
+ data.append(
+ [
+ cert_name,
+ cert.subject.rfc4514_string(),
+ cert_issuer_cn,
+ cert.not_valid_before,
+ cert.not_valid_after,
+ have_private,
+ parent_ca_name,
+ ]
+ )
+
+ print('Certificate Authorities:')
print(tabulate.tabulate(data, headers))
-def show_certificate(name=None, pem=False, fingerprint_hash=None):
- headers = ['Name', 'Type', 'Subject CN', 'Issuer CN', 'Issued', 'Expiry', 'Revoked', 'Private Key', 'CA Present']
+
+@_verify('certificate')
+def show_certificate(
+ raw: bool,
+ name: typing.Optional[str] = None,
+ pem: typing.Optional[bool] = False,
+ fingerprint: typing.Optional[ArgsFingerprint] = None,
+):
+ headers = [
+ 'Name',
+ 'Type',
+ 'Subject CN',
+ 'Issuer CN',
+ 'Issued',
+ 'Expiry',
+ 'Revoked',
+ 'Private Key',
+ 'CA Present',
+ ]
data = []
certs = get_config_certificate()
if certs:
@@ -926,13 +1284,13 @@ def show_certificate(name=None, pem=False, fingerprint_hash=None):
if name and pem:
print(encode_certificate(cert))
return
- elif name and fingerprint_hash:
- print(get_certificate_fingerprint(cert, fingerprint_hash))
+ elif name and fingerprint:
+ print(get_certificate_fingerprint(cert, fingerprint))
return
ca_name = get_certificate_ca(cert, ca_certs)
- cert_subject_cn = cert.subject.rfc4514_string().split(",")[0]
- cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0]
+ cert_subject_cn = cert.subject.rfc4514_string().split(',')[0]
+ cert_issuer_cn = cert.issuer.rfc4514_string().split(',')[0]
cert_type = 'Unknown'
try:
@@ -941,21 +1299,37 @@ def show_certificate(name=None, pem=False, fingerprint_hash=None):
cert_type = 'Server'
elif ext and ExtendedKeyUsageOID.CLIENT_AUTH in ext.value:
cert_type = 'Client'
- except:
+ except Exception:
pass
revoked = 'Yes' if 'revoke' in cert_dict else 'No'
- have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No'
+ have_private = (
+ 'Yes'
+ if 'private' in cert_dict and 'key' in cert_dict['private']
+ else 'No'
+ )
have_ca = f'Yes ({ca_name})' if ca_name else 'No'
- data.append([
- cert_name, cert_type, cert_subject_cn, cert_issuer_cn,
- cert.not_valid_before, cert.not_valid_after,
- revoked, have_private, have_ca])
-
- print("Certificates:")
+ data.append(
+ [
+ cert_name,
+ cert_type,
+ cert_subject_cn,
+ cert_issuer_cn,
+ cert.not_valid_before,
+ cert.not_valid_after,
+ revoked,
+ have_private,
+ have_ca,
+ ]
+ )
+
+ print('Certificates:')
print(tabulate.tabulate(data, headers))
-def show_crl(name=None, pem=False):
+
+def show_crl(
+ raw: bool, name: typing.Optional[str] = None, pem: typing.Optional[bool] = False
+):
headers = ['CA Name', 'Updated', 'Revokes']
data = []
certs = get_config_ca_certificate()
@@ -980,141 +1354,31 @@ def show_crl(name=None, pem=False):
print(encode_certificate(crl))
continue
- certs = get_revoked_by_serial_numbers([revoked.serial_number for revoked in crl])
- data.append([cert_name, crl.last_update, ", ".join(certs)])
+ certs = get_revoked_by_serial_numbers(
+ [revoked.serial_number for revoked in crl]
+ )
+ data.append([cert_name, crl.last_update, ', '.join(certs)])
if name and pem:
return
- print("Certificate Revocation Lists:")
+ print('Certificate Revocation Lists:')
print(tabulate.tabulate(data, headers))
-if __name__ == '__main__':
- parser = argparse.ArgumentParser()
- parser.add_argument('--action', help='PKI action', required=True)
-
- # X509
- parser.add_argument('--ca', help='Certificate Authority', required=False)
- parser.add_argument('--certificate', help='Certificate', required=False)
- parser.add_argument('--crl', help='Certificate Revocation List', required=False)
- parser.add_argument('--sign', help='Sign certificate with specified CA', required=False)
- parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true')
- parser.add_argument('--pem', help='Output using PEM encoding', action='store_true')
- parser.add_argument('--fingerprint', help='Show fingerprint and exit', action='store')
- # SSH
- parser.add_argument('--ssh', help='SSH Key', required=False)
+def show_all(raw: bool):
+ show_certificate_authority(raw)
+ print('\n')
+ show_certificate(raw)
+ print('\n')
+ show_crl(raw)
- # DH
- parser.add_argument('--dh', help='DH Parameters', required=False)
-
- # Key pair
- parser.add_argument('--keypair', help='Key pair', required=False)
-
- # OpenVPN
- parser.add_argument('--openvpn', help='OpenVPN TLS key', required=False)
-
- # WireGuard
- parser.add_argument('--wireguard', help='Wireguard', action='store_true')
- group = parser.add_mutually_exclusive_group()
- group.add_argument('--key', help='Wireguard key pair', action='store_true', required=False)
- group.add_argument('--psk', help='Wireguard pre shared key', action='store_true', required=False)
- parser.add_argument('--interface', help='Install generated keys into running-config for named interface', action='store')
- parser.add_argument('--peer', help='Install generated keys into running-config for peer', action='store')
-
- # Global
- parser.add_argument('--file', help='Write generated keys into specified filename', action='store_true')
- parser.add_argument('--install', help='Install generated keys into running-config', action='store_true')
-
- parser.add_argument('--filename', help='Write certificate into specified filename', action='store')
- parser.add_argument('--key-filename', help='Write key into specified filename', action='store')
-
- parser.add_argument('--no-prompt', action='store_true', help='Perform action non-interactively')
- parser.add_argument('--passphrase', help='A passphrase to decrypt the private key')
-
- args = parser.parse_args()
+if __name__ == '__main__':
try:
- if args.action == 'generate':
- if args.ca:
- if args.sign:
- generate_ca_certificate_sign(args.ca, args.sign, install=args.install, file=args.file)
- else:
- generate_ca_certificate(args.ca, install=args.install, file=args.file)
- elif args.certificate:
- if args.sign:
- generate_certificate_sign(args.certificate, args.sign, install=args.install, file=args.file)
- elif args.self_sign:
- generate_certificate_selfsign(args.certificate, install=args.install, file=args.file)
- else:
- generate_certificate_request(name=args.certificate, install=args.install, file=args.file)
-
- elif args.crl:
- generate_certificate_revocation_list(args.crl, install=args.install, file=args.file)
-
- elif args.ssh:
- generate_ssh_keypair(args.ssh, install=args.install, file=args.file)
-
- elif args.dh:
- generate_dh_parameters(args.dh, install=args.install, file=args.file)
-
- elif args.keypair:
- generate_keypair(args.keypair, install=args.install, file=args.file)
-
- elif args.openvpn:
- generate_openvpn_key(args.openvpn, install=args.install, file=args.file)
-
- elif args.wireguard:
- # WireGuard supports writing key directly into the CLI, but this
- # requires the vyos_libexec_dir environment variable to be set
- os.environ["vyos_libexec_dir"] = "/usr/libexec/vyos"
-
- if args.key:
- generate_wireguard_key(args.interface, install=args.install)
- if args.psk:
- generate_wireguard_psk(args.interface, peer=args.peer, install=args.install)
- elif args.action == 'import':
- if args.ca:
- import_ca_certificate(args.ca, path=args.filename, key_path=args.key_filename,
- no_prompt=args.no_prompt, passphrase=args.passphrase)
- elif args.certificate:
- import_certificate(args.certificate, path=args.filename, key_path=args.key_filename,
- no_prompt=args.no_prompt, passphrase=args.passphrase)
- elif args.crl:
- import_crl(args.crl, args.filename)
- elif args.dh:
- import_dh_parameters(args.dh, args.filename)
- elif args.keypair:
- import_keypair(args.keypair, path=args.filename, key_path=args.key_filename,
- no_prompt=args.no_prompt, passphrase=args.passphrase)
- elif args.openvpn:
- import_openvpn_secret(args.openvpn, args.filename)
- elif args.action == 'show':
- if args.ca:
- ca_name = None if args.ca == 'all' else args.ca
- if ca_name:
- if not conf.exists(['pki', 'ca', ca_name]):
- print(f'CA "{ca_name}" does not exist!')
- exit(1)
- show_certificate_authority(ca_name, args.pem)
- elif args.certificate:
- cert_name = None if args.certificate == 'all' else args.certificate
- if cert_name:
- if not conf.exists(['pki', 'certificate', cert_name]):
- print(f'Certificate "{cert_name}" does not exist!')
- exit(1)
- if args.fingerprint is None:
- show_certificate(None if args.certificate == 'all' else args.certificate, args.pem)
- else:
- show_certificate(args.certificate, fingerprint_hash=args.fingerprint)
- elif args.crl:
- show_crl(None if args.crl == 'all' else args.crl, args.pem)
- else:
- show_certificate_authority()
- print('\n')
- show_certificate()
- print('\n')
- show_crl()
- except KeyboardInterrupt:
- print("Aborted")
- sys.exit(0)
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index a83c8b9d8..3b0031f34 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -41,6 +41,10 @@ service_map = {
'systemd_service': 'pdns-recursor',
'path': ['service', 'dns', 'forwarding'],
},
+ 'haproxy': {
+ 'systemd_service': 'haproxy',
+ 'path': ['load-balancing', 'haproxy'],
+ },
'igmp_proxy': {
'systemd_service': 'igmpproxy',
'path': ['protocols', 'igmp-proxy'],
@@ -53,10 +57,6 @@ service_map = {
'systemd_service': 'avahi-daemon',
'path': ['service', 'mdns', 'repeater'],
},
- 'reverse_proxy': {
- 'systemd_service': 'haproxy',
- 'path': ['load-balancing', 'reverse-proxy'],
- },
'router_advert': {
'systemd_service': 'radvd',
'path': ['service', 'router-advert'],
@@ -83,10 +83,10 @@ services = typing.Literal[
'dhcpv6',
'dns_dynamic',
'dns_forwarding',
+ 'haproxy',
'igmp_proxy',
'ipsec',
'mdns_repeater',
- 'reverse_proxy',
'router_advert',
'snmp',
'ssh',
diff --git a/src/services/api/rest/routers.py b/src/services/api/rest/routers.py
index 47d06b7e9..e52c77fda 100644
--- a/src/services/api/rest/routers.py
+++ b/src/services/api/rest/routers.py
@@ -425,9 +425,9 @@ def create_path_import_pki_no_prompt(path):
correct_paths = ['ca', 'certificate', 'key-pair']
if path[1] not in correct_paths:
return False
- path[1] = '--' + path[1].replace('-', '')
path[3] = '--key-filename'
- return path[1:]
+ path.insert(2, '--name')
+ return ['--pki-type'] + path[1:]
@router.post('/configure')