From cedd1a259105dc231744d58562f0bc80d4294c7a Mon Sep 17 00:00:00 2001 From: pvogt09 <50047961+pvogt09@users.noreply.github.com> Date: Thu, 11 Nov 2021 17:44:57 +0100 Subject: [PATCH] unit test for umask problems in #3177 and #2730 (#3191) * add test for file permissions of $webroot Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * changes sudo to su for running command as user www-data Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * installs PIHOLE_WEB_DEPS to create LIGHTTPD_USER Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * changes stdout to rc Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * use installPihole instead of installPiholeWeb in test Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * try installation process with main Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * mock systemctl Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * removes stickler errors Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * start lighttpd and make webpage test optional Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * test all files and directories in $webroot Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * fix stickler and codefactor warnings Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * set permission for /var/cache if it did not exist before Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * add test case for pihole files Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * fix stickler errors Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * revert "set permission for /var/cache if it did not exist before" and make lighttpd start work Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * add --add-cap=NET_ADMIN to enable FTL start Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * specify DNS server for cURL Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * check files created by FTL Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * reorder code and change nameserver in /etc/resolv.conf Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * resolve with dig instead of relying on /etc/resolv.conf Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * set IP to 127.0.0.1 in setupVars.conf for blockpage tests Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * resolve domain with dig and remove debug output Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * fix stickler errors Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * no git pull in Github Action runs for pull requests Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * --cap-add=ALL test Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * fix stickler errors Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * remove debug code Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * update_repo patch for CentOS 7 in Github Actions Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * removes TODOs and stickler warnings Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * adds trailing slash to domain Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * use only first result from dig Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * domain name resolution does not work reliably in docker container Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * repair executable permission Signed-off-by: pvogt09 <50047961+pvogt09@users.noreply.github.com> * Create mock_command_passthrough that allows intercepting of specific arguments - everything else is passed through to the proper command. Use this new command instead of making changes in basic-install.sh to make the tests pass. Signed-off-by: Adam Warner Co-authored-by: Adam Warner --- test/README.md | 4 +- test/conftest.py | 69 ++++- test/test_automated_install.py | 514 +++++++++++++++++++++++++++++++++ 3 files changed, 581 insertions(+), 6 deletions(-) diff --git a/test/README.md b/test/README.md index b4dd1122..692155b7 100644 --- a/test/README.md +++ b/test/README.md @@ -18,8 +18,8 @@ py.test -vv -n auto -m "build_stage" py.test -vv -n auto -m "not build_stage" ``` -The build_stage tests have to run first to create the docker images, followed by the actual tests which utilize said images. Unless you're changing your dockerfiles you shouldn't have to run the build_stage every time - but it's a good idea to rebuild at least once a day in case the base Docker images or packages change. +The build_stage tests have to run first to create the docker images, followed by the actual tests which utilize said images. Unless you're changing your dockerfiles you shouldn't have to run the build_stage every time - but it's a good idea to rebuild at least once a day in case the base Docker images or packages change. # How do I debug python? -Highly recommended: Setup PyCharm on a **Docker enabled** machine. Having a python debugger like PyCharm changes your life if you've never used it :) +Highly recommended: Setup PyCharm on a **Docker enabled** machine. Having a python debugger like PyCharm changes your life if you've never used it :) diff --git a/test/conftest.py b/test/conftest.py index 13731eb8..2d388fdd 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -64,7 +64,7 @@ def args(request): ''' -t became required when tput began being used ''' - return '-t -d' + return '-t -d --cap-add=ALL' @pytest.fixture(params=[ @@ -100,7 +100,7 @@ def mock_command(script, args, container): in unit tests ''' full_script_path = '/usr/local/bin/{}'.format(script) - mock_script = dedent('''\ + mock_script = dedent(r'''\ #!/bin/bash -e echo "\$0 \$@" >> /var/log/{script} case "\$1" in'''.format(script=script)) @@ -121,13 +121,75 @@ def mock_command(script, args, container): scriptlog=script)) +def mock_command_passthrough(script, args, container): + ''' + Per other mock_command* functions, allows intercepting of commands we don't want to run for real + in unit tests, however also allows only specific arguments to be mocked. Anything not defined will + be passed through to the actual command. + + Example use-case: mocking `git pull` but still allowing `git clone` to work as intended + ''' + orig_script_path = check_output('which {}'.format(script)) + full_script_path = '/usr/local/bin/{}'.format(script) + mock_script = dedent(r'''\ + #!/bin/bash -e + echo "\$0 \$@" >> /var/log/{script} + case "\$1" in'''.format(script=script)) + for k, v in args.items(): + case = dedent(''' + {arg}) + echo {res} + exit {retcode} + ;;'''.format(arg=k, res=v[0], retcode=v[1])) + mock_script += case + mock_script += dedent(r''' + *) + {orig_script_path} "\$@" + ;;'''.format(orig_script_path=orig_script_path)) + mock_script += dedent(''' + esac''') + container.run(''' + cat < {script}\n{content}\nEOF + chmod +x {script} + rm -f /var/log/{scriptlog}'''.format(script=full_script_path, + content=mock_script, + scriptlog=script)) + + +def mock_command_run(script, args, container): + ''' + Allows for setup of commands we don't really want to have to run for real + in unit tests + ''' + full_script_path = '/usr/local/bin/{}'.format(script) + mock_script = dedent(r'''\ + #!/bin/bash -e + echo "\$0 \$@" >> /var/log/{script} + case "\$1 \$2" in'''.format(script=script)) + for k, v in args.items(): + case = dedent(''' + \"{arg}\") + echo {res} + exit {retcode} + ;;'''.format(arg=k, res=v[0], retcode=v[1])) + mock_script += case + mock_script += dedent(''' + esac''') + container.run(''' + cat < {script}\n{content}\nEOF + chmod +x {script} + rm -f /var/log/{scriptlog}'''.format(script=full_script_path, + content=mock_script, + scriptlog=script)) + + def mock_command_2(script, args, container): ''' Allows for setup of commands we don't really want to have to run for real in unit tests ''' full_script_path = '/usr/local/bin/{}'.format(script) - mock_script = dedent('''\ + mock_script = dedent(r'''\ #!/bin/bash -e echo "\$0 \$@" >> /var/log/{script} case "\$1 \$2" in'''.format(script=script)) @@ -147,7 +209,6 @@ def mock_command_2(script, args, container): content=mock_script, scriptlog=script)) - def run_script(Pihole, script): result = Pihole.run(script) assert result.rc == 0 diff --git a/test/test_automated_install.py b/test/test_automated_install.py index 37ebdad2..304a8783 100644 --- a/test/test_automated_install.py +++ b/test/test_automated_install.py @@ -1,3 +1,4 @@ +import pytest from textwrap import dedent import re from .conftest import ( @@ -6,7 +7,9 @@ from .conftest import ( info_box, cross_box, mock_command, + mock_command_run, mock_command_2, + mock_command_passthrough, run_script ) @@ -109,6 +112,7 @@ def test_installPiholeWeb_fresh_install_no_errors(Pihole): confirms all web page assets from Core repo are installed on a fresh build ''' installWeb = Pihole.run(''' + umask 0027 source /opt/pihole/basic-install.sh installPiholeWeb ''') @@ -129,6 +133,516 @@ def test_installPiholeWeb_fresh_install_no_errors(Pihole): assert 'blockingpage.css' in web_directory +def get_directories_recursive(Pihole, directory): + if directory is None: + return directory + ls = Pihole.run('ls -d {}'.format(directory + '/*/')) + directories = list(filter(bool, ls.stdout.splitlines())) + dirs = directories + for dirval in directories: + dir_rec = get_directories_recursive(Pihole, dirval) + if isinstance(dir_rec, str): + dirs.extend([dir_rec]) + else: + dirs.extend(dir_rec) + return dirs + + +def test_installPihole_fresh_install_readableFiles(Pihole): + ''' + confirms all neccessary files are readable by pihole user + ''' + # Whiptail dialog returns Cancel for user prompt + mock_command('whiptail', {'*': ('', '0')}, Pihole) + # mock git pull + mock_command_passthrough('git', {'pull': ('', '0')}, Pihole) + # mock systemctl to not start lighttpd and FTL + mock_command_2( + 'systemctl', + { + 'enable lighttpd': ( + '', + '0' + ), + 'restart lighttpd': ( + '', + '0' + ), + 'start lighttpd': ( + '', + '0' + ), + 'enable pihole-FTL': ( + '', + '0' + ), + 'restart pihole-FTL': ( + '', + '0' + ), + 'start pihole-FTL': ( + '', + '0' + ), + '*': ( + 'echo "systemctl call with $@"', + '0' + ), + }, + Pihole + ) + # try to install man + Pihole.run('command -v apt-get > /dev/null && apt-get install -qq man') + Pihole.run('command -v dnf > /dev/null && dnf install -y man') + Pihole.run('command -v yum > /dev/null && yum install -y man') + # create configuration file + setup_var_file = 'cat < /etc/pihole/setupVars.conf\n' + for k, v in SETUPVARS.items(): + setup_var_file += "{}={}\n".format(k, v) + setup_var_file += "INSTALL_WEB_SERVER=true\n" + setup_var_file += "INSTALL_WEB_INTERFACE=true\n" + setup_var_file += "EOF\n" + Pihole.run(setup_var_file) + install = Pihole.run(''' + export TERM=xterm + export DEBIAN_FRONTEND=noninteractive + umask 0027 + runUnattended=true + useUpdateVars=true + source /opt/pihole/basic-install.sh > /dev/null + runUnattended=true + useUpdateVars=true + main + ''') + assert 0 == install.rc + maninstalled = True + if (info_box + ' man not installed') in install.stdout: + maninstalled = False + piholeuser = 'pihole' + exit_status_success = 0 + test_cmd = 'su --shell /bin/bash --command "test -{0} {1}" -p {2}' + # check files in /etc/pihole for read, write and execute permission + check_etc = test_cmd.format('r', '/etc/pihole', piholeuser) + actual_rc = Pihole.run(check_etc).rc + assert exit_status_success == actual_rc + check_etc = test_cmd.format('x', '/etc/pihole', piholeuser) + actual_rc = Pihole.run(check_etc).rc + assert exit_status_success == actual_rc + # readable and writable dhcp.leases + check_leases = test_cmd.format('r', '/etc/pihole/dhcp.leases', piholeuser) + actual_rc = Pihole.run(check_leases).rc + assert exit_status_success == actual_rc + check_leases = test_cmd.format('w', '/etc/pihole/dhcp.leases', piholeuser) + actual_rc = Pihole.run(check_leases).rc + # readable dns-servers.conf + assert exit_status_success == actual_rc + check_servers = test_cmd.format( + 'r', '/etc/pihole/dns-servers.conf', piholeuser) + actual_rc = Pihole.run(check_servers).rc + assert exit_status_success == actual_rc + # readable GitHubVersions + check_version = test_cmd.format( + 'r', '/etc/pihole/GitHubVersions', piholeuser) + actual_rc = Pihole.run(check_version).rc + assert exit_status_success == actual_rc + # readable install.log + check_install = test_cmd.format( + 'r', '/etc/pihole/install.log', piholeuser) + actual_rc = Pihole.run(check_install).rc + assert exit_status_success == actual_rc + # readable localbranches + check_localbranch = test_cmd.format( + 'r', '/etc/pihole/localbranches', piholeuser) + actual_rc = Pihole.run(check_localbranch).rc + assert exit_status_success == actual_rc + # readable localversions + check_localversion = test_cmd.format( + 'r', '/etc/pihole/localversions', piholeuser) + actual_rc = Pihole.run(check_localversion).rc + assert exit_status_success == actual_rc + # readable logrotate + check_logrotate = test_cmd.format( + 'r', '/etc/pihole/logrotate', piholeuser) + actual_rc = Pihole.run(check_logrotate).rc + assert exit_status_success == actual_rc + # readable macvendor.db + check_macvendor = test_cmd.format( + 'r', '/etc/pihole/macvendor.db', piholeuser) + actual_rc = Pihole.run(check_macvendor).rc + assert exit_status_success == actual_rc + # readable and writeable pihole-FTL.conf + check_FTLconf = test_cmd.format( + 'r', '/etc/pihole/pihole-FTL.conf', piholeuser) + actual_rc = Pihole.run(check_FTLconf).rc + assert exit_status_success == actual_rc + check_FTLconf = test_cmd.format( + 'w', '/etc/pihole/pihole-FTL.conf', piholeuser) + actual_rc = Pihole.run(check_FTLconf).rc + assert exit_status_success == actual_rc + # readable setupVars.conf + check_setup = test_cmd.format( + 'r', '/etc/pihole/setupVars.conf', piholeuser) + actual_rc = Pihole.run(check_setup).rc + assert exit_status_success == actual_rc + # check dnsmasq files + # readable /etc/dnsmasq.conf + check_dnsmasqconf = test_cmd.format( + 'r', '/etc/dnsmasq.conf', piholeuser) + actual_rc = Pihole.run(check_dnsmasqconf).rc + assert exit_status_success == actual_rc + # readable /etc/dnsmasq.d/01-pihole.conf + check_dnsmasqconf = test_cmd.format( + 'r', '/etc/dnsmasq.d', piholeuser) + actual_rc = Pihole.run(check_dnsmasqconf).rc + assert exit_status_success == actual_rc + check_dnsmasqconf = test_cmd.format( + 'x', '/etc/dnsmasq.d', piholeuser) + actual_rc = Pihole.run(check_dnsmasqconf).rc + assert exit_status_success == actual_rc + check_dnsmasqconf = test_cmd.format( + 'r', '/etc/dnsmasq.d/01-pihole.conf', piholeuser) + actual_rc = Pihole.run(check_dnsmasqconf).rc + assert exit_status_success == actual_rc + # check readable and executable /etc/init.d/pihole-FTL + check_init = test_cmd.format( + 'x', '/etc/init.d/pihole-FTL', piholeuser) + actual_rc = Pihole.run(check_init).rc + assert exit_status_success == actual_rc + check_init = test_cmd.format( + 'r', '/etc/init.d/pihole-FTL', piholeuser) + actual_rc = Pihole.run(check_init).rc + assert exit_status_success == actual_rc + # check readable /etc/lighttpd/lighttpd.conf + check_lighttpd = test_cmd.format( + 'r', '/etc/lighttpd/lighttpd.conf', piholeuser) + actual_rc = Pihole.run(check_lighttpd).rc + assert exit_status_success == actual_rc + # check readable and executable manpages + if maninstalled is True: + check_man = test_cmd.format( + 'x', '/usr/local/share/man', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'r', '/usr/local/share/man', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'x', '/usr/local/share/man/man8', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'r', '/usr/local/share/man/man8', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'x', '/usr/local/share/man/man5', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'r', '/usr/local/share/man/man5', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'r', '/usr/local/share/man/man8/pihole.8', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'r', '/usr/local/share/man/man8/pihole-FTL.8', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + check_man = test_cmd.format( + 'r', '/usr/local/share/man/man5/pihole-FTL.conf.5', piholeuser) + actual_rc = Pihole.run(check_man).rc + assert exit_status_success == actual_rc + # check not readable sudoers file + check_sudo = test_cmd.format( + 'r', '/etc/sudoers.d/pihole', piholeuser) + actual_rc = Pihole.run(check_sudo).rc + assert exit_status_success != actual_rc + # check not readable cron file + check_sudo = test_cmd.format( + 'x', '/etc/cron.d/', piholeuser) + actual_rc = Pihole.run(check_sudo).rc + assert exit_status_success == actual_rc + check_sudo = test_cmd.format( + 'r', '/etc/cron.d/', piholeuser) + actual_rc = Pihole.run(check_sudo).rc + assert exit_status_success == actual_rc + check_sudo = test_cmd.format( + 'r', '/etc/cron.d/pihole', piholeuser) + actual_rc = Pihole.run(check_sudo).rc + assert exit_status_success == actual_rc + directories = get_directories_recursive(Pihole, '/etc/.pihole/') + for directory in directories: + check_pihole = test_cmd.format('r', directory, piholeuser) + actual_rc = Pihole.run(check_pihole).rc + check_pihole = test_cmd.format('x', directory, piholeuser) + actual_rc = Pihole.run(check_pihole).rc + findfiles = 'find "{}" -maxdepth 1 -type f -exec echo {{}} \\;;' + filelist = Pihole.run(findfiles.format(directory)) + files = list(filter(bool, filelist.stdout.splitlines())) + for file in files: + check_pihole = test_cmd.format('r', file, piholeuser) + actual_rc = Pihole.run(check_pihole).rc + + +@pytest.mark.parametrize("test_webpage", [True]) +def test_installPihole_fresh_install_readableBlockpage(Pihole, test_webpage): + ''' + confirms all web page assets from Core repo are readable + by $LIGHTTPD_USER on a fresh build + ''' + piholeWebpage = [ + "127.0.0.1", + # "pi.hole" + ] + # Whiptail dialog returns Cancel for user prompt + mock_command('whiptail', {'*': ('', '0')}, Pihole) + # mock git pull + mock_command_passthrough('git', {'pull': ('', '0')}, Pihole) + # mock systemctl to start lighttpd and FTL + ligthttpdcommand = dedent(r'''\"\" + echo 'starting lighttpd with {}' + if [ command -v "apt-get" >/dev/null 2>&1 ]; then + LIGHTTPD_USER="www-data" + LIGHTTPD_GROUP="www-data" + else + LIGHTTPD_USER="lighttpd" + LIGHTTPD_GROUP="lighttpd" + fi + mkdir -p "{run}" + chown {usergroup} "{run}" + mkdir -p "{cache}" + chown {usergroup} "/var/cache" + chown {usergroup} "{cache}" + mkdir -p "{compress}" + chown {usergroup} "{compress}" + mkdir -p "{uploads}" + chown {usergroup} "{uploads}" + chmod 0777 /var + chmod 0777 /var/cache + chmod 0777 "{cache}" + find "{run}" -type d -exec chmod 0777 {chmodarg} \;; + find "{run}" -type f -exec chmod 0666 {chmodarg} \;; + find "{compress}" -type d -exec chmod 0777 {chmodarg} \;; + find "{compress}" -type f -exec chmod 0666 {chmodarg} \;; + find "{uploads}" -type d -exec chmod 0777 {chmodarg} \;; + find "{uploads}" -type f -exec chmod 0666 {chmodarg} \;; + /usr/sbin/lighttpd -tt -f '{config}' + /usr/sbin/lighttpd -f '{config}' + echo \"\"'''.format( + '{}', + usergroup='${{LIGHTTPD_USER}}:${{LIGHTTPD_GROUP}}', + chmodarg='{{}}', + config='/etc/lighttpd/lighttpd.conf', + run='/var/run/lighttpd', + cache='/var/cache/lighttpd', + uploads='/var/cache/lighttpd/uploads', + compress='/var/cache/lighttpd/compress' + ) + ) + FTLcommand = dedent('''\"\" + set -x + /etc/init.d/pihole-FTL restart + echo \"\"''') + mock_command_run( + 'systemctl', + { + 'enable lighttpd': ( + '', + '0' + ), + 'restart lighttpd': ( + ligthttpdcommand.format('restart'), + '0' + ), + 'start lighttpd': ( + ligthttpdcommand.format('start'), + '0' + ), + 'enable pihole-FTL': ( + '', + '0' + ), + 'restart pihole-FTL': ( + FTLcommand, + '0' + ), + 'start pihole-FTL': ( + FTLcommand, + '0' + ), + '*': ( + 'echo "systemctl call with $@"', + '0' + ), + }, + Pihole + ) + # create configuration file + setup_var_file = 'cat < /etc/pihole/setupVars.conf\n' + for k, v in SETUPVARS.items(): + setup_var_file += "{}={}\n".format(k, v) + setup_var_file += "INSTALL_WEB_SERVER=true\n" + setup_var_file += "INSTALL_WEB_INTERFACE=true\n" + setup_var_file += "IPV4_ADDRESS=127.0.0.1\n" + setup_var_file += "EOF\n" + Pihole.run(setup_var_file) + installWeb = Pihole.run(''' + export TERM=xterm + export DEBIAN_FRONTEND=noninteractive + umask 0027 + runUnattended=true + useUpdateVars=true + source /opt/pihole/basic-install.sh > /dev/null + runUnattended=true + useUpdateVars=true + main + echo "LIGHTTPD_USER=${LIGHTTPD_USER}" + echo "webroot=${webroot}" + echo "INSTALL_WEB_INTERFACE=${INSTALL_WEB_INTERFACE}" + echo "INSTALL_WEB_SERVER=${INSTALL_WEB_SERVER}" + ''') + assert 0 == installWeb.rc + piholeuser = 'pihole' + webuser = '' + user = re.findall( + r"^\s*LIGHTTPD_USER=.*$", installWeb.stdout, re.MULTILINE) + for match in user: + webuser = match.replace('LIGHTTPD_USER=', '').strip() + webroot = '' + user = re.findall( + r"^\s*webroot=.*$", installWeb.stdout, re.MULTILINE) + for match in user: + webroot = match.replace('webroot=', '').strip() + if not webroot.strip(): + webroot = '/var/www/html' + installWebInterface = True + interface = re.findall( + r"^\s*INSTALL_WEB_INTERFACE=.*$", installWeb.stdout, re.MULTILINE) + for match in interface: + testvalue = match.replace('INSTALL_WEB_INTERFACE=', '').strip().lower() + if not testvalue.strip(): + installWebInterface = testvalue == "true" + installWebServer = True + server = re.findall( + r"^\s*INSTALL_WEB_SERVER=.*$", installWeb.stdout, re.MULTILINE) + for match in server: + testvalue = match.replace('INSTALL_WEB_SERVER=', '').strip().lower() + if not testvalue.strip(): + installWebServer = testvalue == "true" + # if webserver install was not requested + # at least pihole must be able to read files + if installWebServer is False: + webuser = piholeuser + exit_status_success = 0 + test_cmd = 'su --shell /bin/bash --command "test -{0} {1}" -p {2}' + # check files that need a running FTL to be created + # readable and writeable pihole-FTL.db + check_FTLconf = test_cmd.format( + 'r', '/etc/pihole/pihole-FTL.db', piholeuser) + actual_rc = Pihole.run(check_FTLconf).rc + assert exit_status_success == actual_rc + check_FTLconf = test_cmd.format( + 'w', '/etc/pihole/pihole-FTL.db', piholeuser) + actual_rc = Pihole.run(check_FTLconf).rc + assert exit_status_success == actual_rc + # check directories above $webroot for read and execute permission + check_var = test_cmd.format('r', '/var', webuser) + actual_rc = Pihole.run(check_var).rc + assert exit_status_success == actual_rc + check_var = test_cmd.format('x', '/var', webuser) + actual_rc = Pihole.run(check_var).rc + assert exit_status_success == actual_rc + check_www = test_cmd.format('r', '/var/www', webuser) + actual_rc = Pihole.run(check_www).rc + assert exit_status_success == actual_rc + check_www = test_cmd.format('x', '/var/www', webuser) + actual_rc = Pihole.run(check_www).rc + assert exit_status_success == actual_rc + check_html = test_cmd.format('r', '/var/www/html', webuser) + actual_rc = Pihole.run(check_html).rc + assert exit_status_success == actual_rc + check_html = test_cmd.format('x', '/var/www/html', webuser) + actual_rc = Pihole.run(check_html).rc + assert exit_status_success == actual_rc + # check directories below $webroot for read and execute permission + check_admin = test_cmd.format('r', webroot + '/admin', webuser) + actual_rc = Pihole.run(check_admin).rc + assert exit_status_success == actual_rc + check_admin = test_cmd.format('x', webroot + '/admin', webuser) + actual_rc = Pihole.run(check_admin).rc + assert exit_status_success == actual_rc + directories = get_directories_recursive(Pihole, webroot + '/admin/*/') + for directory in directories: + check_pihole = test_cmd.format('r', directory, webuser) + actual_rc = Pihole.run(check_pihole).rc + check_pihole = test_cmd.format('x', directory, webuser) + actual_rc = Pihole.run(check_pihole).rc + findfiles = 'find "{}" -maxdepth 1 -type f -exec echo {{}} \\;;' + filelist = Pihole.run(findfiles.format(directory)) + files = list(filter(bool, filelist.stdout.splitlines())) + for file in files: + check_pihole = test_cmd.format('r', file, webuser) + actual_rc = Pihole.run(check_pihole).rc + # check web interface files + # change nameserver to pi-hole + # setting nameserver in /etc/resolv.conf to pi-hole does + # not work here because of the way docker uses this file + ns = Pihole.run( + r"sed -i 's/nameserver.*/nameserver 127.0.0.1/' /etc/resolv.conf") + pihole_is_ns = ns.rc == 0 + + def is_ip(address): + m = re.match(r"(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})", address) + return bool(m) + if installWebInterface is True: + check_pihole = test_cmd.format('r', webroot + '/pihole', webuser) + actual_rc = Pihole.run(check_pihole).rc + assert exit_status_success == actual_rc + check_pihole = test_cmd.format('x', webroot + '/pihole', webuser) + actual_rc = Pihole.run(check_pihole).rc + assert exit_status_success == actual_rc + # check most important files in $webroot for read permission + check_index = test_cmd.format( + 'r', webroot + '/pihole/index.php', webuser) + actual_rc = Pihole.run(check_index).rc + assert exit_status_success == actual_rc + check_blockpage = test_cmd.format( + 'r', webroot + '/pihole/blockingpage.css', webuser) + actual_rc = Pihole.run(check_blockpage).rc + assert exit_status_success == actual_rc + if test_webpage is True: + # check webpage for unreadable files + noPHPfopen = re.compile( + (r"PHP Error(%d+):\s+fopen([^)]+):\s+" + + r"failed to open stream: " + + r"Permission denied in"), + re.I) + # using cURL option --dns-servers is not possible + status = ( + 'curl -s --head "{}" | ' + + 'head -n 1 | ' + + 'grep "HTTP/1.[01] [23].." > /dev/null') + digcommand = r"dig A +short {} @127.0.0.1 | head -n 1" + pagecontent = 'curl --verbose -L "{}"' + for page in piholeWebpage: + testpage = "http://" + page + "/admin/" + resolvesuccess = True + if is_ip(page) is False: + dig = Pihole.run(digcommand.format(page)) + testpage = "http://" + dig.stdout.strip() + "/admin/" + resolvesuccess = dig.rc == 0 + if resolvesuccess or pihole_is_ns: + # check HTTP status of blockpage + actual_rc = Pihole.run(status.format(testpage)) + assert exit_status_success == actual_rc.rc + # check for PHP error + actual_output = Pihole.run(pagecontent.format(testpage)) + assert noPHPfopen.match(actual_output.stdout) is None + + def test_update_package_cache_success_no_errors(Pihole): ''' confirms package cache was updated without any errors