certbot / certbot

Certbot is EFF's tool to obtain certs from Let's Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Avoid permission changes on Windows because of wrong assumptions.

ams-tschoening opened this issue · comments

My operating system is (include version):

  • Windows 10 22H2, Build 19045.2130
  • Windows Server 2019 Version 1809, Build 17763.3406

I installed Certbot with (snap, OS package manager, pip, certbot-auto, etc):

Using the official Windows installer:

Certbot's behavior differed from what I expected because:

I've deployed Certbot 1.31.0 to Windows and recognized that it deals with Windows permissions instead of simply let inheritance of NTFS do it's work. This resulted in 1. the default directory structure not being readable by the expected users and 2. individual certificates issued later as well. The problem with the latter is that this happened after I manually enabled inheritance and fixed the broken ACLs Certbot wrongly believed to make sense. One example for Certbots wrong assumptions is that it expects to be executed as admin user because only those have the necessary permissions to create symlinks on Windows by default. But that permission can be set to arbitrary different users as well, so assumptions about group membership etc. should be avoided as much as possible.

In my setup, the web server is executed as a Windows service using a restricted special user account obviously not being a member of the Administrators group. That user needs to be able to read the created certificates and this is easily possible by setting the necessary ACLs. When I create a new directory as restricted user in C:\, that directory is readable by all other users by default already, which wasn't the case for the Certbot created directory because it changes ACLs. So if Certbot wouldn't have cared about ACLs at all, the web server user would be able to read the created certificates easily. After I fixed that, the web server user was able to read the certificates issued during first test, before I corrected the ACLs, because those file were available during fixing the ACLs. I issued additional certificates afterwards and those weren't readable to the web server user again, because it again changed ACLs for the new files in an incompatible way. I needed to enable inheritance and reset the ACLs again to fix this.

Running Certbot with a different user account than the web server makes sense in general as well, as the web server is far more exposed to risks than a weekly and locally running tool. So running Certbot as admin might be OK mostly, while it's pretty sure not for a web server and not common on other platforms as well. Manually maintaining ACLs is unnecessary difficult and form my experience most assumptions software take are simply wrong, like in this case. So it would be far better to simply not deal with these kinds of things, the same is true for many other platforms.

So it would be great if you could at least provide an option to let Certbot not check and change any permissions at least on Windows. The setup would have been far easier this way and has less potencial for surprises at runtime when renewals are processed. The following code builds a concrete DACL with wrong assumptions:

def _generate_dacl(user_sid: Any, mode: int, mask: Optional[int] = None) -> Any:

System and admins don't need to be handled manually if inheritance would simply be used. OTOH, admins is not who run web server in somewhat secure environments. The current user is distinct from the user who runs the web server as well, so not properly handled. Everyone is mostly not used, that's what groups like Users, Authenticated users etc. are for. So I don't understand what manually maintaining the ACL is even trying to achieve in the end, it can only work in less secure environments but even in those Certbot wouldn't need to care at all, as default inheritance would make it all work already.

I've tested again using certbot renew --force-renewal and certbot really breaks permissions at least for the private keys. The following are the permissions for the older cert file after I enabled inheritance and reset things:

Screenshot correct

The following is for the private key of a newly created cert. As one can see, Windows already says that the current user doesn't have permissions to look at things, which in itself is wrong already.

Screenshot missing read perms

After providing admin credentials, one can see that permissions are only available for very few groups and users and none of those are related to the web server user and group necessary to read the private key.

Screenshot admins only

The following seem to be the relevant discussions about the design decisions behind breaking permissions. That assumption is simply wrong and while some default installers might install web servers with admin accounts, one is free to NOT do that, many web servers have deployments based on ZIPs only so that they don't break permissions and stuff.

For what I saw, any serious service (like Nginx or Apache) will require administrative access to be installed, and will register itself under the LocalSystem, or any account with administrative access.[...]

#6356
#6354

So what's the result of all that work? A pretty secure setup with NOT running web servers as admin doesn't work, while a pretty INSECURE setup running web servers as admins does. So all of your approaches did cost time, result in problems and result in less secure setups. I really don't get it why so many projects think they need to deal with permissions... Just don't do it by default! :-)

I've debugged a bit further and it seems the behaviour of Linux and Windows isn't very consistent right now. Both systems use a pretty restrictive default UMASK (BASE_PRIVKEY_MODE = 0o600), but on Linux that is changed using special permissions of the old private key file.

if POSIX_MODE:
    # On Linux, we keep read/write/execute permissions
    # for group and read permissions for everybody.
    old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) &
                (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH))
    return base_mode | old_mode

# On Windows, the mode returned by os.stat is not reliable,
# so we do not keep any permission from the previous private key.
return base_mode

That is not the case on Windows, which seems to result in the support of writing everyone permissions in the created Windows ACL doesn't get triggered. That support seems to only depend on the given mode, which based on the restrictive UMASK doesn't contain any permissions for everyone.

    # Handle everybody rights
    everybody_flags = _generate_windows_flags(analysis['all'])
    if everybody_flags:
        dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone)

I've additionally tested if some old permissions of the old private key are taken over, like is the case for Linux with everyone. Therefore I explicitly set such permissions on the old private key, but it really doesn't work. Certbot only applies pretty restrictive custom permissions on Windows and read access for everyone is not a workaround. One really either needs to make the web server being part of the admins group as well or reset permissions to use inheritance again. :-/

The latter can be achieved by using the following command line after adding the user or group of interest with read permissions and full inheritance on C:\Certbot. In theory, full inheritance is not needed and one could further restrict the principal, but the command line simply applies all permissions based on inheritance and this is the easiest way to not need to apply permissions for individual principals on individual directories. Too many read permissions don't harm too much, but make maintenance easier when only need to care about the root directory.

icacls "C:\Certbot\*" /reset /T