From 03e3da529bfab512bc1fdbb69439b3e6ac95fbf1 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 17 Nov 2015 15:50:06 -0500 Subject: [PATCH] Add TLS support and TSM remote video (WIP) Provide a method for applications to evaluate target certificates. Force them to do so if such a thing is needed. Use this to support TSM remote graphics, which sholud be over https for sake of security. Change-Id: Ie67b629b0021c356d2ea001e24c72ad196e5460d --- pyghmi/exceptions.py | 6 +++++ pyghmi/ipmi/command.py | 28 +++++++++++++++++++++ pyghmi/ipmi/oem/generic.py | 19 ++++++++++++++ pyghmi/ipmi/oem/lenovo/handler.py | 9 +++++++ pyghmi/util/__init__.py | 1 + pyghmi/util/webclient.py | 42 +++++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+) create mode 100644 pyghmi/util/__init__.py create mode 100644 pyghmi/util/webclient.py diff --git a/pyghmi/exceptions.py b/pyghmi/exceptions.py index 3fefd27d..869f4443 100644 --- a/pyghmi/exceptions.py +++ b/pyghmi/exceptions.py @@ -29,6 +29,12 @@ class IpmiException(PyghmiException): self.ipmicode = code +class UnrecognizedCertificate(Exception): + def __init__(self, text='', certdata=None): + super(UnrecognizedCertificate, self).__init__(text) + self.certdata = certdata + + class InvalidParameterValue(PyghmiException): pass diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 3cc1f45b..d1d7b058 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -127,6 +127,24 @@ class Command(object): port=port, kg=kg) + def register_key_handler(self, callback, type='tls'): + """Assign a verification handler for a public key + + When the library attempts to communicate with the management target + using a non-IPMI protocol, it will try to verify a key. This + allows a caller to register a key handler for accepting or rejecting + a public key/certificate. The callback will be passed the peer public + key or certificate. + + :param callback: The function to call with public key/certificate + :param type: Whether the callback is meant to handle 'tls' or 'ssh', + defaults to 'tls' + """ + if type == 'tls': + self._certverify = callback + self.oem_init() + self._oem.register_key_handler(callback, type) + def logged(self, response): self.onlogon(response, self) @@ -270,6 +288,16 @@ class Command(object): else: return lastresponse + def get_video_launchdata(self): + """Get data required to launch a remote video session to target. + + This is a highly proprietary scenario, the return data may vary greatly + host to host. The return should be a dict describing the type of data + and the data. For example {'jnlp': jnlpstring} + """ + self.oem_init() + return self._oem.get_video_launchdata() + def reset_bmc(self): """Do a cold reset in BMC """ diff --git a/pyghmi/ipmi/oem/generic.py b/pyghmi/ipmi/oem/generic.py index cb056ae5..85eb1042 100644 --- a/pyghmi/ipmi/oem/generic.py +++ b/pyghmi/ipmi/oem/generic.py @@ -27,6 +27,25 @@ class OEMHandler(object): def __init__(self, oemid, ipmicmd): pass + def register_key_handler(self, callback, type='tls'): + """Assign a verification handler for a public key + + When the library attempts to communicate with the management target + using a non-IPMI protocol, it will try to verify a key. This + allows a caller to register a key handler for accepting or rejecting + a public key/certificate. The callback will be passed the peer public + key or certificate. + + :param callback: The function to call with public key/certificate + :param type: Whether the callback is meant to handle 'tls' or 'ssh', + defaults to 'tls' + """ + if type == 'tls': + self._certverify = callback + + def get_video_launchdata(self): + return {} + def process_event(self, event, ipmicmd, seldata): """Modify an event according with OEM understanding. diff --git a/pyghmi/ipmi/oem/lenovo/handler.py b/pyghmi/ipmi/oem/lenovo/handler.py index b675658d..fb3bd707 100755 --- a/pyghmi/ipmi/oem/lenovo/handler.py +++ b/pyghmi/ipmi/oem/lenovo/handler.py @@ -33,6 +33,8 @@ from pyghmi.ipmi.oem.lenovo import psu from pyghmi.ipmi.oem.lenovo import raid_controller from pyghmi.ipmi.oem.lenovo import raid_drive +#import pyghmi.util.webclient as wc + inventory.register_inventory_category(cpu) inventory.register_inventory_category(dimm) inventory.register_inventory_category(pci) @@ -115,6 +117,13 @@ class OEMHandler(generic.OEMHandler): self.ipmicmd = ipmicmd self.oem_inventory_info = None + def get_video_launchdata(self): + if self.has_tsm: + return self.get_tsm_launchdata() + + def get_tsm_launchdata(self): + pass + def process_event(self, event, ipmicmd, seldata): if 'oemdata' in event: oemtype = seldata[2] diff --git a/pyghmi/util/__init__.py b/pyghmi/util/__init__.py new file mode 100644 index 00000000..0dead496 --- /dev/null +++ b/pyghmi/util/__init__.py @@ -0,0 +1 @@ +__author__ = 'jjohnson2' diff --git a/pyghmi/util/webclient.py b/pyghmi/util/webclient.py new file mode 100644 index 00000000..3f455147 --- /dev/null +++ b/pyghmi/util/webclient.py @@ -0,0 +1,42 @@ +# Copyright 2015 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This provides ability to do HTTPS in a manner like ssh host keys for the +# sake of typical internal management devices. Compatibility back to python +# 2.6 as is found in commonly used enterprise linux distributions. + +__author__ = 'jjohnson2' +import httplib +import pyghmi.exceptions as pygexc +import socket +import ssl + + +class SecureHTTPConnection(httplib.HTTPConnection): + default_port = httplib.HTTPS_PORT + + def __init__(self, host, port=None, key_file=None, cert_file=None, + ca_certs=None, strict=None, verifycallback=None, **kwargs): + httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs) + self.cert_reqs = ssl.CERT_NONE # verification will be done ssh style.. + self._certverify = verifycallback + + def connect(self): + plainsock = socket.create_connection((self.host, self.port)) + self.sock = ssl.wrap_socket(plainsock, cert_reqs=self.cert_reqs) + # txtcert = self.sock.getpeercert() # currently not possible + bincert = self.sock.getpeercert(binary_form=True) + if not self._certverify(bincert): + raise pygexc.UnrecognizedCertificate('Unknown certificate', + bincert)