Skip to content

Commit ad1c3df

Browse files
authored
Merge pull request #75 from GUI/sockproc-fds
Fix sockproc possibly inheriting nginx's sockets/file descriptors
2 parents 7e49c37 + 80e84a5 commit ad1c3df

File tree

6 files changed

+271
-29
lines changed

6 files changed

+271
-29
lines changed

Makefile

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,7 @@ lint: test_dependencies
203203
LUA_PATH="$(TEST_LUA_SHARE_DIR)/?.lua;$(TEST_LUA_SHARE_DIR)/?/init.lua;;" LUA_CPATH="$(TEST_LUA_LIB_DIR)/?.so;;" $(TEST_VENDOR_DIR)/bin/luacheck lib
204204

205205
test: test_dependencies lint
206-
sudo mkdir -p /tmp/resty-auto-ssl-test-worker-perms
207-
sudo chown nobody /tmp/resty-auto-ssl-test-worker-perms
208-
sudo rm -rf $(TEST_RUN_DIR)/servroot* $(TEST_LOGS_DIR)
209-
mkdir -p $(TEST_LOGS_DIR)
210-
PATH=$(PATH) luarocks make ./lua-resty-auto-ssl-git-1.rockspec
211-
pkill sockproc || true
212-
sudo pkill -U nobody sockproc || true
213-
sudo env TEST_NGINX_RESTY_AUTO_SSL_DIR=/tmp/resty-auto-ssl-test-worker-perms TEST_NGINX_SERVROOT=$(TEST_RUN_DIR)/servroot-worker-perms PATH=$(PATH) PERL5LIB=$(TEST_BUILD_DIR)/lib/perl5 TEST_NGINX_ERROR_LOG=$(TEST_LOGS_DIR)/error-worker-perms.log TEST_NGINX_RESOLVER=$(TEST_NGINX_RESOLVER) prove t/worker_file_permissions.t
214-
sudo pkill -U nobody sockproc || true
215-
TEST_NGINX_SERVROOT=$(TEST_RUN_DIR)/servroot PATH=$(PATH) PERL5LIB=$(TEST_BUILD_DIR)/lib/perl5 TEST_NGINX_ERROR_LOG=$(TEST_LOGS_DIR)/error.log TEST_NGINX_RESOLVER=$(TEST_NGINX_RESOLVER) prove `find $(ROOT_DIR)/t -maxdepth 1 -name "*.t" -not -name "worker_file_permissions.t"`
206+
PATH=$(PATH) ROOT_DIR=$(ROOT_DIR) TEST_RUN_DIR=$(TEST_RUN_DIR) TEST_BUILD_DIR=$(TEST_BUILD_DIR) TEST_LOGS_DIR=$(TEST_LOGS_DIR) TEST_LUA_SHARE_DIR=$(TEST_LUA_SHARE_DIR) TEST_LUA_LIB_DIR=$(TEST_LUA_LIB_DIR) t/run_tests
216207

217208
grind:
218209
env TEST_NGINX_USE_VALGRIND=1 TEST_NGINX_SLEEP=5 $(MAKE) test

bin/letsencrypt_hooks

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ function unchanged_cert {
4747
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
4848
}
4949

50-
HANDLER=$1; shift; $HANDLER $@
50+
HANDLER=$1; shift; $HANDLER "$@"

bin/start_sockproc

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
#!/usr/bin/env bash
22

3-
SOCK_FILE=/tmp/shell.sock
4-
PID_FILE=/tmp/auto-ssl-sockproc.pid
3+
set -u
4+
5+
sock_file=/tmp/shell.sock
6+
pid_file=/tmp/auto-ssl-sockproc.pid
7+
8+
# This script inherits the current directory from wherever nginx is launched,
9+
# which may be a private directory. To prevent the "find" command below from
10+
# generating "failed to restore initial working directory: Permission denied"
11+
# errors, change to the root directory where the nginx user will have
12+
# permissions.
13+
cd / || (echo "WARNING: start_sockproc could not set current directory to /" 1>&2)
514

