What I'm trying to do:
Permit the web server user (http) to run either an explicit iptables command or (preferrably) a script that checks the input for syntax before passing it to iptables without the need for a password.
The problem:
In spite of all my searching of the issue at hand, no changes to visudo, sudoers.d or php configurations appear to make a difference for what I'm trying to do.
The relevant conditions of my server:
[weasel@darwyn ~]$ uname -a
Linux darwyn 5.3.7-arch1-1-ARCH #1 SMP PREEMPT Fri Oct 18 00:17:03 UTC 2019 x86_64 GNU/Linux
nginx version: nginx/1.16.1
PHP 7.3.10 (cli) (built: Sep 26 2019 13:40:03) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.10, Copyright (c) 1998-2018 Zend Technologies
Username: http
UID: 33
Gecos field:
Home directory: /srv/http
Shell: /usr/bin/nologin
No login: yes
Primary group: http
GID: 33
Hushed: no
Running processes: 3
Last logs:
09:29 sudo[648]: pam_unix(sudo:auth): auth could not identify password for [http]
09:29 sudo[649]: pam_unix(sudo:account): account http has expired (account expired)
09:29 sudo[650]: pam_unix(sudo:account): account http has expired (account expired)
Steps I've taken to fix the issue:
/var/log/php-fpm.log tells me that the calls are being initiated but expecting a password:
"sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"
From similar questions I learned that the sudoers file prioritizes the last configuration related to a user. So where I first had
root ALL=(ALL) ALL
weasel ALL=(ALL) ALL
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
I moved the entry down to
root ALL=(ALL) ALL
weasel ALL=(ALL) ALL
## Uncomment to allow members of group wheel to execute any command
# %wheel ALL=(ALL) ALL
## Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL
## Uncomment to allow members of group sudo to execute any command
# %sudo ALL=(ALL) ALL
## Uncomment to allow any user to run sudo if they know the password
## of the user they are running the command as (root by default).
# Defaults targetpw # Ask for the password of the target user
# ALL ALL=(ALL) ALL # WARNING: only use this together with 'Defaults targetpw'
##
## Relocated user http permissions for testing
##
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
## Read drop-in files from /etc/sudoers.d
## (the '#' here does not indicate a comment)
#includedir /etc/sudoers.d
I removed the http entry in visudo and moved it to /etc/sudoers.d/90-MyOverrides.
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
http ALL=NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
http ALL=(http:http) NOPASSWD: /usr/local/bin/blacklist_ip, /usr/bin/iptables
http ALL=(ALL) NOPASSWD: /usr/local/bin/blacklist_ip *, /usr/bin/iptables *
http ALL=NOPASSWD: /usr/local/bin/blacklist_ip *, /usr/bin/iptables *
http ALL=(http:http) NOPASSWD: /usr/local/bin/blacklist_ip *, /usr/bin/iptables *
http ALL=(ALL) NOPASSWD: /usr/bin/iptables -L
http ALL=NOPASSWD: /usr/bin/iptables -L
http ALL=(http:http) NOPASSWD: /usr/bin/iptables -L
To make sure this was having some sort of effect, I tested it on my own user
weasel ALL=(ALL) NOPASSWD: /usr/bin/iptables
I was able to execute iptables commands without a password as user weasel, but the php-fpm log was still requiring a password for user http. I also attempted the command using 'sudo -S' in the hope that a null value for the password would work, however it also errors out.
"[sudo] password for http: "
"sudo: no password was provided"
I checked both /etc/php/php.ini and /etc/php/php-fpm.conf to ensure that the exec/shell_exec commands weren't disabled and that safe mode wasn't active. The resulting debug information in /var/log/php-fpm.log already proves this, but I wanted to be thorough.
This was a bit redundant, but just to void out the possibility that it mattered.
exec("sudo blacklist_ip 185.189.12.135");
shell_exec("sudo blacklist_ip 185.189.12.135");
return:
"sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"
exec("blacklist_ip 185.189.12.135");
shell_exec("blacklist_ip 185.189.12.135");
exec("/usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
shell_exec("/usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
return:
"Fatal: can't open lock file /run/xtables.lock: Permission denied"
exec("sudo /usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
shell_exec("sudo /usr/bin/iptables -A INPUT -s 185.189.12.135 -j DROP");
return:
"sudo: Account expired or PAM config lacks an "account" section for sudo, contact your system administrator"
And with a bit of searching found that according to https://www.sudo.ws/troubleshooting.html:
Q) Sudo says 'Account expired or PAM config lacks an "account" section for sudo, contact your system administrator' and exits but I know my account has not expired.
A) Your PAM config lacks an "account" specification. On Linux this usually means you are missing a line like:
account required pam_unix.so
in /etc/pam.d/sudo.
No one ever mentions this as a cause in any other Q&A related to this topic. Nevertheless, for the sake of thoroughness I looked up information regarding PAM and sudo:
#%PAM-1.0
auth include system-auth
account required pam_unix.so
#account include system-auth
session include system-auth
It made no difference.
I am out of ideas. I need more information on an appropriate PAM module, making the http user passable by PAM or I need some redirection for further assessment.
EDIT Updated to simplify the information for the sake of brevity.
EDIT Added contents of /etc/php/php-fpm.d/www.conf
[www]
user = http
group = http
listen = /run/php-fpm/php-fpm.sock
listen.owner = http
listen.group = http
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
access.log = /var/log/php/$pool.access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
catch_workers_output = yes
php_flag[display_errors] = on
php_admin_value[error_log] = /var/log/php/fpm-php.www.log
php_admin_flag[log_errors] = on
As per Daan's suggestion in the comments, it turns out that at some point the http account's expiry date was set to a time far in the past so that it was functionally never useable for sudo commands. Perhaps this was a decision to future-proof a system against the possibility of injected exec/shell_exec commands by use of PHP in a web server.
The solution:
Daan then recommended a solution
chage -E -1 http
This set the user's expiry to 'never' and made it useable again. The sudo settings now work as expected.