Skip to content

Commit

Permalink
Merge pull request #485 from Greek64/PR
Browse files Browse the repository at this point in the history
Add LETSENCRYPT_MIN_VALIDITY variable
  • Loading branch information
buchdag authored Jan 8, 2019
2 parents 6a90d53 + a0e54d8 commit 7dd2cd6
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 2 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,10 @@ If you want to create multi-domain ([SAN](https://www.digicert.com/subject-alter
If you want to create test certificates that don't have the 5 certs/week/domain limits define the `LETSENCRYPT_TEST` environment variable with a value of `true` (in the containers where you request certificates with `LETSENCRYPT_HOST`). If you want to do this globally for all containers, set `ACME_CA_URI` as described below.

##### Automatic certificate renewal
Every hour (3600 seconds) the certificates are checked and every certificate that will expire in the next [30 days](https://github.com/kuba/simp_le/blob/ecf4290c4f7863bb5427b50cdd78bc3a5df79176/simp_le.py#L72) (90 days / 3) are renewed.
Every hour (3600 seconds) the certificates are checked and per default every certificate that will expire in the next [30 days](https://github.com/zenhack/simp_le/blob/a8a8013c097910f8f3cce046f1077b41b745673b/simp_le.py#L73) (90 days / 3) is renewed.

If you want to manually set a different minimum validity for certificates, you can set the `LETSENCRYPT_MIN_VALIDIDTY` environment variable (in each container that defines the `LETSENCRYPT_HOST` variable) to the desired period in seconds.
Note that the possible values are internally capped at an upper bound of 7603200 (88 days) and a lower bound of 7200 (2 hours) as a security margin, considering that the Let's Encrypt CA does only issues certificates with a lifetime of [90 days](https://letsencrypt.org/2015/11/09/why-90-days.html) (upper bound), the rate limits imposed on certificate renewals are [5 per week](https://letsencrypt.org/docs/rate-limits/) (upper bound), and the fact that the certificates are checked and renewed accordingly every hour (lower bound).

##### Force certificates renewal
If needed, you can force a running letsencrypt-nginx-proxy-companion container to renew all certificates that are currently in use. Replace `nginx-letsencrypt` with the name of your letsencrypt-nginx-proxy-companion container in the following command:
Expand Down
25 changes: 24 additions & 1 deletion app/letsencrypt_service
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ ACME_CA_URI="${ACME_CA_URI:-https://acme-v01.api.letsencrypt.org/directory}"
DEFAULT_KEY_SIZE=4096
REUSE_ACCOUNT_KEYS="$(lc ${REUSE_ACCOUNT_KEYS:-true})"
REUSE_PRIVATE_KEYS="$(lc ${REUSE_PRIVATE_KEYS:-false})"
MIN_VALIDITY_CAP=7603200
DEFAULT_MIN_VALIDITY=2592000

function create_link {
local -r source=${1?missing source argument}
Expand Down Expand Up @@ -174,7 +176,28 @@ function update_certs {

[[ "$(lc $DEBUG)" == true ]] && params_d_str+=" -v"
[[ $REUSE_PRIVATE_KEYS == true ]] && params_d_str+=" --reuse_key"
[[ "${1}" == "--force-renew" ]] && params_d_str+=" --valid_min 7776000"

min_validity="LETSENCRYPT_${cid}_MIN_VALIDITY"
min_validity="${!min_validity}"
if [[ "$min_validity" == "<no value>" ]]; then
min_validity=$DEFAULT_MIN_VALIDITY
fi
# Sanity Check
# Upper Bound
if [[ $min_validity -gt $MIN_VALIDITY_CAP ]]; then
min_validity=$MIN_VALIDITY_CAP
fi
# Lower Bound
if [[ $min_validity -lt $(($seconds_to_wait * 2)) ]]; then
min_validity=$(($seconds_to_wait * 2))
fi

if [[ "${1}" == "--force-renew" ]]; then
# Manually set to highest certificate lifetime given by LE CA
params_d_str+=" --valid_min 7776000"
else
params_d_str+=" --valid_min $min_validity"
fi

# Create directory for the first domain,
# make it root readable only and make it the cwd
Expand Down
1 change: 1 addition & 0 deletions app/letsencrypt_service_data.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ LETSENCRYPT_{{ $cid }}_KEYSIZE="{{ $container.Env.LETSENCRYPT_KEYSIZE }}"
LETSENCRYPT_{{ $cid }}_TEST="{{ $container.Env.LETSENCRYPT_TEST }}"
LETSENCRYPT_{{ $cid }}_ACCOUNT_ALIAS="{{ $container.Env.LETSENCRYPT_ACCOUNT_ALIAS }}"
LETSENCRYPT_{{ $cid }}_RESTART_CONTAINER="{{ $container.Env.LETSENCRYPT_RESTART_CONTAINER }}"
LETSENCRYPT_{{ $cid }}_MIN_VALIDITY="{{ $container.Env.LETSENCRYPT_MIN_VALIDITY }}"
{{ end }}

{{ end }}
1 change: 1 addition & 0 deletions test/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ imageTests+=(
certs_single
certs_san
force_renew
certs_validity
container_restart
permissions_default
permissions_custom
Expand Down
10 changes: 10 additions & 0 deletions test/setup/setup-boulder.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ setup_boulder() {
$GOPATH/src/github.com/letsencrypt/boulder
pushd $GOPATH/src/github.com/letsencrypt/boulder
if [[ "$(uname)" == 'Darwin' ]]; then
# Set Standard Ports
sed -i '' 's/ 5002/ 80/g' test/config/va.json
sed -i '' 's/ 5001/ 443/g' test/config/va.json
# Set certificate lifetime to 88 days
sed -i '' 's/2160h/2112h/g' test/config/ca-a.json
sed -i '' 's/2160h/2112h/g' test/config/ca-b.json
# Modify custom rate limit
sed -i '' 's/le.wtf,le1.wtf/le1.wtf,le2.wtf,le3.wtf/g' test/rate-limit-policies.yml
else
# Set Standard Ports
sed --in-place 's/ 5002/ 80/g' test/config/va.json
sed --in-place 's/ 5001/ 443/g' test/config/va.json
# Set certificate lifetime to 88 days
sed --in-place 's/2160h/2112h/g' test/config/ca-a.json
sed --in-place 's/2160h/2112h/g' test/config/ca-b.json
# Modify custom rate limit
sed --in-place 's/le.wtf,le1.wtf/le1.wtf,le2.wtf,le3.wtf/g' test/rate-limit-policies.yml
fi
docker-compose build --pull
Expand Down
13 changes: 13 additions & 0 deletions test/tests/certs_validity/expected-std-out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Started letsencrypt container for test certs_validity
Started test web server for le1.wtf
Started test web server for le2.wtf
Started test web server for le3.wtf
Symlink to le1.wtf certificate has been generated.
The link is pointing to the file ./le1.wtf/fullchain.pem
Symlink to le2.wtf certificate has been generated.
The link is pointing to the file ./le2.wtf/fullchain.pem
Symlink to le3.wtf certificate has been generated.
The link is pointing to the file ./le3.wtf/fullchain.pem
Certificate for le1.wtf was not renewed.
Certificate for le2.wtf was not renewed.
Certificate for le3.wtf was renewed.
93 changes: 93 additions & 0 deletions test/tests/certs_validity/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/bin/bash

## Test for the LETSENCRYPT_MIN_VALIDITY environment variable.

if [[ -z $TRAVIS_CI ]]; then
le_container_name="$(basename ${0%/*})_$(date "+%Y-%m-%d_%H.%M.%S")"
else
le_container_name="$(basename ${0%/*})"
fi
run_le_container ${1:?} "$le_container_name"

# 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 {
# Remove any remaining Nginx container(s) silently.
for domain in "${domains[@]}"; do
docker rm --force "$domain" > /dev/null 2>&1
done
# 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

# Run a separate nginx container for each domain in the $domains array.
# Default validity
docker run --rm -d \
--name "${domains[0]}" \
-e "VIRTUAL_HOST=${domains[0]}" \
-e "LETSENCRYPT_HOST=${domains[0]}" \
--network boulder_bluenet \
nginx:alpine > /dev/null && echo "Started test web server for ${domains[0]}"
# Manual validity (same as default)
docker run --rm -d \
--name "${domains[1]}" \
-e "VIRTUAL_HOST=${domains[1]}" \
-e "LETSENCRYPT_HOST=${domains[1]}" \
-e "LETSENCRYPT_MIN_VALIDITY=2592000" \
--network boulder_bluenet \
nginx:alpine > /dev/null && echo "Started test web server for ${domains[1]}"
# Manual validity (few seconds shy of MIN_VALIDITY_CAP=7603200)
docker run --rm -d \
--name "${domains[2]}" \
-e "VIRTUAL_HOST=${domains[2]}" \
-e "LETSENCRYPT_HOST=${domains[2]}" \
-e "LETSENCRYPT_MIN_VALIDITY=7603190" \
--network boulder_bluenet \
nginx:alpine > /dev/null && echo "Started test web server for ${domains[2]}"

# Wait for a symlinks
wait_for_symlink "${domains[0]}" "$le_container_name"
wait_for_symlink "${domains[1]}" "$le_container_name"
wait_for_symlink "${domains[2]}" "$le_container_name"
# Grab the expiration times of the certificates
first_cert_expire_1="$(get_cert_expiration_epoch "${domains[0]}" "$le_container_name")"
first_cert_expire_2="$(get_cert_expiration_epoch "${domains[1]}" "$le_container_name")"
first_cert_expire_3="$(get_cert_expiration_epoch "${domains[2]}" "$le_container_name")"

# Wait for ${domains[2]} set certificate validity to expire
sleep 10

# Manually trigger letsencrypt_service
docker exec "$le_container_name" /bin/bash -c "source /app/letsencrypt_service --source-only; update_certs" > /dev/null 2>&1

# Grab the new expiration times of the certificates
second_cert_expire_1="$(get_cert_expiration_epoch "${domains[0]}" "$le_container_name")"
second_cert_expire_2="$(get_cert_expiration_epoch "${domains[1]}" "$le_container_name")"
second_cert_expire_3="$(get_cert_expiration_epoch "${domains[2]}" "$le_container_name")"

if [[ $second_cert_expire_1 -eq $first_cert_expire_1 ]]; then
echo "Certificate for ${domains[0]} was not renewed."
else
echo "Certificate for ${domains[0]} was incorrectly renewed."
echo "First certificate expiration epoch : $first_cert_expire_1."
echo "Second certificate expiration epoch : $second_cert_expire_1."
fi
if [[ $second_cert_expire_2 -eq $first_cert_expire_2 ]]; then
echo "Certificate for ${domains[1]} was not renewed."
else
echo "Certificate for ${domains[1]} was incorrectly renewed."
echo "First certificate expiration epoch : $first_cert_expire_2."
echo "Second certificate expiration epoch : $second_cert_expire_2."
fi
if [[ $second_cert_expire_3 -gt $first_cert_expire_3 ]]; then
echo "Certificate for ${domains[2]} was renewed."
else
echo "Certificate for ${domains[2]} was not renewed."
echo "First certificate expiration epoch : $first_cert_expire_3."
echo "Second certificate expiration epoch : $second_cert_expire_3."
fi

0 comments on commit 7dd2cd6

Please sign in to comment.