2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-01-18 05:33:17 +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:
Jarrod Johnson 2015-11-20 17:05:36 -05:00
parent ba9d62b4e5
commit f6ce9f2c1e
6 changed files with 95 additions and 1 deletions

View File

@ -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'),
},
}

View File

@ -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

View File

@ -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>' \

View File

@ -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:

View File

@ -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:

View File

@ -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)