Skip to content

Commit 4ccc2b8

Browse files
authored
Merge pull request #157 from rohit04saluja/rohit04saluja/skip_verify_gnmi
Skip verification of certificate
2 parents 3a548fc + 6c66545 commit 4ccc2b8

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--------------------------------------------------------------------------------
2+
New
3+
--------------------------------------------------------------------------------
4+
* yang
5+
* Modified Gnmi:
6+
* Added `skip_verify` property for `gnmi` connection
7+
* if `skip_verify` is set to `true` pyats will establishes a secure connection, using the server credentials.
8+
* Verification of certificate is skipped with this option.

connector/src/yang/connector/gnmi.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from google.protobuf import json_format
77
import grpc
88
from . import proto
9+
import ssl
10+
from cryptography import x509
11+
from cryptography.hazmat.backends import default_backend
912

1013

1114
try:
@@ -228,6 +231,7 @@ def __init__(self, *args, **kwargs):
228231
super().__init__(*args, **kwargs)
229232
self.device = kwargs.get('device')
230233
self.dev_args = self.connection_info
234+
self.skip_verify = kwargs.get('skip_verify')
231235
if self.dev_args.get('protocol', '') != 'gnmi':
232236
msg = 'Invalid protocol {0}'.format(self.dev_args.get('protocol', ''))
233237
raise TypeError(msg)
@@ -266,6 +270,7 @@ def connect(self):
266270
dev_args = self.dev_args
267271
username = dev_args.get('username', '')
268272
password = dev_args.get('password', '')
273+
skip_verify = dev_args.get('skip_verify')
269274

270275
if dev_args.get('custom_log', ''):
271276
self.log = dev_args.get('custom_log')
@@ -331,6 +336,28 @@ def connect(self):
331336
if private_key and os.path.isfile(private_key):
332337
private_key = open(private_key, 'rb').read()
333338

339+
if skip_verify and not root:
340+
try:
341+
ssl_cert = ssl.get_server_certificate((str(host), port)).encode("utf-8")
342+
except Exception as e:
343+
self.log.error(f'The SSH certificate cannot be retrieved from {target}')
344+
raise gNMIException(f'The SSH certificate cannot be retrieved from {target}', e)
345+
346+
ssl_cert_deserialized = x509.load_pem_x509_certificate(
347+
ssl_cert, default_backend()
348+
)
349+
350+
try:
351+
ssl_cert_common_name = ssl_cert_deserialized.subject.get_attributes_for_oid(
352+
(x509.oid.NameOID.COMMON_NAME)
353+
)[0].value
354+
options.append(
355+
('grpc.ssl_target_name_override', ssl_cert_common_name),
356+
)
357+
root = ssl_cert
358+
except BaseException as err:
359+
self.log.warning(f'Unable to get common name: {err}')
360+
334361
if any((root, chain, private_key)):
335362
override_name = dev_args.get('ssl_name_override', '')
336363
if override_name:
@@ -359,7 +386,6 @@ def connect(self):
359386
target, channel_creds, options
360387
)
361388
else:
362-
self.channel = grpc.insecure_channel(target)
363389
self.channel = grpc.insecure_channel(target, options)
364390
self.metadata = [
365391
("username", username),

connector/src/yang/connector/tests/test_gnmi.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from pyats.topology import loader
99
from pyats.datastructures import AttrDict
1010

11+
from cryptography import x509
12+
from cryptography.hazmat.backends import default_backend
1113

1214
class TestGnmi(unittest.TestCase):
1315

@@ -133,6 +135,105 @@ def test_connect_proxy(self):
133135
],
134136
}
135137

