Skip to content

Commit ff65faf

Browse files
committed
Enhance Certbot Docker setup with user/group ID configuration and debug options
- Added new environment variables `DEBUG`, `PUID`, and `PGID` to allow customization of user and group IDs for running Certbot. - Updated `entrypoint.sh` to handle UID/GID configuration and added debug logging capabilities. - Modified `Dockerfile` to install necessary packages and create a non-privileged user/group based on provided IDs. - Enhanced `README.md` to document new environment variables. - Updated VSCode settings to include new terms for spell checking. This commit improves the flexibility and security of the Certbot container by allowing it to run under specified user/group IDs and providing debug information when needed.
1 parent 1e94406 commit ff65faf

File tree

4 files changed

+132
-30
lines changed

4 files changed

+132
-30
lines changed

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"cSpell.words": [
33
"CERTBOT",
44
"certonly",
5-
"letsencrypt"
5+
"letsencrypt",
6+
"PGID",
7+
"PUID"
68
]
79
}

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ The following environment variables can be used to customize the Certbot contain
4141
| `CERTBOT_EMAIL` | Email address for Let's Encrypt notifications | - |
4242
| `CERTBOT_KEY_TYPE` | Type of private key to generate | `ecdsa` |
4343
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token for DNS authentication | - |
44+
| `DEBUG` | Enable debug mode (prints more information to the console) | `false` |
45+
| `PUID` | The user ID to run certbot as | `0` |
46+
| `PGID` | The group ID to run certbot as | `0` |
4447
| `RENEWAL_INTERVAL` | Interval between certificate renewal checks | 43200 seconds (12 hours) |
4548
| `REPLACE_SYMLINKS` | Replaces symlinks with direct copies of the files they reference (required for Windows) | `false` |
4649

src/Dockerfile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,31 @@
22
# check=skip=SecretsUsedInArgOrEnv
33
FROM certbot/dns-cloudflare:latest
44

5+
ARG CERTBOT_USER=certbot
6+
ARG CERTBOT_GROUP=certbot
7+
ARG CERTBOT_UID=9999
8+
ARG CERTBOT_GID=9999
9+
510
ENV CERTBOT_DOMAINS="" \
611
CERTBOT_EMAIL="" \
712
CERTBOT_KEY_TYPE="ecdsa" \
813
CLOUDFLARE_API_TOKEN="" \
9-
RENEWAL_INTERVAL=43200
14+
DEBUG=false \
15+
PUID=0 \
16+
PGID=0 \
17+
RENEWAL_INTERVAL=43200 \
18+
REPLACE_SYMLINKS=false
1019

1120
COPY --chmod=700 entrypoint.sh /entrypoint.sh
1221

