def test_write_renewal_config(self): # Mostly tested by the process of creating and updating lineages, # but we can test that this successfully creates files, removes # unneeded items, and preserves comments. temp = os.path.join(self.config.config_dir, "sample-file") temp2 = os.path.join(self.config.config_dir, "sample-file.new") with open(temp, "w") as f: f.write("[renewalparams]\nuseful = value # A useful value\n" "useless = value # Not needed\n") os.chmod(temp, 0o640) target = {} for x in ALL_FOUR: target[x] = "somewhere" archive_dir = "the_archive" relevant_data = {"useful": "new_value"} from certbot import storage storage.write_renewal_config(temp, temp2, archive_dir, target, relevant_data) with open(temp2, "r") as f: content = f.read() # useful value was updated self.assertTrue("useful = new_value" in content) # associated comment was preserved self.assertTrue("A useful value" in content) # useless value was deleted self.assertTrue("useless" not in content) # check version was stored self.assertTrue("version = {0}".format(certbot.__version__) in content) # ensure permissions are copied self.assertEqual(stat.S_IMODE(os.lstat(temp).st_mode), stat.S_IMODE(os.lstat(temp2).st_mode))
def test_wrong_mode(self): os.chmod(self.tempdir, 0o400) try: self.assertFalse(self._call(0o600)) finally: # Without proper write permissions, Windows is unable to delete a folder, # even with admin permissions. Write access must be explicitly set first. os.chmod(self.tempdir, 0o700)
def create_hook(file_path): """Creates an executable file at the specified path. :param str file_path: path to create the file at """ open(file_path, "w").close() os.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR)
def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): os.makedirs(self.challenge_dir) os.chmod(self.challenge_dir, 0o755) responses = [] for achall in self.achalls: responses.append(self._set_up_challenge(achall)) return responses
def _set_up_challenge(self, achall): response, validation = achall.response_and_validation() name = os.path.join(self.challenge_dir, achall.chall.encode("token")) self.configurator.reverter.register_file_creation(True, name) with open(name, 'wb') as f: f.write(validation.encode()) os.chmod(name, 0o644) return response
def _write_out_kind(self, kind, ver, value=None): link = getattr(self.test_rc, kind) if os.path.lexists(link): os.unlink(link) os.symlink(os.path.join(os.path.pardir, os.path.pardir, "archive", "example.org", "{0}{1}.pem".format(kind, ver)), link) with open(link, "wb") as f: f.write(kind.encode('ascii') if value is None else value) if kind == "privkey": os.chmod(link, 0o600)
def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data): """Writes a renewal config file with the specified name and values. :param str o_filename: Absolute path to the previous version of config file :param str n_filename: Absolute path to the new destination of config file :param str archive_dir: Absolute path to the archive directory :param dict target: Maps ALL_FOUR to their symlink paths :param dict relevant_data: Renewal configuration options to save :returns: Configuration object for the new config file :rtype: configobj.ConfigObj """ config = configobj.ConfigObj(o_filename) config["version"] = certbot.__version__ config["archive_dir"] = archive_dir for kind in ALL_FOUR: config[kind] = target[kind] if "renewalparams" not in config: config["renewalparams"] = {} config.comments["renewalparams"] = [ "", "Options used in " "the renewal process" ] config["renewalparams"].update(relevant_data) for k in config["renewalparams"].keys(): if k not in relevant_data: del config["renewalparams"][k] if "renew_before_expiry" not in config: default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] config.initial_comment = ["renew_before_expiry = " + default_interval] # TODO: add human-readable comments explaining other available # parameters logger.debug("Writing new config %s.", n_filename) # Ensure that the file exists open(n_filename, 'a').close() # Copy permissions from the old version of the file, if it exists. if os.path.exists(o_filename): current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) os.chmod(n_filename, current_permissions) with open(n_filename, "wb") as f: config.write(outfile=f) return config
def test_perform_reraises_other_errors(self): self.auth.full_path = os.path.join(self.path, "null") permission_canary = os.path.join(self.path, "rnd") with open(permission_canary, "w") as f: f.write("thingimy") os.chmod(self.path, 0o000) try: open(permission_canary, "r") print("Warning, running tests as root skips permissions tests...") except IOError: # ok, permissions work, test away... self.assertRaises(errors.PluginError, self.auth.perform, []) os.chmod(self.path, 0o700)
def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data): """Writes a renewal config file with the specified name and values. :param str o_filename: Absolute path to the previous version of config file :param str n_filename: Absolute path to the new destination of config file :param str archive_dir: Absolute path to the archive directory :param dict target: Maps ALL_FOUR to their symlink paths :param dict relevant_data: Renewal configuration options to save :returns: Configuration object for the new config file :rtype: configobj.ConfigObj """ config = configobj.ConfigObj(o_filename) config["version"] = certbot.__version__ config["archive_dir"] = archive_dir for kind in ALL_FOUR: config[kind] = target[kind] if "renewalparams" not in config: config["renewalparams"] = {} config.comments["renewalparams"] = ["", "Options used in " "the renewal process"] config["renewalparams"].update(relevant_data) for k in config["renewalparams"].keys(): if k not in relevant_data: del config["renewalparams"][k] if "renew_before_expiry" not in config: default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] config.initial_comment = ["renew_before_expiry = " + default_interval] # TODO: add human-readable comments explaining other available # parameters logger.debug("Writing new config %s.", n_filename) # Ensure that the file exists open(n_filename, 'a').close() # Copy permissions from the old version of the file, if it exists. if os.path.exists(o_filename): current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) os.chmod(n_filename, current_permissions) with open(n_filename, "wb") as f: config.write(outfile=f) return config
def write(values, path): """Write the specified values to a config file. :param dict values: A map of values to write. :param str path: Where to write the values. """ config = configobj.ConfigObj() for key in values: config[key] = values[key] with open(path, "wb") as f: config.write(outfile=f) os.chmod(path, 0o600)
def test_save_certificate(self, mock_parser): # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] tmp_path = tempfile.mkdtemp() os.chmod(tmp_path, 0o755) # TODO: really?? cert_pem = test_util.load_vector(certs[0]) chain_pem = (test_util.load_vector(certs[0]) + test_util.load_vector(certs[1])) candidate_cert_path = os.path.join(tmp_path, "certs", "cert_512.pem") candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem") candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem") mock_parser.verb = "certonly" mock_parser.args = [ "--cert-path", candidate_cert_path, "--chain-path", candidate_chain_path, "--fullchain-path", candidate_fullchain_path ] cert_path, chain_path, fullchain_path = self.client.save_certificate( cert_pem, chain_pem, candidate_cert_path, candidate_chain_path, candidate_fullchain_path) self.assertEqual(os.path.dirname(cert_path), os.path.dirname(candidate_cert_path)) self.assertEqual(os.path.dirname(chain_path), os.path.dirname(candidate_chain_path)) self.assertEqual(os.path.dirname(fullchain_path), os.path.dirname(candidate_fullchain_path)) with open(cert_path, "rb") as cert_file: cert_contents = cert_file.read() self.assertEqual(cert_contents, test_util.load_vector(certs[0])) with open(chain_path, "rb") as chain_file: chain_contents = chain_file.read() self.assertEqual( chain_contents, test_util.load_vector(certs[0]) + test_util.load_vector(certs[1])) shutil.rmtree(tmp_path)
def dir_setup(test_dir, pkg): # pragma: no cover """Setup the directories necessary for the configurator.""" def expanded_tempdir(prefix): """Return the real path of a temp directory with the specified prefix Some plugins rely on real paths of symlinks for working correctly. For example, certbot-apache uses real paths of configuration files to tell a virtual host from another. On systems where TMP itself is a symbolic link, (ex: OS X) such plugins will be confused. This function prevents such a case. """ return os.path.realpath(tempfile.mkdtemp(prefix)) temp_dir = expanded_tempdir("temp") config_dir = expanded_tempdir("config") work_dir = expanded_tempdir("work") os.chmod(temp_dir, constants.CONFIG_DIRS_MODE) os.chmod(config_dir, constants.CONFIG_DIRS_MODE) os.chmod(work_dir, constants.CONFIG_DIRS_MODE) test_configs = pkg_resources.resource_filename( pkg, os.path.join("testdata", test_dir)) shutil.copytree( test_configs, os.path.join(temp_dir, test_dir), symlinks=True) return temp_dir, config_dir, work_dir
def dir_setup(test_dir, pkg): # pragma: no cover """Setup the directories necessary for the configurator.""" def expanded_tempdir(prefix): """Return the real path of a temp directory with the specified prefix Some plugins rely on real paths of symlinks for working correctly. For example, certbot-apache uses real paths of configuration files to tell a virtual host from another. On systems where TMP itself is a symbolic link, (ex: OS X) such plugins will be confused. This function prevents such a case. """ return os.path.realpath(tempfile.mkdtemp(prefix)) temp_dir = expanded_tempdir("temp") config_dir = expanded_tempdir("config") work_dir = expanded_tempdir("work") os.chmod(temp_dir, constants.CONFIG_DIRS_MODE) os.chmod(config_dir, constants.CONFIG_DIRS_MODE) os.chmod(work_dir, constants.CONFIG_DIRS_MODE) test_configs = pkg_resources.resource_filename( pkg, os.path.join("testdata", test_dir)) shutil.copytree(test_configs, os.path.join(temp_dir, test_dir), symlinks=True) return temp_dir, config_dir, work_dir
def test_save_successor_maintains_group_mode(self, mock_rv): # Mock relevant_values() to claim that all values are relevant here # (to avoid instantiating parser) mock_rv.side_effect = lambda x: x for kind in ALL_FOUR: self._write_out_kind(kind, 1) self.test_rc.update_all_links_to(1) self.assertTrue(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 1)).st_mode, 0o600)) os.chmod(self.test_rc.version("privkey", 1), 0o444) # If no new key, permissions should be the same (we didn't write any keys) self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config) self.assertTrue(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 2)).st_mode, 0o444)) # If new key, permissions should be kept as 644 self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config) self.assertTrue(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 3)).st_mode, 0o644)) # If permissions reverted, next renewal will also revert permissions of new key os.chmod(self.test_rc.version("privkey", 3), 0o400) self.test_rc.save_successor(3, b"newcert", b"new_privkey", b"new chain", self.config) self.assertTrue(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600))
def test_save_certificate(self, mock_parser): # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] tmp_path = tempfile.mkdtemp() os.chmod(tmp_path, 0o755) # TODO: really?? cert_pem = test_util.load_vector(certs[0]) chain_pem = (test_util.load_vector(certs[0]) + test_util.load_vector(certs[1])) candidate_cert_path = os.path.join(tmp_path, "certs", "cert_512.pem") candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem") candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem") mock_parser.verb = "certonly" mock_parser.args = ["--cert-path", candidate_cert_path, "--chain-path", candidate_chain_path, "--fullchain-path", candidate_fullchain_path] cert_path, chain_path, fullchain_path = self.client.save_certificate( cert_pem, chain_pem, candidate_cert_path, candidate_chain_path, candidate_fullchain_path) self.assertEqual(os.path.dirname(cert_path), os.path.dirname(candidate_cert_path)) self.assertEqual(os.path.dirname(chain_path), os.path.dirname(candidate_chain_path)) self.assertEqual(os.path.dirname(fullchain_path), os.path.dirname(candidate_fullchain_path)) with open(cert_path, "rb") as cert_file: cert_contents = cert_file.read() self.assertEqual(cert_contents, test_util.load_vector(certs[0])) with open(chain_path, "rb") as chain_file: chain_contents = chain_file.read() self.assertEqual(chain_contents, test_util.load_vector(certs[0]) + test_util.load_vector(certs[1])) shutil.rmtree(tmp_path)
def save_successor(self, prior_version, new_cert, new_privkey, new_chain, cli_config): """Save new cert and chain as a successor of a prior version. Returns the new version number that was created. .. note:: this function does NOT update links to deploy this version :param int prior_version: the old version to which this version is regarded as a successor (used to choose a privkey, if the key has not changed, but otherwise this information is not permanently recorded anywhere) :param bytes new_cert: the new certificate, in PEM format :param bytes new_privkey: the new private key, in PEM format, or ``None``, if the private key has not changed :param bytes new_chain: the new chain, in PEM format :param .NamespaceConfig cli_config: parsed command line arguments :returns: the new version number that was created :rtype: int """ # XXX: assumes official archive location rather than examining links # XXX: consider using os.open for availability of os.O_EXCL # XXX: ensure file permissions are correct; also create directories # if needed (ensuring their permissions are correct) # Figure out what the new version is and hence where to save things self.cli_config = cli_config target_version = self.next_free_version() target = dict( [(kind, os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version))) for kind in ALL_FOUR]) old_privkey = os.path.join( self.archive_dir, "privkey{0}.pem".format(prior_version)) # Distinguish the cases where the privkey has changed and where it # has not changed (in the latter case, making an appropriate symlink # to an earlier privkey version) if new_privkey is None: # The behavior below keeps the prior key by creating a new # symlink to the old key or the target of the old key symlink. if os.path.islink(old_privkey): old_privkey = os.readlink(old_privkey) else: old_privkey = "privkey{0}.pem".format(prior_version) logger.debug("Writing symlink to old private key, %s.", old_privkey) os.symlink(old_privkey, target["privkey"]) else: with util.safe_open(target["privkey"], "wb", chmod=BASE_PRIVKEY_MODE) as f: logger.debug("Writing new private key to %s.", target["privkey"]) f.write(new_privkey) # Preserve gid and (mode & 074) from previous privkey in this lineage. old_mode = stat.S_IMODE(os.stat(old_privkey).st_mode) & \ (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | \ stat.S_IROTH) mode = BASE_PRIVKEY_MODE | old_mode os.chown(target["privkey"], -1, os.stat(old_privkey).st_gid) os.chmod(target["privkey"], mode) # Save everything else with open(target["cert"], "wb") as f: logger.debug("Writing certificate to %s.", target["cert"]) f.write(new_cert) with open(target["chain"], "wb") as f: logger.debug("Writing chain to %s.", target["chain"]) f.write(new_chain) with open(target["fullchain"], "wb") as f: logger.debug("Writing full chain to %s.", target["fullchain"]) f.write(new_cert + new_chain) symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR) # Update renewal config file self.configfile = update_configuration( self.lineagename, self.archive_dir, symlinks, cli_config) self.configuration = config_with_defaults(self.configfile) return target_version
def save_successor(self, prior_version, new_cert, new_privkey, new_chain, cli_config): """Save new cert and chain as a successor of a prior version. Returns the new version number that was created. .. note:: this function does NOT update links to deploy this version :param int prior_version: the old version to which this version is regarded as a successor (used to choose a privkey, if the key has not changed, but otherwise this information is not permanently recorded anywhere) :param bytes new_cert: the new certificate, in PEM format :param bytes new_privkey: the new private key, in PEM format, or ``None``, if the private key has not changed :param bytes new_chain: the new chain, in PEM format :param .NamespaceConfig cli_config: parsed command line arguments :returns: the new version number that was created :rtype: int """ # XXX: assumes official archive location rather than examining links # XXX: consider using os.open for availability of os.O_EXCL # XXX: ensure file permissions are correct; also create directories # if needed (ensuring their permissions are correct) # Figure out what the new version is and hence where to save things self.cli_config = cli_config target_version = self.next_free_version() target = dict([(kind, os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version))) for kind in ALL_FOUR]) old_privkey = os.path.join(self.archive_dir, "privkey{0}.pem".format(prior_version)) # Distinguish the cases where the privkey has changed and where it # has not changed (in the latter case, making an appropriate symlink # to an earlier privkey version) if new_privkey is None: # The behavior below keeps the prior key by creating a new # symlink to the old key or the target of the old key symlink. if os.path.islink(old_privkey): old_privkey = os.readlink(old_privkey) else: old_privkey = "privkey{0}.pem".format(prior_version) logger.debug("Writing symlink to old private key, %s.", old_privkey) os.symlink(old_privkey, target["privkey"]) else: with util.safe_open(target["privkey"], "wb", chmod=BASE_PRIVKEY_MODE) as f: logger.debug("Writing new private key to %s.", target["privkey"]) f.write(new_privkey) # Preserve gid and (mode & 074) from previous privkey in this lineage. old_mode = stat.S_IMODE(os.stat(old_privkey).st_mode) & \ (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | \ stat.S_IROTH) mode = BASE_PRIVKEY_MODE | old_mode os.chown(target["privkey"], -1, os.stat(old_privkey).st_gid) os.chmod(target["privkey"], mode) # Save everything else with open(target["cert"], "wb") as f: logger.debug("Writing certificate to %s.", target["cert"]) f.write(new_cert) with open(target["chain"], "wb") as f: logger.debug("Writing chain to %s.", target["chain"]) f.write(new_chain) with open(target["fullchain"], "wb") as f: logger.debug("Writing full chain to %s.", target["fullchain"]) f.write(new_cert + new_chain) symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR) # Update renewal config file self.configfile = update_configuration(self.lineagename, self.archive_dir, symlinks, cli_config) self.configuration = config_with_defaults(self.configfile) return target_version
def handle_rw_files(_, path, __): """Handle read-only files, that will fail to be removed on Windows.""" os.chmod(path, stat.S_IWRITE) os.remove(path)
def test_ok_mode(self): os.chmod(self.tempdir, 0o600) self.assertTrue(self._call(0o600))