Skip to content

Commit

Permalink
feat: wildcard certificates support
Browse files Browse the repository at this point in the history
Co-authored-by: Nicolas Duchon <nicolas.duchon@gmail.com>
Co-authored-by: Gilles Filippini <pini@debian.org>
  • Loading branch information
buchdag and pini-gh committed Jul 16, 2024
1 parent 712dd94 commit 9772acc
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 23 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ It handles the automated creation, renewal and use of SSL certificates for proxi
* Let's Encrypt / ACME domain validation through `HTTP-01` (by default) or [`DNS-01`](https://github.com/nginx-proxy/acme-companion/blob/main/docs/Let's-Encrypt-and-ACME.md#dns-01-acme-challenge) challenge.
* Automated update and reload of nginx config on certificate creation/renewal.
* Support creation of [Multi-Domain (SAN) Certificates](https://github.com/nginx-proxy/acme-companion/blob/main/docs/Let's-Encrypt-and-ACME.md#multi-domains-certificates).
* Support creation of [Wildcard Certificates](https://community.letsencrypt.org/t/acme-v2-production-environment-wildcards/55578) (with `DNS-01` challenge only).
* Creation of a strong [RFC7919 Diffie-Hellman Group](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A) at startup.
* Work with all versions of docker.

Expand Down
67 changes: 44 additions & 23 deletions app/letsencrypt_service
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ RENEW_PRIVATE_KEYS="$(lc "${RENEW_PRIVATE_KEYS:-true}")"
# Backward compatibility environment variable
REUSE_PRIVATE_KEYS="$(lc "${REUSE_PRIVATE_KEYS:-false}")"

function strip_wildcard {
# Remove wildcard prefix if present
# https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#wildcard-certificates
local -r domain="${1?missing domain argument}"
if [[ "${domain:0:2}" == "*." ]]; then
echo "${domain:2}"
else
echo "$domain"
fi
}

function create_link {
local -r source=${1?missing source argument}
local -r target=${2?missing target argument}
Expand All @@ -27,7 +38,8 @@ function create_link {

function create_links {
local -r base_domain=${1?missing base_domain argument}
local -r domain=${2?missing base_domain argument}
local domain=${2?missing base_domain argument}
domain="$(strip_wildcard "$domain")"

if [[ ! -f "/etc/nginx/certs/$base_domain/fullchain.pem" || \
! -f "/etc/nginx/certs/$base_domain/key.pem" ]]; then
Expand Down Expand Up @@ -75,6 +87,7 @@ function cleanup_links {
for cid in "${LETSENCRYPT_CONTAINERS[@]}"; do
local -n hosts_array="LETSENCRYPT_${cid}_HOST"
for domain in "${hosts_array[@]}"; do
domain="$(strip_wildcard "$domain")"
# Add domain to the array storing currently enabled domains.
ENABLED_DOMAINS+=("$domain")
done
Expand Down Expand Up @@ -128,6 +141,11 @@ function update_cert {
# First domain will be our base domain
local base_domain="${hosts_array[0]}"

local wildcard_certificate='false'
if [[ "${base_domain:0:2}" == "*." ]]; then
wildcard_certificate='true'
fi

local should_restart_container='false'

# Base CLI parameters array, used for both --register-account and --issue
Expand Down Expand Up @@ -160,6 +178,10 @@ function update_cert {

if [[ "$acme_challenge" == "HTTP-01" ]]; then
# HTTP-01 challenge
if [[ "$wildcard_certificate" == 'true' ]]; then
echo "Error: wildcard certificates (${base_domain}) can't be obtained with HTTP-01 challenge"
return 1
fi
params_issue_arr+=(--webroot /usr/share/nginx/html)
elif [[ "$acme_challenge" == "DNS-01" ]]; then
# DNS-01 challenge
Expand Down Expand Up @@ -240,23 +262,28 @@ function update_cert {
local ca_path_dir
ca_path_dir="$(echo "$acme_ca_uri" | cut -d : -f 2- | tr -s / | cut -d / -f 3-)"

local certificate_dir
local relative_certificate_dir
if [[ "$wildcard_certificate" == 'true' ]]; then
relative_certificate_dir="wildcard_${base_domain:2}"
else
relative_certificate_dir="$base_domain"
fi
# If we're going to use one of LE stating endpoints ...
if [[ "$acme_ca_uri" =~ ^https://acme-staging.* ]]; then
# Unset accountemail
# force config dir to 'staging'
unset accountemail
config_home="/etc/acme.sh/staging"
# Prefix test certificate directory with _test_
certificate_dir="/etc/nginx/certs/_test_$base_domain"
else
certificate_dir="/etc/nginx/certs/$base_domain"
relative_certificate_dir="_test_${relative_certificate_dir}"
fi

local absolute_certificate_dir="/etc/nginx/certs/$relative_certificate_dir"
params_issue_arr+=( \
--cert-file "${certificate_dir}/cert.pem" \
--key-file "${certificate_dir}/key.pem" \
--ca-file "${certificate_dir}/chain.pem" \
--fullchain-file "${certificate_dir}/fullchain.pem" \
--cert-file "${absolute_certificate_dir}/cert.pem" \
--key-file "${absolute_certificate_dir}/key.pem" \
--ca-file "${absolute_certificate_dir}/chain.pem" \
--fullchain-file "${absolute_certificate_dir}/fullchain.pem" \
)

[[ ! -d "$config_home" ]] && mkdir -p "$config_home"
Expand Down Expand Up @@ -376,8 +403,8 @@ function update_cert {
[[ "${2:-}" == "--force-renew" ]] && params_issue_arr+=(--force)

# Create directory for the first domain
mkdir -p "$certificate_dir"
set_ownership_and_permissions "$certificate_dir"
mkdir -p "$absolute_certificate_dir"
set_ownership_and_permissions "$absolute_certificate_dir"

for domain in "${hosts_array[@]}"; do
# Add all the domains to certificate
Expand Down Expand Up @@ -408,21 +435,15 @@ function update_cert {
# 0 = success, 2 = RENEW_SKIP
if [[ $acmesh_return == 0 || $acmesh_return == 2 ]]; then
for domain in "${hosts_array[@]}"; do
if [[ $acme_ca_uri =~ ^https://acme-staging.* ]]; then
create_links "_test_$base_domain" "$domain" \
&& should_reload_nginx='true' \
&& should_restart_container='true'
else
create_links "$base_domain" "$domain" \
&& should_reload_nginx='true' \
&& should_restart_container='true'
fi
create_links "$relative_certificate_dir" "$domain" \
&& should_reload_nginx='true' \
&& should_restart_container='true'
done
echo "${COMPANION_VERSION:-}" > "${certificate_dir}/.companion"
set_ownership_and_permissions "${certificate_dir}/.companion"
echo "${COMPANION_VERSION:-}" > "${absolute_certificate_dir}/.companion"
set_ownership_and_permissions "${absolute_certificate_dir}/.companion"
# Make private key root readable only
for file in cert.pem key.pem chain.pem fullchain.pem; do
local file_path="${certificate_dir}/${file}"
local file_path="${absolute_certificate_dir}/${file}"
[[ -e "$file_path" ]] && set_ownership_and_permissions "$file_path"
done
local acme_private_key
Expand Down

0 comments on commit 9772acc

Please sign in to comment.