Skip to content

Commit 066cade

Browse files
committed
Merge branch 'add-water-fix' - Closes #1360
Essentially the issue was as follows: - Dehydrated starts multiple instances of add-water (via dehydrated hook script). - Add-water writes it's pid file, overwriting any other currently running Add-water instances. - Last instance rarely has correct pid file so dehydrated-wrapper / hook script cannot kill it. Fix: - Separated add-water to server & client components. - Server component only runs once and does so in background. - Client can run many times and can pass tokens to be served to server component. - Client component is used by dehydrated hook script to serve multiple tokens with a single "http server".
2 parents 0e8cb45 + 0121983 commit 066cade

File tree

7 files changed

+171
-91
lines changed

7 files changed

+171
-91
lines changed

add-water/add-water.service

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Unit]
2+
Description=Add Water
3+
After=network.target
4+
5+
[Service]
6+
Type=simple
7+
ExecStart=/usr/bin/python /usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv -l /var/log/confconsole/letsencrypt.log
8+
9+
[Install]
10+
WantedBy=multi-user.target

debian/confconsole.install

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ plugins.d/ /usr/lib/confconsole/
22
*.py /usr/lib/confconsole/
33
conf/* /etc/confconsole/
44
share/* /usr/share/confconsole/
5+
add-water/* /lib/systemd/system

plugins.d/Lets_Encrypt/add-water

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/python
2+
3+
# Copyright (c) 2017 TurnKey GNU/Linux - http://www.turnkeylinux.org
4+
#
5+
# Add-Water-Client - Agent to pass tokens to Add Water to serve
6+
# Dehydrated Let's Encrypt challenges
7+
#
8+
# This file is part of Confconsole.
9+
#
10+
# Confconsole is free software; you can redistribute it and/or modify it
11+
# under the terms of the GNU Affero General Public License as published by the
12+
# Free Software Foundation; either version 3 of the License, or (at your
13+
# option) any later version.
14+
15+
from argparse import ArgumentParser
16+
import socket
17+
import sys
18+
19+
if __name__ == '__main__':
20+
parser = ArgumentParser(description="add-water-client - Agent to pass tokens to add-water server")
21+
token_group = parser.add_mutually_exclusive_group()
22+
token_group.add_argument('--deploy', help='path to token file to serve')
23+
token_group.add_argument('--clean', help='path to token file to serve')
24+
args = parser.parse_args()
25+
26+
if args.deploy:
27+
op = 'deploy'
28+
token_path = args.deploy
29+
elif args.clean:
30+
op = 'clean'
31+
token_path = args.clean
32+
else:
33+
print('Nothing to do!')
34+
sys.exit(1)
35+
36+
host = '127.0.0.1'
37+
port = 9977
38+
39+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
40+
sock.connect((host, port))
41+
sock.sendall(op + ' ' + token_path)
42+
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/python
2+
3+
# Copyright (c) 2017 TurnKey GNU/Linux - http://www.turnkeylinux.org
4+
#
5+
# Add-Water - Bottle based python HTTP server to serve
6+
# Dehydrated Let's Encrypt challenges
7+
#
8+
# This file is part of Confconsole.
9+
#
10+
# Confconsole is free software; you can redistribute it and/or modify it
11+
# under the terms of the GNU Affero General Public License as published by the
12+
# Free Software Foundation; either version 3 of the License, or (at your
13+
# option) any later version.
14+
15+
import socket
16+
import datetime
17+
from argparse import ArgumentParser
18+
from Queue import Queue, Empty
19+
from threading import Thread
20+
from os.path import isfile, dirname, basename, abspath
21+
from bottle import get, static_file, run, route, redirect
22+
23+
# "Maintence" page to serve for all requested content, other than LE token
24+
DEFAULT_INDEX = '/usr/share/confconsole/letsencrypt/index.html'
25+
CUSTOM_INDEX = '/var/lib/confconsole/letsencrypt/index.html'
26+
27+
if isfile(CUSTOM_INDEX):
28+
INDEX_PATH = CUSTOM_INDEX
29+
else:
30+
INDEX_PATH = DEFAULT_INDEX
31+
32+
INDEX_FILE = basename(INDEX_PATH)
33+
INDEX_WEBROOT = dirname(INDEX_PATH)
34+
35+
tokens = {}
36+
token_queue = Queue()
37+
38+
def update_tokens():
39+
# pull in new tokens from token queue
40+
while True:
41+
try:
42+
new_token = token_queue.get_nowait()
43+
except Empty:
44+
break
45+
else:
46+
47+
op, token = new_token.split(' ', 1)
48+
49+
token_path = abspath(token)
50+
token_file = basename(token_path)
51+
token_webroot = dirname(token_path)
52+
53+
if op == 'deploy':
54+
tokens[token_file] = token_webroot
55+
elif op == 'clean':
56+
del tokens[token_file]
57+
else:
58+
raise ValueError('Unknown operation specified!')
59+
60+
@get("/.well-known/acme-challenge/<filename:path>")
61+
def challenge(filename):
62+
update_tokens()
63+
if filename in tokens:
64+
token_webroot = tokens[filename]
65+
return static_file(filename, root=token_webroot)
66+
else:
67+
redirect('/')
68+
69+
@route('/')
70+
def index():
71+
update_tokens()
72+
return static_file(INDEX_FILE, root=INDEX_WEBROOT)
73+
74+
@route('<randompath:path>')
75+
def test(randompath):
76+
redirect('/')
77+
78+
#--
79+
80+
def handle_token_input():
81+
host = '127.0.0.1'
82+
port = 9977
83+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
84+
sock.bind((host, port))
85+
sock.listen(1)
86+
87+
# client will only send 1 token per connection, might be
88+
# more effecient way of doing this, but unsure how with dehydrated
89+
while True:
90+
conn, addr = sock.accept()
91+
92+
token = conn.recv(4096).decode('utf8')
93+
print('Got token {} from {}: serving'.format(token, addr))
94+
token_queue.put(token)
95+
96+
conn.close()
97+
98+
sock.close()
99+
100+
if __name__ == '__main__':
101+
parser = ArgumentParser(description="add-water - Bottle based python HTTP server to serve Dehydrated Let's Encrypt challenges")
102+
parser.add_argument('-l', '--logfile', help='path to logfile')
103+
args = parser.parse_args()
104+
105+
input_handler = Thread(target=handle_token_input)
106+
input_handler.start()
107+
108+
print("[{}] Starting Server".format(
109+
datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
110+
run(host='0.0.0.0', port=80)
111+

plugins.d/Lets_Encrypt/dehydrated-wrapper

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ cp $TKL_CERTFILE $TKL_CERTFILE.bak
4343
cp $TKL_KEYFILE $TKL_KEYFILE.bak
4444

4545
BASE_BIN_PATH="/usr/lib/confconsole/plugins.d/Lets_Encrypt"
46-
export HTTP="add-water"
46+
export HTTP="add-water-client"
4747
export HTTP_USR="www-data"
4848
export HTTP_BIN="$BASE_BIN_PATH/$HTTP"
4949
export HTTP_PID=/var/run/$HTTP/pid
@@ -142,8 +142,7 @@ clean_finish() {
142142
if [ "$(check_80)" = "python" ]; then
143143
warning "Python is still listening on port 80"
144144
info "attempting to kill add-water server"
145-
[ -f "$HTTP_PID" ] && kill -9 $(cat $HTTP_PID)
146-
rm -f $HTTP_PID
145+
systemctl stop add-water
147146
fi
148147
[ "$AUTHBIND_USR" = "$HTTP_USR" ] || chown $AUTHBIND_USR $AUTHBIND80
149148
if [ $EXIT_CODE -ne 0 ]; then
@@ -160,6 +159,7 @@ clean_finish() {
160159
else
161160
info "$APP completed successfully."
162161
fi
162+
systemctl stop add-water
163163
exit $EXIT_CODE
164164
}
165165

@@ -248,6 +248,7 @@ else
248248
fi
249249

250250
[ "$AUTHBIND_USR" = "$HTTP_USR" ] || chown $HTTP_USR $AUTHBIND80
251+
systemctl start add-water
251252
info "running dehydrated"
252253
if [ "$DEBUG" = "y" ] || [ "$LOG_INFO" = "y" ]; then
253254
dehydrated --cron $args --config $CONFIG 2>&1 | tee -a $DEBUG_LOG

share/letsencrypt/dehydrated-confconsole.hook.sh

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,14 @@ function deploy_challenge {
2323

2424
hook_log info "Deploying challenge for $DOMAIN"
2525
hook_log info "Serving $WELLKNOWN/$TOKEN_FILENAME on http://$DOMAIN/.well-known/acme-challenge/$TOKEN_FILENAME"
26-
su - -s /bin/bash -c "authbind $HTTP_BIN -d $HTTP_PID -l $HTTP_LOG $WELLKNOWN/$TOKEN_FILENAME"
26+
$HTTP_BIN --deploy "$WELLKNOWN/$TOKEN_FILENAME"
2727
}
2828

2929
function clean_challenge {
3030
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
3131

32-
hook_log info "Stopping $HTTP daemon"
33-
kill -9 $(cat $HTTP_PID)
34-
rm $HTTP_PID
32+
hook_log info "Clean challenge for $DOMAIN"
33+
$HTTP_BIN --clean "$WELLKNOWN/$TOKEN_FILENAME"
3534
}
3635

3736
function deploy_cert {

0 commit comments

Comments
 (0)