Skip to content

Commit e3b03d4

Browse files
authored
[ycabled] add secure channel support for grpc dualtor active-active connectivity (#275)
Signed-off-by: vaibhav-dahiya [email protected] This PR adds support for creating a secure channel for gRPC between SOC and SONiC. the certs and configurations are defined in config DB config': { 'type': 'secure', 'auth_level': 'server', 'log_level': 'info' }, 'certs': { 'client_crt': path', 'client_key': 'path 'ca_crt': 'path, 'grpc_ssl_credential': 'target override' } Using this config parameter we can have secure/insecure as well as mutual/server level authentication between SoC and SONiC. This PR leverages the cert API's in gRPC lib and certs created to create a TLS based handshake if required to setup gRPC channel Description Motivation and Context Required for secure gRPC support between SONiC and SoC How Has This Been Tested? Unit-Tests and running the changes on the testbed
1 parent 005ec30 commit e3b03d4

File tree

2 files changed

+135
-21
lines changed

2 files changed

+135
-21
lines changed

sonic-ycabled/tests/test_y_cable_helper.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -4959,7 +4959,10 @@ def test_check_identifier_presence_and_setup_channel_with_mock_not_none(self):
49594959
@patch('proto_out.linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub', MagicMock(return_value=True))
49604960
def test_setup_grpc_channel_for_port(self):
49614961

4962-
rc = setup_grpc_channel_for_port("Ethernet0", "192.168.0.1")
4962+
with patch('ycable.ycable_utilities.y_cable_helper.y_cable_platform_sfputil') as patched_util:
4963+
4964+
patched_util.get_asic_id_for_logical_port.return_value = 0
4965+
rc = setup_grpc_channel_for_port("Ethernet0", "192.168.0.1")
49634966

49644967
assert(rc == (None, None))
49654968

@@ -5306,4 +5309,25 @@ def test_get_mux_cable_static_info_without_presence(self):
53065309
assert(rc['nic_lane1_postcursor1'] == 'N/A')
53075310
assert(rc['nic_lane1_postcursor2'] == 'N/A')
53085311

5312+
def test_get_grpc_credentials(self):
5313+
5314+
kvp = {}
5315+
type = None
5316+
5317+
rc = get_grpc_credentials(type, kvp)
5318+
5319+
assert(rc == None)
5320+
5321+
5322+
@patch('builtins.open')
5323+
def test_get_grpc_credentials_root(self, open):
5324+
5325+
kvp = {"ca_crt": "file"}
5326+
type = "server"
5327+
5328+
mock_file = MagicMock()
5329+
mock_file.read = MagicMock(return_value=bytes('abcdefgh', 'utf-8'))
5330+
open.return_value = mock_file
5331+
rc = get_grpc_credentials(type, kvp)
53095332

5333+
assert(rc != None)

sonic-ycabled/ycable/ycable_utilities/y_cable_helper.py

+110-20
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@
5656
# port id 0 -> maps to T0
5757
# port id 1 -> maps to LT0
5858

59+
GRPC_CLIENT_OPTIONS = [
60+
('grpc.keepalive_timeout_ms', 8000),
61+
('grpc.keepalive_time_ms', 4000),
62+
('grpc.keepalive_permit_without_calls', True),
63+
('grpc.http2.max_pings_without_data', 0)
64+
]
65+
5966
SYSLOG_IDENTIFIER = "y_cable_helper"
6067

6168
helper_logger = logger.Logger(SYSLOG_IDENTIFIER)
@@ -360,13 +367,78 @@ def retry_setup_grpc_channel_for_port(port, asic_index):
360367
grpc_port_stubs[port] = stub
361368
return True
362369

