summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/add-pr-labels.yml3
-rw-r--r--.github/workflows/auto-author-assign.yml2
-rw-r--r--.github/workflows/chceck-pr-message.yml4
-rw-r--r--.github/workflows/check-pr-conflicts.yml2
-rw-r--r--.github/workflows/check-stale.yml2
-rw-r--r--.github/workflows/check-unused-imports.yml4
-rw-r--r--.github/workflows/codeql.yml4
-rw-r--r--.github/workflows/label-backport.yml2
-rw-r--r--.github/workflows/linit-j2.yml18
-rw-r--r--.github/workflows/repo-sync.yml6
-rw-r--r--.github/workflows/sonarcloud.yml20
-rw-r--r--Makefile3
-rw-r--r--data/configd-include.json2
-rw-r--r--data/templates/conntrackd/conntrackd.op-mode.j213
-rw-r--r--data/templates/system/40_usb_autosuspend.j25
-rw-r--r--interface-definitions/container.xml.in29
-rw-r--r--interface-definitions/include/bgp/peer-group.xml.i2
-rw-r--r--interface-definitions/include/version/openvpn-version.xml.i2
-rw-r--r--interface-definitions/nat_cgnat.xml.in6
-rw-r--r--interface-definitions/system_option.xml.in19
-rw-r--r--interface-definitions/vpn_openconnect.xml.in2
-rw-r--r--op-mode-definitions/generate-ipsec-profile.xml.in10
-rw-r--r--op-mode-definitions/lldp.xml.in17
-rw-r--r--op-mode-definitions/monitor-log.xml.in41
-rw-r--r--op-mode-definitions/pki.xml.in2
-rw-r--r--op-mode-definitions/restart-ssh.xml.in2
-rw-r--r--op-mode-definitions/show-log.xml.in41
-rw-r--r--op-mode-definitions/traffic-dump.xml.in55
-rw-r--r--python/vyos/config_mgmt.py4
-rw-r--r--python/vyos/configdict.py2
-rw-r--r--python/vyos/configquery.py60
-rw-r--r--python/vyos/configsession.py10
-rw-r--r--python/vyos/defaults.py5
-rw-r--r--python/vyos/ethtool.py4
-rw-r--r--python/vyos/ifconfig/macsec.py2
-rw-r--r--python/vyos/utils/__init__.py4
-rw-r--r--python/vyos/utils/auth.py16
-rw-r--r--python/vyos/utils/configfs.py37
-rw-r--r--python/vyos/utils/cpu.py (renamed from python/vyos/cpu.py)1
-rw-r--r--python/vyos/utils/dict.py7
-rw-r--r--python/vyos/utils/error.py24
-rw-r--r--smoketest/config-tests/container-simple1
-rw-r--r--smoketest/config-tests/dialup-router-wireguard-ipv68
-rw-r--r--smoketest/configs/container-simple5
-rw-r--r--smoketest/scripts/cli/base_accel_ppp_test.py2
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py5
-rwxr-xr-xsmoketest/scripts/cli/test_container.py5
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_macsec.py8
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py20
-rwxr-xr-xsmoketest/scripts/cli/test_system_login.py10
-rwxr-xr-xsrc/completion/list_container_sysctl_parameters.sh20
-rwxr-xr-xsrc/conf_mode/container.py57
-rwxr-xr-xsrc/conf_mode/interfaces_macsec.py10
-rwxr-xr-xsrc/conf_mode/interfaces_openvpn.py4
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py30
-rwxr-xr-xsrc/conf_mode/pki.py2
-rwxr-xr-xsrc/conf_mode/service_snmp.py28
-rwxr-xr-xsrc/conf_mode/system_login.py50
-rwxr-xr-xsrc/conf_mode/system_option.py18
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py43
-rwxr-xr-xsrc/migration-scripts/firewall/15-to-165
-rwxr-xr-xsrc/migration-scripts/interfaces/20-to-217
-rwxr-xr-xsrc/migration-scripts/openvpn/1-to-274
-rwxr-xr-xsrc/op_mode/conntrack_sync.py25
-rwxr-xr-xsrc/op_mode/cpu.py12
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py57
-rwxr-xr-xsrc/op_mode/lldp.py23
-rwxr-xr-xsrc/op_mode/pki.py22
-rwxr-xr-xsrc/op_mode/powerctrl.py2
-rw-r--r--src/op_mode/tcpdump.py165
-rwxr-xr-xsrc/op_mode/uptime.py4
-rwxr-xr-xsrc/services/vyos-configd10
-rw-r--r--src/shim/vyshim.c11
73 files changed, 967 insertions, 270 deletions
diff --git a/.github/workflows/add-pr-labels.yml b/.github/workflows/add-pr-labels.yml
index 1723cceb0..a7ee8446f 100644
--- a/.github/workflows/add-pr-labels.yml
+++ b/.github/workflows/add-pr-labels.yml
@@ -8,6 +8,7 @@ on:
- crux
- equuleus
- sagitta
+ - circinus
permissions:
pull-requests: write
@@ -15,5 +16,5 @@ permissions:
jobs:
add-pr-label:
- uses: vyos/.github/.github/workflows/add-pr-labels.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/add-pr-labels.yml@current
secrets: inherit
diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml
index c3696ea47..61612cce3 100644
--- a/.github/workflows/auto-author-assign.yml
+++ b/.github/workflows/auto-author-assign.yml
@@ -10,5 +10,5 @@ permissions:
jobs:
assign-author:
- uses: vyos/.github/.github/workflows/assign-author.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/assign-author.yml@current
secrets: inherit
diff --git a/.github/workflows/chceck-pr-message.yml b/.github/workflows/chceck-pr-message.yml
index 460662014..c567a5934 100644
--- a/.github/workflows/chceck-pr-message.yml
+++ b/.github/workflows/chceck-pr-message.yml
@@ -7,6 +7,8 @@ on:
- current
- crux
- equuleus
+ - sagitta
+ - circinus
types: [opened, synchronize, edited]
permissions:
@@ -15,5 +17,5 @@ permissions:
jobs:
check-pr-title:
- uses: vyos/.github/.github/workflows/check-pr-message.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/check-pr-message.yml@current
secrets: inherit
diff --git a/.github/workflows/check-pr-conflicts.yml b/.github/workflows/check-pr-conflicts.yml
index 0c659e6ed..f09e66415 100644
--- a/.github/workflows/check-pr-conflicts.yml
+++ b/.github/workflows/check-pr-conflicts.yml
@@ -10,5 +10,5 @@ permissions:
jobs:
check-pr-conflict-call:
- uses: vyos/.github/.github/workflows/check-pr-merge-conflict.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/check-pr-merge-conflict.yml@current
secrets: inherit
diff --git a/.github/workflows/check-stale.yml b/.github/workflows/check-stale.yml
index b5ec533f1..2adbee2f6 100644
--- a/.github/workflows/check-stale.yml
+++ b/.github/workflows/check-stale.yml
@@ -9,5 +9,5 @@ permissions:
jobs:
stale:
- uses: vyos/.github/.github/workflows/check-stale.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/check-stale.yml@current
secrets: inherit
diff --git a/.github/workflows/check-unused-imports.yml b/.github/workflows/check-unused-imports.yml
index aada264f7..322d4f3a8 100644
--- a/.github/workflows/check-unused-imports.yml
+++ b/.github/workflows/check-unused-imports.yml
@@ -3,7 +3,9 @@ on:
pull_request:
branches:
- current
+ - equuleus
- sagitta
+ - circinus
workflow_dispatch:
permissions:
@@ -11,5 +13,5 @@ permissions:
jobs:
check-unused-imports:
- uses: vyos/.github/.github/workflows/check-unused-imports.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/check-unused-imports.yml@current
secrets: inherit
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index f6472784d..12654e42e 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -2,7 +2,7 @@ name: "Perform CodeQL Analysis"
on:
push:
- branches: [ "current", "sagitta", "equuleus" ]
+ branches: [ "current", "sagitta", "equuleus", "circinus" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "current" ]
@@ -16,7 +16,7 @@ permissions:
jobs:
codeql-analysis-call:
- uses: vyos/.github/.github/workflows/codeql-analysis.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/codeql-analysis.yml@current
secrets: inherit
with:
languages: "['python']"
diff --git a/.github/workflows/label-backport.yml b/.github/workflows/label-backport.yml
index 9192b8184..efbd4388f 100644
--- a/.github/workflows/label-backport.yml
+++ b/.github/workflows/label-backport.yml
@@ -8,5 +8,5 @@ permissions:
jobs:
mergifyio-backport:
- uses: vyos/.github/.github/workflows/label-backport.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/label-backport.yml@current
secrets: inherit
diff --git a/.github/workflows/linit-j2.yml b/.github/workflows/linit-j2.yml
deleted file mode 100644
index 364a65a14..000000000
--- a/.github/workflows/linit-j2.yml
+++ /dev/null
@@ -1,18 +0,0 @@
----
-name: J2 Lint
-
-on:
- pull_request:
- branches:
- - current
- - crux
- - equuleus
-
-permissions:
- pull-requests: write
- contents: read
-
-jobs:
- j2lint:
- uses: vyos/.github/.github/workflows/lint-j2.yml@feature/T6349-reusable-workflows
- secrets: inherit
diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml
index 36f323cdd..585c04704 100644
--- a/.github/workflows/repo-sync.yml
+++ b/.github/workflows/repo-sync.yml
@@ -1,16 +1,16 @@
-name: Repo-sync
+name: Repo-sync circinus-stream
on:
pull_request_target:
types:
- closed
branches:
- - current
+ - circinus-stream
workflow_dispatch:
jobs:
trigger-sync:
- uses: vyos/.github/.github/workflows/trigger-repo-sync.yml@feature/T6349-reusable-workflows
+ uses: vyos/.github/.github/workflows/trigger-repo-sync.yml@circinus-stream
secrets:
REMOTE_REPO: ${{ secrets.REMOTE_REPO }}
REMOTE_OWNER: ${{ secrets.REMOTE_OWNER }}
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
new file mode 100644
index 000000000..d59460fe2
--- /dev/null
+++ b/.github/workflows/sonarcloud.yml
@@ -0,0 +1,20 @@
+name: Sonar Checks
+on:
+ push:
+ branches:
+ - circinus-stream
+ pull_request_target:
+ types: [opened, synchronize, reopened]
+jobs:
+ sonar-cloud:
+ name: SonarCloud
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+ - name: SonarCloud Scan
+ uses: SonarSource/sonarcloud-github-action@master
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/Makefile b/Makefile
index 3b26273d6..cc382e206 100644
--- a/Makefile
+++ b/Makefile
@@ -61,12 +61,13 @@ op_mode_definitions: $(op_xml_obj)
rm -f $(OP_TMPL_DIR)/clear/node.def
rm -f $(OP_TMPL_DIR)/delete/node.def
- # XXX: ping, traceroute and mtr must be able to recursivly call themselves as the
+ # XXX: tcpdump, ping, traceroute and mtr must be able to recursivly call themselves as the
# options are provided from the scripts themselves
ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/
ln -s ../node.tag $(OP_TMPL_DIR)/traceroute/node.tag/node.tag/
ln -s ../node.tag $(OP_TMPL_DIR)/mtr/node.tag/node.tag/
ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traceroute/node.tag/node.tag/
+ ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traffic/interface/node.tag/node.tag/
# XXX: test if there are empty node.def files - this is not allowed as these
# could mask help strings or mandatory priority statements
diff --git a/data/configd-include.json b/data/configd-include.json
index dcee50306..420960fe7 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -79,6 +79,7 @@
"service_router-advert.py",
"service_salt-minion.py",
"service_sla.py",
+"service_snmp.py",
"service_ssh.py",
"service_tftp-server.py",
"service_webproxy.py",
@@ -92,6 +93,7 @@
"system_ip.py",
"system_ipv6.py",
"system_lcd.py",
+"system_login.py",
"system_login_banner.py",
"system_logs.py",
"system_option.py",
diff --git a/data/templates/conntrackd/conntrackd.op-mode.j2 b/data/templates/conntrackd/conntrackd.op-mode.j2
deleted file mode 100644
index 82f7e2859..000000000
--- a/data/templates/conntrackd/conntrackd.op-mode.j2
+++ /dev/null
@@ -1,13 +0,0 @@
-Source Destination Protocol
-{% for parsed in data if parsed.flow.meta is vyos_defined %}
-{% for key in parsed.flow.meta %}
-{% if key['@direction'] == 'original' %}
-{% set saddr = key.layer3.src | bracketize_ipv6 %}
-{% set sport = key.layer4.sport %}
-{% set daddr = key.layer3.dst | bracketize_ipv6 %}
-{% set dport = key.layer4.dport %}
-{% set protocol = key.layer4['@protoname'] %}
-{{ "%-48s" | format(saddr ~ ':' ~ sport) }} {{ "%-48s" | format(daddr ~ ':' ~ dport) }} {{ protocol }}
-{% endif %}
-{% endfor %}
-{% endfor %}
diff --git a/data/templates/system/40_usb_autosuspend.j2 b/data/templates/system/40_usb_autosuspend.j2
new file mode 100644
index 000000000..01ba86420
--- /dev/null
+++ b/data/templates/system/40_usb_autosuspend.j2
@@ -0,0 +1,5 @@
+{% set autosuspend = "auto" %}
+{% if disable_usb_autosuspend is vyos_defined %}
+{% set autosuspend = "on" %}
+{% endif %}
+ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="{{ autosuspend }}"
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 1ad7215e5..6ea44a6d4 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -71,6 +71,35 @@
<multi/>
</properties>
</leafNode>
+ <node name="sysctl">
+ <properties>
+ <help>Configure namespaced kernel parameters of the container</help>
+ </properties>
+ <children>
+ <tagNode name="parameter">
+ <properties>
+ <help>Sysctl key name</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_container_sysctl_parameters.sh</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Sysctl key name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="sysctl"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Sysctl configuration value</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
#include <include/generic-description.xml.i>
<tagNode name="device">
<properties>
diff --git a/interface-definitions/include/bgp/peer-group.xml.i b/interface-definitions/include/bgp/peer-group.xml.i
index 3866fc017..c80d4a394 100644
--- a/interface-definitions/include/bgp/peer-group.xml.i
+++ b/interface-definitions/include/bgp/peer-group.xml.i
@@ -3,7 +3,7 @@
<properties>
<help>Peer group for this peer</help>
<completionHelp>
- <path>protocols bgp peer-group</path>
+ <path>${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-5} peer-group</path>
</completionHelp>
<valueHelp>
<format>txt</format>
diff --git a/interface-definitions/include/version/openvpn-version.xml.i b/interface-definitions/include/version/openvpn-version.xml.i
index b4dd742a3..e4eb13b7c 100644
--- a/interface-definitions/include/version/openvpn-version.xml.i
+++ b/interface-definitions/include/version/openvpn-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/openvpn-version.xml.i -->
-<syntaxVersion component='openvpn' version='1'></syntaxVersion>
+<syntaxVersion component='openvpn' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/nat_cgnat.xml.in b/interface-definitions/nat_cgnat.xml.in
index fce5e655d..71f4d67b0 100644
--- a/interface-definitions/nat_cgnat.xml.in
+++ b/interface-definitions/nat_cgnat.xml.in
@@ -8,6 +8,12 @@
<priority>221</priority>
</properties>
<children>
+ <leafNode name="log-allocation">
+ <properties>
+ <help>Log IP address and port allocation</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<node name="pool">
<properties>
<help>External and internal pool parameters</help>
diff --git a/interface-definitions/system_option.xml.in b/interface-definitions/system_option.xml.in
index fe517d17d..e78a53552 100644
--- a/interface-definitions/system_option.xml.in
+++ b/interface-definitions/system_option.xml.in
@@ -49,6 +49,19 @@
<valueless/>
</properties>
</leafNode>
+ <node name="debug">
+ <properties>
+ <help>Dynamic debugging for kernel module</help>
+ </properties>
+ <children>
+ <leafNode name="wireguard">
+ <properties>
+ <help>Dynamic debugging for Wireguard module</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
<leafNode name="keyboard-layout">
@@ -183,6 +196,12 @@
</properties>
<defaultValue>12-hour</defaultValue>
</leafNode>
+ <leafNode name="disable-usb-autosuspend">
+ <properties>
+ <help>Disable autosuspend for all USB devices</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in
index 7849d6886..a2f040b2f 100644
--- a/interface-definitions/vpn_openconnect.xml.in
+++ b/interface-definitions/vpn_openconnect.xml.in
@@ -275,7 +275,7 @@
<help>SSL Certificate, SSL Key and CA</help>
</properties>
<children>
- #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/ca-certificate-multi.xml.i>
#include <include/pki/certificate-key.xml.i>
</children>
</node>
diff --git a/op-mode-definitions/generate-ipsec-profile.xml.in b/op-mode-definitions/generate-ipsec-profile.xml.in
index b7203d7d1..afa299da2 100644
--- a/op-mode-definitions/generate-ipsec-profile.xml.in
+++ b/op-mode-definitions/generate-ipsec-profile.xml.in
@@ -28,7 +28,7 @@
<script>${vyos_completion_dir}/list_local_ips.sh --both</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7"</command>
+ <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7"</command>
<children>
<tagNode name="name">
<properties>
@@ -37,7 +37,7 @@
<list>&lt;name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9"</command>
+ <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9"</command>
<children>
<tagNode name="profile">
<properties>
@@ -46,7 +46,7 @@
<list>&lt;name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9" --profile "${11}"</command>
+ <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9" --profile "${11}"</command>
</tagNode>
</children>
</tagNode>
@@ -57,7 +57,7 @@
<list>&lt;name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9"</command>
+ <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9"</command>
<children>
<tagNode name="name">
<properties>
@@ -66,7 +66,7 @@
<list>&lt;name&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9" --name "${11}"</command>
+ <command>sudo ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9" --name "${11}"</command>
</tagNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/lldp.xml.in b/op-mode-definitions/lldp.xml.in
index 985262a89..dc1331cc8 100644
--- a/op-mode-definitions/lldp.xml.in
+++ b/op-mode-definitions/lldp.xml.in
@@ -13,6 +13,12 @@
</properties>
<command>${vyos_op_scripts_dir}/lldp.py show_neighbors</command>
<children>
+ <node name="detail">
+ <properties>
+ <help>Show extended detail for LLDP neighbors</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/lldp.py show_neighbors --detail</command>
+ </node>
<tagNode name="interface">
<properties>
<help>Show LLDP for specified interface</help>
@@ -21,6 +27,17 @@
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/lldp.py show_neighbors --interface $5</command>
+ <children>
+ <node name="detail">
+ <properties>
+ <help>Show detailed LLDP for specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/lldp.py show_neighbors --interface $5 --detail</command>
+ </node>
+ </children>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 559952e25..a2d5d924a 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -359,6 +359,47 @@
</properties>
<command>journalctl --no-hostname --boot --follow --unit keepalived.service</command>
</leafNode>
+ <node name="wireless">
+ <properties>
+ <help>Monitor last lines of Wireless interface log</help>
+ </properties>
+ <children>
+ <node name="wpa-supplicant">
+ <properties>
+ <help>Monitor last lines of WPA supplicant</help>
+ </properties>
+ <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --follow --unit "wpa_supplicant@*.service"; else echo "No wireless interface configured!"; fi</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Monitor last lines of specific wireless interface supplicant</help>
+ <completionHelp>
+ <path>interfaces wireless</path>
+ </completionHelp>
+ </properties>
+ <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "station" ]]; then journalctl --no-hostname --boot --follow --unit "wpa_supplicant@$6.service"; else echo "Wireless interface $6 not configured as station!"; fi</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="hostapd">
+ <properties>
+ <help>Monitor last lines of host access point daemon</help>
+ </properties>
+ <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --follow --unit "hostapd@*.service"; else echo "No wireless interface configured!"; fi</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Monitor last lines of specific host access point interface</help>
+ <completionHelp>
+ <path>interfaces wireless</path>
+ </completionHelp>
+ </properties>
+ <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "access-point" ]]; then journalctl --no-hostname --boot --follow --unit "hostapd@$6.service"; else echo "Wireless interface $6 not configured as access-point!"; fi</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index f76b4f4e1..254ef08cc 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -490,6 +490,7 @@
<properties>
<help>Show PKI x509 certificates</help>
</properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action show</command>
<children>
<leafNode name="ca">
<properties>
@@ -570,7 +571,6 @@
</children>
</tagNode>
</children>
- <command>${vyos_op_scripts_dir}/pki.py --action show</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/restart-ssh.xml.in b/op-mode-definitions/restart-ssh.xml.in
index 6504cc18a..543cafc24 100644
--- a/op-mode-definitions/restart-ssh.xml.in
+++ b/op-mode-definitions/restart-ssh.xml.in
@@ -6,7 +6,7 @@
<properties>
<help>Restart SSH service</help>
</properties>
- <command>if cli-shell-api existsActive service ssh; then sudo systemctl restart ssh.service; else echo "Service SSH not configured"; fi</command>
+ <command>if cli-shell-api existsActive service ssh; then sudo systemctl restart "ssh@*.service"; else echo "Service SSH not configured"; fi</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index c3aa324ba..7ae3b890b 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -762,6 +762,47 @@
</properties>
<command>journalctl --no-hostname --boot --unit keepalived.service</command>
</leafNode>
+ <node name="wireless">
+ <properties>
+ <help>Show log for Wireless interface</help>
+ </properties>
+ <children>
+ <node name="wpa-supplicant">
+ <properties>
+ <help>Show log for WPA supplicant</help>
+ </properties>
+ <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --unit "wpa_supplicant@*.service"; else echo "No wireless interface configured!"; fi</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show log for specific wireless interface supplicant</help>
+ <completionHelp>
+ <path>interfaces wireless</path>
+ </completionHelp>
+ </properties>
+ <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "station" ]]; then journalctl --no-hostname --boot --unit "wpa_supplicant@$6.service"; else echo "Wireless interface $6 not configured as station!"; fi</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="hostapd">
+ <properties>
+ <help>Show log for host access point daemon</help>
+ </properties>
+ <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --unit "hostapd@*.service"; else echo "No wireless interface configured!"; fi</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show log for specific host access point daemon interface</help>
+ <completionHelp>
+ <path>interfaces wireless</path>
+ </completionHelp>
+ </properties>
+ <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "access-point" ]]; then journalctl --no-hostname --boot --unit "hostapd@$6.service"; else echo "Wireless interface $6 not configured as access-point!"; fi</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="webproxy">
<properties>
<help>Show log for Webproxy</help>
diff --git a/op-mode-definitions/traffic-dump.xml.in b/op-mode-definitions/traffic-dump.xml.in
index 76e3ddce5..e86e69736 100644
--- a/op-mode-definitions/traffic-dump.xml.in
+++ b/op-mode-definitions/traffic-dump.xml.in
@@ -8,7 +8,7 @@
</properties>
<children>
<tagNode name="interface">
- <command>sudo tcpdump -i $4</command>
+ <command>${vyos_op_scripts_dir}/tcpdump.py $4</command>
<properties>
<help>Monitor traffic dump from an interface</help>
<completionHelp>
@@ -16,54 +16,15 @@
</completionHelp>
</properties>
<children>
- <node name="verbose">
- <command>sudo tcpdump -vvv -ne -i $4</command>
+ <leafNode name="node.tag">
<properties>
- <help>Provide more detailed packets for each monitored traffic</help>
+ <help>Traffic capture options</help>
+ <completionHelp>
+ <script>${vyos_op_scripts_dir}/tcpdump.py --get-options-nested "${COMP_WORDS[@]}"</script>
+ </completionHelp>
</properties>
- <children>
- <tagNode name="filter">
- <command>sudo tcpdump -vvv -ne -i $4 "${@:6}"</command>
- <properties>
- <help>Monitor traffic matching filter conditions</help>
- </properties>
- </tagNode>
- <tagNode name="save">
- <command>sudo tcpdump -vvv -ne -i $4 -w $6</command>
- <properties>
- <help>Save traffic dump from an interface to a file</help>
- </properties>
- <children>
- <tagNode name="filter">
- <command>sudo tcpdump -vvv -ne -i $4 -w $6 "${@:8}"</command>
- <properties>
- <help>Save a dump of traffic matching filter conditions to a file</help>
- </properties>
- </tagNode>
- </children>
- </tagNode>
- </children>
- </node>
- <tagNode name="filter">
- <command>sudo tcpdump -n -i $4 "${@:6}"</command>
- <properties>
- <help>Monitor traffic matching filter conditions</help>
- </properties>
- </tagNode>
- <tagNode name="save">
- <command>sudo tcpdump -n -i $4 -w $6</command>
- <properties>
- <help>Save traffic dump from an interface to a file</help>
- </properties>
- <children>
- <tagNode name="filter">
- <command>sudo tcpdump -n -i $4 -w $6 "${@:8}"</command>
- <properties>
- <help>Save a dump of traffic matching filter conditions to a file</help>
- </properties>
- </tagNode>
- </children>
- </tagNode>
+ <command>${vyos_op_scripts_dir}/tcpdump.py "${@:4}"</command>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index 70b6ea203..d518737ca 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -81,9 +81,11 @@ def save_config(target, json_out=None):
if rc != 0:
logger.critical(f'save config failed: {out}')
-def unsaved_commits() -> bool:
+def unsaved_commits(allow_missing_config=False) -> bool:
if get_full_version_data()['boot_via'] == 'livecd':
return False
+ if allow_missing_config and not os.path.exists(config_file):
+ return True
tmp_save = '/tmp/config.running'
save_config(tmp_save)
ret = not cmp(tmp_save, config_file, shallow=False)
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 870d7cfda..5a353b110 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -631,7 +631,7 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
Return a dictionary with the necessary interface config keys.
"""
- from vyos.cpu import get_core_count
+ from vyos.utils.cpu import get_core_count
from vyos.template import is_ipv4
dict = config.get_config_dict(base, key_mangling=('-', '_'),
diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py
index 71ad5b4f0..5d6ca9be9 100644
--- a/python/vyos/configquery.py
+++ b/python/vyos/configquery.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-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
@@ -19,6 +19,8 @@ settings from op mode, and execution of arbitrary op mode commands.
'''
import os
+import json
+import subprocess
from vyos.utils.process import STDOUT
from vyos.utils.process import popen
@@ -27,6 +29,14 @@ from vyos.utils.boot import boot_configuration_complete
from vyos.config import Config
from vyos.configsource import ConfigSourceSession, ConfigSourceString
from vyos.defaults import directories
+from vyos.configtree import ConfigTree
+from vyos.utils.dict import embed_dict
+from vyos.utils.dict import get_sub_dict
+from vyos.utils.dict import mangle_dict_keys
+from vyos.utils.error import cli_shell_api_err
+from vyos.xml_ref import multi_to_list
+from vyos.xml_ref import is_tag
+from vyos.base import Warning
config_file = os.path.join(directories['config'], 'config.boot')
@@ -133,4 +143,50 @@ def query_context(config_query_class=CliShellApiConfigQuery,
run = op_run_class()
return query, run
-
+def verify_mangling(key_mangling):
+ if not (isinstance(key_mangling, tuple) and
+ len(key_mangling) == 2 and
+ isinstance(key_mangling[0], str) and
+ isinstance(key_mangling[1], str)):
+ raise ValueError("key_mangling must be a tuple of two strings")
+
+def op_mode_run(cmd):
+ """ low-level to avoid overhead """
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ out = p.stdout.read()
+ p.wait()
+ return p.returncode, out.decode()
+
+def op_mode_config_dict(path=None, key_mangling=None,
+ no_tag_node_value_mangle=False,
+ no_multi_convert=False, get_first_key=False):
+
+ if path is None:
+ path = []
+ command = ['/bin/cli-shell-api', '--show-active-only', 'showConfig']
+
+ rc, out = op_mode_run(command + path)
+ if rc == cli_shell_api_err.VYOS_EMPTY_CONFIG:
+ out = ''
+ if rc == cli_shell_api_err.VYOS_INVALID_PATH:
+ Warning(out)
+ return {}
+
+ ct = ConfigTree(out)
+ d = json.loads(ct.to_json())
+ # cli-shell-api output includes last path component if tag node
+ if is_tag(path):
+ config_dict = embed_dict(path[:-1], d)
+ else:
+ config_dict = embed_dict(path, d)
+
+ if not no_multi_convert:
+ config_dict = multi_to_list([], config_dict)
+
+ if key_mangling is not None:
+ verify_mangling(key_mangling)
+ config_dict = mangle_dict_keys(config_dict,
+ key_mangling[0], key_mangling[1],
+ no_tag_node_value_mangle=no_tag_node_value_mangle)
+
+ return get_sub_dict(config_dict, path, get_first_key=get_first_key)
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index beec6010b..ccf2ce8f2 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -1,5 +1,4 @@
-# configsession -- the write API for the VyOS running config
-# Copyright (C) 2019-2023 VyOS maintainers and contributors
+# Copyright (C) 2019-2024 VyOS maintainers and contributors
#
# 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;
@@ -12,11 +11,14 @@
# You should have received a copy of the GNU Lesser General Public License along with this library;
# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# configsession -- the write API for the VyOS running config
+
import os
import re
import sys
import subprocess
+from vyos.defaults import directories
from vyos.utils.process import is_systemd_service_running
from vyos.utils.dict import dict_to_paths
@@ -58,7 +60,7 @@ def inject_vyos_env(env):
env['VYOS_HEADLESS_CLIENT'] = 'vyos_http_api'
env['vyatta_bindir']= '/opt/vyatta/bin'
env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
- env['vyatta_configdir'] = '/opt/vyatta/config'
+ env['vyatta_configdir'] = directories['vyos_configdir']
env['vyatta_datadir'] = '/opt/vyatta/share'
env['vyatta_datarootdir'] = '/opt/vyatta/share'
env['vyatta_libdir'] = '/opt/vyatta/lib'
@@ -70,7 +72,7 @@ def inject_vyos_env(env):
env['vyos_bin_dir'] = '/usr/bin'
env['vyos_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates'
env['vyos_completion_dir'] = '/usr/libexec/vyos/completion'
- env['vyos_configdir'] = '/opt/vyatta/config'
+ 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'
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index e7cd69a8b..9ccd925ce 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-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
@@ -35,6 +35,7 @@ directories = {
'vyos_udev_dir' : '/run/udev/vyos',
'isc_dhclient_dir' : '/run/dhclient',
'dhcp6_client_dir' : '/run/dhcp6c',
+ 'vyos_configdir' : '/opt/vyatta/config'
}
config_status = '/tmp/vyos-config-status'
@@ -44,7 +45,7 @@ cfg_group = 'vyattacfg'
cfg_vintage = 'vyos'
-commit_lock = '/opt/vyatta/config/.lock'
+commit_lock = os.path.join(directories['vyos_configdir'], '.lock')
component_version_json = os.path.join(directories['data'], 'component-versions.json')
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index d45c9c272..80bb56fa2 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -16,6 +16,7 @@
import re
from json import loads
+from vyos.utils.network import interface_exists
from vyos.utils.process import popen
# These drivers do not support using ethtool to change the speed, duplex, or
@@ -64,6 +65,9 @@ class Ethtool:
def __init__(self, ifname):
# Get driver used for interface
+ if not interface_exists(ifname):
+ raise ValueError(f'Interface "{ifname}" does not exist!')
+
out, _ = popen(f'ethtool --driver {ifname}')
driver = re.search(r'driver:\s(\w+)', out)
if driver:
diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py
index bde1d9aec..383905814 100644
--- a/python/vyos/ifconfig/macsec.py
+++ b/python/vyos/ifconfig/macsec.py
@@ -66,7 +66,7 @@ class MACsecIf(Interface):
cmd = 'ip macsec add {ifname} rx port 1 address'.format(**self.config)
cmd += f' {peer_config["mac"]}'
self._cmd(cmd)
- # Add the rx-key to the address
+ # Add the encryption key to the address
cmd += f' sa 0 pn 1 on key 01 {peer_config["key"]}'
self._cmd(cmd)
diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py
index 12ef2d3b8..90620071b 100644
--- a/python/vyos/utils/__init__.py
+++ b/python/vyos/utils/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# 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
@@ -17,7 +17,9 @@ from vyos.utils import assertion
from vyos.utils import auth
from vyos.utils import boot
from vyos.utils import commit
+from vyos.utils import configfs
from vyos.utils import convert
+from vyos.utils import cpu
from vyos.utils import dict
from vyos.utils import file
from vyos.utils import io
diff --git a/python/vyos/utils/auth.py b/python/vyos/utils/auth.py
index a59858d72..a0b3e1cae 100644
--- a/python/vyos/utils/auth.py
+++ b/python/vyos/utils/auth.py
@@ -1,6 +1,6 @@
# authutils -- miscelanneous functions for handling passwords and publis keys
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2023-2024 VyOS maintainers and contributors
#
# 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;
@@ -11,13 +11,12 @@
# 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import re
from vyos.utils.process import cmd
-
def make_password_hash(password):
""" Makes a password hash for /etc/shadow using mkpasswd """
@@ -39,3 +38,14 @@ def split_ssh_public_key(key_string, defaultname=""):
raise ValueError("Bad key type \'{0}\', must be one of must be one of ssh-rsa, ssh-dss, ecdsa-sha2-nistp<256|384|521> or ssh-ed25519".format(key_type))
return({"type": key_type, "data": key_data, "name": key_name})
+
+def get_current_user() -> str:
+ import os
+ current_user = 'nobody'
+ # During CLI "owner" script execution we use SUDO_USER
+ if 'SUDO_USER' in os.environ:
+ current_user = os.environ['SUDO_USER']
+ # During op-mode or config-mode interactive CLI we use USER
+ elif 'USER' in os.environ:
+ current_user = os.environ['USER']
+ return current_user
diff --git a/python/vyos/utils/configfs.py b/python/vyos/utils/configfs.py
new file mode 100644
index 000000000..8617f0129
--- /dev/null
+++ b/python/vyos/utils/configfs.py
@@ -0,0 +1,37 @@
+# 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/>.
+
+import os
+
+def delete_cli_node(cli_path: list):
+ from shutil import rmtree
+ for config_dir in ['VYATTA_TEMP_CONFIG_DIR', 'VYATTA_CHANGES_ONLY_DIR']:
+ tmp = os.path.join(os.environ[config_dir], '/'.join(cli_path))
+ # delete CLI node
+ if os.path.exists(tmp):
+ rmtree(tmp)
+
+def add_cli_node(cli_path: list, value: str=None):
+ from vyos.utils.auth import get_current_user
+ from vyos.utils.file import write_file
+
+ current_user = get_current_user()
+ for config_dir in ['VYATTA_TEMP_CONFIG_DIR', 'VYATTA_CHANGES_ONLY_DIR']:
+ # store new value
+ tmp = os.path.join(os.environ[config_dir], '/'.join(cli_path))
+ write_file(f'{tmp}/node.val', value, user=current_user, group='vyattacfg', mode=0o664)
+ # mark CLI node as modified
+ if config_dir == 'VYATTA_CHANGES_ONLY_DIR':
+ write_file(f'{tmp}/.modified', '', user=current_user, group='vyattacfg', mode=0o664)
diff --git a/python/vyos/cpu.py b/python/vyos/utils/cpu.py
index cae5f5f4d..3bea5ac12 100644
--- a/python/vyos/cpu.py
+++ b/python/vyos/utils/cpu.py
@@ -28,7 +28,6 @@ but nothing is certain.
import re
-
def _read_cpuinfo():
with open('/proc/cpuinfo', 'r') as f:
lines = f.read().strip()
diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py
index d36b6fcfb..062ab9c81 100644
--- a/python/vyos/utils/dict.py
+++ b/python/vyos/utils/dict.py
@@ -307,6 +307,13 @@ def dict_to_paths(d: dict) -> list:
for r in func(d, []):
yield r
+def embed_dict(p: list[str], d: dict) -> dict:
+ path = p.copy()
+ ret = d
+ while path:
+ ret = {path.pop(): ret}
+ return ret
+
def check_mutually_exclusive_options(d, keys, required=False):
""" Checks if a dict has at most one or only one of
mutually exclusive keys.
diff --git a/python/vyos/utils/error.py b/python/vyos/utils/error.py
new file mode 100644
index 000000000..8d4709bff
--- /dev/null
+++ b/python/vyos/utils/error.py
@@ -0,0 +1,24 @@
+# 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/>.
+
+from enum import IntEnum
+
+class cli_shell_api_err(IntEnum):
+ """ vyatta-cfg/src/vyos-errors.h """
+ VYOS_SUCCESS = 0
+ VYOS_GENERAL_FAILURE = 1
+ VYOS_INVALID_PATH = 2
+ VYOS_EMPTY_CONFIG = 3
+ VYOS_CONFIG_PARSE_ERROR = 4
diff --git a/smoketest/config-tests/container-simple b/smoketest/config-tests/container-simple
index cc80ef4cf..5af365cf9 100644
--- a/smoketest/config-tests/container-simple
+++ b/smoketest/config-tests/container-simple
@@ -11,3 +11,4 @@ set container name c02 allow-host-networks
set container name c02 allow-host-pid
set container name c02 capability 'sys-time'
set container name c02 image 'busybox:stable'
+set container name c02 sysctl parameter kernel.msgmax value '8192' \ No newline at end of file
diff --git a/smoketest/config-tests/dialup-router-wireguard-ipv6 b/smoketest/config-tests/dialup-router-wireguard-ipv6
index c054b4650..814a62d55 100644
--- a/smoketest/config-tests/dialup-router-wireguard-ipv6
+++ b/smoketest/config-tests/dialup-router-wireguard-ipv6
@@ -192,10 +192,6 @@ set service snmp location 'CLOUD'
set system conntrack expect-table-size '2048'
set system conntrack hash-size '32768'
set system conntrack table-size '262144'
-set system conntrack timeout icmp '30'
-set system conntrack timeout other '600'
-set system conntrack timeout udp other '300'
-set system conntrack timeout udp stream '300'
set system domain-name 'vyos.net'
set system host-name 'r1'
set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
@@ -216,6 +212,10 @@ set firewall global-options receive-redirects 'disable'
set firewall global-options send-redirects 'enable'
set firewall global-options source-validation 'disable'
set firewall global-options syn-cookies 'enable'
+set firewall global-options timeout icmp '30'
+set firewall global-options timeout other '600'
+set firewall global-options timeout udp other '300'
+set firewall global-options timeout udp stream '300'
set firewall global-options twa-hazards-protection 'disable'
set firewall group address-group DMZ-RDP-SERVER address '172.16.33.40'
set firewall group address-group DMZ-RDP-SERVER description 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata'
diff --git a/smoketest/configs/container-simple b/smoketest/configs/container-simple
index 82983afb7..b98a440b5 100644
--- a/smoketest/configs/container-simple
+++ b/smoketest/configs/container-simple
@@ -10,6 +10,11 @@ container {
allow-host-pid
cap-add sys-time
image busybox:stable
+ sysctl {
+ parameter kernel.msgmax {
+ value "8192"
+ }
+ }
}
}
interfaces {
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py
index 212dc58ab..c6f6cb804 100644
--- a/smoketest/scripts/cli/base_accel_ppp_test.py
+++ b/smoketest/scripts/cli/base_accel_ppp_test.py
@@ -19,7 +19,7 @@ from configparser import ConfigParser
from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv4
-from vyos.cpu import get_core_count
+from vyos.utils.cpu import get_core_count
from vyos.utils.process import process_named_running
from vyos.utils.process import cmd
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index efaa74fe0..4bcc50453 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -74,6 +74,11 @@ class VyOSUnitTestSHIM:
print('del ' + ' '.join(config))
self._session.delete(config)
+ def cli_discard(self):
+ if self.debug:
+ print('DISCARD')
+ self._session.discard()
+
def cli_commit(self):
self._session.commit()
# during a commit there is a process opening commit_lock, and run() returns 0
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index 90f821c60..3dd97a175 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -80,6 +80,7 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
+ self.cli_set(base_path + ['name', cont_name, 'sysctl', 'parameter', 'kernel.msgmax', 'value', '4096'])
# commit changes
self.cli_commit()
@@ -91,6 +92,10 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
# Check for running process
self.assertEqual(process_named_running(PROCESS_NAME), pid)
+ # verify
+ tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax')
+ self.assertEqual(tmp, 'kernel.msgmax = 4096')
+
def test_cpu_limit(self):
cont_name = 'c2'
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index a4e6840ca..d73895b7f 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -225,11 +225,11 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
self.cli_delete(self._base_path + [interface, 'security', 'mka'])
- # check validate() - tx-key required
+ # check validate() - key required
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- # check validate() - tx-key length must match cipher
+ # check validate() - key length must match cipher
self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
@@ -239,7 +239,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- # check validate() - enabled peer must have both rx-key and MAC defined
+ # check validate() - enabled peer must have both key and MAC defined
self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
@@ -252,7 +252,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac])
- # check validate() - peer rx-key length must match cipher
+ # check validate() - peer key length must match cipher
self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher2])
self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2])
with self.assertRaises(ConfigSessionError):
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index e1e9a4ec7..9ca661e87 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -164,6 +164,12 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['shared-secret-key', 'ovpn_test'])
+ # check validate() - cannot specify "encryption cipher" in client mode
+ self.cli_set(path + ['encryption', 'cipher', 'aes192gcm'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(path + ['encryption', 'cipher'])
+
self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
@@ -191,7 +197,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
auth_hash = 'sha1'
self.cli_set(path + ['device-type', 'tun'])
- self.cli_set(path + ['encryption', 'cipher', 'aes256'])
+ self.cli_set(path + ['encryption', 'ncp-ciphers', 'aes256'])
self.cli_set(path + ['hash', auth_hash])
self.cli_set(path + ['mode', 'client'])
self.cli_set(path + ['persistent-tunnel'])
@@ -221,7 +227,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'remote {remote_host}', config)
self.assertIn(f'persist-tun', config)
self.assertIn(f'auth {auth_hash}', config)
- self.assertIn(f'cipher AES-256-CBC', config)
+ self.assertIn(f'data-ciphers AES-256-CBC', config)
# TLS options
self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config)
@@ -328,6 +334,12 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['tls', 'dh-params'])
+ # check validate() - cannot specify "encryption cipher" in server mode
+ self.cli_set(path + ['encryption', 'cipher', 'aes256'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(path + ['encryption', 'cipher'])
+
# Now test the other path with tls role passive
self.cli_set(path + ['tls', 'role', 'passive'])
# check validate() - cannot specify "tcp-active" when "tls role" is "passive"
@@ -359,7 +371,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
port = str(2000 + ii)
self.cli_set(path + ['device-type', 'tun'])
- self.cli_set(path + ['encryption', 'cipher', 'aes192'])
+ self.cli_set(path + ['encryption', 'ncp-ciphers', 'aes192'])
self.cli_set(path + ['hash', auth_hash])
self.cli_set(path + ['mode', 'server'])
self.cli_set(path + ['local-port', port])
@@ -404,7 +416,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'persist-key', config)
self.assertIn(f'proto udp', config) # default protocol
self.assertIn(f'auth {auth_hash}', config)
- self.assertIn(f'cipher AES-192-CBC', config)
+ self.assertIn(f'data-ciphers AES-192-CBC', config)
self.assertIn(f'topology subnet', config)
self.assertIn(f'lport {port}', config)
self.assertIn(f'push "redirect-gateway def1"', config)
diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py
index 3f249660d..28abba012 100755
--- a/smoketest/scripts/cli/test_system_login.py
+++ b/smoketest/scripts/cli/test_system_login.py
@@ -24,6 +24,7 @@ from subprocess import Popen, PIPE
from pwd import getpwall
from vyos.configsession import ConfigSessionError
+from vyos.utils.auth import get_current_user
from vyos.utils.process import cmd
from vyos.utils.file import read_file
from vyos.template import inc_ip
@@ -334,5 +335,14 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'secret={tacacs_secret}', nss_tacacs_conf)
self.assertIn(f'server={server}', nss_tacacs_conf)
+ def test_delete_current_user(self):
+ current_user = get_current_user()
+
+ # We are not allowed to delete the current user
+ self.cli_delete(base_path + ['user', current_user])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_discard()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/completion/list_container_sysctl_parameters.sh b/src/completion/list_container_sysctl_parameters.sh
new file mode 100755
index 000000000..cf8d006e5
--- /dev/null
+++ b/src/completion/list_container_sysctl_parameters.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# 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/>.
+
+declare -a vals
+eval "vals=($(/sbin/sysctl -N -a|grep -E '^(fs.mqueue|net)\.|^(kernel.msgmax|kernel.msgmnb|kernel.msgmni|kernel.sem|kernel.shmall|kernel.shmmax|kernel.shmmni|kernel.shm_rmid_forced)$'))"
+echo ${vals[@]}
+exit 0
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 3efeb9b40..ded370a7a 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -29,7 +29,7 @@ from vyos.configdict import node_changed
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
-from vyos.cpu import get_core_count
+from vyos.utils.cpu import get_core_count
from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import cmd
@@ -43,6 +43,7 @@ from vyos.template import render
from vyos.xml_ref import default_value
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
config_containers = '/etc/containers/containers.conf'
@@ -50,16 +51,19 @@ config_registry = '/etc/containers/registries.conf'
config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
+
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
return cmd(command)
+
def network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)
+
# Common functions
def get_config(config=None):
if config:
@@ -86,21 +90,22 @@ def get_config(config=None):
# registry is a tagNode with default values - merge the list from
# default_values['registry'] into the tagNode variables
if 'registry' not in container:
- container.update({'registry' : {}})
+ container.update({'registry': {}})
default_values = default_value(base + ['registry'])
for registry in default_values:
- tmp = {registry : {}}
+ tmp = {registry: {}}
container['registry'] = dict_merge(tmp, container['registry'])
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
- if tmp: container.update({'network_remove' : tmp})
+ if tmp: container.update({'network_remove': tmp})
tmp = node_changed(conf, base + ['name'])
- if tmp: container.update({'container_remove' : tmp})
+ if tmp: container.update({'container_remove': tmp})
return container
+
def verify(container):
# bail out early - looks like removal from running config
if not container:
@@ -125,8 +130,8 @@ def verify(container):
# of image upgrade and deletion.
image = container_config['image']
if run(f'podman image exists {image}') != 0:
- Warning(f'Image "{image}" used in container "{name}" does not exist '\
- f'locally. Please use "add container image {image}" to add it '\
+ Warning(f'Image "{image}" used in container "{name}" does not exist ' \
+ f'locally. Please use "add container image {image}" to add it ' \
f'to the system! Container "{name}" will not be started!')
if 'cpu_quota' in container_config:
@@ -167,11 +172,11 @@ def verify(container):
# We can not use the first IP address of a network prefix as this is used by podman
if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
'reserved for the container engine!')
if cnt_ipv4 > 1 or cnt_ipv6 > 1:
- raise ConfigError(f'Only one IP address per address family can be used for '\
+ raise ConfigError(f'Only one IP address per address family can be used for ' \
f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
if 'device' in container_config:
@@ -186,6 +191,13 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for var, cfg in container_config['sysctl']['parameter'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'sysctl parameter {var} has no value assigned!')
+ if var.startswith('net.') and 'allow_host_networks' in container_config:
+ raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!')
+
if 'environment' in container_config:
for var, cfg in container_config['environment'].items():
if 'value' not in cfg:
@@ -219,7 +231,8 @@ def verify(container):
# Can not set both allow-host-networks and network at the same time
if {'allow_host_networks', 'network'} <= set(container_config):
- raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
+ raise ConfigError(
+ f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
# gid cannot be set without uid
if 'gid' in container_config and 'uid' not in container_config:
@@ -235,8 +248,10 @@ def verify(container):
raise ConfigError(f'prefix for network "{network}" must be defined!')
for prefix in network_config['prefix']:
- if is_ipv4(prefix): v4_prefix += 1
- elif is_ipv6(prefix): v6_prefix += 1
+ if is_ipv4(prefix):
+ v4_prefix += 1
+ elif is_ipv6(prefix):
+ v6_prefix += 1
if v4_prefix > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
@@ -262,6 +277,7 @@ def verify(container):
return None
+
def generate_run_arguments(name, container_config):
image = container_config['image']
cpu_quota = container_config['cpu_quota']
@@ -269,6 +285,12 @@ def generate_run_arguments(name, container_config):
shared_memory = container_config['shared_memory']
restart = container_config['restart']
+ # Add sysctl options
+ sysctl_opt = ''
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for k, v in container_config['sysctl']['parameter'].items():
+ sysctl_opt += f" --sysctl {k}={v['value']}"
+
# Add capability options. Should be in uppercase
capabilities = ''
if 'capability' in container_config:
@@ -341,7 +363,7 @@ def generate_run_arguments(name, container_config):
if 'allow_host_pid' in container_config:
host_pid = '--pid host'
- container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} ' \
+ container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}'
@@ -375,6 +397,7 @@ def generate_run_arguments(name, container_config):
return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
+
def generate(container):
# bail out early - looks like removal from running config
if not container:
@@ -387,7 +410,7 @@ def generate(container):
for network, network_config in container['network'].items():
tmp = {
'name': network,
- 'id' : sha256(f'{network}'.encode()).hexdigest(),
+ 'id': sha256(f'{network}'.encode()).hexdigest(),
'driver': 'bridge',
'network_interface': f'pod-{network}',
'subnets': [],
@@ -399,7 +422,7 @@ def generate(container):
}
}
for prefix in network_config['prefix']:
- net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
+ net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
if is_ipv6(prefix):
@@ -418,11 +441,12 @@ def generate(container):
file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
run_args = generate_run_arguments(name, container_config)
- render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args,},
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, },
formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
return None
+
def apply(container):
# Delete old containers if needed. We can't delete running container
# Option "--force" allows to delete containers with any status
@@ -485,6 +509,7 @@ def apply(container):
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/interfaces_macsec.py b/src/conf_mode/interfaces_macsec.py
index eb0ca9a8b..3ede4377a 100755
--- a/src/conf_mode/interfaces_macsec.py
+++ b/src/conf_mode/interfaces_macsec.py
@@ -103,9 +103,9 @@ def verify(macsec):
# Logic to check static configuration
if dict_search('security.static', macsec) != None:
- # tx-key must be defined
+ # key must be defined
if dict_search('security.static.key', macsec) == None:
- raise ConfigError('Static MACsec tx-key must be defined.')
+ raise ConfigError('Static MACsec key must be defined.')
tx_len = len(dict_search('security.static.key', macsec))
@@ -119,12 +119,12 @@ def verify(macsec):
if 'peer' not in macsec['security']['static']:
raise ConfigError('Must have at least one peer defined for static MACsec')
- # For every enabled peer, make sure a MAC and rx-key is defined
+ # For every enabled peer, make sure a MAC and key is defined
for peer, peer_config in macsec['security']['static']['peer'].items():
if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config):
- raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.')
+ raise ConfigError('Every enabled MACsec static peer must have a MAC address and key defined!')
- # check rx-key length against cipher suite
+ # check key length against cipher suite
rx_len = len(peer_config['key'])
if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN:
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index 627cc90ba..017010a61 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -515,6 +515,10 @@ def verify(openvpn):
print('Warning: using dh-params and EC keys simultaneously will ' \
'lead to DH ciphers being used instead of ECDH')
+ if dict_search('encryption.cipher', openvpn):
+ raise ConfigError('"encryption cipher" option is deprecated for TLS mode. '
+ 'Use "encryption ncp-ciphers" instead')
+
if dict_search('encryption.cipher', openvpn) == 'none':
print('Warning: "encryption none" was specified!')
print('No encryption will be performed and data is transmitted in ' \
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index d429f6e21..cb336a35c 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -16,9 +16,11 @@
import ipaddress
import jmespath
+import logging
import os
from sys import exit
+from logging.handlers import SysLogHandler
from vyos.config import Config
from vyos.template import render
@@ -32,6 +34,18 @@ airbag.enable()
nftables_cgnat_config = '/run/nftables-cgnat.nft'
+# Logging
+logger = logging.getLogger('cgnat')
+logger.setLevel(logging.DEBUG)
+
+syslog_handler = SysLogHandler(address="/dev/log")
+syslog_handler.setLevel(logging.INFO)
+
+formatter = logging.Formatter('%(name)s: %(message)s')
+syslog_handler.setFormatter(formatter)
+
+logger.addHandler(syslog_handler)
+
class IPOperations:
def __init__(self, ip_prefix: str):
@@ -356,6 +370,22 @@ def apply(config):
return None
cmd(f'nft --file {nftables_cgnat_config}')
+ # Logging allocations
+ if 'log_allocation' in config:
+ allocations = config['proto_map_elements']
+ allocations = allocations.split(',')
+ for allocation in allocations:
+ try:
+ # Split based on the delimiters used in the nft data format
+ internal_host, rest = allocation.split(' : ')
+ external_host, port_range = rest.split(' . ')
+ # Log the parsed data
+ logger.info(
+ f"Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}")
+ except ValueError as e:
+ # Log error message
+ logger.error(f"Error processing line '{allocation}': {e}")
+
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index f37cac524..4a0e86f32 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -232,7 +232,7 @@ def get_config(config=None):
path = search['path']
path_str = ' '.join(path + found_path)
- print(f'PKI: Updating config: {path_str} {item_name}')
+ #print(f'PKI: Updating config: {path_str} {item_name}')
if path[0] == 'interfaces':
ifname = found_path[0]
diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py
index 6565ffd60..6f025cc23 100755
--- a/src/conf_mode/service_snmp.py
+++ b/src/conf_mode/service_snmp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2023 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -26,10 +26,12 @@ from vyos.snmpv3_hashgen import plaintext_to_md5
from vyos.snmpv3_hashgen import plaintext_to_sha1
from vyos.snmpv3_hashgen import random
from vyos.template import render
-from vyos.utils.process import call
-from vyos.utils.permission import chmod_755
+from vyos.utils.configfs import delete_cli_node
+from vyos.utils.configfs import add_cli_node
from vyos.utils.dict import dict_search
from vyos.utils.network import is_addr_assigned
+from vyos.utils.process import call
+from vyos.utils.permission import chmod_755
from vyos.version import get_version_data
from vyos import ConfigError
from vyos import airbag
@@ -192,12 +194,8 @@ def generate(snmp):
return None
if 'v3' in snmp:
- # net-snmp is now regenerating the configuration file in the background
- # thus we need to re-open and re-read the file as the content changed.
- # After that we can no read the encrypted password from the config and
- # replace the CLI plaintext password with its encrypted version.
- os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos'
-
+ # SNMPv3 uses a hashed password. If CLI defines a plaintext password,
+ # we will hash it in the background and replace the CLI node!
if 'user' in snmp['v3']:
for user, user_config in snmp['v3']['user'].items():
if dict_search('auth.type', user_config) == 'sha':
@@ -212,8 +210,9 @@ def generate(snmp):
snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp
del snmp['v3']['user'][user]['auth']['plaintext_password']
- call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null')
- call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null')
+ cli_base = ['service', 'snmp', 'v3', 'user', user, 'auth']
+ delete_cli_node(cli_base + ['plaintext-password'])
+ add_cli_node(cli_base + ['encrypted-password'], value=tmp)
if dict_search('privacy.plaintext_password', user_config) is not None:
tmp = hash(dict_search('privacy.plaintext_password', user_config),
@@ -222,8 +221,9 @@ def generate(snmp):
snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp
del snmp['v3']['user'][user]['privacy']['plaintext_password']
- call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null')
- call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null')
+ cli_base = ['service', 'snmp', 'v3', 'user', user, 'privacy']
+ delete_cli_node(cli_base + ['plaintext-password'])
+ add_cli_node(cli_base + ['encrypted-password'], value=tmp)
# Write client config file
render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp)
@@ -246,7 +246,7 @@ def apply(snmp):
return None
# start SNMP daemon
- call(f'systemctl restart {systemd_service}')
+ call(f'systemctl reload-or-restart {systemd_service}')
# Enable AgentX in FRR
# This should be done for each daemon individually because common command
diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py
index 20121f170..439fa645b 100755
--- a/src/conf_mode/system_login.py
+++ b/src/conf_mode/system_login.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2023 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -26,14 +26,15 @@ from time import sleep
from vyos.config import Config
from vyos.configverify import verify_vrf
-from vyos.defaults import directories
from vyos.template import render
from vyos.template import is_ipv4
+from vyos.utils.auth import get_current_user
+from vyos.utils.configfs import delete_cli_node
+from vyos.utils.configfs import add_cli_node
from vyos.utils.dict import dict_search
from vyos.utils.file import chown
from vyos.utils.process import cmd
from vyos.utils.process import call
-from vyos.utils.process import rc_cmd
from vyos.utils.process import run
from vyos.utils.process import DEVNULL
from vyos import ConfigError
@@ -125,10 +126,9 @@ def verify(login):
# This check is required as the script is also executed from vyos-router
# init script and there is no SUDO_USER environment variable available
# during system boot.
- if 'SUDO_USER' in os.environ:
- cur_user = os.environ['SUDO_USER']
- if cur_user in login['rm_users']:
- raise ConfigError(f'Attempting to delete current user: {cur_user}')
+ tmp = get_current_user()
+ if tmp in login['rm_users']:
+ raise ConfigError(f'Attempting to delete current user: {tmp}')
if 'user' in login:
system_users = getpwall()
@@ -221,35 +221,13 @@ def generate(login):
login['user'][user]['authentication']['encrypted_password'] = encrypted_password
del login['user'][user]['authentication']['plaintext_password']
- # remove old plaintext password and set new encrypted password
- env = os.environ.copy()
- env['vyos_libexec_dir'] = directories['base']
-
# Set default commands for re-adding user with encrypted password
- del_user_plain = f"system login user {user} authentication plaintext-password"
- add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'"
-
- lvl = env['VYATTA_EDIT_LEVEL']
- # We're in config edit level, for example "edit system login"
- # Change default commands for re-adding user with encrypted password
- if lvl != '/':
- # Replace '/system/login' to 'system login'
- lvl = lvl.strip('/').split('/')
- # Convert command str to list
- del_user_plain = del_user_plain.split()
- # New command exclude level, for example "edit system login"
- del_user_plain = del_user_plain[len(lvl):]
- # Convert string to list
- del_user_plain = " ".join(del_user_plain)
-
- add_user_encrypt = add_user_encrypt.split()
- add_user_encrypt = add_user_encrypt[len(lvl):]
- add_user_encrypt = " ".join(add_user_encrypt)
-
- ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env)
- if ret: raise ConfigError(out)
- ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env)
- if ret: raise ConfigError(out)
+ del_user_plain = ['system', 'login', 'user', user, 'authentication', 'plaintext-password']
+ add_user_encrypt = ['system', 'login', 'user', user, 'authentication', 'encrypted-password']
+
+ delete_cli_node(del_user_plain)
+ add_cli_node(add_user_encrypt, value=encrypted_password)
+
else:
try:
if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config):
@@ -283,8 +261,6 @@ def generate(login):
if os.path.isfile(tacacs_nss_config_file):
os.unlink(tacacs_nss_config_file)
-
-
# NSS must always be present on the system
render(nss_config_file, 'login/nsswitch.conf.j2', login,
permission=0o644, user='root', group='root')
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index a2e5db575..ad4c0deae 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -24,6 +24,9 @@ from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
from vyos.system import grub_util
from vyos.template import render
+from vyos.utils.dict import dict_search
+from vyos.utils.file import write_file
+from vyos.utils.kernel import check_kmod
from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_addr_assigned
@@ -35,6 +38,8 @@ airbag.enable()
curlrc_config = r'/etc/curlrc'
ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
+usb_autosuspend = r'/etc/udev/rules.d/40-usb-autosuspend.rules'
+kernel_dynamic_debug = r'/sys/kernel/debug/dynamic_debug/control'
time_format_to_locale = {
'12-hour': 'en_US.UTF-8',
'24-hour': 'en_GB.UTF-8'
@@ -85,6 +90,7 @@ def verify(options):
def generate(options):
render(curlrc_config, 'system/curlrc.j2', options)
render(ssh_config, 'system/ssh_config.j2', options)
+ render(usb_autosuspend, 'system/40_usb_autosuspend.j2', options)
cmdline_options = []
if 'kernel' in options:
@@ -155,6 +161,18 @@ def apply(options):
time_format = time_format_to_locale.get(options['time_format'])
cmd(f'localectl set-locale LC_TIME={time_format}')
+ cmd('udevadm control --reload-rules')
+
+ # Enable/disable dynamic debugging for kernel modules
+ modules = ['wireguard']
+ modules_enabled = dict_search('kernel.debug', options) or []
+ for module in modules:
+ if module in modules_enabled:
+ check_kmod(module)
+ write_file(kernel_dynamic_debug, f'module {module} +p')
+ else:
+ write_file(kernel_dynamic_debug, f'module {module} -p')
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 8159fedea..42785134f 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -21,14 +21,17 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configverify import verify_pki_certificate
from vyos.configverify import verify_pki_ca_certificate
-from vyos.pki import wrap_certificate
+from vyos.pki import find_chain
+from vyos.pki import encode_certificate
+from vyos.pki import load_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
-from vyos.utils.process import call
+from vyos.utils.dict import dict_search
+from vyos.utils.file import write_file
from vyos.utils.network import check_port_availability
-from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_listen_port_bind_service
-from vyos.utils.dict import dict_search
+from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from passlib.hash import sha512_crypt
from time import sleep
@@ -142,7 +145,8 @@ def verify(ocserv):
verify_pki_certificate(ocserv, ocserv['ssl']['certificate'])
if 'ca_certificate' in ocserv['ssl']:
- verify_pki_ca_certificate(ocserv, ocserv['ssl']['ca_certificate'])
+ for ca_cert in ocserv['ssl']['ca_certificate']:
+ verify_pki_ca_certificate(ocserv, ca_cert)
# Check network settings
if "network_settings" in ocserv:
@@ -219,25 +223,36 @@ def generate(ocserv):
if "ssl" in ocserv:
cert_file_path = os.path.join(cfg_dir, 'cert.pem')
cert_key_path = os.path.join(cfg_dir, 'cert.key')
- ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem')
+
if 'certificate' in ocserv['ssl']:
cert_name = ocserv['ssl']['certificate']
pki_cert = ocserv['pki']['certificate'][cert_name]
- with open(cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_cert['certificate']))
+ loaded_pki_cert = load_certificate(pki_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in ocserv['pki']['ca'].values()} if 'ca' in ocserv['pki'] else {}
+
+ cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
+
+ write_file(cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain))
if 'private' in pki_cert and 'key' in pki_cert['private']:
- with open(cert_key_path, 'w') as f:
- f.write(wrap_private_key(pki_cert['private']['key']))
+ write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
if 'ca_certificate' in ocserv['ssl']:
- ca_name = ocserv['ssl']['ca_certificate']
- pki_ca_cert = ocserv['pki']['ca'][ca_name]
+ ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem')
+ ca_chains = []
+
+ for ca_name in ocserv['ssl']['ca_certificate']:
+ pki_ca_cert = ocserv['pki']['ca'][ca_name]
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ ca_chains.append(
+ '\n'.join(encode_certificate(c) for c in ca_full_chain))
- with open(ca_cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_ca_cert['certificate']))
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
# Render config
render(ocserv_conf, 'ocserv/ocserv_config.j2', ocserv)
diff --git a/src/migration-scripts/firewall/15-to-16 b/src/migration-scripts/firewall/15-to-16
index 7c8d38fe6..28df1256e 100755
--- a/src/migration-scripts/firewall/15-to-16
+++ b/src/migration-scripts/firewall/15-to-16
@@ -42,8 +42,9 @@ if not config.exists(conntrack_base):
for protocol in ['icmp', 'tcp', 'udp', 'other']:
if config.exists(conntrack_base + [protocol]):
- if not config.exists(firewall_base):
+ if not config.exists(firewall_base + ['timeout']):
config.set(firewall_base + ['timeout'])
+
config.copy(conntrack_base + [protocol], firewall_base + ['timeout', protocol])
config.delete(conntrack_base + [protocol])
@@ -52,4 +53,4 @@ try:
f.write(config.to_string())
except OSError as e:
print("Failed to save the modified config: {}".format(e))
- exit(1) \ No newline at end of file
+ exit(1)
diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21
index 14ad0fe4d..05a0c7237 100755
--- a/src/migration-scripts/interfaces/20-to-21
+++ b/src/migration-scripts/interfaces/20-to-21
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,6 +22,7 @@ from sys import argv
from vyos.ethtool import Ethtool
from vyos.configtree import ConfigTree
+from vyos.utils.network import interface_exists
if len(argv) < 2:
print("Must specify file name!")
@@ -38,6 +39,10 @@ if not config.exists(base):
exit(0)
for ifname in config.list_nodes(base):
+ # Bail out early if interface vanished from system
+ if not interface_exists(ifname):
+ continue
+
eth = Ethtool(ifname)
# If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is
diff --git a/src/migration-scripts/openvpn/1-to-2 b/src/migration-scripts/openvpn/1-to-2
new file mode 100755
index 000000000..1f82a2128
--- /dev/null
+++ b/src/migration-scripts/openvpn/1-to-2
@@ -0,0 +1,74 @@
+#!/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/>.
+#
+# Removes --cipher option (deprecated) from OpenVPN configs
+# and moves it to --data-ciphers for server and client modes
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['interfaces', 'openvpn']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ ovpn_intfs = config.list_nodes(['interfaces', 'openvpn'])
+ for i in ovpn_intfs:
+ # Remove 'encryption cipher' and add this value to 'encryption ncp-ciphers'
+ # for server and client mode.
+ # Site-to-site mode still can use --cipher option
+ cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher']
+ ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers']
+ if config.exists(cipher_path):
+ if config.exists(['interfaces', 'openvpn', i, 'shared-secret-key']):
+ continue
+ cipher = config.return_value(cipher_path)
+ config.delete(cipher_path)
+ if cipher == 'none':
+ if not config.exists(ncp_cipher_path):
+ config.delete(['interfaces', 'openvpn', i, 'encryption'])
+ continue
+
+ ncp_ciphers = []
+ if config.exists(ncp_cipher_path):
+ ncp_ciphers = config.return_values(ncp_cipher_path)
+ config.delete(ncp_cipher_path)
+
+ # need to add the deleted cipher at the first place in the list
+ if cipher in ncp_ciphers:
+ ncp_ciphers.remove(cipher)
+ ncp_ciphers.insert(0, cipher)
+
+ for c in ncp_ciphers:
+ config.set(ncp_cipher_path, value=c, replace=False)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 6c86ff492..f3b09b452 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -19,6 +19,8 @@ import sys
import syslog
import xmltodict
+from tabulate import tabulate
+
import vyos.opmode
from vyos.configquery import CliShellApiConfigQuery
@@ -27,7 +29,6 @@ from vyos.utils.commit import commit_in_progress
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
-from vyos.template import render_to_string
conntrackd_bin = '/usr/sbin/conntrackd'
conntrackd_config = '/run/conntrackd/conntrackd.conf'
@@ -59,6 +60,26 @@ def flush_cache(direction):
if tmp > 0:
raise vyos.opmode.Error('Failed to clear {direction} cache')
+def get_formatted_output(data):
+ data_entries = []
+ for parsed in data:
+ for meta in parsed.get('flow', {}).get('meta', []):
+ direction = meta['@direction']
+ if direction == 'original':
+ src = meta['layer3']['src']
+ dst = meta['layer3']['dst']
+ sport = meta['layer4'].get('sport')
+ dport = meta['layer4'].get('dport')
+ protocol = meta['layer4'].get('@protoname')
+ orig_src = f'{src}:{sport}' if sport else src
+ orig_dst = f'{dst}:{dport}' if dport else dst
+
+ data_entries.append([orig_src, orig_dst, protocol])
+
+ headers = ["Source", "Destination", "Protocol"]
+ output = tabulate(data_entries, headers, tablefmt="simple")
+ return output
+
def from_xml(raw, xml):
out = []
for line in xml.splitlines():
@@ -70,7 +91,7 @@ def from_xml(raw, xml):
if raw:
return out
else:
- return render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out})
+ return get_formatted_output(out)
def restart():
is_configured()
diff --git a/src/op_mode/cpu.py b/src/op_mode/cpu.py
index d53663c17..1a0f7392f 100755
--- a/src/op_mode/cpu.py
+++ b/src/op_mode/cpu.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2016-2022 VyOS maintainers and contributors
+# Copyright (C) 2016-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -16,8 +16,9 @@
import sys
-import vyos.cpu
import vyos.opmode
+from vyos.utils.cpu import get_cpus
+from vyos.utils.cpu import get_core_count
from jinja2 import Template
@@ -37,15 +38,15 @@ CPU model(s): {{models | join(", ")}}
""")
def _get_raw_data():
- return vyos.cpu.get_cpus()
+ return get_cpus()
def _format_cpus(cpu_data):
env = {'cpus': cpu_data}
return cpu_template.render(env).strip()
def _get_summary_data():
- count = vyos.cpu.get_core_count()
- cpu_data = vyos.cpu.get_cpus()
+ count = get_core_count()
+ cpu_data = get_cpus()
models = [c['model name'] for c in cpu_data]
env = {'count': count, "models": models}
@@ -79,4 +80,3 @@ if __name__ == '__main__':
except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
-
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index 169a15840..b193d8109 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -21,6 +21,7 @@ from socket import getfqdn
from cryptography.x509.oid import NameOID
from vyos.configquery import ConfigTreeQuery
+from vyos.config import config_dict_mangle_acme
from vyos.pki import CERT_BEGIN
from vyos.pki import CERT_END
from vyos.pki import find_chain
@@ -123,6 +124,8 @@ pki_base = ['pki']
conf = ConfigTreeQuery()
if not conf.exists(config_base):
exit('IPsec remote-access is not configured!')
+if not conf.exists(pki_base):
+ exit('PKI is not configured!')
profile_name = 'VyOS IKEv2 Profile'
if args.profile:
@@ -147,30 +150,36 @@ tmp = getfqdn().split('.')
tmp = reversed(tmp)
data['rfqdn'] = '.'.join(tmp)
-pki = conf.get_config_dict(pki_base, get_first_key=True)
-cert_name = data['authentication']['x509']['certificate']
-
-cert_data = load_certificate(pki['certificate'][cert_name]['certificate'])
-data['cert_common_name'] = cert_data.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
-data['ca_common_name'] = cert_data.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
-data['ca_certificates'] = []
-
-loaded_ca_certs = {load_certificate(c['certificate'])
- for c in pki['ca'].values()} if 'ca' in pki else {}
-
-for ca_name in data['authentication']['x509']['ca_certificate']:
- loaded_ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
- ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
- for ca in ca_full_chain:
- tmp = {
- 'ca_name' : ca.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
- 'ca_chain' : encode_certificate(ca).replace(CERT_BEGIN, '').replace(CERT_END, '').replace('\n', ''),
- }
- data['ca_certificates'].append(tmp)
-
-# Remove duplicate list entries for CA certificates, as they are added by their common name
-# https://stackoverflow.com/a/9427216
-data['ca_certificates'] = [dict(t) for t in {tuple(d.items()) for d in data['ca_certificates']}]
+if args.os == 'ios':
+ pki = conf.get_config_dict(pki_base, get_first_key=True)
+ if 'certificate' in pki:
+ for certificate in pki['certificate']:
+ pki['certificate'][certificate] = config_dict_mangle_acme(certificate, pki['certificate'][certificate])
+
+ cert_name = data['authentication']['x509']['certificate']
+
+
+ cert_data = load_certificate(pki['certificate'][cert_name]['certificate'])
+ data['cert_common_name'] = cert_data.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+ data['ca_common_name'] = cert_data.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+ data['ca_certificates'] = []
+
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in pki['ca'].values()} if 'ca' in pki else {}
+
+ for ca_name in data['authentication']['x509']['ca_certificate']:
+ loaded_ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ for ca in ca_full_chain:
+ tmp = {
+ 'ca_name' : ca.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
+ 'ca_chain' : encode_certificate(ca).replace(CERT_BEGIN, '').replace(CERT_END, '').replace('\n', ''),
+ }
+ data['ca_certificates'].append(tmp)
+
+ # Remove duplicate list entries for CA certificates, as they are added by their common name
+ # https://stackoverflow.com/a/9427216
+ data['ca_certificates'] = [dict(t) for t in {tuple(d.items()) for d in data['ca_certificates']}]
esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'],
key_mangling=('-', '_'), get_first_key=True)
diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py
index 58cfce443..fac622b81 100755
--- a/src/op_mode/lldp.py
+++ b/src/op_mode/lldp.py
@@ -120,7 +120,12 @@ def _get_formatted_output(raw_data):
tmp.append('')
# Remote interface
- interface = jmespath.search('port.descr', values)
+ interface = None
+ if jmespath.search('port.id.type', values) == 'ifname':
+ # Remote peer has explicitly returned the interface name as the PortID
+ interface = jmespath.search('port.id.value', values)
+ if not interface:
+ interface = jmespath.search('port.descr', values)
if not interface:
interface = jmespath.search('port.id.value', values)
if not interface:
@@ -136,11 +141,17 @@ def _get_formatted_output(raw_data):
@_verify
def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Optional[bool]):
- lldp_data = _get_raw_data(interface=interface, detail=detail)
- if raw:
- return lldp_data
- else:
- return _get_formatted_output(lldp_data)
+ if raw or not detail:
+ lldp_data = _get_raw_data(interface=interface, detail=detail)
+ if raw:
+ return lldp_data
+ else:
+ return _get_formatted_output(lldp_data)
+ else: # non-raw, detail
+ tmp = 'lldpcli -f text show neighbors details'
+ if interface:
+ tmp += f' ports {interface}'
+ return cmd(tmp)
if __name__ == "__main__":
try:
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 361b60e0e..9ce166c7d 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -426,11 +426,15 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
return None
cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=True, is_sub_ca=True)
- passphrase = ask_passphrase()
+
+ passphrase = None
+ if private_key is not None:
+ passphrase = ask_passphrase()
if not install and not file:
print(encode_certificate(cert))
- print(encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ print(encode_private_key(private_key, passphrase=passphrase))
return None
if install:
@@ -438,7 +442,8 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
if file:
write_file(f'{name}.pem', encode_certificate(cert))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ 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)
@@ -492,11 +497,15 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
return None
cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False)
- passphrase = ask_passphrase()
+
+ passphrase = None
+ if private_key is not None:
+ passphrase = ask_passphrase()
if not install and not file:
print(encode_certificate(cert))
- print(encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ print(encode_private_key(private_key, passphrase=passphrase))
return None
if install:
@@ -504,7 +513,8 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
if file:
write_file(f'{name}.pem', encode_certificate(cert))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ 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()
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index 6c8f802b5..cb4a175dd 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -110,7 +110,7 @@ def check_unsaved_config():
from vyos.config_mgmt import unsaved_commits
from vyos.utils.boot import boot_configuration_success
- if unsaved_commits() and boot_configuration_success():
+ if unsaved_commits(allow_missing_config=True) and boot_configuration_success():
print("Warning: there are unsaved configuration changes!")
print("Run 'save' command if you do not want to lose those changes after reboot/shutdown.")
else:
diff --git a/src/op_mode/tcpdump.py b/src/op_mode/tcpdump.py
new file mode 100644
index 000000000..607b59603
--- /dev/null
+++ b/src/op_mode/tcpdump.py
@@ -0,0 +1,165 @@
+#! /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 sys
+
+from vyos.utils.process import call
+
+options = {
+ 'dump': {
+ 'cmd': '{command} -A',
+ 'type': 'noarg',
+ 'help': 'Print each packet (minus its link level header) in ASCII.'
+ },
+ 'hexdump': {
+ 'cmd': '{command} -X',
+ 'type': 'noarg',
+ 'help': 'Print each packet (minus its link level header) in both hex and ASCII.'
+ },
+ 'filter': {
+ 'cmd': '{command} \'{value}\'',
+ 'type': '<pcap-filter>',
+ 'help': 'Match traffic for capture and display with a pcap-filter expression.'
+ },
+ 'numeric': {
+ 'cmd': '{command} -nn',
+ 'type': 'noarg',
+ 'help': 'Do not attempt to resolve addresses, protocols or services to names.'
+ },
+ 'save': {
+ 'cmd': '{command} -w {value}',
+ 'type': '<file>',
+ 'help': 'Write captured raw packets to <file> rather than parsing or printing them out.'
+ },
+ 'verbose': {
+ 'cmd': '{command} -vvv -ne',
+ 'type': 'noarg',
+ 'help': 'Parse packets with increased detail output, including link-level headers and extended decoding protocol sanity checks.'
+ },
+}
+
+tcpdump = 'sudo /usr/bin/tcpdump'
+
+class List(list):
+ def first(self):
+ return self.pop(0) if self else ''
+
+ def last(self):
+ return self.pop() if self else ''
+
+ def prepend(self, value):
+ self.insert(0, value)
+
+
+def completion_failure(option: str) -> None:
+ """
+ Shows failure message after TAB when option is wrong
+ :param option: failure option
+ :type str:
+ """
+ sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option))
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def expansion_failure(option, completions):
+ reason = 'Ambiguous' if completions else 'Invalid'
+ sys.stderr.write(
+ '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv),
+ option))
+ if completions:
+ sys.stderr.write(' Possible completions:\n ')
+ sys.stderr.write('\n '.join(completions))
+ sys.stderr.write('\n')
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def complete(prefix):
+ return [o for o in options if o.startswith(prefix)]
+
+
+def convert(command, args):
+ while args:
+ shortname = args.first()
+ longnames = complete(shortname)
+ if len(longnames) != 1:
+ expansion_failure(shortname, longnames)
+ longname = longnames[0]
+ if options[longname]['type'] == 'noarg':
+ command = options[longname]['cmd'].format(
+ command=command, value='')
+ elif not args:
+ sys.exit(f'monitor traffic: missing argument for {longname} option')
+ else:
+ command = options[longname]['cmd'].format(
+ command=command, value=args.first())
+ return command
+
+
+if __name__ == '__main__':
+ args = List(sys.argv[1:])
+ ifname = args.first()
+
+ # Slightly simplified & tweaked version of the code from mtr.py - it may be
+ # worthwhile to combine and centralise this in a common module.
+ if ifname == '--get-options-nested':
+ args.first() # pop monitor
+ args.first() # pop traffic
+ args.first() # pop interface
+ args.first() # pop <ifname>
+ usedoptionslist = []
+ while args:
+ option = args.first() # pop option
+ matched = complete(option) # get option parameters
+ usedoptionslist.append(option) # list of used options
+ # Select options
+ if not args:
+ # remove from Possible completions used options
+ for o in usedoptionslist:
+ if o in matched:
+ matched.remove(o)
+ if not matched:
+ sys.stdout.write('<nocomps>')
+ else:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+
+ if len(matched) > 1:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+ # If option doesn't have value
+ if matched:
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+ else:
+ # Unexpected option
+ completion_failure(option)
+
+ value = args.first() # pop option's value
+ if not args:
+ matched = complete(option)
+ helplines = options[matched[0]]['type']
+ # Run helpfunction to get list of possible values
+ if 'helpfunction' in options[matched[0]]:
+ result = options[matched[0]]['helpfunction']()
+ if result:
+ helplines = '\n' + ' '.join(result)
+ sys.stdout.write(helplines)
+ sys.exit(0)
+
+ command = convert(tcpdump, args)
+ call(f'{command} -i {ifname}')
diff --git a/src/op_mode/uptime.py b/src/op_mode/uptime.py
index 059a4c3f6..559eed24c 100755
--- a/src/op_mode/uptime.py
+++ b/src/op_mode/uptime.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -29,8 +29,8 @@ def _get_uptime_seconds():
def _get_load_averages():
from re import search
+ from vyos.utils.cpu import get_core_count
from vyos.utils.process import cmd
- from vyos.cpu import get_core_count
data = cmd("uptime")
matches = search(r"load average:\s*(?P<one>[0-9\.]+)\s*,\s*(?P<five>[0-9\.]+)\s*,\s*(?P<fifteen>[0-9\.]+)\s*", data)
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index c89c486e5..d92b539c8 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -179,8 +179,13 @@ def initialization(socket):
pid_string = socket.recv().decode("utf-8", "ignore")
resp = "pid"
socket.send(resp.encode())
+ sudo_user_string = socket.recv().decode("utf-8", "ignore")
+ resp = "sudo_user"
+ socket.send(resp.encode())
logger.debug(f"config session pid is {pid_string}")
+ logger.debug(f"config session sudo_user is {sudo_user_string}")
+
try:
session_out = os.readlink(f"/proc/{pid_string}/fd/1")
session_mode = 'w'
@@ -192,6 +197,8 @@ def initialization(socket):
session_out = script_stdout_log
session_mode = 'a'
+ os.environ['SUDO_USER'] = sudo_user_string
+
try:
configsource = ConfigSourceString(running_config_text=active_string,
session_config_text=session_string)
@@ -266,9 +273,6 @@ if __name__ == '__main__':
cfg_group = grp.getgrnam(CFG_GROUP)
os.setgid(cfg_group.gr_gid)
- os.environ['SUDO_USER'] = 'vyos'
- os.environ['SUDO_GID'] = str(cfg_group.gr_gid)
-
def sig_handler(signum, frame):
shutdown()
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index 41723e7a4..4d836127d 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -178,6 +178,13 @@ int initialization(void* Requester)
strsep(&pid_val, "_");
debug_print("config session pid: %s\n", pid_val);
+ char *sudo_user = getenv("SUDO_USER");
+ if (!sudo_user) {
+ char nobody[] = "nobody";
+ sudo_user = nobody;
+ }
+ debug_print("sudo_user is %s\n", sudo_user);
+
debug_print("Sending init announcement\n");
char *init_announce = mkjson(MKJSON_OBJ, 1,
MKJSON_STRING, "type", "init");
@@ -240,6 +247,10 @@ int initialization(void* Requester)
zmq_recv(Requester, buffer, 16, 0);
debug_print("Received pid receipt\n");
+ debug_print("Sending config session sudo_user\n");
+ zmq_send(Requester, sudo_user, strlen(sudo_user), 0);
+ zmq_recv(Requester, buffer, 16, 0);
+ debug_print("Received sudo_user receipt\n");
return 0;
}