summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Playle <dplayle@bloomberg.net>2018-08-01 16:52:27 +0100
committerDaniel Playle <dplayle@bloomberg.net>2018-08-03 10:12:46 +0100
commit54ce4927b1d9218e7a7405a4e2ae14aa9231f736 (patch)
tree601e7cb50b56f3c86f55ff205bdfb86a60ae0a88
parent4a637d631814b0d3e20fd2d4ffb8b9d2f60d04a6 (diff)
downloadbuildstream-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.py73
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))