138+
@patch('yang.connector.gnmi.grpc.secure_channel')
139+
@patch('yang.connector.gnmi.grpc.composite_channel_credentials')
140+
@patch('yang.connector.gnmi.grpc.metadata_call_credentials')
141+
@patch('yang.connector.gnmi.grpc.ssl_channel_credentials')
142+
@patch('yang.connector.gnmi.x509.load_pem_x509_certificate')
143+
@patch('yang.connector.gnmi.ssl.get_server_certificate')
144+
def test_connect_with_skip_verify(
145+
self, mock_get_server_certificate, mock_load_pem_x509_certificate,
146+
mock_ssl_channel_credentials, mock_metadata_call_credentials,
147+
mock_composite_channel_credentials, mock_secure_channel, *_
148+
):
149+
yaml = """
150+
devices:
151+
dummy:
152+
type: dummy_device
153+
connections:
154+
Gnmi:
155+
class: yang.connector.Gnmi
156+
protocol: gnmi
157+
ip : 1.2.3.4
158+
port: 830
159+
username: admin
160+
password: admin
161+
skip_verify: true
162+
"""
163+
testbed = loader.load(yaml)
164+
device = testbed.devices['dummy']
165+
device.connect(alias='gnmi', via='Gnmi')
166+
mock_get_server_certificate.assert_called_once_with(('1.2.3.4', '830'))
167+
mock_load_pem_x509_certificate.assert_called_once_with(
168+
mock_get_server_certificate.return_value.encode('utf-8'),
169+
default_backend()
170+
)
171+
mock_load_pem_x509_certificate.return_value.subject.get_attributes_for_oid.assert_called_once_with(
172+
(x509.NameOID.COMMON_NAME)
173+
)
174+
mock_ssl_channel_credentials.assert_called_once_with(
175+
mock_get_server_certificate.return_value.encode('utf-8'), None, None
176+
)
177+
self.assertEqual(
178+
mock_secure_channel.call_args[0][2][-1],
179+
(
180+
'grpc.ssl_target_name_override',
181+
mock_load_pem_x509_certificate.return_value.subject.get_attributes_for_oid.return_value[0].value
182+
)
183+
)
184+
185+
@patch('yang.connector.gnmi.ssl.get_server_certificate', side_effect=Exception)
186+
def test_connect_with_skip_verify_get_server_certificate_exception(self, *_):
187+
yaml = """
188+
devices:
189+
dummy:
190+
type: dummy_device
191+
connections:
192+
Gnmi:
193+
class: yang.connector.Gnmi
194+
protocol: gnmi
195+
ip: 1.2.3.4
196+
port: 830
197+
username: admin
198+
password: admin
199+
skip_verify: true
200+
"""
201+
testbed = loader.load(yaml)
202+
device = testbed.devices['dummy']
203+
with self.assertRaises(Exception):
204+
device.connect(alias='gnmi', via='Gnmi')
205+
206+
@patch('yang.connector.gnmi.ssl.get_server_certificate')
207+
@patch('yang.connector.gnmi.grpc.insecure_channel')
208+
@patch('yang.connector.gnmi.x509.load_pem_x509_certificate')
209+
def test_connect_with_skip_verify_get_attributes_for_oid_exception(
210+
self, mock_load_pem_x509_certificate, mock_insecure_channel, *_
211+
):
212+
yaml = """
213+
devices:
214+
dummy:
215+
type: dummy_device
216+
connections:
217+
Gnmi:
218+
class: yang.connector.Gnmi
219+
protocol: gnmi
220+
ip: 1.2.3.4
221+
port: 830
222+
username: admin
223+
password: admin
224+
skip_verify: true
225+
"""
226+
mock_load_pem_x509_certificate.return_value.subject.get_attributes_for_oid.side_effect = BaseException
227+
testbed = loader.load(yaml)
228+
device = testbed.devices['dummy']
229+
device.connect(alias='gnmi', via='Gnmi')
230+
mock_insecure_channel.assert_called_once_with(
231+
'1.2.3.4:830', [
232+
('grpc.max_receive_message_length', 1000000000),
233+
('grpc.max_send_message_length', 1000000000)
234+
]
235+
)
236+
136237
def test_xpath_to_path_elem(self):
137238
"""Test converting Genie content data to cisco_gnmi format."""
138239
modules, message, origin = xpath_util.xml_path_to_path_elem(self.request)

0 commit comments

Comments
 (0)