615
# We want to launch sockproc from inside nginx using os.execute (mainly just to
716
# simplify the deployment of auto-ssl, so you don't have to worry about running
@@ -16,34 +25,39 @@ PID_FILE=/tmp/auto-ssl-sockproc.pid
1625
# See:
1726
# http://stackoverflow.com/a/4839945/222487
1827
# http://stackoverflow.com/a/23104923/222487
28+
file_descriptors_cleared="false"
1929
if [ -d /proc ]; then
20-
SELF=$BASHPID
21-
FILE_DESCRIPTORS=$(find /proc/$SELF/fd -type l -exec basename {} ';')
22-
for FD in $FILE_DESCRIPTORS; do
23-
if ((FD > 2)); then
24-
eval "exec $FD>&-"
30+
current_pid=$BASHPID
31+
file_descriptors=$(find /proc/"$current_pid"/fd -type l)
32+
for fd_path in $file_descriptors; do
33+
# Extract just the fd number from the end of the path.
34+
fd=${fd_path##*/}
35+
if ((fd > 2)); then
36+
eval "exec $fd>&-" && file_descriptors_cleared="true"
2537
fi
2638
done
39+
fi
2740

28-
# If /proc isn't supported (eg, OS X), fallback to a simpler, naive mechanism
29-
# to wipe file descriptors > 2.
30-
else
41+
# If /proc isn't supported (eg, OS X), or an error occurred during the
42+
# proc-based "find", then fallback to a simpler, naive mechanism to wipe file
43+
# descriptors > 2.
44+
if [ "$file_descriptors_cleared" != "true" ]; then
3145
END=255
32-
for ((FD=3; FD <= END; FD++)); do
33-
eval "exec $FD>&-"
46+
for ((fd=3; fd <= END; fd++)); do
47+
eval "exec $fd>&-"
3448
done
3549
fi
3650

3751
# When nginx shuts down, the sockproc daemon continues to run. So when nginx
3852
# subsequently starts again, we'll also make sure we restart sockproc.
39-
if [ -e $PID_FILE ]; then
40-
PID=$(cat $PID_FILE)
41-
kill $PID || true
53+
if [ -e "$pid_file" ]; then
54+
PID=$(cat "$pid_file")
55+
kill "$PID" || true
4256
fi
4357

4458
# Just make sure the socket file is cleaned up.
45-
rm -f $SOCK_FILE || true
59+
rm -f "$sock_file" || true
4660

4761
# Start the sockproc daemon.
48-
BIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
49-
$BIN_DIR/sockproc $SOCK_FILE $PID_FILE
62+
bin_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63+
"$bin_dir"/sockproc "$sock_file" "$pid_file"

lib/resty/auto-ssl/utils/start_sockproc.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ local auto_ssl = require "resty.auto-ssl"
22
local lock = require "resty.lock"
33

44
local function start()
5+
ngx.log(ngx.NOTICE, "auto-ssl: starting sockproc")
56
local exit_status = os.execute("umask 0022 && " .. auto_ssl.lua_root .. "/bin/resty-auto-ssl/start_sockproc")
67
-- Lua 5.2+ returns boolean. Prior versions return status code.
78
if exit_status == 0 or exit_status == true then

t/run_tests

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
3+
set -e -u -x
4+
5+
# Install the library via luarocks (so we test an installed version to ensure
6+
# all the files are part of the install process).
7+
luarocks make ./lua-resty-auto-ssl-git-1.rockspec
8+
9+
# Clean the test files before each run.
10+
sudo rm -rf "$TEST_RUN_DIR"/servroot* "$TEST_LOGS_DIR"/*
11+
mkdir -p "$TEST_LOGS_DIR"
12+
13+
# Default environment
14+
export PERL5LIB=$TEST_BUILD_DIR/lib/perl5
15+
export TEST_NGINX_RESTY_AUTO_SSL_DIR=/tmp/resty-auto-ssl-test
16+
export TEST_NGINX_SERVROOT=$TEST_RUN_DIR/servroot
17+
export TEST_NGINX_ERROR_LOG=$TEST_LOGS_DIR/error.log
18+
19+
cleanup_sockproc() {
20+
# Cleanup sockproc in between tests, since we run some tests as root, and
21+
# others as the current user, which can lead to the sockproc files being
22+
# owned by different users (but in a real production environment, this
23+
# shouldn't be an issue, since it will typically run as a single user).
24+
sudo pkill sockproc || true
25+
sudo rm -f /tmp/shell.sock /tmp/auto-ssl-sockproc.pid
26+
}
27+
28+
# Environment variables to pass along when we run tests as root with nginx
29+
# workers (we use different directories for these tests, so the file
30+
# permissions don't conflict with the tests run as the current user).
31+
worker_env=(
32+
"PATH=$PATH"
33+
"PERL5LIB=$PERL5LIB"
34+
"TEST_NGINX_RESTY_AUTO_SSL_DIR=/tmp/resty-auto-ssl-test-worker-perms"
35+
"TEST_NGINX_SERVROOT=$TEST_RUN_DIR/servroot-worker-perms"
36+
"TEST_NGINX_ERROR_LOG=$TEST_LOGS_DIR/error-worker-perms.log"
37+
"TEST_NGINX_RESOLVER=${TEST_NGINX_RESOLVER:-}"
38+
)
39+
40+
# Create the worker-perms test directory with the proper permissions.
41+
sudo mkdir -p /tmp/resty-auto-ssl-test-worker-perms
42+
sudo chown nobody /tmp/resty-auto-ssl-test-worker-perms
43+
44+
# Run tests as root that test the behavior when nginx has a master process
45+
# running as root, and worker processes running as a less privileged user.
46+
cleanup_sockproc
47+
sudo env "${worker_env[@]}" prove t/worker_file_permissions.t
48+
49+
# Check the behavior of how sockproc gets started (both as the current user and
50+
# as root with worker processes), to ensure that sockproc doesn't inherit
51+
# nginx's file descriptors.
52+
cleanup_sockproc
53+
sudo env "${worker_env[@]}" prove t/sockproc_file_descriptors.t
54+
55+
# Ensure file descriptors don't get inherited when nginx is started from a
56+
# directory that the worker processes won't have permissions to (there was
57+
# previously a bug with how we started sockproc that led to the descriptors not
58+
# being cleared if the nginx worker user didn't have permissions to the pwd of
59+
# nginx's master process).
60+
cleanup_sockproc
61+
sudo env "${worker_env[@]}" sh -c "cd /root && prove $ROOT_DIR/t/sockproc_file_descriptors.t"
62+
63+
cleanup_sockproc
64+
find "$ROOT_DIR"/t -maxdepth 1 -name "*.t" -not -name "worker_file_permissions.t" -not -name "sockproc_file_descriptors.t" -print0 | xargs -r0 prove

t/sockproc_file_descriptors.t

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use strict;
2+
use warnings;
3+
use Test::Nginx::Socket::Lua;
4+
use Cwd qw(abs_path);
5+
use File::Basename;
6+
my $root_dir = dirname(dirname(abs_path(__FILE__)));
7+
require "$root_dir/t/inc/setup.pl";
8+
AutoSsl::setup();
9+
10+
my ($nobody_user, $nobody_passwd, $nobody_uid, $nobody_gid) = getpwnam "nobody";
11+
$ENV{TEST_NGINX_NOBODY_USER} = $nobody_user;
12+
$ENV{TEST_NGINX_NOBODY_GROUP} = getgrgid($nobody_gid);
13+
14+
repeat_each(1);
15+
16+
plan tests => repeat_each() * (blocks() * 7);
17+
18+
check_accum_error_log();
19+
no_long_string();
20+
no_shuffle();
21+
master_on();
22+
workers(2);
23+
24+
run_tests();
25+
26+
__DATA__
27+
28+
=== TEST 1: issues a new SSL certificate and stores it as a file
29+
--- main_config
30+
user $TEST_NGINX_NOBODY_USER $TEST_NGINX_NOBODY_GROUP;
31+
--- http_config
32+
resolver $TEST_NGINX_RESOLVER;
33+
lua_shared_dict auto_ssl 1m;
34+
lua_shared_dict auto_ssl_settings 64k;
35+
36+
init_by_lua_block {
37+
auto_ssl = (require "resty.auto-ssl").new({
38+
dir = "$TEST_NGINX_RESTY_AUTO_SSL_DIR",
39+
ca = "https://acme-staging.api.letsencrypt.org/directory",
40+
storage_adapter = "resty.auto-ssl.storage_adapters.file",
41+
allow_domain = function(domain)
42+
return true
43+
end,
44+
})
45+
auto_ssl:init()
46+
}
47+
48+
init_worker_by_lua_block {
49+
auto_ssl:init_worker()
50+
}
51+
52+
server {
53+
listen 9443 ssl;
54+
ssl_certificate $TEST_NGINX_ROOT_DIR/t/certs/example_fallback.crt;
55+
ssl_certificate_key $TEST_NGINX_ROOT_DIR/t/certs/example_fallback.key;
56+
ssl_certificate_by_lua_block {
57+
auto_ssl:ssl_certificate()
58+
}
59+
60+
location /foo {
61+
server_tokens off;
62+
more_clear_headers Date;
63+
echo "foo";
64+
}
65+
}
66+
67+
server {
68+
listen 9080;
69+
location /.well-known/acme-challenge/ {
70+
content_by_lua_block {
71+
auto_ssl:challenge_server()
72+
}
73+
}
74+
}
75+
76+
server {
77+
listen 127.0.0.1:8999;
78+
location / {
79+
content_by_lua_block {
80+
auto_ssl:hook_server()
81+
}
82+
}
83+
}
84+
--- config
85+
lua_ssl_trusted_certificate $TEST_NGINX_ROOT_DIR/t/certs/letsencrypt_staging_chain.pem;
86+
lua_ssl_verify_depth 5;
87+
location /t {
88+
content_by_lua_block {
89+
local run_command = require "resty.auto-ssl.utils.run_command"
90+
91+
local function cleanup_sockproc()
92+
run_command("pkill sockproc")
93+
local _, output, err = run_command("rm -f /tmp/shell.sock /tmp/auto-ssl-sockproc.pid")
94+
if err then
95+
ngx.say("failed to remove sockproc files: ", err)
96+
return nil, err
97+
end
98+
end
99+
100+
local function print_file_descriptors(as_user)
101+
local _, output, err = run_command("lsof -n -P -l -R -c sockproc -a -d '0-255' -F pnf | sed -n '1!p' | sed -e 's/ type=STREAM//g'")
102+
if err then
103+
ngx.say("failed to run lsof: ", err)
104+
return nil, err
105+
end
106+
ngx.say(output)
107+
end
108+
109+
ngx.say("already running:")
110+
print_file_descriptors("root")
111+
112+
cleanup_sockproc()
113+
ngx.say("none running:")
114+
print_file_descriptors("root")
115+
116+
ngx.say("current dir as current user:")
117+
cleanup_sockproc()
118+
os.execute("umask 0022 && " .. auto_ssl.lua_root .. "/bin/resty-auto-ssl/start_sockproc")
119+
print_file_descriptors("root")
120+
121+
ngx.say("/tmp dir as current user:")
122+
cleanup_sockproc()
123+
os.execute("cd /tmp && umask 0022 && " .. auto_ssl.lua_root .. "/bin/resty-auto-ssl/start_sockproc")
124+
print_file_descriptors("root")
125+
126+
ngx.say("the end")
127+
}
128+
}
129+
--- timeout: 30s
130+
--- request
131+
GET /t
132+
--- response_body
133+
already running:
134+
f0
135+
n/dev/null
136+
f1
137+
n/dev/null
138+
f2
139+
n/dev/null
140+
f3
141+
n/tmp/shell.sock
142+
143+
none running:
144+
145+
current dir as current user:
146+
f0
147+
n/dev/null
148+
f1
149+
n/dev/null
150+
f2
151+
n/dev/null
152+
f3
153+
n/tmp/shell.sock
154+
155+
/tmp dir as current user:
156+
f0
157+
n/dev/null
158+
f1
159+
n/dev/null
160+
f2
161+
n/dev/null
162+
f3
163+
n/tmp/shell.sock
164+
165+
the end
166+
--- error_log
167+
auto-ssl: starting sockproc
168+
--- no_error_log
169+
[warn]
170+
[error]
171+
[alert]
172+
[emerg]

0 commit comments

Comments
 (0)