Compare commits

...

2 Commits
master ... dev

Author SHA1 Message Date
Nicolas Duchon
1e9dca8a5a
Test unit for standalone certificates 2018-11-23 21:25:35 +01:00
Nicolas Duchon
e0492cfd5b
Add standalone certificate feature
Standalone certificates are generated from a static user provided
configuration file rather than from the dynamicaly generated (from
running containers environment variables) letsencrypt_service_data file.
2018-11-23 21:10:26 +01:00
10 changed files with 156 additions and 12 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ go/
nginx.tmpl
test/local_test_env.sh
test/tests/docker_api/expected-std-out.txt
test/tests/certs_standalone/letsencrypt_user_data

View File

@ -47,6 +47,7 @@ after_failure:
- docker logs default_cert
- docker logs certs_single
- docker logs certs_san
- docker logs certs_standalone
- docker logs force_renew
- docker logs permissions_default
- docker logs permissions_custom

View File

@ -162,6 +162,7 @@ if [[ "$*" == "/bin/bash /app/start.sh" ]]; then
check_writable_directory '/etc/nginx/certs'
check_writable_directory '/etc/nginx/vhost.d'
check_writable_directory '/usr/share/nginx/html'
[[ -f /app/letsencrypt_user_data ]] && check_writable_directory '/etc/nginx/conf.d'
check_deprecated_env_var
check_default_cert_key
check_dh_group

View File

