def test_copy_ownership_windows(self): system = win32security.ConvertStringSidToSid(SYSTEM_SID) security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR security.SetSecurityDescriptorOwner(system, False) with mock.patch('win32security.GetFileSecurity') as mock_get: with mock.patch('win32security.SetFileSecurity') as mock_set: mock_get.return_value = security filesystem.copy_ownership_and_apply_mode('dummy', self.probe_path, 0o700, copy_user=True, copy_group=False) self.assertEqual(mock_set.call_count, 2) first_call = mock_set.call_args_list[0] security = first_call[0][2] self.assertEqual(system, security.GetSecurityDescriptorOwner()) second_call = mock_set.call_args_list[1] security = second_call[0][2] dacl = security.GetSecurityDescriptorDacl() everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) self.assertTrue(dacl.GetAceCount()) self.assertFalse([ dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == everybody ])
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 test_copy_ownership_and_apply_mode_linux(self): with mock.patch('os.chown') as mock_chown: with mock.patch('os.chmod') as mock_chmod: with mock.patch('os.stat') as mock_stat: mock_stat.return_value.st_uid = 50 mock_stat.return_value.st_gid = 51 filesystem.copy_ownership_and_apply_mode( 'dummy', self.probe_path, 0o700, copy_user=True, copy_group=True) mock_chown.assert_called_once_with(self.probe_path, 50, 51) mock_chmod.assert_called_once_with(self.probe_path, 0o700)
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, 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 = os.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): 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.info( "Unable to change owner and uid of webroot directory" ) logger.debug("Error was: %s", exception) except OSError as exception: if exception.errno not in (errno.EEXIST, errno.EISDIR): raise errors.PluginError( "Couldn't create root for {0} http-01 " "challenge responses: {1}".format( name, exception)) finally: os.umask(old_umask)
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