def create_hook(file_path): """Creates an executable file at the specified path. :param str file_path: path to create the file at """ util.safe_open(file_path, mode="w", chmod=0o744).close()
def test_has_same_ownership(self): path1 = os.path.join(self.tempdir, 'test1') path2 = os.path.join(self.tempdir, 'test2') util.safe_open(path1, 'w').close() util.safe_open(path2, 'w').close() self.assertTrue(filesystem.has_same_ownership(path1, path2))
def test_valid_file_with_unsafe_permissions(self): log = self._MockLoggingHandler() dns_common.logger.addHandler(log) path = os.path.join(self.tempdir, 'too-permissive-file.ini') util.safe_open(path, "wb", 0o744).close() dns_common.CredentialsConfiguration(path) self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")]))
def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr # If we have a value for new-authz, save it for forwards # compatibility with older versions of Certbot. If we don't # have a value for new-authz, this is an ACMEv2 directory where # an older version of Certbot won't work anyway. if hasattr(acme.directory, "new-authz"): regr = RegistrationResourceWithNewAuthzrURI( new_authzr_uri=acme.directory.new_authz, body={}, uri=regr.uri) else: regr = messages.RegistrationResource( body={}, uri=regr.uri) regr_file.write(regr.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path( account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr # If we have a value for new-authz, save it for forwards # compatibility with older versions of Certbot. If we don't # have a value for new-authz, this is an ACMEv2 directory where # an older version of Certbot won't work anyway. if hasattr(acme.directory, "new-authz"): regr = RegistrationResourceWithNewAuthzrURI( new_authzr_uri=acme.directory.new_authz, body={}, uri=regr.uri) else: regr = messages.RegistrationResource(body={}, uri=regr.uri) regr_file.write(regr.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path(account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _create_challenge_dirs(self): path_map = self.conf("map") if not path_map: raise errors.PluginError( "Missing parts of webroot configuration; please set either " "--webroot-path and --domains, or --webroot-map. Run with " " --help webroot for examples.") for name, path in path_map.items(): self.full_roots[name] = os.path.join(path, os.path.normcase( challenges.HTTP01.URI_ROOT_PATH)) logger.debug("Creating root challenges validation dir at %s", self.full_roots[name]) # Change the permissions to be writable (GH #1389) # Umask is used instead of chmod to ensure the client can also # run as non-root (GH #1795) old_umask = filesystem.umask(0o022) try: # We ignore the last prefix in the next iteration, # as it does not correspond to a folder path ('/' or 'C:') for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): if os.path.isdir(prefix): # Don't try to create directory if it already exists, as some filesystems # won't reliably raise EEXIST or EISDIR if directory exists. continue try: # Set owner as parent directory if possible, apply mode for Linux/Windows. # For Linux, this is coupled with the "umask" call above because # os.mkdir's "mode" parameter may not always work: # https://docs.python.org/3/library/os.html#os.mkdir filesystem.mkdir(prefix, 0o755) self._created_dirs.append(prefix) try: filesystem.copy_ownership_and_apply_mode( path, prefix, 0o755, copy_user=True, copy_group=True) except (OSError, AttributeError) as exception: logger.warning("Unable to change owner and uid of webroot directory") logger.debug("Error was: %s", exception) except OSError as exception: raise errors.PluginError( "Couldn't create root for {0} http-01 " "challenge responses: {1}".format(name, exception)) finally: filesystem.umask(old_umask) # On Windows, generate a local web.config file that allows IIS to serve expose # challenge files despite the fact they do not have a file extension. if not filesystem.POSIX_MODE: web_config_path = os.path.join(self.full_roots[name], "web.config") if os.path.exists(web_config_path): logger.info("A web.config file has not been created in " "%s because another one already exists.", self.full_roots[name]) continue logger.info("Creating a web.config file in %s to allow IIS " "to serve challenge files.", self.full_roots[name]) with safe_open(web_config_path, mode="w", chmod=0o644) as web_config: web_config.write(_WEB_CONFIG_CONTENT)
def __init__(self) -> None: self._workdir = tempfile.mkdtemp() self.path = os.path.join(self._workdir, 'log') stream = util.safe_open(self.path, mode='w', chmod=0o600) super().__init__(stream) # Super constructor assigns the provided stream object to self.stream. # Let's help mypy be aware of this by giving a type hint. self.stream: IO[str] self._delete = True
def save(self, account): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr_file.write(account.regr.json_dumps()) with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path(account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _save(self, account, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr_file.write(account.regr.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path( account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _open_pem_file(cli_arg_path, pem_path): """Open a pem file. If cli_arg_path was set by the client, open that. Otherwise, uniquify the file path. :param str cli_arg_path: the cli arg name, e.g. cert_path :param str pem_path: the pem file path to open :returns: a tuple of file object and its absolute file path """ if cli.set_by_cli(cli_arg_path): return util.safe_open(pem_path, chmod=0o644, mode="wb"),\ os.path.abspath(pem_path) uniq = util.unique_file(pem_path, 0o644, "wb") return uniq[0], os.path.abspath(uniq[1])
def _perform_single(self, achall): response, validation = achall.response_and_validation() root_path = self.full_roots[achall.domain] validation_path = self._get_validation_path(root_path, achall) logger.debug("Attempting to save validation to %s", validation_path) # Change permissions to be world-readable, owner-writable (GH #1795) old_umask = os.umask(0o022) try: with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: validation_file.write(validation.encode()) finally: os.umask(old_umask) self.performed[root_path].add(achall) return response
def _setup_challenge_cert(self, achall, cert_key=None): """Generate and write out challenge certificate.""" cert_path = self.get_cert_path(achall) key_path = self.get_key_path(achall) # Register the path before you write out the file self.configurator.reverter.register_file_creation(True, key_path) self.configurator.reverter.register_file_creation(True, cert_path) response, (cert, key) = achall.response_and_validation(cert_key=cert_key) cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) # Write out challenge cert and key with open(cert_path, "wb") as cert_chall_fd: cert_chall_fd.write(cert_pem) with util.safe_open(key_path, "wb", chmod=0o400) as key_file: key_file.write(key_pem) return response
def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr with_uri = RegistrationResourceWithNewAuthzrURI( new_authzr_uri=acme.directory.new_authz, body=regr.body, uri=regr.uri, terms_of_service=regr.terms_of_service) regr_file.write(with_uri.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path( account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _setup_challenge_cert(self, achall, cert_key=None): """Generate and write out challenge certificate.""" cert_path = self.get_cert_path(achall) key_path = self.get_key_path(achall) # Register the path before you write out the file self.configurator.reverter.register_file_creation(True, key_path) self.configurator.reverter.register_file_creation(True, cert_path) response, (cert, key) = achall.response_and_validation(cert_key=cert_key) cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) # Write out challenge cert and key with open(cert_path, "wb") as cert_chall_fd: cert_chall_fd.write(cert_pem) with util.safe_open(key_path, 'wb', chmod=0o400) as key_file: key_file.write(key_pem) return response
def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr with_uri = RegistrationResourceWithNewAuthzrURI( new_authzr_uri=acme.directory.new_authz, body=regr.body, uri=regr.uri, terms_of_service=regr.terms_of_service) regr_file.write(with_uri.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path(account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _create(self, account, dir_path): # type: (Account, str) -> None with util.safe_open(self._key_path(dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps())
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)
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 = { 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 = filesystem.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 & MASK_FOR_PRIVATE_KEY_PERMISSIONS) # from previous privkey in this lineage. mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) filesystem.copy_ownership_and_apply_mode(old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) # 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 = {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 _create_probe(tempdir): filesystem.chmod(tempdir, 0o744) probe_path = os.path.join(tempdir, 'probe') util.safe_open(probe_path, 'w', chmod=0o744).close() return probe_path
def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): # pylint: disable=too-many-locals """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): os.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): config_file.close() raise errors.CertStorageError( "archive directory exists for " + lineagename) if os.path.exists(live_dir): config_file.close() raise errors.CertStorageError( "live directory exists for " + lineagename) os.mkdir(archive) os.mkdir(live_dir) logger.debug("Archive directory %s and live " "directory %s created.", archive, live_dir) # Put the data into the appropriate files on disk target = dict([(kind, os.path.join(live_dir, kind + ".pem")) for kind in ALL_FOUR]) archive_target = dict([(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)
def __init__(self): self._workdir = tempfile.mkdtemp() self.path = os.path.join(self._workdir, 'log') stream = util.safe_open(self.path, mode='w', chmod=0o600) super().__init__(stream) self._delete = True
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