def test_rollback_finalize_checkpoint_valid_inputs(self): config3 = self._setup_three_checkpoints() # Check resulting backup directory self.assertEqual(len(os.listdir(self.config.backup_dir)), 3) # Check rollbacks # First rollback self.reverter.rollback_checkpoints(1) self.assertEqual(read_in(self.config1), "update config1") self.assertEqual(read_in(self.config2), "update config2") # config3 was not included in checkpoint self.assertEqual(read_in(config3), "Final form config3") # Second rollback self.reverter.rollback_checkpoints(1) self.assertEqual(read_in(self.config1), "update config1") self.assertEqual(read_in(self.config2), "directive-dir2") self.assertFalse(os.path.isfile(config3)) # One dir left... check title all_dirs = os.listdir(self.config.backup_dir) self.assertEqual(len(all_dirs), 1) self.assertTrue( "First Checkpoint" in get_save_notes( os.path.join(self.config.backup_dir, all_dirs[0]))) # Final rollback self.reverter.rollback_checkpoints(1) self.assertEqual(read_in(self.config1), "directive-dir1")
def test_rollback_finalize_checkpoint_valid_inputs(self): config3 = self._setup_three_checkpoints() # Check resulting backup directory self.assertEqual(len(os.listdir(self.config.backup_dir)), 3) # Check rollbacks # First rollback self.reverter.rollback_checkpoints(1) self.assertEqual(read_in(self.config1), "update config1") self.assertEqual(read_in(self.config2), "update config2") # config3 was not included in checkpoint self.assertEqual(read_in(config3), "Final form config3") # Second rollback self.reverter.rollback_checkpoints(1) self.assertEqual(read_in(self.config1), "update config1") self.assertEqual(read_in(self.config2), "directive-dir2") self.assertFalse(os.path.isfile(config3)) # One dir left... check title all_dirs = os.listdir(self.config.backup_dir) self.assertEqual(len(all_dirs), 1) self.assertTrue("First Checkpoint" in get_save_notes( os.path.join(self.config.backup_dir, all_dirs[0]))) # Final rollback self.reverter.rollback_checkpoints(1) self.assertEqual(read_in(self.config1), "directive-dir1")
def _find_all_for_server_path(self, server_path): accounts_dir = self.config.accounts_dir_for_server_path(server_path) try: candidates = os.listdir(accounts_dir) except OSError: return [] accounts = [] for account_id in candidates: try: accounts.append(self._load_for_server_path(account_id, server_path)) except errors.AccountStorageError: logger.debug("Account loading problem", exc_info=True) if not accounts and server_path in constants.LE_REUSE_SERVERS: # find all for the next link down prev_server_path = constants.LE_REUSE_SERVERS[server_path] prev_accounts = self._find_all_for_server_path(prev_server_path) # if we found something, link to that if prev_accounts: try: self._symlink_to_accounts_dir(prev_server_path, server_path) except OSError: return [] accounts = prev_accounts return accounts
def _load_for_server_path(self, account_id, server_path): account_dir_path = self._account_dir_path_for_server_path(account_id, server_path) if not os.path.isdir(account_dir_path): # isdir is also true for symlinks if server_path in constants.LE_REUSE_SERVERS: prev_server_path = constants.LE_REUSE_SERVERS[server_path] prev_loaded_account = self._load_for_server_path(account_id, prev_server_path) # we didn't error so we found something, so create a symlink to that accounts_dir = self.config.accounts_dir_for_server_path(server_path) # If accounts_dir isn't empty, make an account specific symlink if os.listdir(accounts_dir): self._symlink_to_account_dir(prev_server_path, server_path, account_id) else: self._symlink_to_accounts_dir(prev_server_path, server_path) return prev_loaded_account else: raise errors.AccountNotFound( "Account at %s does not exist" % account_dir_path) try: with open(self._regr_path(account_dir_path)) as regr_file: regr = messages.RegistrationResource.json_loads(regr_file.read()) with open(self._key_path(account_dir_path)) as key_file: key = jose.JWK.json_loads(key_file.read()) with open(self._metadata_path(account_dir_path)) as metadata_file: meta = Account.Meta.json_loads(metadata_file.read()) except IOError as error: raise errors.AccountStorageError(error) return Account(regr, key, meta)
def _find_all_for_server_path(self, server_path): accounts_dir = self.config.accounts_dir_for_server_path(server_path) try: candidates = os.listdir(accounts_dir) except OSError: return [] accounts = [] for account_id in candidates: try: accounts.append(self._load_for_server_path(account_id, server_path)) except errors.AccountStorageError: logger.debug("Account loading problem", exc_info=True) if not accounts and server_path in constants.LE_REUSE_SERVERS: # find all for the next link down prev_server_path = constants.LE_REUSE_SERVERS[server_path] prev_accounts = self._find_all_for_server_path(prev_server_path) # if we found something, link to that if prev_accounts: try: self._symlink_to_accounts_dir(prev_server_path, server_path) except OSError: return [] accounts = prev_accounts return accounts
def _load_for_server_path(self, account_id, server_path): account_dir_path = self._account_dir_path_for_server_path(account_id, server_path) if not os.path.isdir(account_dir_path): # isdir is also true for symlinks if server_path in constants.LE_REUSE_SERVERS: prev_server_path = constants.LE_REUSE_SERVERS[server_path] prev_loaded_account = self._load_for_server_path(account_id, prev_server_path) # we didn't error so we found something, so create a symlink to that accounts_dir = self.config.accounts_dir_for_server_path(server_path) # If accounts_dir isn't empty, make an account specific symlink if os.listdir(accounts_dir): self._symlink_to_account_dir(prev_server_path, server_path, account_id) else: self._symlink_to_accounts_dir(prev_server_path, server_path) return prev_loaded_account else: raise errors.AccountNotFound( "Account at %s does not exist" % account_dir_path) try: with open(self._regr_path(account_dir_path)) as regr_file: regr = messages.RegistrationResource.json_loads(regr_file.read()) with open(self._key_path(account_dir_path)) as key_file: key = jose.JWK.json_loads(key_file.read()) with open(self._metadata_path(account_dir_path)) as metadata_file: meta = Account.Meta.json_loads(metadata_file.read()) except IOError as error: raise errors.AccountStorageError(error) acc = Account(regr, key, meta) if acc.id != account_id: raise errors.AccountStorageError( "Account ids mismatch (expected: {0}, found: {1}".format( account_id, acc.id)) return acc
def setUp( self, test_dir="debian_apache_2_4/multiple_vhosts", config_root="debian_apache_2_4/multiple_vhosts/apache2", vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available" ): # pylint: disable=arguments-differ self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( test_dir=test_dir, pkg=__name__) self.config_path = os.path.join(self.temp_dir, config_root) self.vhost_path = os.path.join(self.temp_dir, vhost_root) self.rsa512jwk = jose.JWKRSA.load( test_util.load_vector("rsa512_key.pem")) self.config = get_apache_configurator(self.config_path, vhost_root, self.config_dir, self.work_dir) # Make sure all vhosts in sites-enabled are symlinks (Python packaging # does not preserve symlinks) sites_enabled = os.path.join(self.config_path, "sites-enabled") if not os.path.exists(sites_enabled): return for vhost_basename in os.listdir(sites_enabled): # Keep the one non-symlink test vhost in place if vhost_basename == "non-symlink.conf": continue vhost = os.path.join(sites_enabled, vhost_basename) if not os.path.islink(vhost): # pragma: no cover os.remove(vhost) target = os.path.join(os.path.pardir, "sites-available", vhost_basename) os.symlink(target, vhost)
def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", config_root="debian_apache_2_4/multiple_vhosts/apache2", vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"): # pylint: disable=arguments-differ super(ApacheTest, self).setUp() self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( test_dir=test_dir, pkg="certbot_apache.tests") self.config_path = os.path.join(self.temp_dir, config_root) self.vhost_path = os.path.join(self.temp_dir, vhost_root) self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( "rsa512_key.pem")) self.config = get_apache_configurator(self.config_path, vhost_root, self.config_dir, self.work_dir) # Make sure all vhosts in sites-enabled are symlinks (Python packaging # does not preserve symlinks) sites_enabled = os.path.join(self.config_path, "sites-enabled") if not os.path.exists(sites_enabled): return for vhost_basename in os.listdir(sites_enabled): # Keep the one non-symlink test vhost in place if vhost_basename == "non-symlink.conf": continue vhost = os.path.join(sites_enabled, vhost_basename) if not os.path.islink(vhost): # pragma: no cover os.remove(vhost) target = os.path.join( os.path.pardir, "sites-available", vhost_basename) os.symlink(target, vhost)
def view_config_changes(self): """Displays all saved checkpoints. All checkpoints are printed by :meth:`certbot.interfaces.IDisplay.notification`. .. todo:: Decide on a policy for error handling, OSError IOError... :raises .errors.ReverterError: If invalid directory structure. """ warnings.warn( "The view_config_changes method has been deprecated and will be" " removed in a future release. If you were using this method to" " implement the view_config_changes method of IInstaller, know that" " that method has been removed from the plugin interface and is no" " longer used by Certbot.", DeprecationWarning, stacklevel=2) backups = os.listdir(self.config.backup_dir) backups.sort(reverse=True) if not backups: logger.info("Certbot has not saved backups of your configuration") return None # Make sure there isn't anything unexpected in the backup folder # There should only be timestamped (float) directories try: for bkup in backups: float(bkup) except ValueError: raise errors.ReverterError("Invalid directories in {0}".format( self.config.backup_dir)) output = [] for bkup in backups: output.append(time.ctime(float(bkup))) cur_dir = os.path.join(self.config.backup_dir, bkup) with open(os.path.join(cur_dir, "CHANGES_SINCE")) as changes_fd: output.append(changes_fd.read()) output.append("Affected files:") with open(os.path.join(cur_dir, "FILEPATHS")) as paths_fd: filepaths = paths_fd.read().splitlines() for path in filepaths: output.append(" {0}".format(path)) if os.path.isfile(os.path.join(cur_dir, "NEW_FILES")): with open(os.path.join(cur_dir, "NEW_FILES")) as new_fd: output.append("New Configuration Files:") filepaths = new_fd.read().splitlines() for path in filepaths: output.append(" {0}".format(path)) output.append('\n') zope.component.getUtility(interfaces.IDisplay).notification( '\n'.join(output), force_interactive=True, pause=False) return None
def view_config_changes(self, for_logging=False, num=None): """Displays all saved checkpoints. All checkpoints are printed by :meth:`certbot.interfaces.IDisplay.notification`. .. todo:: Decide on a policy for error handling, OSError IOError... :raises .errors.ReverterError: If invalid directory structure. """ backups = os.listdir(self.config.backup_dir) backups.sort(reverse=True) if num: backups = backups[:num] if not backups: logger.info("Certbot has not saved backups of your configuration") return None # Make sure there isn't anything unexpected in the backup folder # There should only be timestamped (float) directories try: for bkup in backups: float(bkup) except ValueError: raise errors.ReverterError( "Invalid directories in {0}".format(self.config.backup_dir)) output = [] for bkup in backups: output.append(time.ctime(float(bkup))) cur_dir = os.path.join(self.config.backup_dir, bkup) with open(os.path.join(cur_dir, "CHANGES_SINCE")) as changes_fd: output.append(changes_fd.read()) output.append("Affected files:") with open(os.path.join(cur_dir, "FILEPATHS")) as paths_fd: filepaths = paths_fd.read().splitlines() for path in filepaths: output.append(" {0}".format(path)) if os.path.isfile(os.path.join(cur_dir, "NEW_FILES")): with open(os.path.join(cur_dir, "NEW_FILES")) as new_fd: output.append("New Configuration Files:") filepaths = new_fd.read().splitlines() for path in filepaths: output.append(" {0}".format(path)) output.append(os.linesep) if for_logging: return os.linesep.join(output) zope.component.getUtility(interfaces.IDisplay).notification( os.linesep.join(output), force_interactive=True, pause=False) return None
def view_config_changes(self, for_logging=False, num=None): """Displays all saved checkpoints. All checkpoints are printed by :meth:`certbot.interfaces.IDisplay.notification`. .. todo:: Decide on a policy for error handling, OSError IOError... :raises .errors.ReverterError: If invalid directory structure. """ backups = os.listdir(self.config.backup_dir) backups.sort(reverse=True) if num: backups = backups[:num] if not backups: logger.info("Certbot has not saved backups of your configuration") return None # Make sure there isn't anything unexpected in the backup folder # There should only be timestamped (float) directories try: for bkup in backups: float(bkup) except ValueError: raise errors.ReverterError( "Invalid directories in {0}".format(self.config.backup_dir)) output = [] for bkup in backups: output.append(time.ctime(float(bkup))) cur_dir = os.path.join(self.config.backup_dir, bkup) with open(os.path.join(cur_dir, "CHANGES_SINCE")) as changes_fd: output.append(changes_fd.read()) output.append("Affected files:") with open(os.path.join(cur_dir, "FILEPATHS")) as paths_fd: filepaths = paths_fd.read().splitlines() for path in filepaths: output.append(" {0}".format(path)) if os.path.isfile(os.path.join(cur_dir, "NEW_FILES")): with open(os.path.join(cur_dir, "NEW_FILES")) as new_fd: output.append("New Configuration Files:") filepaths = new_fd.read().splitlines() for path in filepaths: output.append(" {0}".format(path)) output.append(os.linesep) if for_logging: return os.linesep.join(output) zope.component.getUtility(interfaces.IDisplay).notification( os.linesep.join(output), force_interactive=True, pause=False) return None
def list_hooks(dir_path): """List paths to all hooks found in dir_path in sorted order. :param str dir_path: directory to search :returns: `list` of `str` :rtype: sorted list of absolute paths to executables in dir_path """ paths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) return sorted(path for path in paths if util.is_exe(path))
def list_hooks(dir_path): """List paths to all hooks found in dir_path in sorted order. :param str dir_path: directory to search :returns: `list` of `str` :rtype: sorted list of absolute paths to executables in dir_path """ paths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) return sorted(path for path in paths if util.is_exe(path))
def list_hooks(dir_path): """List paths to all hooks found in dir_path in sorted order. :param str dir_path: directory to search :returns: `list` of `str` :rtype: sorted list of absolute paths to executables in dir_path """ allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] return sorted(hooks)
def test_no_change(self, mock_read): mock_read.side_effect = OSError("cannot even") try: self.reverter.add_to_checkpoint(self.sets[0], "save1") except OSError: pass self.reverter.finalize_checkpoint("blah") path = os.listdir(self.reverter.config.backup_dir)[0] no_change = os.path.join(self.reverter.config.backup_dir, path, "CHANGES_SINCE") with open(no_change, "r") as f: x = f.read() self.assertTrue("No changes" in x)
def test_no_change(self, mock_read): mock_read.side_effect = OSError("cannot even") try: self.reverter.add_to_checkpoint(self.sets[0], "save1") except OSError: pass self.reverter.finalize_checkpoint("blah") path = os.listdir(self.reverter.config.backup_dir)[0] no_change = os.path.join(self.reverter.config.backup_dir, path, "CHANGES_SINCE") with open(no_change, "r") as f: x = f.read() self.assertTrue("No changes" in x)
def delete(self, account_id): """Delete registration info from disk :param account_id: id of account which should be deleted """ account_dir_path = self._account_dir_path(account_id) if not os.path.isdir(account_dir_path): raise errors.AccountNotFound( "Account at %s does not exist" % account_dir_path) # Step 1: Delete account specific links and the directory self._delete_account_dir_for_server_path(account_id, self.config.server_path) # Step 2: Remove any accounts links and directories that are now empty if not os.listdir(self.config.accounts_dir): self._delete_accounts_dir_for_server_path(self.config.server_path)
def delete(self, account_id): """Delete registration info from disk :param account_id: id of account which should be deleted """ account_dir_path = self._account_dir_path(account_id) if not os.path.isdir(account_dir_path): raise errors.AccountNotFound( "Account at %s does not exist" % account_dir_path) # Step 1: Delete account specific links and the directory self._delete_account_dir_for_server_path(account_id, self.config.server_path) # Step 2: Remove any accounts links and directories that are now empty if not os.listdir(self.config.accounts_dir): self._delete_accounts_dir_for_server_path(self.config.server_path)
def make_lineage(config_dir: str, testfile: str, ec: bool = False) -> str: """Creates a lineage defined by testfile. This creates the archive, live, and renewal directories if necessary and creates a simple lineage. :param str config_dir: path to the configuration directory :param str testfile: configuration file to base the lineage on :param bool ec: True if we generate the lineage with an ECDSA key :returns: path to the renewal conf file for the created lineage :rtype: str """ lineage_name = testfile[:-len('.conf')] conf_dir = os.path.join(config_dir, constants.RENEWAL_CONFIGS_DIR) archive_dir = os.path.join(config_dir, constants.ARCHIVE_DIR, lineage_name) live_dir = os.path.join(config_dir, constants.LIVE_DIR, lineage_name) for directory in ( archive_dir, conf_dir, live_dir, ): if not os.path.exists(directory): filesystem.makedirs(directory) sample_archive = vector_path( 'sample-archive{}'.format('-ec' if ec else '')) for kind in os.listdir(sample_archive): shutil.copyfile(os.path.join(sample_archive, kind), os.path.join(archive_dir, kind)) for kind in storage.ALL_FOUR: os.symlink(os.path.join(archive_dir, '{0}1.pem'.format(kind)), os.path.join(live_dir, '{0}.pem'.format(kind))) conf_path = os.path.join(config_dir, conf_dir, testfile) with open(vector_path(testfile)) as src: with open(conf_path, 'w') as dst: dst.writelines( line.replace('MAGICDIR', config_dir) for line in src) return conf_path
def make_lineage(config_dir, testfile): """Creates a lineage defined by testfile. This creates the archive, live, and renewal directories if necessary and creates a simple lineage. :param str config_dir: path to the configuration directory :param str testfile: configuration file to base the lineage on :returns: path to the renewal conf file for the created lineage :rtype: str """ lineage_name = testfile[:-len('.conf')] conf_dir = os.path.join( config_dir, constants.RENEWAL_CONFIGS_DIR) archive_dir = os.path.join( config_dir, constants.ARCHIVE_DIR, lineage_name) live_dir = os.path.join( config_dir, constants.LIVE_DIR, lineage_name) for directory in (archive_dir, conf_dir, live_dir,): if not os.path.exists(directory): os.makedirs(directory) sample_archive = vector_path('sample-archive') for kind in os.listdir(sample_archive): shutil.copyfile(os.path.join(sample_archive, kind), os.path.join(archive_dir, kind)) for kind in storage.ALL_FOUR: os.symlink(os.path.join(archive_dir, '{0}1.pem'.format(kind)), os.path.join(live_dir, '{0}.pem'.format(kind))) conf_path = os.path.join(config_dir, conf_dir, testfile) with open(vector_path(testfile)) as src: with open(conf_path, 'w') as dst: dst.writelines( line.replace('MAGICDIR', config_dir) for line in src) return conf_path
def available_versions(self, kind): """Which alternative versions of the specified kind of item exist? The archive directory where the current version is stored is consulted to obtain the list of alternatives. :param str kind: the lineage member item ( ``cert``, ``privkey``, ``chain``, or ``fullchain``) :returns: all of the version numbers that currently exist :rtype: `list` of `int` """ if kind not in ALL_FOUR: raise errors.CertStorageError("unknown kind of item") where = os.path.dirname(self.current_target(kind)) files = os.listdir(where) pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) matches = [pattern.match(f) for f in files] return sorted([int(m.groups()[0]) for m in matches if m])
def available_versions(self, kind): """Which alternative versions of the specified kind of item exist? The archive directory where the current version is stored is consulted to obtain the list of alternatives. :param str kind: the lineage member item ( ``cert``, ``privkey``, ``chain``, or ``fullchain``) :returns: all of the version numbers that currently exist :rtype: `list` of `int` """ if kind not in ALL_FOUR: raise errors.CertStorageError("unknown kind of item") where = os.path.dirname(self.current_target(kind)) files = os.listdir(where) pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) matches = [pattern.match(f) for f in files] return sorted([int(m.groups()[0]) for m in matches if m])
def _archive_files(candidate_lineage, filetype): """ In order to match things like: /etc/letsencrypt/archive/example.com/chain1.pem. Anonymous functions which call this function are eventually passed (in a list) to `match_and_check_overlaps` to help specify the acceptable_matches. :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to be searched. :param str filetype: main file name prefix e.g. "fullchain" or "chain". :returns: Files in candidate_lineage's archive dir that match the provided filetype. :rtype: list of str or None """ archive_dir = candidate_lineage.archive_dir pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) if re.match("{0}[0-9]*.pem".format(filetype), f)] if pattern: return pattern return None
def _archive_files(candidate_lineage: storage.RenewableCert, filetype: str) -> Optional[List[str]]: """ In order to match things like: /etc/letsencrypt/archive/example.com/chain1.pem. Anonymous functions which call this function are eventually passed (in a list) to `match_and_check_overlaps` to help specify the acceptable_matches. :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to be searched. :param str filetype: main file name prefix e.g. "fullchain" or "chain". :returns: Files in candidate_lineage's archive dir that match the provided filetype. :rtype: list of str or None """ archive_dir = candidate_lineage.archive_dir pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) if re.match("{0}[0-9]*.pem".format(filetype), f)] if pattern: return pattern return None
def rollback_checkpoints(self, rollback=1): """Revert 'rollback' number of configuration checkpoints. :param int rollback: Number of checkpoints to reverse. A str num will be cast to an integer. So "2" is also acceptable. :raises .ReverterError: if there is a problem with the input or if the function is unable to correctly revert the configuration checkpoints """ try: rollback = int(rollback) except ValueError: logger.error("Rollback argument must be a positive integer") raise errors.ReverterError("Invalid Input") # Sanity check input if rollback < 0: logger.error("Rollback argument must be a positive integer") raise errors.ReverterError("Invalid Input") backups = os.listdir(self.config.backup_dir) backups.sort() if not backups: logger.warning( "Certbot hasn't modified your configuration, so rollback " "isn't available.") elif len(backups) < rollback: logger.warning("Unable to rollback %d checkpoints, only %d exist", rollback, len(backups)) while rollback > 0 and backups: cp_dir = os.path.join(self.config.backup_dir, backups.pop()) try: self._recover_checkpoint(cp_dir) except errors.ReverterError: logger.critical("Failed to load checkpoint during rollback") raise errors.ReverterError( "Unable to load checkpoint during rollback") rollback -= 1
def rollback_checkpoints(self, rollback=1): """Revert 'rollback' number of configuration checkpoints. :param int rollback: Number of checkpoints to reverse. A str num will be cast to an integer. So "2" is also acceptable. :raises .ReverterError: if there is a problem with the input or if the function is unable to correctly revert the configuration checkpoints """ try: rollback = int(rollback) except ValueError: logger.error("Rollback argument must be a positive integer") raise errors.ReverterError("Invalid Input") # Sanity check input if rollback < 0: logger.error("Rollback argument must be a positive integer") raise errors.ReverterError("Invalid Input") backups = os.listdir(self.config.backup_dir) backups.sort() if not backups: logger.warning( "Certbot hasn't modified your configuration, so rollback " "isn't available.") elif len(backups) < rollback: logger.warning("Unable to rollback %d checkpoints, only %d exist", rollback, len(backups)) while rollback > 0 and backups: cp_dir = os.path.join(self.config.backup_dir, backups.pop()) try: self._recover_checkpoint(cp_dir) except errors.ReverterError: logger.critical("Failed to load checkpoint during rollback") raise errors.ReverterError( "Unable to load checkpoint during rollback") rollback -= 1
def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): """Create a new certificate lineage. Attempts to create a certificate lineage -- enrolled for potential future renewal -- with the (suggested) lineage name lineagename, and the associated cert, privkey, and chain (the associated fullchain will be created automatically). Optional configurator and renewalparams record the configuration that was originally used to obtain this cert, so that it can be reused later during automated renewal. Returns a new RenewableCert object referring to the created lineage. (The actual lineage name, as well as all the relevant file paths, will be available within this object.) :param str lineagename: the suggested name for this lineage (normally the current cert's first subject DNS name) :param str cert: the initial certificate version in PEM format :param str privkey: the private key in PEM format :param str chain: the certificate chain in PEM format :param .NamespaceConfig cli_config: parsed command line arguments :returns: the newly-created RenewalCert object :rtype: :class:`storage.renewableCert` """ # Examine the configuration and find the new lineage's name for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, cli_config.live_dir): if not os.path.exists(i): filesystem.makedirs(i, 0o700) logger.debug("Creating directory %s.", i) config_file, config_filename = util.unique_lineage_name( cli_config.renewal_configs_dir, lineagename) base_readme_path = os.path.join(cli_config.live_dir, README) if not os.path.exists(base_readme_path): _write_live_readme_to(base_readme_path, is_base_dir=True) # Determine where on disk everything will go # lineagename will now potentially be modified based on which # renewal configuration file could actually be created lineagename = lineagename_for_filename(config_filename) archive = full_archive_path(None, cli_config, lineagename) live_dir = _full_live_path(cli_config, lineagename) if os.path.exists(archive) and (not os.path.isdir(archive) or os.listdir(archive)): config_file.close() raise errors.CertStorageError("archive directory exists for " + lineagename) if os.path.exists(live_dir) and (not os.path.isdir(live_dir) or os.listdir(live_dir)): config_file.close() raise errors.CertStorageError("live directory exists for " + lineagename) for i in (archive, live_dir): if not os.path.exists(i): filesystem.makedirs(i) logger.debug("Creating directory %s.", i) # Put the data into the appropriate files on disk target = { kind: os.path.join(live_dir, kind + ".pem") for kind in ALL_FOUR } archive_target = { kind: os.path.join(archive, kind + "1.pem") for kind in ALL_FOUR } for kind in ALL_FOUR: os.symlink(_relpath_from_file(archive_target[kind], target[kind]), target[kind]) with open(target["cert"], "wb") as f: logger.debug("Writing certificate to %s.", target["cert"]) f.write(cert) with util.safe_open(archive_target["privkey"], "wb", chmod=BASE_PRIVKEY_MODE) as f: logger.debug("Writing private key to %s.", target["privkey"]) f.write(privkey) # XXX: Let's make sure to get the file permissions right here with open(target["chain"], "wb") as f: logger.debug("Writing chain to %s.", target["chain"]) f.write(chain) with open(target["fullchain"], "wb") as f: # assumes that OpenSSL.crypto.dump_certificate includes # ending newline character logger.debug("Writing full chain to %s.", target["fullchain"]) f.write(cert + chain) # Write a README file to the live directory readme_path = os.path.join(live_dir, README) _write_live_readme_to(readme_path) # Document what we've done in a new renewal config file config_file.close() # Save only the config items that are relevant to renewal values = relevant_values(vars(cli_config.namespace)) new_config = write_renewal_config(config_filename, config_filename, archive, target, values) return cls(new_config.filename, cli_config)