Conditional DBM handling

This commit is contained in:
Joona Hoikkala 2018-08-24 16:31:16 +03:00
parent 85187ea90d
commit c63607b7f9
No known key found for this signature in database
GPG key ID: D5AA86BBF9B29A5C
2 changed files with 57 additions and 33 deletions

View file

@ -198,6 +198,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Temporary state for AutoHSTS enhancement
self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]]
self._ocsp_prefetch = {} # type: Dict[str, str]
self._ocsp_dbm_bsddb = False
# These will be set in the prepare function
self._prepared = False
@ -2561,15 +2562,53 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if handler.ocsp_request_to_file(ocsp_workfile):
# Guaranteed good response
cache_path = os.path.join(self.config.config_dir, "ocsp", "ocsp_cache")
# dbm.open automatically adds the file extension, it will be
db = dbm.open(cache_path, "c")
db = self._ocsp_dbm_open(cache_path)
cert_sha = apache_util.certid_sha1(cert_path)
db[cert_sha] = self._ocsp_response_dbm(ocsp_workfile)
db.close()
self._ocsp_dbm_close(db)
else:
logger.warning("Encountered an issue while trying to prefetch OCSP "
"response for certificate: %s", cert_path)
def _ensure_ocsp_prefetch_compatibility(self):
"""Make sure that the operating system supports the required libraries
to manage Apache DBM files.
:raises: errors.NotSupportedError
"""
try:
import bsddb
except ImportError:
import dbm
if not hasattr(dbm, 'ndbm'):
msg = ("Unfortunately your operating system does not have a "
"compatible database module available for managing "
"Apache cache database.")
raise errors.NotSupportedError(msg)
def _ocsp_dbm_open(self, filepath):
"""Helper method to open an DBM file in a way that depends on the platform
that Certbot is run on. Returns an open database structure."""
try:
import bsddb
self._ocsp_dbm_bsddb = True
cache_path = filepath + ".db"
database = bsddb.hashopen(cache_path, 'c')
except ImportError:
# Python3 doesn't have bsddb module, so we use dbm.ndbm instead
import dbm
database = dbm.ndbm.open(filepath, 'c')
return database
def _ocsp_dbm_close(self, database):
"""Helper method to sync and close a DBM file, in a way required by the
used dbm implementation."""
if self._ocsp_dbm_bsddb:
database.sync()
database.close()
else:
database.close()
def _ocsp_response_dbm(self, workfile):
"""Creates a dbm entry for OCSP response data
@ -2645,6 +2684,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
TLS. i.e. clients would not have to query the OCSP responder.
"""
# Fail early if we are not able to support this
self._ensure_ocsp_prefetch_compatibility()
prefetch_vhosts = set()
for domain in domains:
matched_vhosts = self.choose_vhosts(domain, create_if_no_ssl=False)
@ -2658,6 +2699,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self._enable_ocsp_stapling(vh, None, prefetch=True)
self._ocsp_prefetch_save(lineage.cert_path, lineage.chain_path)
self.restart()
logger.warning(apache_util.certid_sha1_hex(lineage.cert_path))
self._ocsp_refresh(lineage.cert_path, lineage.chain_path)
def update_ocsp_prefetch(self, _unused_lineage):

View file

@ -12,19 +12,6 @@ class OCSPPrefetchTest(util.ApacheTest):
"""Tests for OCSP Prefetch feature"""
# pylint: disable=protected-access
def openDBM(self, path):
self.config._ensure_ocsp_dirs()
try:
import dbm.ndbm as dbm # pragma: no cover
d = dbm.open(path, 'c')
except ImportError: # pragma: no cover
# dbm.ndbm only available on Python3
import anydbm # pragma: no cover
anydbm._names = ["dbm"]
d = anydbm.open(path, 'c')
return d
def setUp(self): # pylint: disable=arguments-differ
super(OCSPPrefetchTest, self).setUp()
@ -73,6 +60,7 @@ class OCSPPrefetchTest(util.ApacheTest):
@mock.patch("certbot_apache.constants.OCSP_INTERNAL_TTL", 0)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_ocsp_prefetch_refresh(self, _mock_restart):
db_path = os.path.join(self.config_dir, "ocsp", "ocsp_cache")
def ocsp_req_mock(workfile):
"""Method to mock the OCSP request and write response to file"""
with open(workfile, 'w') as fh:
@ -84,11 +72,12 @@ class OCSPPrefetchTest(util.ApacheTest):
self.call_mocked(self.config.enable_ocsp_prefetch,
self.lineage,
["ocspvhost.com"])
odbm = dbm.open(os.path.join(self.config_dir, "ocsp", "ocsp_cache"), 'c')
odbm = self.config._ocsp_dbm_open(db_path)
self.assertEquals(len(odbm.keys()), 1)
# The actual response data is prepended by Apache timestamp
self.assertTrue(odbm[odbm.keys()[0]].endswith(b'MOCKRESPONSE'))
odbm.close()
self.config._ocsp_dbm_close(odbm)
with mock.patch(ocsp_path, side_effect=ocsp_req_mock) as mock_ocsp:
self.call_mocked(self.config.update_ocsp_prefetch, None)
@ -115,17 +104,18 @@ class OCSPPrefetchTest(util.ApacheTest):
@mock.patch("certbot_apache.configurator.ApacheConfigurator.config_test")
def test_ocsp_prefetch_backup_db(self, _mock_test):
db_path = os.path.join(self.config_dir, "ocsp", "ocsp_cache.db")
db_path = os.path.join(self.config_dir, "ocsp", "ocsp_cache")
def ocsp_del_db():
"""Side effect of _reload() that deletes the DBM file, like Apache
does when restarting"""
os.remove(db_path)
self.assertFalse(os.path.isfile(db_path))
full_db_path = db_path + ".db"
os.remove(full_db_path)
self.assertFalse(os.path.isfile(full_db_path))
self.config._ensure_ocsp_dirs()
odbm = dbm.open(db_path[:-3], 'c')
odbm = self.config._ocsp_dbm_open(db_path)
odbm["mock_key"] = b'mock_value'
odbm.close()
self.config._ocsp_dbm_close(odbm)
# Mock OCSP prefetch dict to signify that there should be a db
self.config._ocsp_prefetch = {"mock": "value"}
@ -133,9 +123,9 @@ class OCSPPrefetchTest(util.ApacheTest):
with mock.patch(rel_path, side_effect=ocsp_del_db) as mock_reload:
self.config.restart()
odbm = dbm.open(db_path[:-3], 'c')
odbm = self.config._ocsp_dbm_open(db_path)
self.assertEquals(odbm["mock_key"], b'mock_value')
odbm.close()
self.config._ocsp_dbm_close(odbm)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.config_test")
@mock.patch("certbot_apache.configurator.ApacheConfigurator._reload")
@ -172,14 +162,6 @@ class OCSPPrefetchTest(util.ApacheTest):
self.config.update_ocsp_prefetch(None)
self.assertFalse(mock_refresh.called)
def test_dbm_format(self):
db_path = os.path.join(self.config_dir, "ocsp", "ocsp_cache.db")
dobject = self.openDBM(db_path)
dbm_dir = dir(dobject)
dbm_dir.append(dobject.__module__)
self.assertEquals(1, dbm_dir)
if __name__ == "__main__":
unittest.main() # pragma: no cover