mirror of
https://github.com/certbot/certbot.git
synced 2026-02-24 02:10:27 -05:00
This PR attempts to better inform people about the problem identified at https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/. I was hesitant to add the flag --no-permissions-check, however, if there's some obscure distro out there (or custom user setup) that has a strange users and groups, I didn't want us to either: Have to put out a bug fix release Refuse to fix the problem and let them deal with warnings on every run * add check_permissions.py * Update letsencrypt-auto.template. * build letsencrypt-auto * Add test_permissions_warnings to auto_test * Allow uid/gid < 1000. * Add --no-permissions-check to Certbot. * Add --no-permissions-check to certbot-auto. * Add test farm test that letsencrypt-auto is quiet. As a bonus, this new test will catch problems like the one that the caused 0.33.1 point release. * Update CHANGELOG about permissions check. * Update permissions comment. * Fix symlink handling. * Use a better default in auto_test.py.
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
"""Verifies certbot-auto cannot be modified by unprivileged users.
|
|
|
|
This script takes the path to certbot-auto as its only command line
|
|
argument. It then checks that the file can only be modified by uid/gid
|
|
< 1000 and if other users can modify the file, it prints a warning with
|
|
a suggestion on how to solve the problem.
|
|
|
|
Permissions on symlinks in the absolute path of certbot-auto are ignored
|
|
and only the canonical path to certbot-auto is checked. There could be
|
|
permissions problems due to the symlinks that are unreported by this
|
|
script, however, issues like this were not caused by our documentation
|
|
and are ignored for the sake of simplicity.
|
|
|
|
All warnings are printed to stdout rather than stderr so all stderr
|
|
output from this script can be suppressed to avoid printing messages if
|
|
this script fails for some reason.
|
|
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import os
|
|
import stat
|
|
import sys
|
|
|
|
|
|
FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/'
|
|
|
|
|
|
def has_safe_permissions(path):
|
|
"""Returns True if the given path has secure permissions.
|
|
|
|
The permissions are considered safe if the file is only writable by
|
|
uid/gid < 1000.
|
|
|
|
The reason we allow more IDs than 0 is because on some systems such
|
|
as Debian, system users/groups other than uid/gid 0 are used for the
|
|
path we recommend in our instructions which is /usr/local/bin. 1000
|
|
was chosen because on Debian 0-999 is reserved for system IDs[1] and
|
|
on RHEL either 0-499 or 0-999 is reserved depending on the
|
|
version[2][3]. Due to these differences across different OSes, this
|
|
detection isn't perfect so we only determine permissions are
|
|
insecure when we can be reasonably confident there is a problem
|
|
regardless of the underlying OS.
|
|
|
|
[1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes
|
|
[2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups
|
|
[3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups
|
|
|
|
:param str path: filesystem path to check
|
|
:returns: True if the path has secure permissions, otherwise, False
|
|
:rtype: bool
|
|
|
|
"""
|
|
# os.stat follows symlinks before obtaining information about a file.
|
|
stat_result = os.stat(path)
|
|
if stat_result.st_mode & stat.S_IWOTH:
|
|
return False
|
|
if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000:
|
|
return False
|
|
if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000:
|
|
return False
|
|
return True
|
|
|
|
|
|
def main(certbot_auto_path):
|
|
current_path = os.path.realpath(certbot_auto_path)
|
|
last_path = None
|
|
permissions_ok = True
|
|
# This loop makes use of the fact that os.path.dirname('/') == '/'.
|
|
while current_path != last_path and permissions_ok:
|
|
permissions_ok = has_safe_permissions(current_path)
|
|
last_path = current_path
|
|
current_path = os.path.dirname(current_path)
|
|
|
|
if not permissions_ok:
|
|
print('{0} has insecure permissions!'.format(certbot_auto_path))
|
|
print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1])
|