diff options
author | Daniel Playle <dplayle@bloomberg.net> | 2018-08-01 16:52:27 +0100 |
---|---|---|
committer | Daniel Playle <dplayle@bloomberg.net> | 2018-08-03 10:12:46 +0100 |
commit | 54ce4927b1d9218e7a7405a4e2ae14aa9231f736 (patch) | |
tree | 601e7cb50b56f3c86f55ff205bdfb86a60ae0a88 | |
parent | 4a637d631814b0d3e20fd2d4ffb8b9d2f60d04a6 (diff) | |
download | buildstream-dp0/513/cas-cache-client-certs.tar.gz |
Support dynamic client certificates for CAS cachedp0/513/cas-cache-client-certs
Previously, to update the client certificates for the CAS cache, one was
required to restart the CAS cache server. This change makes it so that
no restart it required.
This feature has been implemented by performing a stat on the client
certificates file on every connection. If this file has changed in some
way, then we reload this file.
-rw-r--r-- | buildstream/_artifactcache/casserver.py | 73 |
1 files changed, 57 insertions, 16 deletions
diff --git a/buildstream/_artifactcache/casserver.py b/buildstream/_artifactcache/casserver.py index 1456095be..bedbac66b 100644 --- a/buildstream/_artifactcache/casserver.py +++ b/buildstream/_artifactcache/casserver.py @@ -72,6 +72,56 @@ def create_server(repo, *, enable_push): return server +class _SSLServerCredentialsCallable: + + def __init__(self, server_key, server_cert, client_certs): + self.server_key = server_key + self.server_cert = server_cert + self.client_certs = client_certs + self.client_certs_stat = None + self.load_server_key() + self.load_server_cert() + self.load_client_certs() + + def load_server_key(self): + with open(self.server_key, 'rb') as f: + self.server_key_bytes = f.read() + + def load_server_cert(self): + with open(self.server_cert, 'rb') as f: + self.server_cert_bytes = f.read() + + def load_client_certs(self): + if self.client_certs: + with open(self.client_certs, 'rb') as f: + stat = os.fstat(f.fileno()) + if stat != self.client_certs_stat: + # The stat is different. We need to reload the client certs + self.client_certs_bytes = f.read() + self.client_certs_stat = stat + return True + return False + else: + self.client_certs_stat = None + self.client_certs_bytes = None + # Nothing has been loaded + return False + + def get_ssl_server_credentials(self): + return grpc.ssl_server_certificate_configuration( + private_key_certificate_chain_pairs=[(self.server_key_bytes, self.server_cert_bytes)], + root_certificates=self.client_certs_bytes, + ) + + def __call__(self): + if self.load_client_certs(): + # We performed a reload + return self.get_ssl_server_credentials() + else: + # Nothing changed + return None + + @click.command(short_help="CAS Artifact Server") @click.option('--port', '-p', type=click.INT, required=True, help="Port number") @click.option('--server-key', help="Private server key for TLS (PEM-encoded)") @@ -94,22 +144,13 @@ def server_main(repo, port, server_key, server_cert, client_certs, enable_push): sys.exit(-1) if use_tls: - # Read public/private key pair - with open(server_key, 'rb') as f: - server_key_bytes = f.read() - with open(server_cert, 'rb') as f: - server_cert_bytes = f.read() - - if client_certs: - with open(client_certs, 'rb') as f: - client_certs_bytes = f.read() - else: - client_certs_bytes = None - - credentials = grpc.ssl_server_credentials([(server_key_bytes, server_cert_bytes)], - root_certificates=client_certs_bytes, - require_client_auth=bool(client_certs)) - server.add_secure_port('[::]:{}'.format(port), credentials) + credentials_gen = _SSLServerCredentialsCallable(server_key, server_cert, client_certs) + initial_credentials = credentials_gen.get_ssl_server_credentials() + dynamic_credentials = grpc.dynamic_ssl_server_credentials( + initial_certificate_configuration=initial_credentials, + certificate_configuration_fetcher=credentials_gen, + require_client_authentication=bool(client_certs)) + server.add_secure_port('[::]:{}'.format(port), dynamic_credentials) else: server.add_insecure_port('[::]:{}'.format(port)) |