mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-09-02 17:49:34 +00:00
fix: reload api if certificate changed on disk (#1887)
This PR fixes #1886. We get the certificate serial number on server startup and store it in `api.__ssl_serial_number` On reload, we get again the serial number from disk and compare it to the one stored in `api.__ssl_serial_number`: if different, then the api will be reloaded (even if the config file didn't change)
This commit is contained in:
@@ -74,6 +74,7 @@ class Patroni(AbstractPatroniDaemon):
|
||||
if local:
|
||||
self.tags = self.get_tags()
|
||||
self.request.reload_config(self.config)
|
||||
if local or self.api.reload_local_certificate():
|
||||
self.api.reload_config(self.config['restapi'])
|
||||
self.watchdog.reload_config(self.config)
|
||||
self.postgresql.reload_config(self.config['postgresql'], sighup)
|
||||
|
||||
+22
-1
@@ -616,6 +616,8 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
||||
self.http_extra_headers = {}
|
||||
self.reload_config(config)
|
||||
self.daemon = True
|
||||
self.__ssl_serial_number = None
|
||||
self._received_new_cert = False
|
||||
|
||||
def query(self, sql, *params):
|
||||
cursor = None
|
||||
@@ -701,6 +703,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
||||
|
||||
self.__listen = listen
|
||||
self.__ssl_options = ssl_options
|
||||
self._received_new_cert = False # reset to False after reload_config()
|
||||
|
||||
self.__httpserver_init(host, port)
|
||||
Thread.__init__(self, target=self.serve_forever)
|
||||
@@ -723,6 +726,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
||||
ctx.verify_mode = modes[verify_client]
|
||||
else:
|
||||
logger.error('Bad value in the "restapi.verify_client": %s', verify_client)
|
||||
self.__ssl_serial_number = self.get_certificate_serial_number()
|
||||
self.socket = ctx.wrap_socket(self.socket, server_side=True)
|
||||
if reloading_config:
|
||||
self.start()
|
||||
@@ -750,6 +754,23 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
||||
_, request = request # SSLSocket
|
||||
return super(RestApiServer, self).shutdown_request(request)
|
||||
|
||||
def get_certificate_serial_number(self):
|
||||
if self.__ssl_options.get('certfile'):
|
||||
import ssl
|
||||
try:
|
||||
crt = ssl._ssl._test_decode_cert(self.__ssl_options['certfile'])
|
||||
return crt.get('serialNumber')
|
||||
except ssl.SSLError as e:
|
||||
logger.error('Failed to get serial number from certificate %s: %r', self.__ssl_options['certfile'], e)
|
||||
|
||||
def reload_local_certificate(self):
|
||||
if self.__protocol == 'https':
|
||||
on_disk_cert_serial_number = self.get_certificate_serial_number()
|
||||
if on_disk_cert_serial_number != self.__ssl_serial_number:
|
||||
self._received_new_cert = True
|
||||
self.__ssl_serial_number = on_disk_cert_serial_number
|
||||
return True
|
||||
|
||||
def reload_config(self, config):
|
||||
if 'listen' not in config: # changing config in runtime
|
||||
raise ValueError('Can not find "restapi.listen" config')
|
||||
@@ -763,7 +784,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
||||
if isinstance(config.get('verify_client'), six.string_types):
|
||||
ssl_options['verify_client'] = config['verify_client'].lower()
|
||||
|
||||
if self.__listen != config['listen'] or self.__ssl_options != ssl_options:
|
||||
if self.__listen != config['listen'] or self.__ssl_options != ssl_options or self._received_new_cert:
|
||||
self.__initialize(config['listen'], ssl_options)
|
||||
|
||||
self.__auth_key = base64.b64encode(config['auth'].encode('utf-8')) if 'auth' in config else None
|
||||
|
||||
Reference in New Issue
Block a user