From 534f8399afae4c601086a3f818257c55badb8a32 Mon Sep 17 00:00:00 2001 From: vdahiya12 <67608553+vdahiya12@users.noreply.github.com> Date: Tue, 27 Sep 2022 10:54:16 -0700 Subject: [PATCH] [ycabled] add support for getting grpc secerts via shared file (#298) This PR adds support for adding the secrets to grpc client in active-active configuration via a shared file /etc/sonic/grpc_secrets.json. Using this file, secrets configuration in populated inside CONFIG_DB, absence of this file will assume insecure gRPC client. Description Motivation and Context How Has This Been Tested? testing with UT and putting the changes on Arista testbed Signed-off-by: vaibhav-dahiya vdahiya@microsoft.com --- sonic-ycabled/tests/test_y_cable_helper.py | 20 +++++- .../ycable/ycable_utilities/y_cable_helper.py | 62 ++++++++++++++++++- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/sonic-ycabled/tests/test_y_cable_helper.py b/sonic-ycabled/tests/test_y_cable_helper.py index d23ed551fa68..8b97d0865695 100644 --- a/sonic-ycabled/tests/test_y_cable_helper.py +++ b/sonic-ycabled/tests/test_y_cable_helper.py @@ -10,9 +10,9 @@ import time if sys.version_info >= (3, 3): - from unittest.mock import MagicMock, patch + from unittest.mock import MagicMock, patch, mock_open else: - from mock import MagicMock, patch + from mock import MagicMock, patch, mock_open daemon_base.db_connect = MagicMock() @@ -5458,6 +5458,7 @@ def test_get_mux_cable_static_info_without_presence(self): assert(rc['nic_lane1_postcursor1'] == 'N/A') assert(rc['nic_lane1_postcursor2'] == 'N/A') + @patch('os.path.isfile', MagicMock(return_value=True)) def test_get_grpc_credentials(self): kvp = {} @@ -5469,6 +5470,7 @@ def test_get_grpc_credentials(self): @patch('builtins.open') + @patch('os.path.isfile', MagicMock(return_value=True)) def test_get_grpc_credentials_root(self, open): kvp = {"ca_crt": "file"} @@ -5498,3 +5500,17 @@ def test_handle_ycable_enable_disable_tel_notification_probe(self): fvp_m = {"log_verbosity": "debug"} rc = handle_ycable_enable_disable_tel_notification(fvp_m, "Y_CABLE") assert(rc == None) + + + @patch('builtins.open') + def test_apply_grpc_secrets_configuration(self, open): + + parsed_data = {'GRPCCLIENT': {'config': {'type': 'secure', 'auth_level': 'server', 'log_level': 'info'}, 'certs': {'client_crt': 'one.crt', 'client_key': 'one.key', 'ca_crt': 'ss.crt', 'grpc_ssl_credential': 'jj.tsl'}}} + + mock_file = MagicMock() + open.return_value = mock_file + #json_load.return_value = parsed_data + with patch('json.load') as patched_util: + patched_util.return_value = parsed_data + rc = apply_grpc_secrets_configuration(None) + assert(rc == None) diff --git a/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py b/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py index 8e735e4f2314..2c20028532de 100644 --- a/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py +++ b/sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py @@ -5,6 +5,7 @@ import datetime import ipaddress +import json import os import re import sys @@ -115,6 +116,8 @@ PORT_INSTANCE_ERROR } +SECRETS_PATH = "/etc/sonic/grpc_secrets.json" + def format_mapping_identifier(string): """ Takes an arbitrary string and creates a valid entity for port mapping file. @@ -369,11 +372,49 @@ def retry_setup_grpc_channel_for_port(port, asic_index): grpc_port_stubs[port] = stub return True +def apply_grpc_secrets_configuration(SECRETS_PATH): + + + f = open(SECRETS_PATH, 'rb') + parsed_data = json.load(f) + + config_db, grpc_config = {}, {} + namespaces = multi_asic.get_front_end_namespaces() + for namespace in namespaces: + asic_id = multi_asic.get_asic_index_from_namespace(namespace) + config_db[asic_id] = daemon_base.db_connect("CONFIG_DB", namespace) + grpc_config[asic_id] = swsscommon.Table(config_db[asic_id], "GRPCCLIENT") + + + asic_index = multi_asic.get_asic_index_from_namespace(DEFAULT_NAMESPACE) + grpc_client_config = parsed_data.get("GRPCCLIENT", None) + if grpc_client_config is not None: + config = grpc_client_config.get("config", None) + if config is not None: + type = config.get("type",None) + auth_level = config.get("auth_level",None) + log_level = config.get("log_level", None) + fvs_updated = swsscommon.FieldValuePairs([('type', type), + ('auth_level',auth_level ), + ('log_level',log_level)]) + grpc_config[asic_index].set('config', fvs_updated) + certs = grpc_client_config.get("certs", None) + if certs is not None: + client_crt = certs.get("client_crt", None) + client_key = certs.get("client_key", None) + ca_crt = certs.get("ca_crt", None) + grpc_ssl_credential = certs.get("grpc_ssl_credential",None) + fvs_updated = swsscommon.FieldValuePairs([('client_crt', client_crt), + ('client_key', client_key), + ('grpc_ssl_credential', grpc_ssl_credential), + ('ca_crt',ca_crt)]) + grpc_config[asic_index].set('certs', fvs_updated) + def get_grpc_credentials(type, kvp): root_file = kvp.get("ca_crt", None) - if root_file is not None: + if root_file is not None and os.path.isfile(root_file): root_cert = open(root_file, 'rb').read() else: helper_logger.log_error("grpc credential channel setup no root file in config_db") @@ -381,14 +422,14 @@ def get_grpc_credentials(type, kvp): if type == "mutual": cert_file = kvp.get("client_crt", None) - if cert_file is not None: + if cert_file is not None and os.path.isfile(cert_file): cert_chain = open(cert_file, 'rb').read() else: helper_logger.log_error("grpc credential channel setup no cert file for mutual authentication in config_db") return None key_file = kvp.get("client_key", None) - if key_file is not None: + if key_file is not None and os.path.isfile(key_file): key = open(key_file, 'rb').read() else: helper_logger.log_error("grpc credential channel setup no key file for mutual authentication in config_db") @@ -695,6 +736,8 @@ def setup_grpc_channels(stop_event): if read_side == -1: read_side = process_loopback_interface_and_get_read_side(loopback_keys) + if os.path.isfile(SECRETS_PATH): + apply_grpc_secrets_configuration(SECRETS_PATH) helper_logger.log_debug("Y_CABLE_DEBUG:while setting up grpc channels read side = {}".format(read_side)) @@ -1377,6 +1420,8 @@ def init_ports_status_for_y_cable(platform_sfp, platform_chassis, y_cable_presen if read_side == -1: read_side = process_loopback_interface_and_get_read_side(loopback_keys) + if os.path.isfile(SECRETS_PATH): + apply_grpc_secrets_configuration(SECRETS_PATH) # Init PORT_STATUS table if ports are on Y cable logical_port_list = y_cable_platform_sfputil.logical @@ -1439,6 +1484,8 @@ def change_ports_status_for_y_cable_change_event(port_dict, y_cable_presence, st if read_side == -1: read_side = process_loopback_interface_and_get_read_side(loopback_keys) + if os.path.isfile(SECRETS_PATH): + apply_grpc_secrets_configuration(SECRETS_PATH) # Init PORT_STATUS table if ports are on Y cable and an event is received @@ -1500,6 +1547,7 @@ def delete_ports_status_for_y_cable(): state_db, config_db, port_tbl, y_cable_tbl = {}, {}, {}, {} y_cable_tbl_keys = {} static_tbl, mux_tbl = {}, {} + grpc_config = {} namespaces = multi_asic.get_front_end_namespaces() for namespace in namespaces: asic_id = multi_asic.get_asic_index_from_namespace(namespace) @@ -1513,6 +1561,14 @@ def delete_ports_status_for_y_cable(): mux_tbl[asic_id] = swsscommon.Table( state_db[asic_id], MUX_CABLE_INFO_TABLE) port_tbl[asic_id] = swsscommon.Table(config_db[asic_id], "MUX_CABLE") + grpc_config[asic_id] = swsscommon.Table(config_db[asic_id], "GRPCCLIENT") + + + if read_side != -1: + asic_index = multi_asic.get_asic_index_from_namespace(DEFAULT_NAMESPACE) + if os.path.isfile(SECRETS_PATH): + grpc_config[asic_index]._del("config") + grpc_config[asic_index]._del("certs") # delete PORTS on Y cable table if ports on Y cable logical_port_list = y_cable_platform_sfputil.logical