diff --git a/src/cisco_gnmi/__init__.py b/src/cisco_gnmi/__init__.py index 73dc717..0eb3982 100644 --- a/src/cisco_gnmi/__init__.py +++ b/src/cisco_gnmi/__init__.py @@ -30,4 +30,4 @@ from .xe import XEClient from .builder import ClientBuilder -__version__ = "1.0.8" +__version__ = "1.0.9" diff --git a/src/cisco_gnmi/builder.py b/src/cisco_gnmi/builder.py index 5b008d9..0f6d445 100644 --- a/src/cisco_gnmi/builder.py +++ b/src/cisco_gnmi/builder.py @@ -158,11 +158,25 @@ def set_secure( ------- self """ + self.__secure = True self.__root_certificates = root_certificates self.__private_key = private_key self.__certificate_chain = certificate_chain return self + def _set_insecure(self): + """Sets the flag to use an insecure channel. + THIS IS AGAINST SPECIFICATION and should not + be used unless necessary and secure transport + is already well understood. + + Returns + ------- + self + """ + self.__secure = False + return self + def set_secure_from_file( self, root_certificates=None, private_key=None, certificate_chain=None ): @@ -276,44 +290,62 @@ def construct(self, return_channel=False): Client or NXClient or XEClient or XRClient """ channel = None - channel_ssl_creds = None - channel_metadata_creds = None - channel_creds = None - channel_ssl_creds = grpc.ssl_channel_credentials( - self.__root_certificates, self.__private_key, self.__certificate_chain - ) - if self.__username and self.__password: - LOGGER.debug("Using username/password call authentication.") - channel_metadata_creds = grpc.metadata_call_credentials( - CiscoAuthPlugin(self.__username, self.__password) - ) - if channel_ssl_creds and channel_metadata_creds: - LOGGER.debug("Using SSL/metadata authentication composite credentials.") - channel_creds = grpc.composite_channel_credentials( - channel_ssl_creds, channel_metadata_creds + if self.__secure: + LOGGER.debug("Using secure channel.") + channel_metadata_creds = None + if self.__username and self.__password: + LOGGER.debug("Using username/password call authentication.") + channel_metadata_creds = grpc.metadata_call_credentials( + CiscoAuthPlugin(self.__username, self.__password) + ) + channel_ssl_creds = grpc.ssl_channel_credentials( + self.__root_certificates, self.__private_key, self.__certificate_chain ) - else: - LOGGER.debug("Using SSL credentials, no metadata authentication.") - channel_creds = channel_ssl_creds - if self.__ssl_target_name_override is not False: - if self.__ssl_target_name_override is None: - if not self.__root_certificates: - raise Exception("Deriving override requires root certificate!") - self.__ssl_target_name_override = get_cn_from_cert( - self.__root_certificates + channel_creds = None + if channel_ssl_creds and channel_metadata_creds: + LOGGER.debug("Using SSL/metadata authentication composite credentials.") + channel_creds = grpc.composite_channel_credentials( + channel_ssl_creds, channel_metadata_creds + ) + else: + LOGGER.debug( + "Using SSL credentials, no channel metadata authentication." ) - LOGGER.warning( - "Overriding SSL option from certificate could increase MITM susceptibility!" + channel_creds = channel_ssl_creds + if self.__ssl_target_name_override is not False: + if self.__ssl_target_name_override is None: + if not self.__root_certificates: + raise Exception("Deriving override requires root certificate!") + self.__ssl_target_name_override = get_cn_from_cert( + self.__root_certificates + ) + LOGGER.warning( + "Overriding SSL option from certificate could increase MITM susceptibility!" + ) + self.set_channel_option( + "grpc.ssl_target_name_override", self.__ssl_target_name_override ) - self.set_channel_option( - "grpc.ssl_target_name_override", self.__ssl_target_name_override + channel = grpc.secure_channel( + self.__target_netloc.netloc, channel_creds, self.__channel_options ) - channel = grpc.secure_channel( - self.__target_netloc.netloc, channel_creds, self.__channel_options - ) + else: + LOGGER.warning( + "Insecure gRPC channel is against gNMI specification, personal data may be compromised." + ) + channel = grpc.insecure_channel(self.__target_netloc.netloc) if self.__client_class is None: self.set_os() - client = self.__client_class(channel) + client = None + if self.__secure: + client = self.__client_class(channel) + else: + client = self.__client_class( + channel, + default_call_metadata=[ + ("username", self.__username), + ("password", self.__password), + ], + ) self._reset() if return_channel: return client, channel @@ -336,4 +368,5 @@ def _reset(self): self.__password = None self.__channel_options = None self.__ssl_target_name_override = False + self.__secure = True return self diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py index ac2bf46..bacfa8d 100644 --- a/src/cisco_gnmi/cli.py +++ b/src/cisco_gnmi/cli.py @@ -293,7 +293,9 @@ def __gen_client(args): builder = ClientBuilder(args.netloc) builder.set_os(args.os) builder.set_call_authentication(args.username, args.password) - if not any([args.root_certificates, args.private_key, args.certificate_chain]): + if args.insecure: + builder._set_insecure() + elif not any([args.root_certificates, args.private_key, args.certificate_chain]): builder.set_secure_from_target() else: builder.set_secure_from_file( @@ -339,6 +341,7 @@ def __common_args_handler(parser): action="store_true", ) parser.add_argument("-debug", help="Print debug messages.", action="store_true") + parser.add_argument("-insecure", help=argparse.SUPPRESS, action="store_true") args = parser.parse_args(sys.argv[2:]) logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) args.username = input("Username: ") diff --git a/src/cisco_gnmi/client.py b/src/cisco_gnmi/client.py index 136884e..11df9ca 100755 --- a/src/cisco_gnmi/client.py +++ b/src/cisco_gnmi/client.py @@ -83,7 +83,7 @@ class Client(object): # gNMI uses nanoseconds, baseline to seconds _NS_IN_S = int(1e9) - def __init__(self, grpc_channel, timeout=_C_MAX_LONG): + def __init__(self, grpc_channel, timeout=_C_MAX_LONG, default_call_metadata=None): """gNMI initialization wrapper which simply wraps some aspects of the gNMI stub. Parameters @@ -91,14 +91,13 @@ def __init__(self, grpc_channel, timeout=_C_MAX_LONG): grpc_channel : grpc.Channel The gRPC channel to initialize the gNMI stub with. Use ClientBuilder if unfamiliar with gRPC. - username : str - Username to authenticate gNMI RPCs. - password : str - Password to authenticate gNMI RPCs. timeout : uint Timeout for gRPC functionality. + default_call_metadata : list + Metadata to be sent with each gRPC call. """ self.service = proto.gnmi_pb2_grpc.gNMIStub(grpc_channel) + self.default_call_metadata = default_call_metadata self._channel = grpc_channel def capabilities(self): @@ -115,7 +114,9 @@ def capabilities(self): """ message = proto.gnmi_pb2.CapabilityRequest() LOGGER.debug(str(message)) - response = self.service.Capabilities(message) + response = self.service.Capabilities( + message, metadata=self.default_call_metadata + ) return response def get( @@ -172,7 +173,7 @@ def get( LOGGER.debug(str(request)) - get_response = self.service.Get(request) + get_response = self.service.Get(request, metadata=self.default_call_metadata) return get_response def set( @@ -219,7 +220,7 @@ def set( LOGGER.debug(str(request)) - response = self.service.Set(request) + response = self.service.Set(request, metadata=self.default_call_metadata) return response def subscribe(self, request_iter, extensions=None): @@ -262,7 +263,8 @@ def validate_request(request): return subscribe_request response_stream = self.service.Subscribe( - (validate_request(request) for request in request_iter) + (validate_request(request) for request in request_iter), + metadata=self.default_call_metadata, ) return response_stream