Skip to content

Commit

Permalink
[ycabled] add support for getting grpc secerts via shared file (#298)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vdahiya12 authored Sep 27, 2022
1 parent 3622aac commit 534f839
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 5 deletions.
20 changes: 18 additions & 2 deletions sonic-ycabled/tests/test_y_cable_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 = {}
Expand All @@ -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"}
Expand Down Expand Up @@ -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)
62 changes: 59 additions & 3 deletions sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import datetime
import ipaddress
import json
import os
import re
import sys
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -369,26 +372,64 @@ 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")
return None

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")
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit 534f839

Please sign in to comment.