mirror of
				https://github.com/xcat2/confluent.git
				synced 2025-10-31 11:22:28 +00:00 
			
		
		
		
	Add infrastructure for TLS certificate handling
When connecting to peer devices that use TLS, provide a mechanism of tracking peer fingerprint and handling missing or mismatch of fingerprint.
This commit is contained in:
		| @@ -1,6 +1,7 @@ | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| # Copyright 2014 IBM Corporation | ||||
| # 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. | ||||
| @@ -263,4 +264,18 @@ node = { | ||||
|                         'IPMI are given their own settings with distinct ' | ||||
|                         'behaviors'), | ||||
|     }, | ||||
|     'pubkeys.addpolicy': { | ||||
|         'description': ('Policy to use when encountering unknown public ' | ||||
|                         'keys.  Choices are "automatic" to accept and ' | ||||
|                         'store new key if no key known and "manual" ' | ||||
|                         'to always reject a new key, even if no key known' | ||||
|                         'Note that if the trusted CA verifies the certificate,' | ||||
|                         ' that is accepted ignoring this policy.  Default ' | ||||
|                         'policy is "automatic"'), | ||||
|         'valid_values': ('automatic', 'manual'), | ||||
|     }, | ||||
|     'pubkeys.tls_hardwaremanager': { | ||||
|         'description':  ('Fingerprint of the TLS certificate recognized as' | ||||
|                          'belonging to the hardware manager of the server'), | ||||
|     }, | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| # Copyright 2014 IBM Corporation | ||||
| # 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. | ||||
| @@ -14,9 +15,15 @@ | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| import json | ||||
|  | ||||
|  | ||||
| class ConfluentException(Exception): | ||||
|     pass | ||||
|     apierrorcode = 500 | ||||
|     apierrorstr = 'Unexpected Error' | ||||
|  | ||||
|     def get_error_body(self): | ||||
|         return self.apierrorstr | ||||
|  | ||||
|  | ||||
| class NotFoundException(ConfluentException): | ||||
| @@ -42,6 +49,7 @@ class TargetEndpointBadCredentials(ConfluentException): | ||||
|     # failed | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class LockedCredentials(ConfluentException): | ||||
|     # A request was performed that required a credential, but the credential | ||||
|     # store is locked | ||||
| @@ -58,6 +66,20 @@ class NotImplementedException(ConfluentException): | ||||
|     # the requested task. http code 501 | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class GlobalConfigError(ConfluentException): | ||||
|     # The configuration in the global config file is not right | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class PubkeyInvalid(ConfluentException): | ||||
|     apierrorcode = 502 | ||||
|     apierrorstr = '502 - Invalid certificate or key on target' | ||||
|  | ||||
|     def __init__(self, text, fingerprint, attribname): | ||||
|         super(PubkeyInvalid, self).__init__(self, text) | ||||
|         self.fingerprint = fingerprint | ||||
|         self.errorbody = json.dumps({attribname: fingerprint}) | ||||
|  | ||||
|     def get_error_body(self): | ||||
|         return self.errorbody | ||||
|   | ||||
| @@ -439,6 +439,12 @@ def resourcehandler_backend(env, start_response): | ||||
|         except exc.NotImplementedException: | ||||
|             start_response('501 Not Implemented', headers) | ||||
|             yield '501 Not Implemented' | ||||
|         except exc.ConfluentException as e: | ||||
|             if e.apierrorcode == 500: | ||||
|                 # raise generics to trigger the tracelog | ||||
|                 raise | ||||
|             start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr)) | ||||
|             yield e.get_error_body() | ||||
|  | ||||
| def _assemble_html(responses, resource, querydict, url, extension): | ||||
|     yield '<html><head><meta charset="UTF-8"><title>' \ | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import atexit | ||||
| import confluent.exceptions as exc | ||||
| import confluent.interface.console as conapi | ||||
| import confluent.messages as msg | ||||
| import confluent.util as util | ||||
| import eventlet | ||||
| import eventlet.event | ||||
| import eventlet.green.threading as threading | ||||
| @@ -94,6 +95,9 @@ class IpmiCommandWrapper(ipmicommand.Command): | ||||
|                       'secret.hardwaremanagementpassword', 'secret.ipmikg', | ||||
|                       'hardwaremanagement.manager'), self._attribschanged) | ||||
|         super(self.__class__, self).__init__(**kwargs) | ||||
|         self.register_key_handler( | ||||
|             util.TLSCertVerifier( | ||||
|                 cfm, node, 'pubkeys.tls_hardwaremanager').verify_cert) | ||||
|  | ||||
|     def _attribschanged(self, nodeattribs, configmanager, **kwargs): | ||||
|         try: | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| # Copyright 2014 IBM Corporation | ||||
| # 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. | ||||
| @@ -145,6 +146,14 @@ def sessionhdl(connection, authname, skipauth=False): | ||||
|             send_data(connection, {'errorcode': 500, | ||||
|                                       'error': 'Locked Credential Store'}) | ||||
|             send_data(connection, {'_requestdone': 1}) | ||||
|         except exc.ConfluentException as e: | ||||
|             if e.apierrorcode == 500: | ||||
|                 tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, | ||||
|                          event=log.Events.stacktrace) | ||||
|             send_data(connection, {'errorcode': e.apierrorcode, | ||||
|                                    'error': e.apierrorstr, | ||||
|                                    'detail': e.get_error_body()}) | ||||
|             send_data(connection, {'_requestdone': 1}) | ||||
|         except SystemExit: | ||||
|             sys.exit(0) | ||||
|         except: | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||
|  | ||||
| # Copyright 2014 IBM Corporation | ||||
| # 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. | ||||
| @@ -16,6 +17,9 @@ | ||||
|  | ||||
| # Various utility functions that do not neatly fit into one category or another | ||||
| import base64 | ||||
| import confluent.exceptions as cexc | ||||
| import confluent.log as log | ||||
| import hashlib | ||||
| import os | ||||
| import struct | ||||
|  | ||||
| @@ -56,3 +60,37 @@ def monotonic_time(): | ||||
|     """ | ||||
|     # for now, just support POSIX systems | ||||
|     return os.times()[4] | ||||
|  | ||||
| class TLSCertVerifier(object): | ||||
|     def __init__(self, configmanager, node, fieldname): | ||||
|         self.cfm = configmanager | ||||
|         self.node = node | ||||
|         self.fieldname = fieldname | ||||
|  | ||||
|     def verify_cert(self, certificate): | ||||
|         fingerprint = 'sha512$' + hashlib.sha512(certificate).hexdigest() | ||||
|         storedprint = self.cfm.get_node_attributes(self.node, (self.fieldname,) | ||||
|                                                    ) | ||||
|         if self.fieldname not in storedprint[self.node]:  # no stored value, check | ||||
|                                                    # policy for next action | ||||
|             newpolicy = self.cfm.get_node_attributes(self.node, | ||||
|                                                      ('pubkeys.addpolicy',)) | ||||
|             if ('pubkeys.addpolicy' in newpolicy[self.node] and | ||||
|                     'value' in newpolicy[self.node]['pubkeys.addpolicy'] and | ||||
|                     newpolicy[self.node]['pubkeys.addpolicy']['value'] == 'manual'): | ||||
|                 # manual policy means always raise unless a match is set | ||||
|                 # manually | ||||
|                 raise cexc.PubkeyInvalid('New certificate detected', | ||||
|                                          fingerprint, self.fieldname) | ||||
|             # since the policy is not manual, go ahead and add new key | ||||
|             # after logging to audit log | ||||
|             auditlog = log.Logger('audit') | ||||
|             auditlog.log({'node': self.node, 'event': 'certautoadd', | ||||
|                           'fingerprint': fingerprint}) | ||||
|             self.cfm.set_node_attributes( | ||||
|                 {self.node: {self.fieldname: fingerprint}}) | ||||
|             return True | ||||
|         elif storedprint[self.node][self.fieldname]['value'] == fingerprint: | ||||
|             return True | ||||
|         raise cexc.PubKeyInvalid( | ||||
|             'Mismatched certificate detected', fingerprint, self.fieldname) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user