From b935741f6c93abae1c7aac41da92b475bbe14815 Mon Sep 17 00:00:00 2001 From: Adam Young Date: Fri, 28 Feb 2014 11:28:01 -0500 Subject: [PATCH] Atomic write of certificate files and revocation list Using a rename from a temporary file to avoid having partial writes. Closes-Bug: #1285833 Change-Id: I3e70c795be357ceab8e5a1d12202c04fd88a02b8 --- keystoneclient/middleware/auth_token.py | 64 ++++++++++--------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py index 41de990ef..427e7c5c6 100644 --- a/keystoneclient/middleware/auth_token.py +++ b/keystoneclient/middleware/auth_token.py @@ -1324,6 +1324,24 @@ class AuthProtocol(object): self.token_revocation_list = self.fetch_revocation_list() return self._token_revocation_list + def _atomic_write_to_signing_dir(self, file_name, value): + # In Python2, encoding is slow so the following check avoids it if it + # is not absolutely necessary. + if isinstance(value, six.text_type): + value = value.encode('utf-8') + + def _atomic_write(destination, data): + with tempfile.NamedTemporaryFile(dir=self.signing_dirname, + delete=False) as f: + f.write(data) + os.rename(f.name, destination) + + try: + _atomic_write(file_name, value) + except (OSError, IOError): + self.verify_signing_dir() + _atomic_write(file_name, value) + @token_revocation_list.setter def token_revocation_list(self, value): """Save a revocation list to memory and to disk. @@ -1333,15 +1351,7 @@ class AuthProtocol(object): """ self._token_revocation_list = jsonutils.loads(value) self.token_revocation_list_fetched_time = timeutils.utcnow() - - with tempfile.NamedTemporaryFile(dir=self.signing_dirname, - delete=False) as f: - # In Python2, encoding is slow so the following check avoids it if - # it is not absolutely necessary. - if isinstance(value, six.text_type): - value = value.encode('utf-8') - f.write(value) - os.rename(f.name, self.revoked_file_name) + self._atomic_write_to_signing_dir(self.revoked_file_name, value) def fetch_revocation_list(self, retry=True): headers = {'X-Auth-Token': self.get_admin_token()} @@ -1360,42 +1370,18 @@ class AuthProtocol(object): raise ServiceError('Revocation list improperly formatted.') return self.cms_verify(data['signed']) - def fetch_signing_cert(self): - path = '/v2.0/certificates/signing' + def _fetch_cert_file(self, cert_file_name, cert_type): + path = '/v2.0/certificates/' + cert_type response = self._http_request('GET', path) - - def write_cert_file(data): - with open(self.signing_cert_file_name, 'w') as certfile: - certfile.write(data) - if response.status_code != 200: raise exceptions.CertificateConfigError(response.text) + self._atomic_write_to_signing_dir(cert_file_name, response.text) - try: - try: - write_cert_file(response.text) - except IOError: - self.verify_signing_dir() - write_cert_file(response.text) - except (AssertionError, KeyError): - self.LOG.warn( - "Unexpected response from keystone service: %s", response.text) - raise ServiceError('invalid json response') + def fetch_signing_cert(self): + self._fetch_cert_file(self.signing_cert_file_name, 'signing') def fetch_ca_cert(self): - path = '/v2.0/certificates/ca' - response = self._http_request('GET', path) - - if response.status_code != 200: - raise exceptions.CertificateConfigError(response.text) - - try: - with open(self.signing_ca_file_name, 'w') as certfile: - certfile.write(response.text) - except (AssertionError, KeyError): - self.LOG.warn( - "Unexpected response from keystone service: %s", response.text) - raise ServiceError('invalid json response') + self._fetch_cert_file(self.signing_ca_file_name, 'ca') def filter_factory(global_conf, **local_conf):