22+
RUN apk update && \
23+
apk add --no-cache shadow su-exec && \
24+
addgroup -g "${CERTBOT_GID}" "${CERTBOT_GROUP}" && \
25+
adduser -u "${CERTBOT_UID}" -G "${CERTBOT_GROUP}" -D -H "${CERTBOT_USER}" && \
26+
mkdir -p /var/log/letsencrypt && \
27+
chown 700 /var/log/letsencrypt && \
28+
rm -rf /var/cache/apk/*
29+
1330
ENTRYPOINT ["/entrypoint.sh"]
1431

1532
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \

src/entrypoint.sh

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
#!/bin/sh
2+
set -e
3+
default_uid=0
4+
default_gid=0
5+
default_unprivileged_user=certbot
6+
default_unprivileged_group=certbot
7+
8+
if [ "$DEBUG" = "true" ]; then
9+
set -x
10+
fi
211

312
################################################################################
413
# Functions
@@ -9,6 +18,41 @@ cleanup() {
918
exit 0
1019
}
1120

21+
debug_print() {
22+
if [ "$DEBUG" = "true" ]; then
23+
echo "$1"
24+
fi
25+
}
26+
27+
configure_uid_and_gid() {
28+
debug_print "Preparing environment for $PUID:$PGID..."
29+
30+
# Handle existing user with the same UID
31+
if id -u "${PUID}" >/dev/null 2>&1; then
32+
old_user=$(id -nu "${PUID}")
33+
debug_print "UID ${PUID} already exists for user ${old_user}. Moving to a new UID."
34+
usermod -u "999${PUID}" "${old_user}"
35+
fi
36+
37+
# Handle existing group with the same GID
38+
if getent group "${PGID}" >/dev/null 2>&1; then
39+
old_group=$(getent group "${PGID}" | cut -d: -f1)
40+
debug_print "GID ${PGID} already exists for group ${old_group}. Moving to a new GID."
41+
groupmod -g "999${PGID}" "${old_group}"
42+
fi
43+
44+
# Change UID and GID of run_as user and group
45+
usermod -u "${PUID}" "${default_unprivileged_user}" 2>&1 >/dev/null || echo "Error changing user ID."
46+
groupmod -g "${PGID}" "${default_unprivileged_user}" 2>&1 >/dev/null || echo "Error changing group ID."
47+
48+
# Ensure the correct permissions are set for all required directories
49+
chown -R "${default_unprivileged_user}:${default_unprivileged_group}" \
50+
/etc/letsencrypt \
51+
/var/lib/letsencrypt \
52+
/var/log/letsencrypt \
53+
/opt/certbot
54+
}
55+
1256
configure_windows_file_permissions() {
1357
# Permissions must be created after volumes have been mounted; otherwise, windows file system permissions will override
1458
# the permissions set within the container.
@@ -40,8 +84,28 @@ replace_symlinks() {
4084
done
4185
}
4286

87+
is_default_privileges() {
88+
[ "${PUID:-$default_uid}" = "$default_uid" ] && [ "${PGID:-$default_gid}" = "$default_gid" ]
89+
}
90+
4391
run_certbot() {
44-
certbot certonly \
92+
# Ensure the log directory is set to 700
93+
chmod 700 /var/log/letsencrypt
94+
chown "${PUID}:${PGID}" /var/log/letsencrypt
95+
96+
if is_default_privileges; then
97+
certbot_cmd="certbot"
98+
else
99+
certbot_cmd="su-exec ${default_unprivileged_user} certbot"
100+
fi
101+
102+
debug_print "Running certbot with command: $certbot_cmd"
103+
104+
# Add -v flag if DEBUG is enabled
105+
debug_flag=""
106+
[ "$DEBUG" = "true" ] && debug_flag="-v"
107+
108+
$certbot_cmd $debug_flag certonly \
45109
--dns-cloudflare \
46110
--dns-cloudflare-credentials /cloudflare.ini \
47111
-d "$CERTBOT_DOMAINS" \
@@ -79,6 +143,10 @@ trap cleanup TERM INT
79143

80144
validate_environment_variables
81145

146+
if ! is_default_privileges; then
147+
configure_uid_and_gid
148+
fi
149+
82150
if [ "$REPLACE_SYMLINKS" = "true" ]; then
83151
configure_windows_file_permissions
84152
fi
@@ -110,32 +178,44 @@ echo "-----------------------------------------------------------"
110178
# Create Cloudflare configuration file
111179
echo "dns_cloudflare_api_token = $CLOUDFLARE_API_TOKEN" > /cloudflare.ini
112180
chmod 600 /cloudflare.ini
181+
if ! is_default_privileges; then
182+
chown "${PUID}:${PGID}" /cloudflare.ini
183+
fi
113184

114-
# Run certbot initially to get the certificates
115-
run_certbot
116-
117-
# Infinite loop to keep the container running and periodically check for renewals
118-
while true; do
119-
# POSIX-compliant way to show next run time
120-
current_timestamp=$(date +%s)
121-
next_timestamp=$((current_timestamp + RENEWAL_INTERVAL))
122-
next_run=$(date -r "$next_timestamp" '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S %z')
123-
echo "Next certificate renewal check will be at ${next_run}"
124-
125-
# Store PID of sleep process and wait for it
126-
sleep "$RENEWAL_INTERVAL" &
127-
sleep_pid=$!
128-
wait $sleep_pid
129-
wait_status=$?
130-
131-
# Check if we received a signal (more portable check)
132-
case $wait_status in
133-
0) : ;; # Normal exit
134-
*) cleanup ;;
135-
esac
136-
137-
if ! run_certbot; then
138-
echo "Error: Certificate renewal failed. Exiting."
139-
exit 1
185+
# Check if a command was passed to the container
186+
if [ $# -gt 0 ]; then
187+
if is_default_privileges; then
188+
exec "$@"
189+
else
190+
exec su-exec "${default_unprivileged_user}" "$@"
140191
fi
141-
done
192+
else
193+
# Run certbot initially to get the certificates
194+
run_certbot
195+
196+
# Infinite loop to keep the container running and periodically check for renewals
197+
while true; do
198+
# POSIX-compliant way to show next run time
199+
current_timestamp=$(date +%s)
200+
next_timestamp=$((current_timestamp + RENEWAL_INTERVAL))
201+
next_run=$(date -r "$next_timestamp" '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S %z')
202+
echo "Next certificate renewal check will be at ${next_run}"
203+
204+
# Store PID of sleep process and wait for it
205+
sleep "$RENEWAL_INTERVAL" &
206+
sleep_pid=$!
207+
wait $sleep_pid
208+
wait_status=$?
209+
210+
# Check if we received a signal (more portable check)
211+
case $wait_status in
212+
0) : ;; # Normal exit
213+
*) cleanup ;;
214+
esac
215+
216+
if ! run_certbot; then
217+
echo "Error: Certificate renewal failed. Exiting."
218+
exit 1
219+
fi
220+
done
221+
fi

0 commit comments

Comments
 (0)