diff options
-rw-r--r-- | interface-definitions/include/policy/route-common.xml.i | 7 | ||||
-rw-r--r-- | interface-definitions/include/rip/interface.xml.i | 7 | ||||
-rw-r--r-- | interface-definitions/nat66.xml.in | 14 | ||||
-rw-r--r-- | op-mode-definitions/show-interfaces.xml.in | 6 | ||||
-rw-r--r-- | op-mode-definitions/show-vrf.xml.in | 14 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 14 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_https.py | 102 | ||||
-rwxr-xr-x | src/op_mode/interfaces.py | 56 | ||||
-rwxr-xr-x | src/op_mode/pki.py | 14 |
9 files changed, 203 insertions, 31 deletions
diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i index 4405f9c26..b8581b03e 100644 --- a/interface-definitions/include/policy/route-common.xml.i +++ b/interface-definitions/include/policy/route-common.xml.i @@ -2,12 +2,7 @@ #include <include/policy/route-rule-action.xml.i>
#include <include/generic-description.xml.i>
#include <include/firewall/firewall-mark.xml.i>
-<leafNode name="disable">
- <properties>
- <help>Option to disable firewall rule</help>
- <valueless/>
- </properties>
-</leafNode>
+#include <include/generic-disable-node.xml.i>
<node name="fragment">
<properties>
<help>IP fragment match</help>
diff --git a/interface-definitions/include/rip/interface.xml.i b/interface-definitions/include/rip/interface.xml.i index 8007f0208..7c64d0708 100644 --- a/interface-definitions/include/rip/interface.xml.i +++ b/interface-definitions/include/rip/interface.xml.i @@ -19,12 +19,7 @@ <help>Split horizon parameters</help> </properties> <children> - <leafNode name="disable"> - <properties> - <help>Disable split horizon on specified interface</help> - <valueless/> - </properties> - </leafNode> + #include <include/generic-disable-node.xml.i> <leafNode name="poison-reverse"> <properties> <help>Disable split horizon on specified interface</help> diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in index 2fd95e03a..1518de8bd 100644 --- a/interface-definitions/nat66.xml.in +++ b/interface-definitions/nat66.xml.in @@ -25,12 +25,7 @@ </properties> <children> #include <include/generic-description.xml.i> - <leafNode name="disable"> - <properties> - <help>Disable NAT66 rule</help> - <valueless/> - </properties> - </leafNode> + #include <include/generic-disable-node.xml.i> #include <include/nat-exclude.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/outbound-interface-no-group.xml.i> @@ -141,12 +136,7 @@ </properties> <children> #include <include/generic-description.xml.i> - <leafNode name="disable"> - <properties> - <help>Disable NAT66 rule</help> - <valueless/> - </properties> - </leafNode> + #include <include/generic-disable-node.xml.i> #include <include/nat-exclude.xml.i> <leafNode name="log"> <properties> diff --git a/op-mode-definitions/show-interfaces.xml.in b/op-mode-definitions/show-interfaces.xml.in index dc61a6f5c..b58e0efea 100644 --- a/op-mode-definitions/show-interfaces.xml.in +++ b/op-mode-definitions/show-interfaces.xml.in @@ -20,6 +20,12 @@ </properties> <command>${vyos_op_scripts_dir}/interfaces.py show</command> </leafNode> + <leafNode name="summary"> + <properties> + <help>Show summary information of all interfaces</help> + </properties> + <command>${vyos_op_scripts_dir}/interfaces.py show_summary_extended</command> + </leafNode> </children> </node> </children> diff --git a/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in index 9728eb1fa..c18649844 100644 --- a/op-mode-definitions/show-vrf.xml.in +++ b/op-mode-definitions/show-vrf.xml.in @@ -7,6 +7,14 @@ <help>Show VRF (Virtual Routing and Forwarding) information</help> </properties> <command>${vyos_op_scripts_dir}/vrf.py show</command> + <children> + <leafNode name="vni"> + <properties> + <help>Show information on VRF/VXLAN VNI mapping</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> </node> <tagNode name="vrf"> <properties> @@ -23,6 +31,12 @@ </properties> <command>ip vrf pids "$3"</command> </leafNode> + <leafNode name="vni"> + <properties> + <help>Show VXLAN VNI association</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> </children> </tagNode> </children> diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 050095364..1586710db 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -496,12 +496,12 @@ class Interface(Control): from hashlib import sha256 # Get processor ID number - cpu_id = self._cmd('sudo dmidecode -t 4 | grep ID | head -n1 | sed "s/.*ID://;s/ //g"') + cpu_id = self._cmd('sudo dmidecode -t 4 | grep ID | head -n1 | sed "s/.*ID://;s/ //g"') # XXX: T3894 - it seems not all systems have eth0 - get a list of all # available Ethernet interfaces on the system (without VLAN subinterfaces) # and then take the first one. - all_eth_ifs = [x for x in Section.interfaces('ethernet') if '.' not in x] + all_eth_ifs = Section.interfaces('ethernet', vlan=False) first_mac = Interface(all_eth_ifs[0]).get_mac() sha = sha256() @@ -571,6 +571,16 @@ class Interface(Control): self._cmd(f'ip link set dev {self.ifname} netns {netns}') return True + def get_vrf(self): + """ + Get VRF from interface + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_vrf() + """ + return self.get_interface('vrf') + def set_vrf(self, vrf: str) -> bool: """ Add/Remove interface from given VRF instance. diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 1ae5c104c..a18e7dfac 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest +import json from requests import request from urllib3.exceptions import InsecureRequestWarning @@ -138,6 +139,13 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): # Must get HTTP code 401 on missing key (Unauthorized) self.assertEqual(r.status_code, 401) + # Check path config + payload = {'data': '{"op": "showConfig", "path": ["system", "login"]}', 'key': f'{key}'} + r = request('POST', url, verify=False, headers=headers, data=payload) + response = r.json() + vyos_user_exists = 'vyos' in response.get('data', {}).get('user', {}) + self.assertTrue(vyos_user_exists, "The 'vyos' user does not exist in the response.") + # GraphQL auth test: a missing key will return status code 400, as # 'key' is a non-nullable field in the schema; an incorrect key is # caught by the resolver, and returns success 'False', so one must @@ -240,5 +248,99 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): success = r.json()['data']['ShowVersion']['success'] self.assertTrue(success) + @ignore_warning(InsecureRequestWarning) + def test_api_show(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/show' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "show", "path": ["system", "image"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_generate(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/generate' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "generate", "path": ["macsec", "mka", "cak", "gcm-aes-256"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_configure(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/configure' + headers = {} + conf_interface = 'dum0' + conf_address = '192.0.2.44/32' + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload_path = [ + "interfaces", + "dummy", + f"{conf_interface}", + "address", + f"{conf_address}", + ] + + payload = {'data': json.dumps({"op": "set", "path": payload_path}), 'key': key} + + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_config_file(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/config-file' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "save"}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + @ignore_warning(InsecureRequestWarning) + def test_api_reset(self): + address = '127.0.0.1' + key = 'VyOS-key' + url = f'https://{address}/reset' + headers = {} + + self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key]) + self.cli_commit() + + payload = { + 'data': '{"op": "reset", "path": ["ip", "arp", "table"]}', + 'key': f'{key}', + } + r = request('POST', url, verify=False, headers=headers, data=payload) + self.assertEqual(r.status_code, 200) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py index 782e178c6..c626535b5 100755 --- a/src/op_mode/interfaces.py +++ b/src/op_mode/interfaces.py @@ -243,6 +243,9 @@ def _get_summary_data(ifname: typing.Optional[str], res_intf['admin_state'] = interface.get_admin_state() res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] res_intf['description'] = interface.get_alias() + res_intf['mtu'] = interface.get_mtu() + res_intf['mac'] = interface.get_mac() + res_intf['vrf'] = interface.get_vrf() ret.append(res_intf) @@ -373,6 +376,51 @@ def _format_show_summary(data): return 0 @catch_broken_pipe +def _format_show_summary_extended(data): + headers = ["Interface", "IP Address", "MAC", "VRF", "MTU", "S/L", "Description"] + table_data = [] + + print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down') + + for intf in data: + if 'unhandled' in intf: + continue + + ifname = intf['ifname'] + oper_state = 'u' if intf['oper_state'] in ('up', 'unknown') else 'D' + admin_state = 'u' if intf['admin_state'] in ('up', 'unknown') else 'A' + addrs = intf['addr'] or ['-'] + description = '\n'.join(_split_text(intf['description'], 0)) + mac = intf['mac'] if intf['mac'] else 'n/a' + mtu = intf['mtu'] if intf['mtu'] else 'n/a' + vrf = intf['vrf'] if intf['vrf'] else 'default' + + ip_addresses = '\n'.join(ip for ip in addrs) + + # Create a row for the table + row = [ + ifname, + ip_addresses, + mac, + vrf, + mtu, + f"{admin_state}/{oper_state}", + description, + ] + + # Append the row to the table data + table_data.append(row) + + for intf in data: + if 'unhandled' in intf: + string = {'C': 'u/D', 'D': 'A/D'}[intf['state']] + table_data.append([intf['ifname'], '', '', '', '', string, '']) + + print(tabulate(table_data, headers)) + + return 0 + +@catch_broken_pipe def _format_show_counters(data: list): data_entries = [] for entry in data: @@ -408,6 +456,14 @@ def show_summary(raw: bool, intf_name: typing.Optional[str], return data return _format_show_summary(data) +def show_summary_extended(raw: bool, intf_name: typing.Optional[str], + intf_type: typing.Optional[str], + vif: bool, vrrp: bool): + data = _get_summary_data(intf_name, intf_type, vif, vrrp) + if raw: + return data + return _format_show_summary_extended(data) + def show_counters(raw: bool, intf_name: typing.Optional[str], intf_type: typing.Optional[str], vif: bool, vrrp: bool): diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index 35c7ce0e2..6c854afb5 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -896,11 +896,15 @@ def show_certificate(name=None, pem=False): cert_subject_cn = cert.subject.rfc4514_string().split(",")[0] cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] cert_type = 'Unknown' - ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) - if ext and ExtendedKeyUsageOID.SERVER_AUTH in ext.value: - cert_type = 'Server' - elif ext and ExtendedKeyUsageOID.CLIENT_AUTH in ext.value: - cert_type = 'Client' + + try: + ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + if ext and ExtendedKeyUsageOID.SERVER_AUTH in ext.value: + cert_type = 'Server' + elif ext and ExtendedKeyUsageOID.CLIENT_AUTH in ext.value: + cert_type = 'Client' + except: + pass revoked = 'Yes' if 'revoke' in cert_dict else 'No' have_private = 'Yes' if 'private' in cert_dict and 'key' in cert_dict['private'] else 'No' |