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( filesystem.check_mode(self.test_rc.version("privkey", 1), 0o600)) filesystem.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( filesystem.check_mode(self.test_rc.version("privkey", 2), 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( filesystem.check_mode(self.test_rc.version("privkey", 3), 0o644)) # If permissions reverted, next renewal will also revert permissions of new key filesystem.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( filesystem.check_mode(self.test_rc.version("privkey", 4), 0o600))
def test_user_admin_dacl_consistency(self): # Set ownership of target to authenticated user authenticated_user, _, _ = win32security.LookupAccountName( "", win32api.GetUserName()) security_owner = _get_security_owner(self.probe_path) _set_owner(self.probe_path, security_owner, authenticated_user) filesystem.chmod(self.probe_path, 0o700) security_dacl = _get_security_dacl(self.probe_path) # We expect three ACE: one for admins, one for system, and one for the user self.assertEqual( security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 3) # Set ownership of target to Administrators user group admin_user = win32security.ConvertStringSidToSid(ADMINS_SID) security_owner = _get_security_owner(self.probe_path) _set_owner(self.probe_path, security_owner, admin_user) filesystem.chmod(self.probe_path, 0o700) security_dacl = _get_security_dacl(self.probe_path) # We expect only two ACE: one for admins, one for system, # since the user is also the admins group self.assertEqual( security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2)
def setUp(self): from certbot_dns_acmedns.dns_acmedns import Authenticator super(AuthenticatorTest, self).setUp() self.reg_file = os.path.join(self.tempdir, 'acmedns-registration.json') with open(self.reg_file, 'w') as fp: json.dump(ACMEDNS_REGISTRATION, fp) filesystem.chmod(self.reg_file, 0o600) path = os.path.join(self.tempdir, 'certbot-acmedns-credentials.ini') dns_test_common.write( { "acmedns_api_url": ACMEDNS_URL, "acmedns_registration_file": self.reg_file }, path) self.config = mock.MagicMock( acmedns_credentials=path, acmedns_propagation_seconds=0) # don't wait during tests self.auth = Authenticator(self.config, "acmedns") self.mock_client = mock.MagicMock() # _get_acmedns_client | pylint: disable=protected-access self.auth._get_acmedns_client = mock.MagicMock( return_value=self.mock_client)
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") filesystem.chmod(temp, 0o640) target = {} for x in ALL_FOUR: target[x] = "somewhere" archive_dir = "the_archive" relevant_data = {"useful": "new_value"} from certbot._internal 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): filesystem.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. filesystem.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() filesystem.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR)
def test_group_permissions_noop(self): filesystem.chmod(self.probe_path, 0o700) ref_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() filesystem.chmod(self.probe_path, 0o740) cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() self.assertTrue(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access
def handle_rw_files(_, path, __): """Handle read-only files, that will fail to be removed on Windows.""" filesystem.chmod(path, stat.S_IWRITE) try: os.remove(path) except (IOError, OSError): # TODO: remote the try/except once all logic from windows file permissions is merged if os.name != 'nt': raise
def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): os.makedirs(self.challenge_dir) filesystem.chmod(self.challenge_dir, 0o755) responses = [] for achall in self.achalls: responses.append(self._set_up_challenge(achall)) return responses
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, encoding='utf-8', default_encoding='utf-8') 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"]: 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 with open(n_filename, 'a'): pass # 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) filesystem.chmod(n_filename, current_permissions) with open(n_filename, "wb") as f: config.write(outfile=f) return config
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()) filesystem.chmod(name, 0o644) return response
def test_compute_private_key_mode(self): filesystem.chmod(self.probe_path, 0o777) new_mode = filesystem.compute_private_key_mode(self.probe_path, 0o600) if POSIX_MODE: # On Linux RWX permissions for group and R permission for world # are persisted from the existing moe self.assertEqual(new_mode, 0o674) else: # On Windows no permission is persisted self.assertEqual(new_mode, 0o600)
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": filesystem.chmod(link, 0o600)
def test_symlink_loop_mitigation(self): link1_path = os.path.join(self.tempdir, 'link1') link2_path = os.path.join(self.tempdir, 'link2') link3_path = os.path.join(self.tempdir, 'link3') os.symlink(link1_path, link2_path) os.symlink(link2_path, link3_path) os.symlink(link3_path, link1_path) with self.assertRaises(RuntimeError) as error: filesystem.chmod(link1_path, 0o755) self.assertTrue('link1 is a loop!' in str(error.exception))
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") filesystem.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, []) filesystem.chmod(self.path, 0o700)
def test_world_permission(self): everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) filesystem.chmod(self.probe_path, 0o700) dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == everybody]) filesystem.chmod(self.probe_path, 0o704) dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() self.assertTrue([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == everybody])
def test_symlink_resolution(self): link_path = os.path.join(self.tempdir, 'link') os.symlink(self.probe_path, link_path) ref_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() ref_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl() filesystem.chmod(link_path, 0o700) # Assert the real file is impacted, not the link. cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() cur_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl() self.assertFalse(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access self.assertTrue(filesystem._compare_dacls(ref_dacl_link, cur_dacl_link)) # pylint: disable=protected-access
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) filesystem.chmod(path, 0o600)
def test_copy_ownership_and_mode_windows(self): src = self.probe_path dst = _create_probe(self.tempdir, name='dst') filesystem.chmod(src, 0o700) self.assertTrue(filesystem.check_mode(src, 0o700)) self.assertTrue(filesystem.check_mode(dst, 0o744)) # Checking an actual change of owner is tricky during a unit test, since we do not know # if any user exists beside the current one. So we mock _copy_win_ownership. It's behavior # have been checked theoretically with test_copy_ownership_and_apply_mode_windows. with mock.patch('certbot.compat.filesystem._copy_win_ownership') as mock_copy_owner: filesystem.copy_ownership_and_mode(src, dst) mock_copy_owner.assert_called_once_with(src, dst) self.assertTrue(filesystem.check_mode(dst, 0o700))
def _test_flag(self, everyone_mode, windows_flag): # Note that flag is tested against `everyone`, not `user`, because practically these unit # tests are executed with admin privilege, so current user is effectively the admins group, # and so will always have all rights. filesystem.chmod(self.probe_path, 0o700 | everyone_mode) dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) acls_everybody = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == everybody] self.assertEqual(len(acls_everybody), 1) acls_everybody = acls_everybody[0] self.assertEqual(acls_everybody[1], windows_flag)
def setUp(self): from certbot_dns_acmedns.dns_acmedns import _AcmeDNSClient self.fake_client = mock.MagicMock() self.ACMEDNS_REGISTRATION_FILE = tempfile.NamedTemporaryFile() with open(self.ACMEDNS_REGISTRATION_FILE.name, 'w') as fp: json.dump(ACMEDNS_REGISTRATION, fp) filesystem.chmod(self.ACMEDNS_REGISTRATION_FILE.name, 0o600) self.acmedns_client = _AcmeDNSClient( api_url=ACMEDNS_URL, credentials_file=self.ACMEDNS_REGISTRATION_FILE.name, ttl=self.TTL) self.acmedns_client.client = self.fake_client
def test_admin_permissions(self): system = win32security.ConvertStringSidToSid(SYSTEM_SID) admins = win32security.ConvertStringSidToSid(ADMINS_SID) filesystem.chmod(self.probe_path, 0o400) dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() system_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == system] admin_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) if dacl.GetAce(index)[2] == admins] self.assertEqual(len(system_aces), 1) self.assertEqual(len(admin_aces), 1) self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS)
def test_save_certificate(self, mock_parser): # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] tmp_path = tempfile.mkdtemp() filesystem.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 filesystem.realpath(tempfile.mkdtemp(prefix)) temp_dir = expanded_tempdir("temp") config_dir = expanded_tempdir("config") work_dir = expanded_tempdir("work") filesystem.chmod(temp_dir, constants.CONFIG_DIRS_MODE) filesystem.chmod(config_dir, constants.CONFIG_DIRS_MODE) filesystem.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_check_min_permissions(self): filesystem.chmod(self.probe_path, 0o744) self.assertTrue(filesystem.has_min_permissions(self.probe_path, 0o744)) filesystem.chmod(self.probe_path, 0o700) self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) filesystem.chmod(self.probe_path, 0o741) self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744))
def test_is_world_reachable(self): filesystem.chmod(self.probe_path, 0o744) self.assertTrue(filesystem.has_world_permissions(self.probe_path)) filesystem.chmod(self.probe_path, 0o700) self.assertFalse(filesystem.has_world_permissions(self.probe_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) filesystem.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 test_check_mode(self): self.assertTrue(filesystem.check_mode(self.probe_path, 0o744)) filesystem.chmod(self.probe_path, 0o700) self.assertFalse(filesystem.check_mode(self.probe_path, 0o744))
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 test_ok_mode(self): filesystem.chmod(self.tempdir, 0o600) self.assertTrue(self._call(0o600))