Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge dev branch into master (network segregation feature) #739

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
container_restart,
permissions_default,
permissions_custom,
networks_segregation,
symlinks,
]
setup: [2containers, 3containers]
Expand Down
106 changes: 66 additions & 40 deletions app/letsencrypt_service_data.tmpl
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
{{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }}
{{ $scopedContainersString := "" }}

{{ range $hosts, $containers := groupBy $ "Env.LETSENCRYPT_HOST" }}
{{ if trim $hosts }}
{{ range $container := $containers }}
{{ $cid := printf "%.12s" $container.ID }}
{{ if $CurrentContainer.Env.NETWORK_SCOPE }}
{{ range $containerNetwork := $container.Networks }}
{{ if eq $CurrentContainer.Env.NETWORK_SCOPE $containerNetwork.Name }}
{{ $scopedContainersString = (printf "%s %s" $scopedContainersString $cid) }}
{{ end }}
{{ end }}
{{ else }}
{{ $scopedContainersString = (printf "%s %s" $scopedContainersString $cid) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}

{{ $scopedContainersSlice := split (trim $scopedContainersString) " " }}

LETSENCRYPT_CONTAINERS=(
{{ range $hosts, $containers := groupBy $ "Env.LETSENCRYPT_HOST" }}
{{ if trim $hosts }}
{{ range $container := $containers }}
{{ if parseBool (coalesce $container.Env.LETSENCRYPT_SINGLE_DOMAIN_CERTS "false") }}
{{ range $host := split $hosts "," }}
{{ $host := trim $host }}
{{- "\t"}}'{{ printf "%.12s" $container.ID }}_{{ sha1 $host }}'
{{ $cid := printf "%.12s" $container.ID }}
{{ if intersect $scopedContainersSlice (split $cid " ") }}
{{ if parseBool (coalesce $container.Env.LETSENCRYPT_SINGLE_DOMAIN_CERTS "false") }}
{{ range $host := split $hosts "," }}
{{- "\t"}}'{{ printf "%s_%s" $cid (sha1 (trim $host)) }}'
{{ end }}
{{ else }}
'{{ $cid }}'
{{ end }}
{{ else }}
{{- "\t"}}'{{ printf "%.12s" $container.ID }}'
{{ end }}
{{ end }}
{{ end }}
Expand All @@ -19,41 +43,43 @@ LETSENCRYPT_CONTAINERS=(
{{ $hosts := trimSuffix "," $hosts }}
{{ range $container := $containers }}
{{ $cid := printf "%.12s" $container.ID }}
{{ if parseBool (coalesce $container.Env.LETSENCRYPT_SINGLE_DOMAIN_CERTS "false") }}
{{ range $host := split $hosts "," }}
{{ $host := trim $host }}
{{ $host := trimSuffix "." $host }}
{{ $hostHash := sha1 $host }}
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_HOST=('{{ $host }}')
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_KEYSIZE="{{ $container.Env.LETSENCRYPT_KEYSIZE }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_TEST="{{ $container.Env.LETSENCRYPT_TEST }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_EMAIL="{{ $container.Env.LETSENCRYPT_EMAIL }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_CA_URI="{{ $container.Env.ACME_CA_URI }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_PREFERRED_CHAIN="{{ $container.Env.ACME_PREFERRED_CHAIN }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_OCSP="{{ $container.Env.ACME_OCSP }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_EAB_KID="{{ $container.Env.ACME_EAB_KID }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_EAB_HMAC_KEY="{{ $container.Env.ACME_EAB_HMAC_KEY }}"
{{- "\n" }}ZEROSSL_{{ $cid }}_{{ $hostHash }}_API_KEY="{{ $container.Env.ZEROSSL_API_KEY }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_RESTART_CONTAINER="{{ $container.Env.LETSENCRYPT_RESTART_CONTAINER }}"
{{ if intersect $scopedContainersSlice (split $cid " ") }}
{{ if parseBool (coalesce $container.Env.LETSENCRYPT_SINGLE_DOMAIN_CERTS "false") }}
{{ range $host := split $hosts "," }}
{{ $host := trim $host }}
{{ $host := trimSuffix "." $host }}
{{ $hostHash := sha1 $host }}
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_HOST=('{{ $host }}')
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_KEYSIZE="{{ $container.Env.LETSENCRYPT_KEYSIZE }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_TEST="{{ $container.Env.LETSENCRYPT_TEST }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_EMAIL="{{ $container.Env.LETSENCRYPT_EMAIL }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_CA_URI="{{ $container.Env.ACME_CA_URI }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_PREFERRED_CHAIN="{{ $container.Env.ACME_PREFERRED_CHAIN }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_OCSP="{{ $container.Env.ACME_OCSP }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_EAB_KID="{{ $container.Env.ACME_EAB_KID }}"
{{- "\n" }}ACME_{{ $cid }}_{{ $hostHash }}_EAB_HMAC_KEY="{{ $container.Env.ACME_EAB_HMAC_KEY }}"
{{- "\n" }}ZEROSSL_{{ $cid }}_{{ $hostHash }}_API_KEY="{{ $container.Env.ZEROSSL_API_KEY }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_{{ $hostHash }}_RESTART_CONTAINER="{{ $container.Env.LETSENCRYPT_RESTART_CONTAINER }}"
{{ end }}
{{ else }}
{{- "\n" }}LETSENCRYPT_{{ $cid }}_HOST=(
{{- range $host := split $hosts "," }}
{{- $host := trim $host }}
{{- $host := trimSuffix "." $host -}}
'{{ $host }}'{{ " " }}
{{- end -}}
)
{{- "\n" }}LETSENCRYPT_{{ $cid }}_KEYSIZE="{{ $container.Env.LETSENCRYPT_KEYSIZE }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_TEST="{{ $container.Env.LETSENCRYPT_TEST }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_EMAIL="{{ $container.Env.LETSENCRYPT_EMAIL }}"
{{- "\n" }}ACME_{{ $cid }}_CA_URI="{{ $container.Env.ACME_CA_URI }}"
{{- "\n" }}ACME_{{ $cid }}_PREFERRED_CHAIN="{{ $container.Env.ACME_PREFERRED_CHAIN }}"
{{- "\n" }}ACME_{{ $cid }}_OCSP="{{ $container.Env.ACME_OCSP }}"
{{- "\n" }}ACME_{{ $cid }}_EAB_KID="{{ $container.Env.ACME_EAB_KID }}"
{{- "\n" }}ACME_{{ $cid }}_EAB_HMAC_KEY="{{ $container.Env.ACME_EAB_HMAC_KEY }}"
{{- "\n" }}ZEROSSL_{{ $cid }}_API_KEY="{{ $container.Env.ZEROSSL_API_KEY }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_RESTART_CONTAINER="{{ $container.Env.LETSENCRYPT_RESTART_CONTAINER }}"
{{ end }}
{{ else }}
{{- "\n" }}LETSENCRYPT_{{ $cid }}_HOST=(
{{- range $host := split $hosts "," }}
{{- $host := trim $host }}
{{- $host := trimSuffix "." $host -}}
'{{ $host }}'{{ " " }}
{{- end -}}
)
{{- "\n" }}LETSENCRYPT_{{ $cid }}_KEYSIZE="{{ $container.Env.LETSENCRYPT_KEYSIZE }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_TEST="{{ $container.Env.LETSENCRYPT_TEST }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_EMAIL="{{ $container.Env.LETSENCRYPT_EMAIL }}"
{{- "\n" }}ACME_{{ $cid }}_CA_URI="{{ $container.Env.ACME_CA_URI }}"
{{- "\n" }}ACME_{{ $cid }}_PREFERRED_CHAIN="{{ $container.Env.ACME_PREFERRED_CHAIN }}"
{{- "\n" }}ACME_{{ $cid }}_OCSP="{{ $container.Env.ACME_OCSP }}"
{{- "\n" }}ACME_{{ $cid }}_EAB_KID="{{ $container.Env.ACME_EAB_KID }}"
{{- "\n" }}ACME_{{ $cid }}_EAB_HMAC_KEY="{{ $container.Env.ACME_EAB_HMAC_KEY }}"
{{- "\n" }}ZEROSSL_{{ $cid }}_API_KEY="{{ $container.Env.ZEROSSL_API_KEY }}"
{{- "\n" }}LETSENCRYPT_{{ $cid }}_RESTART_CONTAINER="{{ $container.Env.LETSENCRYPT_RESTART_CONTAINER }}"
{{ end }}
{{ end }}
{{ end }}
19 changes: 18 additions & 1 deletion docs/Container-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ You can also create test certificates per container (see [Test certificates](./L

* `DHPARAM_BITS` - Change the size of the Diffie-Hellman key generated by the container from the default value of 2048 bits. For example `--env DHPARAM_BITS=1024` to support some older clients like Java 6 and 7.

* `NETWORK_SCOPE` – The network name, that the container requesting a certificate MUST be connected to, in order to be discovered. You may find this option useful, when the host machine has multiple public IP addresses and you want to run separate nginx-proxy containers that will handle separate services with a proper networking isolation.

If you set this environment variable, you MUST connect the nginx-proxy container to the same network. For example:

```bash
$ docker run --detach \
--name nginx-proxy-letsencrypt \
--volumes-from nginx-proxy \
--volume /path/to/certs:/etc/nginx/certs:rw \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \
--network domains_group_a
--env "NETWORK_SCOPE=domains_group_a" \
jrcs/letsencrypt-nginx-proxy-companion
```

The created companion will discover only the containers, that are also connected to the `domains_group_a` network.

* `CA_BUNDLE` - This is a test only variable [for use with Pebble](https://github.com/letsencrypt/pebble#avoiding-client-https-errors). It changes the trusted root CA used by `acme.sh`, from the default Alpine trust store to the CA bundle file located at the provided path (inside the container). Do **not** use it in production unless you are running your own ACME CA.

* `CERTS_UPDATE_INTERVAL` - 3600 seconds by default, this defines how often the container will check if the certificates require update.
* `CERTS_UPDATE_INTERVAL` - 3600 seconds by default, this defines how often the container will check if the certificates require update.
1 change: 1 addition & 0 deletions test/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ globalTests+=(
container_restart
permissions_default
permissions_custom
networks_segregation
symlinks
)

Expand Down
1 change: 1 addition & 0 deletions test/tests/networks_segregation/expected-std-out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

76 changes: 76 additions & 0 deletions test/tests/networks_segregation/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash

## Test for single domain certificates.

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

# 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" /app/cleanup_test_artifacts
# Stop the LE container
docker stop "$le_container_name" > /dev/null
# Drop temp network
docker network rm "le_test_other_net1" > /dev/null
docker network rm "le_test_other_net2" > /dev/null
}
trap cleanup EXIT

docker network create "le_test_other_net1" > /dev/null
docker network create "le_test_other_net2" > /dev/null

networks_map=("$desired_network" le_test_other_net1 le_test_other_net2)

# Run a separate nginx container for each domain in the $domains array.
# Start all the containers in a row so that docker-gen debounce timers fire only once.
i=0
for domain in "${domains[@]}"; do
if ! docker run --rm -d \
--name "$domain" \
-e "VIRTUAL_HOST=${domain}" \
-e "LETSENCRYPT_HOST=${domain}" \
--network "${networks_map[i]}" \
nginx:alpine > /dev/null;
then
echo "Could not start test web server for $domain"
elif [[ "${DRY_RUN:-}" == 1 ]]; then
echo "Started test web server for $domain"
fi

i=$(( i + 1 ))
done

i=0
for domain in "${domains[@]}"; do
if [ "${networks_map[i]}" != "$desired_network" ]; then
[[ "${DRY_RUN:-}" == 1 ]] && echo "$domain is not in $desired_network, cert should not be generated";

service_data="$(docker exec "$le_container_name" cat /app/letsencrypt_service_data)"
if grep -q "$domain" <<< "$service_data"; then
echo "Domain $domain is on data list, but MUST not!"
else
[[ "${DRY_RUN:-}" == 1 ]] && echo "Domain $domain was not included in the service_data."
fi
else
[[ "${DRY_RUN:-}" == 1 ]] && echo "$domain is in $desired_network, cert should be generated";

# Wait for a symlink at /etc/nginx/certs/$domain.crt
wait_for_symlink "$domain" "$le_container_name"
fi
# Stop the Nginx container silently.
docker stop "$domain" > /dev/null
i=$(( i + 1 ))
done