diff options
24 files changed, 395 insertions, 113 deletions
diff --git a/.github/workflows/add-pr-labels.yml b/.github/workflows/add-pr-labels.yml index 1723cceb0..adef2b857 100644 --- a/.github/workflows/add-pr-labels.yml +++ b/.github/workflows/add-pr-labels.yml @@ -15,5 +15,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 5eb2d840a..a9548f909 100644 --- a/.github/workflows/chceck-pr-message.yml +++ b/.github/workflows/chceck-pr-message.yml @@ -16,5 +16,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 0f0cff3ec..835cc1180 100644 --- a/.github/workflows/check-unused-imports.yml +++ b/.github/workflows/check-unused-imports.yml @@ -12,5 +12,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..3b654c0db 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -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/repo-sync.yml b/.github/workflows/repo-sync.yml index 36f323cdd..6da2fb40d 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -10,7 +10,7 @@ on: 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@current 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..5fa005631 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,20 @@ +name: Sonar Checks +on: + push: + branches: + - current + 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 }} @@ -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/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/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/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><name></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><name></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><name></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><name></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/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/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/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/migration-scripts/openvpn/1-to-2 b/src/migration-scripts/openvpn/1-to-2 new file mode 100644 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/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/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}') |