diff options
-rw-r--r-- | data/templates/https/nginx.default.j2 | 2 | ||||
-rw-r--r-- | python/vyos/configsession.py | 10 | ||||
-rw-r--r-- | python/vyos/ifconfig/macsec.py | 4 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_macsec.py | 35 | ||||
-rwxr-xr-x | src/services/vyos-http-api-server | 66 |
5 files changed, 101 insertions, 16 deletions
diff --git a/data/templates/https/nginx.default.j2 b/data/templates/https/nginx.default.j2 index 468640b4b..dde839e9f 100644 --- a/data/templates/https/nginx.default.j2 +++ b/data/templates/https/nginx.default.j2 @@ -36,7 +36,7 @@ server { ssl_protocols TLSv1.2 TLSv1.3; # proxy settings for HTTP API, if enabled; 503, if not - location ~ ^/(retrieve|configure|config-file|image|container-image|generate|show|reset|docs|openapi.json|redoc|graphql) { + location ~ ^/(retrieve|configure|config-file|image|container-image|generate|show|reboot|reset|poweroff|docs|openapi.json|redoc|graphql) { {% if server.api %} proxy_pass http://unix:/run/api.sock; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 6d4b2af59..9802ebae4 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -35,6 +35,8 @@ REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset'] +REBOOT = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reboot'] +POWEROFF = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'poweroff'] OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add'] OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete'] @@ -220,10 +222,18 @@ class ConfigSession(object): out = self.__run_command(SHOW + path) return out + def reboot(self, path): + out = self.__run_command(REBOOT + path) + return out + def reset(self, path): out = self.__run_command(RESET + path) return out + def poweroff(self, path): + out = self.__run_command(POWEROFF + path) + return out + def add_container_image(self, name): out = self.__run_command(OP_CMD_ADD + ['container', 'image'] + [name]) return out diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index 9329c5ee7..bde1d9aec 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -45,6 +45,10 @@ class MACsecIf(Interface): # create tunnel interface cmd = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config) cmd += f' cipher {self.config["security"]["cipher"]}' + + if 'encrypt' in self.config["security"]: + cmd += ' encrypt on' + self._cmd(cmd) # Check if using static keys diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index ea0f00071..d8d564792 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import re import unittest @@ -23,9 +22,10 @@ from netifaces import interfaces from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.utils.process import cmd from vyos.utils.file import read_file from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists +from vyos.utils.process import cmd from vyos.utils.process import process_named_running PROCESS_NAME = 'wpa_supplicant' @@ -35,10 +35,6 @@ def get_config_value(interface, key): tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) return tmp[0] -def get_cipher(interface): - tmp = get_interface_config(interface) - return tmp['linkinfo']['info_data']['cipher_suite'].lower() - class MACsecInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): @@ -117,6 +113,10 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): tmp = read_file(f'/sys/class/net/{interface}/mtu') self.assertEqual(tmp, '1460') + # Encryption enabled? + tmp = get_interface_config(interface) + self.assertTrue(tmp['linkinfo']['info_data']['encrypt']) + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -138,10 +138,11 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): # final commit and verify self.cli_commit() - self.assertIn(interface, interfaces()) + self.assertTrue(interface_exists(interface)) # Verify proper cipher suite (T4537) - self.assertEqual(cipher, get_cipher(interface)) + tmp = get_interface_config(interface) + self.assertEqual(cipher, tmp['linkinfo']['info_data']['cipher_suite'].lower()) def test_macsec_gcm_aes_256(self): src_interface = 'eth0' @@ -161,10 +162,11 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): # final commit and verify self.cli_commit() - self.assertIn(interface, interfaces()) + self.assertTrue(interface_exists(interface)) # Verify proper cipher suite (T4537) - self.assertEqual(cipher, get_cipher(interface)) + tmp = get_interface_config(interface) + self.assertEqual(cipher, tmp['linkinfo']['info_data']['cipher_suite'].lower()) def test_macsec_source_interface(self): # Ensure source-interface can bot be part of any other bond or bridge @@ -191,7 +193,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): # final commit and verify self.cli_commit() - self.assertIn(interface, interfaces()) + self.assertTrue(interface_exists(interface)) def test_macsec_static_keys(self): src_interface = 'eth0' @@ -205,7 +207,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): peer_mac = '00:11:22:33:44:55' self.cli_set(self._base_path + [interface]) - # Encrypt link + # Encrypt link self.cli_set(self._base_path + [interface, 'security', 'encrypt']) # check validate() - source interface is mandatory @@ -261,9 +263,12 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): # final commit and verify self.cli_commit() - self.assertIn(interface, interfaces()) - self.assertEqual(cipher2, get_cipher(interface)) - self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}')) + + self.assertTrue(interface_exists(interface)) + tmp = get_interface_config(interface) + self.assertEqual(cipher2, tmp['linkinfo']['info_data']['cipher_suite'].lower()) + # Encryption enabled? + self.assertTrue(tmp['linkinfo']['info_data']['encrypt']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index daee24257..85d7884b6 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -223,6 +223,19 @@ class ShowModel(ApiModel): } } +class RebootModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "reboot", + "path": ["op", "mode", "path"], + } + } + class ResetModel(ApiModel): op: StrictStr path: List[StrictStr] @@ -236,6 +249,19 @@ class ResetModel(ApiModel): } } +class PoweroffModel(ApiModel): + op: StrictStr + path: List[StrictStr] + + class Config: + schema_extra = { + "example": { + "key": "id_key", + "op": "poweroff", + "path": ["op", "mode", "path"], + } + } + class Success(BaseModel): success: bool @@ -713,6 +739,26 @@ def show_op(data: ShowModel): return success(res) +@app.post('/reboot') +def reboot_op(data: RebootModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'reboot': + res = session.reboot(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + @app.post('/reset') def reset_op(data: ResetModel): session = app.state.vyos_session @@ -733,6 +779,26 @@ def reset_op(data: ResetModel): return success(res) +@app.post('/poweroff') +def poweroff_op(data: PoweroffModel): + session = app.state.vyos_session + + op = data.op + path = data.path + + try: + if op == 'poweroff': + res = session.poweroff(path) + else: + return error(400, f"'{op}' is not a valid operation") + except ConfigSessionError as e: + return error(400, str(e)) + except Exception as e: + logger.critical(traceback.format_exc()) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + ### # GraphQL integration |