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:
parent
ba9d62b4e5
commit
f6ce9f2c1e
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user