@ -36,6 +36,33 @@ function add_location_configuration {
return 1
}
function add_standalone_configuration {
local domain="${1:?}"
cat > "/etc/nginx/conf.d/standalone-cert-$domain.conf" << EOF
server {
server_name $domain;
listen 80;
access_log /var/log/nginx/access.log vhost;
location ^~ /.well-known/acme-challenge/ {
auth_basic off;
allow all;
root /usr/share/nginx/html;
try_files \$uri =404;
break;
}
}
EOF
}
function remove_all_standalone_configurations {
local old_shopt_options=$(shopt -p) # Backup shopt options
shopt -s nullglob
for file in "/etc/nginx/conf.d/standalone-cert-"*".conf"; do
rm -f "$file"
done
eval "$old_shopt_options" # Restore shopt options
}
function remove_all_location_configurations {
local old_shopt_options=$(shopt -p) # Backup shopt options
shopt -s nullglob

View File

@ -45,6 +45,8 @@ function create_links {
}
function cleanup_links {
local -a LETSENCRYPT_CONTAINERS
local -a LETSENCRYPT_STANDALONE_CERTS
local -a ENABLED_DOMAINS
local -a SYMLINKED_DOMAINS
local -a DISABLED_DOMAINS
@ -60,9 +62,10 @@ function cleanup_links {
[[ $DEBUG == true ]] && echo "Symlinked domains: ${SYMLINKED_DOMAINS[*]}"
# Create an array containing domains that are considered
# enabled (ie present on /app/letsencrypt_service_data).
# shellcheck source=/dev/null
source /app/letsencrypt_service_data
# enabled (ie present on /app/letsencrypt_service_data or /app/letsencrypt_user_data).
[[ -f /app/letsencrypt_service_data ]] && source /app/letsencrypt_service_data
[[ -f /app/letsencrypt_user_data ]] && source /app/letsencrypt_user_data
LETSENCRYPT_CONTAINERS+=( "${LETSENCRYPT_STANDALONE_CERTS[@]}" )
for cid in "${LETSENCRYPT_CONTAINERS[@]}"; do
host_varname="LETSENCRYPT_${cid}_HOST"
hosts_array="${host_varname}[@]"
@ -75,7 +78,7 @@ function cleanup_links {
# Create an array containing only domains for which a symlinked private key exists
# in /etc/nginx/certs but that no longer have a corresponding LETSENCRYPT_HOST set
# on an active container.
# on an active container or on /app/letsencrypt_user_data
if [[ ${#SYMLINKED_DOMAINS[@]} -gt 0 ]]; then
mapfile -t DISABLED_DOMAINS < <(echo "${SYMLINKED_DOMAINS[@]}" \
"${ENABLED_DOMAINS[@]}" \
@ -115,15 +118,34 @@ function cleanup_links {
}
function update_certs {
local -a LETSENCRYPT_CONTAINERS
local -a LETSENCRYPT_STANDALONE_CERTS
check_nginx_proxy_container_run || return
[[ -f /app/letsencrypt_service_data ]] || return
# Load relevant container settings
unset LETSENCRYPT_CONTAINERS
# shellcheck source=/dev/null
source /app/letsencrypt_service_data
if [[ -f /app/letsencrypt_service_data ]]; then
source /app/letsencrypt_service_data
else
echo "Warning: /app/letsencrypt_service_data not found, skipping data from containers."
fi
# Load settings for standalone certs
if [[ -f /app/letsencrypt_user_data ]]; then
if source /app/letsencrypt_user_data; then
for cid in "${LETSENCRYPT_STANDALONE_CERTS[@]}"; do
host_varname="LETSENCRYPT_${cid}_HOST"
hosts_array="${host_varname}[@]"
for domain in "${!hosts_array}"; do
add_standalone_configuration "$domain"
done
done
reload_nginx
LETSENCRYPT_CONTAINERS+=( "${LETSENCRYPT_STANDALONE_CERTS[@]}" )
else
echo "Warning: could not source /app/letsencrypt_user_data, skipping user data"
fi
fi
should_reload_nginx='false'
for cid in "${LETSENCRYPT_CONTAINERS[@]}"; do
@ -138,13 +160,13 @@ function update_certs {
params_d_str=""
email_varname="LETSENCRYPT_${cid}_EMAIL"
email_address="${!email_varname}"
email_address="${!email_varname:-"<no value>"}"
if [[ "$email_address" != "<no value>" ]]; then
params_d_str+=" --email $email_address"
fi
keysize_varname="LETSENCRYPT_${cid}_KEYSIZE"
cert_keysize="${!keysize_varname}"
cert_keysize="${!keysize_varname:-"<no value>"}"
if [[ "$cert_keysize" == "<no value>" ]]; then
cert_keysize=$DEFAULT_KEY_SIZE
fi
@ -164,7 +186,7 @@ function update_certs {
fi
account_varname="LETSENCRYPT_${cid}_ACCOUNT_ALIAS"
account_alias="${!account_varname}"
account_alias="${!account_varname:-"<no value>"}"
if [[ "$account_alias" == "<no value>" ]]; then
account_alias=default
fi
@ -249,6 +271,13 @@ function update_certs {
[[ $simp_le_return -eq 0 ]] && should_reload_nginx='true'
fi
for domain in "${!hosts_array}"; do
if [[ -f "/etc/nginx/conf.d/standalone-cert-$domain.conf" ]]; then
[[ $DEBUG == true ]] && echo "Debug: removing standalone configuration file /etc/nginx/conf.d/standalone-cert-$domain.conf"
rm -f "/etc/nginx/conf.d/standalone-cert-$domain.conf" && should_reload_nginx='true'
fi
done
done
cleanup_links && should_reload_nginx='true'

View File

@ -7,6 +7,7 @@ term_handler() {
source /app/functions.sh
remove_all_location_configurations
remove_all_standalone_configurations
exit 0
}

View File

@ -11,6 +11,7 @@ imageTests+=(
default_cert
certs_single
certs_san
certs_standalone
force_renew
permissions_default
permissions_custom

View File

@ -9,6 +9,7 @@ case $SETUP in
--name $NGINX_CONTAINER_NAME \
--env "DHPARAM_BITS=256" \
-v /etc/nginx/vhost.d \
-v /etc/nginx/conf.d \
-v /usr/share/nginx/html \
-v /var/run/docker.sock:/tmp/docker.sock:ro \
--label com.github.jrcs.letsencrypt_nginx_proxy_companion.test_suite \

View File

@ -0,0 +1,8 @@
Started letsencrypt container for test certs_standalone
Symlink to le1.wtf certificate has been generated.
The link is pointing to the file ./le1.wtf/fullchain.pem
Domain le1.wtf is on certificate.
Symlink to le2.wtf certificate has been generated.
The link is pointing to the file ./le2.wtf/fullchain.pem
Domain le2.wtf is on certificate.
Domain le3.wtf is on certificate.

View File

@ -0,0 +1,74 @@
#!/bin/bash
## Test for standalone certificates.
if [[ -z $TRAVIS_CI ]]; then
le_container_name="$(basename ${0%/*})_$(date "+%Y-%m-%d_%H.%M.%S")"
else
le_container_name="$(basename ${0%/*})"
fi
# Create the $domains array from comma separated domains in TEST_DOMAINS.
IFS=',' read -r -a domains <<< "$TEST_DOMAINS"
# Cleanup function with EXIT trap
function cleanup {
# Cleanup the files created by this run of the test to avoid foiling following test(s).
docker exec "$le_container_name" bash -c 'rm -rf /etc/nginx/certs/le?.wtf*'
# Stop the LE container
docker stop "$le_container_name" > /dev/null
}
trap cleanup EXIT
# Create letsencrypt_user_data with a single domain cert
cat > ${TRAVIS_BUILD_DIR}/test/tests/certs_standalone/letsencrypt_user_data <<EOF
LETSENCRYPT_STANDALONE_CERTS=('single')
LETSENCRYPT_single_HOST=('${domains[0]}')
EOF
run_le_container ${1:?} "$le_container_name" \
"--volume ${TRAVIS_BUILD_DIR}/test/tests/certs_standalone/letsencrypt_user_data:/app/letsencrypt_user_data"
# Wait for a symlink at /etc/nginx/certs/${domains[0]}.crt
# then grab the certificate in text form ...
wait_for_symlink "${domains[0]}" "$le_container_name"
created_cert="$(docker exec "$le_container_name" \
openssl x509 -in /etc/nginx/certs/${domains[0]}/cert.pem -text -noout)"
# Check if the domain is on the certificate.
if grep -q "${domains[0]}" <<< "$created_cert"; then
echo "Domain ${domains[0]} is on certificate."
else
echo "Domain ${domains[0]} did not appear on certificate."
fi
docker exec "$le_container_name" bash -c "[[ -f /etc/nginx/conf.d/standalone-cert-${domains[0]}.conf ]]" \
&& echo "Standalone configuration for ${domains[0]} wasn't correctly removed."
# Add another (SAN) certificate to letsencrypt_user_data
cat > ${TRAVIS_BUILD_DIR}/test/tests/certs_standalone/letsencrypt_user_data <<EOF
LETSENCRYPT_STANDALONE_CERTS=('single' 'san')
LETSENCRYPT_single_HOST=('${domains[0]}')
LETSENCRYPT_san_HOST=('${domains[1]}' '${domains[2]}')
EOF
# Manually trigger the service loop
docker exec "$le_container_name" /app/signal_le_service > /dev/null
# Wait for a symlink at /etc/nginx/certs/${domains[1]}.crt
# then grab the certificate in text form ...
wait_for_symlink "${domains[1]}" "$le_container_name"
created_cert="$(docker exec "$le_container_name" \
openssl x509 -in /etc/nginx/certs/${domains[1]}/cert.pem -text -noout)"
for domain in "${domains[1]}" "${domains[2]}"; do
# Check if the domain is on the certificate.
if grep -q "$domain" <<< "$created_cert"; then
echo "Domain $domain is on certificate."
else
echo "Domain $domain did not appear on certificate."
fi
done
docker exec "$le_container_name" bash -c "[[ ! -f /etc/nginx/conf.d/standalone-cert-${domains[1]}.conf ]]" \
|| echo "Standalone configuration for ${domains[1]} wasn't correctly removed."