370+
371+
def get_grpc_credentials(type, kvp):
372+
373+
root_file = kvp.get("ca_crt", None)
374+
if root_file is not None:
375+
root_cert = open(root_file, 'rb').read()
376+
else:
377+
helper_logger.log_error("grpc credential channel setup no root file in config_db")
378+
return None
379+
380+
if type == "mutual":
381+
cert_file = kvp.get("client_crt", None)
382+
if cert_file is not None:
383+
cert_chain = open(cert_file, 'rb').read()
384+
else:
385+
helper_logger.log_error("grpc credential channel setup no cert file for mutual authentication in config_db")
386+
return None
387+
388+
key_file = kvp.get("client_key", None)
389+
if key_file is not None:
390+
key = open(key_file, 'rb').read()
391+
else:
392+
helper_logger.log_error("grpc credential channel setup no key file for mutual authentication in config_db")
393+
return None
394+
395+
credential = grpc.ssl_channel_credentials(
396+
root_certificates=root_cert,
397+
private_key=key,
398+
certificate_chain=cert_chain)
399+
elif type == "server":
400+
credential = grpc.ssl_channel_credentials(
401+
root_certificates=root_cert)
402+
else:
403+
#should not happen
404+
helper_logger.log_error("grpc credential channel setup no type specified for authentication in config_db")
405+
return None
406+
407+
return credential
408+
409+
def create_channel(type,level, kvp, soc_ip):
410+
411+
retries = 3
412+
for _ in range(retries):
413+
414+
if type == "secure":
415+
credential = get_grpc_credentials(level, kvp)
416+
target_name = kvp.get("grpc_ssl_credential", None)
417+
if credential is None or target_name is None:
418+
return (None, None)
419+
420+
GRPC_CLIENT_OPTIONS.append(('grpc.ssl_target_name_override', '{}'.format(target_name)))
421+
422+
channel = grpc.secure_channel("{}:{}".format(soc_ip, GRPC_PORT), credential, options=GRPC_CLIENT_OPTIONS)
423+
else:
424+
channel = grpc.insecure_channel("{}:{}".format(soc_ip, GRPC_PORT), options=GRPC_CLIENT_OPTIONS)
425+
426+
stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel)
427+
428+
channel_ready = grpc.channel_ready_future(channel)
429+
430+
try:
431+
channel_ready.result(timeout=2)
432+
except grpc.FutureTimeoutError:
433+
channel = None
434+
stub = None
435+
else:
436+
break
437+
438+
return channel, stub
439+
363440
def setup_grpc_channel_for_port(port, soc_ip):
364-
"""
365-
root_cert = open('/etc/sonic/credentials/ca-chain-bundle.cert.pem', 'rb').read()
366-
key = open('/etc/sonic/credentials/client.key.pem', 'rb').read()
367-
cert_chain = open('/etc/sonic/credentials/client.cert.pem', 'rb').read()
368441

369-
"""
370442
"""
371443
Dummy values for lab for now
372444
TODO remove these once done
@@ -381,23 +453,41 @@ def setup_grpc_channel_for_port(port, soc_ip):
381453
"""
382454
helper_logger.log_notice("Setting up gRPC channel for RPC's {} {}".format(port,soc_ip))
383455

384-
retries = 3
385-
for _ in range(retries):
386-
channel = grpc.insecure_channel("{}:{}".format(soc_ip, GRPC_PORT), options=[('grpc.keepalive_timeout_ms', 8000),
387-
('grpc.keepalive_time_ms', 4000),
388-
('grpc.keepalive_permit_without_calls', True),
389-
('grpc.http2.max_pings_without_data', 0)])
390-
stub = linkmgr_grpc_driver_pb2_grpc.DualToRActiveStub(channel)
456+
config_db,grpc_config = {}, {}
457+
namespaces = multi_asic.get_front_end_namespaces()
458+
for namespace in namespaces:
459+
asic_id = multi_asic.get_asic_index_from_namespace(namespace)
460+
config_db[asic_id] = daemon_base.db_connect("CONFIG_DB", namespace)
461+
grpc_config[asic_id] = swsscommon.Table(config_db[asic_id], "GRPCCLIENT")
391462

392-
channel_ready = grpc.channel_ready_future(channel)
463+
asic_index = y_cable_platform_sfputil.get_asic_id_for_logical_port(port)
393464

394-
try:
395-
channel_ready.result(timeout=2)
396-
except grpc.FutureTimeoutError:
397-
channel = None
398-
stub = None
399-
else:
400-
break
465+
#if no config from config DB, treat channel to be as insecure
466+
type = "insecure"
467+
level = "server"
468+
469+
(status, fvs) = grpc_config[asic_index].get("config")
470+
if status is False:
471+
helper_logger.log_warning(
472+
"Could not retreive fieldvalue pairs for {}, inside config_db table kvp config for {} for setting up channel type".format(port, grpc_config[asic_index].getTableName()))
473+
else:
474+
grpc_config_dict = dict(fvs)
475+
type = grpc_config_dict.get("type", None)
476+
level = grpc_config_dict.get("auth_level", None)
477+
478+
479+
kvp = {}
480+
if type == "secure":
481+
(status, fvs) = grpc_config[asic_index].get("certs")
482+
if status is False:
483+
helper_logger.log_warning(
484+
"Could not retreive fieldvalue pairs for {}, inside config_db table kvp certs for {} for setting up channel type".format(port, grpc_config[asic_index].getTableName()))
485+
#if type is secure, must have certs defined
486+
return (None, None)
487+
kvp = dict(fvs)
488+
489+
490+
channel, stub = create_channel(type, level, kvp, soc_ip)
401491

402492
if stub is None:
403493
helper_logger.log_warning("stub was not setup for gRPC soc ip {} port {}, no gRPC soc server running ?".format(soc_ip, port))

0 commit comments

Comments
 (0)