Compare commits
32 Commits
test-integ
...
test-probl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4552b31987 | ||
|
|
6c1d7ad59a | ||
|
|
9f40826fb5 | ||
|
|
7d1ced2aa0 | ||
|
|
d43d293fb6 | ||
|
|
9c714b3453 | ||
|
|
9a32631191 | ||
|
|
2e1c79ed2a | ||
|
|
57a3e9450a | ||
|
|
d0ce83453a | ||
|
|
8e01f7f561 | ||
|
|
7e1f284d36 | ||
|
|
0b6c5d6003 | ||
|
|
17c80ad60d | ||
|
|
6b724380a2 | ||
|
|
907c688337 | ||
|
|
f804fe3836 | ||
|
|
f3142d92f2 | ||
|
|
831c08f0a5 | ||
|
|
bc13b66c4f | ||
|
|
0ca8352748 | ||
|
|
50ebbefd0f | ||
|
|
53f79c9029 | ||
|
|
f143582734 | ||
|
|
39ca987406 | ||
|
|
44c07ffc8c | ||
|
|
4b9d834e46 | ||
|
|
91e9022848 | ||
|
|
e822f8a4ad | ||
|
|
3dca99f211 | ||
|
|
b640e5bf36 | ||
|
|
f26cf5a3a3 |
112
.travis.yml
112
.travis.yml
@@ -96,111 +96,6 @@ matrix:
|
||||
env: TOXENV=nginxroundtrip
|
||||
<<: *not-on-master
|
||||
|
||||
# Extended test suite on cron jobs and pushes to tested branches other than master
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37 CERTBOT_NO_PIN=1
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_xenial
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_jessie
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_centos6
|
||||
services: docker
|
||||
<<: *extended-test-suite
|
||||
- sudo: required
|
||||
env: TOXENV=docker_dev
|
||||
services: docker
|
||||
addons:
|
||||
apt:
|
||||
packages: # don't install nginx and apache
|
||||
- libaugeas0
|
||||
<<: *extended-test-suite
|
||||
- language: generic
|
||||
env: TOXENV=py27
|
||||
os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python2
|
||||
<<: *extended-test-suite
|
||||
- language: generic
|
||||
env: TOXENV=py3
|
||||
os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python3
|
||||
<<: *extended-test-suite
|
||||
|
||||
# container-based infrastructure
|
||||
sudo: false
|
||||
@@ -228,10 +123,3 @@ after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- secure: "SGWZl3ownKx9xKVV2VnGt7DqkTmutJ89oJV9tjKhSs84kLijU6EYdPnllqISpfHMTxXflNZuxtGo0wTDYHXBuZL47w1O32W6nzuXdra5zC+i4sYQwYULUsyfOv9gJX8zWAULiK0Z3r0oho45U+FR5ZN6TPCidi8/eGU+EEPwaAw="
|
||||
on_cancel: never
|
||||
on_success: never
|
||||
on_failure: always
|
||||
use_notice: true
|
||||
|
||||
@@ -5,6 +5,7 @@ from acme.magic_typing import List, Set # pylint: disable=unused-import, no-nam
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_apache.obj import VirtualHost # pylint: disable=unused-import
|
||||
@@ -169,7 +170,7 @@ class ApacheHttp01(common.TLSSNI01):
|
||||
def _set_up_challenges(self):
|
||||
if not os.path.isdir(self.challenge_dir):
|
||||
os.makedirs(self.challenge_dir)
|
||||
os.chmod(self.challenge_dir, 0o755)
|
||||
filesystem.chmod(self.challenge_dir, 0o755)
|
||||
|
||||
responses = []
|
||||
for achall in self.achalls:
|
||||
@@ -185,7 +186,7 @@ class ApacheHttp01(common.TLSSNI01):
|
||||
self.configurator.reverter.register_file_creation(True, name)
|
||||
with open(name, 'wb') as f:
|
||||
f.write(validation.encode())
|
||||
os.chmod(name, 0o644)
|
||||
filesystem.chmod(name, 0o644)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from certbot import achallenges
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.tests import acme_util
|
||||
from certbot.tests import util as certbot_util
|
||||
|
||||
@@ -1366,7 +1367,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot"))
|
||||
os.chmod(tmp_path, 0o755)
|
||||
filesystem.chmod(tmp_path, 0o755)
|
||||
mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path"
|
||||
mock_a = "certbot_apache.parser.ApacheParser.add_include"
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Remember to update setup.py to match the package versions below.
|
||||
acme[dev]==0.29.0
|
||||
certbot[dev]==0.34.0
|
||||
-e .[dev]
|
||||
|
||||
@@ -10,7 +10,7 @@ version = '0.35.0.dev0'
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=0.34.0',
|
||||
'certbot>=0.35.0.dev0',
|
||||
'mock',
|
||||
'python-augeas',
|
||||
'setuptools',
|
||||
|
||||
133
certbot/compat/filesystem.py
Normal file
133
certbot/compat/filesystem.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Compat module to handle files security on Windows and Linux"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os # pylint: disable=os-module-forbidden
|
||||
import stat
|
||||
|
||||
try:
|
||||
import ntsecuritycon # pylint: disable=import-error
|
||||
import win32security # pylint: disable=import-error
|
||||
except ImportError:
|
||||
POSIX_MODE = True
|
||||
else:
|
||||
POSIX_MODE = False
|
||||
|
||||
|
||||
def chmod(file_path, mode):
|
||||
# type: (str, int) -> None
|
||||
"""
|
||||
Apply a POSIX mode on given file_path:
|
||||
* for Linux, the POSIX mode will be directly applied using chmod,
|
||||
* for Windows, the POSIX mode will be translated into a Windows DACL that make sense for
|
||||
Certbot context, and applied to the file using kernel calls.
|
||||
|
||||
The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot,
|
||||
is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the
|
||||
method _generate_windows_flags().
|
||||
|
||||
:param str file_path: Path of the file
|
||||
:param int mode: POSIX mode to apply
|
||||
"""
|
||||
if POSIX_MODE:
|
||||
os.chmod(file_path, mode)
|
||||
else:
|
||||
_apply_win_mode(file_path, mode)
|
||||
|
||||
|
||||
def _apply_win_mode(file_path, mode):
|
||||
# Resolve symbolic links
|
||||
if os.path.islink(file_path):
|
||||
link_path = file_path
|
||||
file_path = os.readlink(file_path)
|
||||
if not os.path.isabs(file_path):
|
||||
file_path = os.path.join(os.path.dirname(link_path), file_path)
|
||||
# Get owner sid of the file
|
||||
security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION)
|
||||
user = security.GetSecurityDescriptorOwner()
|
||||
|
||||
# New DACL, that will overwrite existing one (including inherited permissions)
|
||||
dacl = _generate_dacl(user, mode)
|
||||
|
||||
# Apply the new DACL
|
||||
security.SetSecurityDescriptorDacl(1, dacl, 0)
|
||||
win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security)
|
||||
|
||||
|
||||
def _generate_dacl(user_sid, mode):
|
||||
analysis = _analyze_mode(mode)
|
||||
|
||||
# Get standard accounts from "well-known" sid
|
||||
# See the list here:
|
||||
# https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
|
||||
system = win32security.ConvertStringSidToSid('S-1-5-18')
|
||||
admins = win32security.ConvertStringSidToSid('S-1-5-32-544')
|
||||
everyone = win32security.ConvertStringSidToSid('S-1-1-0')
|
||||
|
||||
# New dacl, without inherited permissions
|
||||
dacl = win32security.ACL()
|
||||
|
||||
# If user is already system or admins, any ACE defined here would be superseeded by
|
||||
# the full control ACE that will be added after.
|
||||
if str(user_sid) not in [str(system), str(admins)]:
|
||||
# Handle user rights
|
||||
user_flags = _generate_windows_flags(analysis['user'])
|
||||
if user_flags:
|
||||
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid)
|
||||
|
||||
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, ntsecuritycon.FILE_ALL_ACCESS, everyone)
|
||||
|
||||
# Handle administrator rights
|
||||
full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True})
|
||||
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins)
|
||||
|
||||
return dacl
|
||||
|
||||
|
||||
def _analyze_mode(mode):
|
||||
return {
|
||||
'user': {
|
||||
'read': mode & stat.S_IRUSR,
|
||||
'write': mode & stat.S_IWUSR,
|
||||
'execute': mode & stat.S_IXUSR,
|
||||
},
|
||||
'all': {
|
||||
'read': mode & stat.S_IROTH,
|
||||
'write': mode & stat.S_IWOTH,
|
||||
'execute': mode & stat.S_IXOTH,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _generate_windows_flags(rights_desc):
|
||||
# Some notes about how each POSIX right is interpreted.
|
||||
#
|
||||
# For the rights read and execute, we have a pretty bijective relation between
|
||||
# POSIX flags and their generic counterparts on Windows, so we use them directly
|
||||
# (respectively ntsecuritycon.GENERIC_READ) and (respectively ntsecuritycon.GENERIC_EXECUTE).
|
||||
#
|
||||
# But ntsecuritycon.GENERIC_WRITE does not correspond to what one could expect from a write
|
||||
# access on Linux: for Windows, GENERIC_WRITE does not include delete, move or
|
||||
# rename. This is something that requires ntsecuritycon.GENERIC_ALL.
|
||||
# So to reproduce the write right as POSIX, we will apply ntsecuritycon.GENERIC_ALL
|
||||
# substracted of the rights corresponding to POSIX read and POSIX execute.
|
||||
#
|
||||
# Finally, having read + write + execute gives a ntsecuritycon.GENERIC_ALL,
|
||||
# so a "Full Control" on the file.
|
||||
#
|
||||
# A complete list of the rights defined on NTFS can be found here:
|
||||
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders
|
||||
flag = 0
|
||||
if rights_desc['read']:
|
||||
flag = flag | ntsecuritycon.FILE_GENERIC_READ
|
||||
if rights_desc['write']:
|
||||
flag = flag | (ntsecuritycon.FILE_ALL_ACCESS
|
||||
^ ntsecuritycon.FILE_GENERIC_READ
|
||||
^ ntsecuritycon.FILE_GENERIC_EXECUTE
|
||||
# Following bit is never set for file/directory objects using the
|
||||
# ntsecuritycon.FILE_* flags, but is effectively present when
|
||||
# the "Full Permissions" are applied from Windows UI.
|
||||
^ 512)
|
||||
if rights_desc['execute']:
|
||||
flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE
|
||||
|
||||
return flag
|
||||
@@ -29,3 +29,22 @@ std_sys.modules[__name__ + '.path'] = path
|
||||
|
||||
# Clean all remaining importables that are not from the core os module.
|
||||
del ourselves, std_os, std_sys
|
||||
|
||||
|
||||
# Chmod is the root of all evil for our security model on Windows. With the default implementation
|
||||
# of os.chmod on Windows, almost all bits on mode will be ignored, and only a general RO or RW will
|
||||
# be applied. The DACL, the inner mechanism to control file access on Windows, will stay on its
|
||||
# default definition, giving effectively at least read permissions to any one, as the default
|
||||
# permissions on root path will be inherit by the file (as NTFS state), and root path can be read
|
||||
# by anyone. So the given mode needs to be translated into a secured and not inherited DACL that
|
||||
# will be applied to this file using filesystem.chmod, calling internally the win32security
|
||||
# module to construct and apply the DACL. Complete security model to translate a POSIX mode into
|
||||
# a suitable DACL on Windows for Certbot can be found here:
|
||||
# https://github.com/certbot/certbot/issues/6356
|
||||
# Basically, it states that appropriate permissions will be set for the owner, nothing for the
|
||||
# group, appropriate permissions for the "Everyone" group, and all permissions to the
|
||||
# "Administrators" group + "System" user, as they can do everything anyway.
|
||||
def chmod(*unused_args, **unused_kwargs): # pylint: disable=function-redefined
|
||||
"""Method os.chmod() is forbidden"""
|
||||
raise RuntimeError('Usage of os.chmod() is forbidden. ' # pragma: no cover
|
||||
'Use certbot.compat.filesystem.chmod() instead.')
|
||||
|
||||
@@ -20,6 +20,7 @@ from certbot import interfaces
|
||||
from certbot import reverter
|
||||
from certbot import util
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.plugins.storage import PluginStorage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -482,9 +483,9 @@ def dir_setup(test_dir, pkg): # pragma: no cover
|
||||
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)
|
||||
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))
|
||||
|
||||
@@ -8,7 +8,7 @@ import six
|
||||
from acme import challenges
|
||||
|
||||
from certbot import achallenges
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.tests import acme_util
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
@@ -60,4 +60,4 @@ def write(values, path):
|
||||
with open(path, "wb") as f:
|
||||
config.write(outfile=f)
|
||||
|
||||
os.chmod(path, 0o600)
|
||||
filesystem.chmod(path, 0o600)
|
||||
|
||||
@@ -19,6 +19,7 @@ from certbot import achallenges
|
||||
from certbot import errors
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.display import util as display_util
|
||||
from certbot.tests import acme_util
|
||||
from certbot.tests import util as test_util
|
||||
@@ -132,14 +133,14 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
permission_canary = os.path.join(self.path, "rnd")
|
||||
with open(permission_canary, "w") as f:
|
||||
f.write("thingimy")
|
||||
os.chmod(self.path, 0o000)
|
||||
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, [])
|
||||
os.chmod(self.path, 0o700)
|
||||
filesystem.chmod(self.path, 0o700)
|
||||
|
||||
@test_util.skip_on_windows('On Windows, there is no chown.')
|
||||
@mock.patch("certbot.plugins.webroot.os.chown")
|
||||
|
||||
@@ -20,6 +20,7 @@ from certbot import errors
|
||||
from certbot import util
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.plugins import common as plugins_common
|
||||
from certbot.plugins import disco as plugins_disco
|
||||
|
||||
@@ -143,7 +144,7 @@ def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_d
|
||||
# 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)
|
||||
filesystem.chmod(n_filename, current_permissions)
|
||||
|
||||
with open(n_filename, "wb") as f:
|
||||
config.write(outfile=f)
|
||||
@@ -1110,7 +1111,7 @@ class RenewableCert(object):
|
||||
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)
|
||||
filesystem.chmod(target["privkey"], mode)
|
||||
|
||||
# Save everything else
|
||||
with open(target["cert"], "wb") as f:
|
||||
|
||||
@@ -12,6 +12,7 @@ import certbot.tests.util as test_util
|
||||
from certbot import account
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot import util
|
||||
|
||||
KEY = test_util.load_vector("rsa512_key.pem")
|
||||
@@ -423,7 +424,7 @@ class ClientTest(ClientTestCommon):
|
||||
# pylint: disable=too-many-locals
|
||||
certs = ["cert_512.pem", "cert-san_512.pem"]
|
||||
tmp_path = tempfile.mkdtemp()
|
||||
os.chmod(tmp_path, 0o755) # TODO: really??
|
||||
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]))
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""Tests for certbot.compat."""
|
||||
import unittest
|
||||
|
||||
import certbot.tests.util as test_util
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
@@ -19,3 +21,7 @@ class OsReplaceTest(test_util.TempDirTestCase):
|
||||
|
||||
self.assertFalse(os.path.exists(src))
|
||||
self.assertTrue(os.path.exists(dst))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
47
certbot/tests/compat/filesystem_test.py
Normal file
47
certbot/tests/compat/filesystem_test.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Unit test for security module."""
|
||||
import unittest
|
||||
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.tests.util import TempDirTestCase
|
||||
|
||||
|
||||
class FilesystemTest(TempDirTestCase):
|
||||
"""Unit tests for filesystem module."""
|
||||
@unittest.skipIf(os.name != 'nt', reason='Test specific to Windows security')
|
||||
def test_user_admin_dacl_consistency(self):
|
||||
import win32security # pylint: disable=import-error
|
||||
import win32api # pylint: disable=import-error
|
||||
|
||||
target = os.path.join(self.tempdir, 'target')
|
||||
open(target, 'w').close()
|
||||
|
||||
# Set ownership of target to authenticated user
|
||||
authenticated_user, _, _ = win32security.LookupAccountName("", win32api.GetUserName())
|
||||
security_owner = win32security.GetFileSecurity(target, win32security.OWNER_SECURITY_INFORMATION)
|
||||
security_owner.SetSecurityDescriptorOwner(authenticated_user, False)
|
||||
win32security.SetFileSecurity(target, win32security.OWNER_SECURITY_INFORMATION, security_owner)
|
||||
|
||||
filesystem.chmod(target, 0o700)
|
||||
|
||||
security_dacl = win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION)
|
||||
dacl = security_dacl.GetSecurityDescriptorDacl()
|
||||
# We expect three ACE: one for admins, one for system, and one for the user
|
||||
self.assertEqual(dacl.GetAceCount(), 3)
|
||||
|
||||
# Set ownership of target to Administrators user group
|
||||
admin_user = win32security.ConvertStringSidToSid('S-1-5-32-544')
|
||||
security_owner = win32security.GetFileSecurity(target, win32security.OWNER_SECURITY_INFORMATION)
|
||||
security_owner.SetSecurityDescriptorOwner(admin_user, False)
|
||||
win32security.SetFileSecurity(target, win32security.OWNER_SECURITY_INFORMATION, security_owner)
|
||||
|
||||
filesystem.chmod(target, 0o700)
|
||||
|
||||
security_dacl = win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION)
|
||||
dacl = security_dacl.GetSecurityDescriptorDacl()
|
||||
# We expect only two ACE: one for admins, one for system, since the user is also the admins group
|
||||
self.assertEqual(dacl.GetAceCount(), 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
10
certbot/tests/compat/os_test.py
Normal file
10
certbot/tests/compat/os_test.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import unittest
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
|
||||
class OsTest(unittest.TestCase):
|
||||
"""Unit tests for os module."""
|
||||
def test_forbidden_methods(self):
|
||||
for method in ['chmod']:
|
||||
self.assertRaises(RuntimeError, getattr(os, method))
|
||||
@@ -7,6 +7,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.tests import util
|
||||
|
||||
|
||||
@@ -65,7 +66,7 @@ class HookTest(util.ConfigTestCase):
|
||||
"""Common base class for hook tests."""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
def _call(cls, *args, **kwargs): # pragma: no cover
|
||||
"""Calls the method being tested with the given arguments."""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -488,7 +489,7 @@ def create_hook(file_path):
|
||||
|
||||
"""
|
||||
open(file_path, "w").close()
|
||||
os.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR)
|
||||
filesystem.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -15,6 +15,7 @@ import certbot.tests.util as test_util
|
||||
from certbot import errors
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.storage import ALL_FOUR
|
||||
|
||||
CERT = test_util.load_cert('cert_512.pem')
|
||||
@@ -132,7 +133,7 @@ class BaseRenewableCertTest(test_util.ConfigTestCase):
|
||||
with open(link, "wb") as f:
|
||||
f.write(kind.encode('ascii') if value is None else value)
|
||||
if kind == "privkey":
|
||||
os.chmod(link, 0o600)
|
||||
filesystem.chmod(link, 0o600)
|
||||
|
||||
def _write_out_ex_kinds(self):
|
||||
for kind in ALL_FOUR:
|
||||
@@ -572,7 +573,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
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)
|
||||
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(misc.compare_file_modes(
|
||||
@@ -582,7 +583,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
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)
|
||||
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(misc.compare_file_modes(
|
||||
os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600))
|
||||
@@ -776,7 +777,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
with open(temp, "w") as f:
|
||||
f.write("[renewalparams]\nuseful = value # A useful value\n"
|
||||
"useless = value # Not needed\n")
|
||||
os.chmod(temp, 0o640)
|
||||
filesystem.chmod(temp, 0o640)
|
||||
target = {}
|
||||
for x in ALL_FOUR:
|
||||
target[x] = "somewhere"
|
||||
|
||||
@@ -27,6 +27,7 @@ from certbot import lock
|
||||
from certbot import storage
|
||||
from certbot import util
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.display import util as display_util
|
||||
|
||||
|
||||
@@ -340,8 +341,12 @@ class TempDirTestCase(unittest.TestCase):
|
||||
|
||||
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)
|
||||
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
|
||||
pass
|
||||
shutil.rmtree(self.tempdir, onerror=handle_rw_files)
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import certbot.tests.util as test_util
|
||||
from certbot import errors
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
|
||||
|
||||
class RunScriptTest(unittest.TestCase):
|
||||
@@ -188,17 +189,19 @@ class CheckPermissionsTest(test_util.TempDirTestCase):
|
||||
return check_permissions(self.tempdir, mode, self.uid)
|
||||
|
||||
def test_ok_mode(self):
|
||||
os.chmod(self.tempdir, 0o600)
|
||||
filesystem.chmod(self.tempdir, 0o600)
|
||||
self.assertTrue(self._call(0o600))
|
||||
|
||||
# TODO: reactivate the test when all logic from windows file permissions is merged.
|
||||
@test_util.broken_on_windows
|
||||
def test_wrong_mode(self):
|
||||
os.chmod(self.tempdir, 0o400)
|
||||
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.
|
||||
os.chmod(self.tempdir, 0o700)
|
||||
filesystem.chmod(self.tempdir, 0o700)
|
||||
|
||||
|
||||
class UniqueFileTest(test_util.TempDirTestCase):
|
||||
|
||||
14
setup.py
14
setup.py
@@ -3,7 +3,8 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
from distutils.version import StrictVersion
|
||||
from setuptools import find_packages, setup, __version__ as setuptools_version
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
# Workaround for http://bugs.python.org/issue8876, see
|
||||
@@ -52,6 +53,17 @@ install_requires = [
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
# Add pywin32 on Windows platforms to handle low-level system calls.
|
||||
# This dependency needs to be added using environment markers to avoid its installation on Linux.
|
||||
# However environment markers are supported only with setuptools >= 36.2.
|
||||
# So this dependency is not added for old Linux distributions with old setuptools,
|
||||
# in order to allow these systems to build certbot from sources.
|
||||
if StrictVersion(setuptools_version) >= StrictVersion('36.2'):
|
||||
install_requires.append('pywin32 ; sys_platform == \'win32\'')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
|
||||
dev_extras = [
|
||||
'astroid==1.6.5',
|
||||
'coverage',
|
||||
|
||||
@@ -12,7 +12,7 @@ DEFAULT_PACKAGES = [
|
||||
'certbot_dns_sakuracloud', 'certbot_nginx', 'certbot_postfix', 'letshelp_certbot']
|
||||
|
||||
COVER_THRESHOLDS = {
|
||||
'certbot': {'linux': 98, 'windows': 93},
|
||||
'certbot': {'linux': 97, 'windows': 96},
|
||||
'acme': {'linux': 100, 'windows': 99},
|
||||
'certbot_apache': {'linux': 100, 'windows': 100},
|
||||
'certbot_dns_cloudflare': {'linux': 98, 'windows': 98},
|
||||
|
||||
Reference in New Issue
Block a user