-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(PROTOTYPE): complete initial implementation
- Loading branch information
0 parents
commit c8ff356
Showing
12 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
FROM docker.io/cisagov/postfix:latest | ||
|
||
ENV ENV_FILE "" | ||
ENV DKIM_DELAY "30" | ||
ENV DNS_PROPAGATION_DELAY "30" | ||
ENV RENEW_INTERVAL_IN_DAYS "7" | ||
ENV TEST_MODE "1" | ||
|
||
ARG PROVIDER="aws" | ||
|
||
RUN mkdir -p certbot /usr/local/share/certs/providers /usr/local/share/certs/scripts /run/secrets | ||
COPY providers/"${PROVIDER}".bash /usr/local/share/certs/providers | ||
COPY scripts/*.bash /usr/local/share/certs/scripts | ||
|
||
RUN apt-get update \ | ||
&& \ | ||
apt install -y \ | ||
certbot \ | ||
jq \ | ||
procps \ | ||
psmisc \ | ||
&& \ | ||
bash -c " \ | ||
source /usr/local/share/certs/providers/${PROVIDER}.bash \ | ||
&& \ | ||
provider_dependencies \ | ||
" \ | ||
&& \ | ||
rm -rf /var/lib/apt/lists/* | ||
|
||
COPY entrypoint.sh entrypoint.sh | ||
RUN chmod +x entrypoint.sh | ||
|
||
ENTRYPOINT ["./entrypoint.sh"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# SMTP Docker Container | ||
|
||
Wraps [cisagov/postfix-docker](https://github.com/cisagov/postfix-docker) in automation for generating [Let's Encrypt](https://letsencrypt.org/) SSL certificates and dkim DNS records. | ||
|
||
## Build Arguments | ||
|
||
| Name | Value | Default | | ||
|----------|-----------------------------------------------|---------| | ||
| PROVIDER | "aws" or "cloudflare" to customize container. | aws | | ||
|
||
## Environment Variables | ||
|
||
You may set the following environment variables to customize the container's behaviour: | ||
|
||
| Name | Value | Default | | ||
|------------------------|--------------------------------------------------------------|------------| | ||
| CONTACT_EMAIL | Let's Encrypt Contact Email. | No Default | | ||
| DKIM_DELAY | The time to wait for opendkim to generate a dkim value. | 30 | | ||
| DNS_PROPAGATION_DELAY | The time for Let's Encrypt to wait for DNS changes. | 30 | | ||
| PRIMARY_DOMAIN | The domain postfix is running for. | No Default | | ||
| RENEW_INTERVAL_IN_DAYS | The interval (in days) to attempt to renew the certificates. | 7 | | ||
| TEST_MODE | Set to "0" after you have tested certificate generation. | 1 | | ||
|
||
### DNS Providers | ||
|
||
Each DNS provider has its own set of additional required environment variables. | ||
|
||
#### AWS DNS Provider | ||
|
||
There are no defaults for provider environment variables. | ||
|
||
| Name | Value | | ||
|-----------------------|-------------------------------------------------| | ||
| AWS_ACCESS_KEY_ID | The AWS access key to use to access Route 53. | | ||
| AWS_HOSTED_ZONE_ID | The AWS ID for the zone hosted in Route 53. | | ||
| AWS_SECRET_ACCESS_KEY | The associated AWS secret key for that account. | | ||
|
||
Please see the [certbot plugin documentation](https://certbot-dns-route53.readthedocs.io/en/stable/) for further details. | ||
|
||
#### Cloudflare DNS Provider | ||
|
||
There are no defaults for provider environment variables. | ||
|
||
| Name | Value | | ||
|----------------------|-----------------------------------------------------------------------------------------------------------------------------| | ||
| CLOUDFLARE_API_TOKEN | The restricted Cloudflare API Token for this domain. | | ||
| CLOUDFLARE_ZONE_ID | The [zone id](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/) of the domain in Cloudflare. | | ||
|
||
Please see the [certbot plugin documentation](https://certbot-dns-cloudflare.readthedocs.io/en/stable/) for further details. | ||
|
||
### Using an Env File | ||
|
||
Alternatively, you can *mount* a single env file containing all required values. | ||
This file should adhere to the standard Env File format: | ||
```bash | ||
ENV_NAME_1="ENV_VALUE_1" | ||
ENV_NAME_2="ENV_VALUE_2" | ||
ENV_NAME_3="ENV_VALUE_3" | ||
``` | ||
|
||
Configure this environment variable to tell the container where to find the Env File: | ||
|
||
| Name | Value | Default | | ||
|----------|--------------------------------------------------------|------------| | ||
| ENV_FILE | Mounted location of the env file inside the container. | No Default | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
#!/bin/bash | ||
|
||
set -eo pipefail | ||
|
||
trap terminate SIGINT SIGTERM ERR EXIT | ||
|
||
env_file() { | ||
if [[ -n "${ENV_FILE}" ]]; then | ||
set -a | ||
# shellcheck disable=SC1090 | ||
source "${ENV_FILE}" | ||
set +a | ||
fi | ||
} | ||
|
||
import () { | ||
# $1 - path to scripts | ||
# $2 - description of import | ||
for SCRIPT in "${1}"/*.bash; do | ||
echo "CONTAINER > Import ${2}: ${SCRIPT}" | ||
# shellcheck disable=SC1090 | ||
source "${SCRIPT}"; | ||
done | ||
} | ||
|
||
terminate() { | ||
ERROR_CODE="$?" | ||
echo "CONTAINER > ERROR CODE: ${ERROR_CODE}" | ||
exit "${ERROR_CODE}" | ||
} | ||
|
||
main() { | ||
|
||
env_file | ||
|
||
import /usr/local/share/certs/providers "DNS Provider" | ||
import /usr/local/share/certs/scripts "Script Library" | ||
|
||
# shellcheck disable=SC2034 | ||
if [[ "${TEST_MODE}" == "1" ]]; then | ||
TEST_MODE="--test-cert" | ||
else | ||
TEST_MODE="" | ||
fi | ||
|
||
create # Create initial certificates | ||
renew & # Start certificate renewal process | ||
dkim & # Start deferred dkim update process | ||
|
||
echo "CONTAINER > Starting postfix ..." | ||
./docker-entrypoint.sh "postfix" "-v" "start-fg" | ||
} | ||
|
||
main "$@" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# DNS Providers | ||
|
||
DNS providers should provide a script file named in the following format: | ||
``` | ||
[PROVIDER].bash | ||
``` | ||
|
||
The script itself should define 4 functions: | ||
|
||
```bash | ||
provider_create() { | ||
# Call certbot to create the certificates from scratch. | ||
} | ||
|
||
provider_dependencies() { | ||
# Commands to install the provider's dependencies. | ||
} | ||
|
||
provider_dkim() { | ||
# Extract the dkim TXT record settings from "/etc/opendkim/keys/${PRIMARY_DOMAIN}/mail.txt" | ||
# Update the "mail._domainkey.${PRIMARY_DOMAIN}" TXT record with the extracted content. | ||
} | ||
|
||
provider_renew() { | ||
# Call certbot to renew existing certificates. | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#!/bin/bash | ||
|
||
provider_create() { | ||
certbot certonly "${TEST_MODE}" --dns-route53 --dns-route53-propagation-seconds "${DNS_PROPAGATION_DELAY}" -d "${PRIMARY_DOMAIN}" -m "${CONTACT_EMAIL}" --agree-tos --no-eff-email | ||
} | ||
|
||
provider_dependencies() { | ||
apt install -y awscli python3-certbot-dns-route53 | ||
} | ||
|
||
provider_dkim() { | ||
|
||
local OPERATION | ||
local RESOURCE_RECORD | ||
|
||
readarray -t "DKIM_TXT_RECORD_CONTENT" < <(cut -d"(" -f2 "/etc/opendkim/keys/${PRIMARY_DOMAIN}/mail.txt" | cut -d")" -f1 | tr -d ' "\n\t' | fmt -w 255) | ||
RESOURCE_RECORD="$(printf "\"%s\"\n" "${DKIM_TXT_RECORD_CONTENT[@]}" | jq -R . | jq -sr 'map( { "Value": . } )')" | ||
|
||
OPERATION="$(jq -n --arg domain "mail._domainkey.${PRIMARY_DOMAIN}" --argjson record "${RESOURCE_RECORD}" ' | ||
{ | ||
"Comment": "Update the dkim TXT record.", | ||
"Changes": [ | ||
{ | ||
"Action": "UPSERT", | ||
"ResourceRecordSet": { | ||
"Name": $domain, | ||
"Type": "TXT", | ||
"TTL": 300, | ||
"ResourceRecords": $record | ||
} | ||
} | ||
] | ||
} | ||
')" | ||
|
||
aws route53 change-resource-record-sets --hosted-zone-id "${AWS_HOSTED_ZONE_ID}" --change-batch "${OPERATION}" | ||
} | ||
|
||
provider_renew() { | ||
certbot renew "${TEST_MODE}" --dns-route53 --dns-route53-propagation-seconds "${DNS_PROPAGATION_DELAY}" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
#!/bin/bash | ||
|
||
provider_create() { | ||
write_credential_file | ||
certbot certonly "${TEST_MODE}" --dns-cloudflare --dns-cloudflare-credentials /tmp/cloudflare --dns-cloudflare-propagation-seconds "${DNS_PROPAGATION_DELAY}" -d "${PRIMARY_DOMAIN}" -m "${CONTACT_EMAIL}" --agree-tos --no-eff-email | ||
} | ||
|
||
provider_dependencies() { | ||
apt install -y curl python3-certbot-dns-cloudflare | ||
} | ||
|
||
provider_dkim() { | ||
|
||
local DKIM_CONTENT | ||
local METHOD | ||
local PARSED_NAME | ||
local PARSED_ID | ||
local PAYLOAD | ||
local RESPONSE | ||
|
||
dkim_create() { | ||
local CURL_RESPONSE | ||
|
||
CURL_RESPONSE="$( | ||
curl -X POST \ | ||
--fail \ | ||
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ | ||
-H "Content-Type: application/json" \ | ||
-d "${PAYLOAD}" \ | ||
-sL \ | ||
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records" | ||
)" | ||
echo "${CURL_RESPONSE}" | ||
} | ||
|
||
dkim_get() { | ||
local CURL_RESPONSE | ||
|
||
CURL_RESPONSE="$( | ||
curl -X GET \ | ||
--fail \ | ||
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ | ||
-H "Content-Type: application/json" \ | ||
-d "${PAYLOAD}" \ | ||
-sL \ | ||
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records?type=TXT&match=all" | ||
)" | ||
echo "${CURL_RESPONSE}" | ||
} | ||
|
||
dkim_update() { | ||
# $1: Record ID | ||
|
||
local CURL_RESPONSE | ||
|
||
CURL_RESPONSE="$( | ||
curl -X PUT \ | ||
--fail \ | ||
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ | ||
-H "Content-Type: application/json" \ | ||
-d "${PAYLOAD}" \ | ||
-sL \ | ||
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${1}" | ||
)" | ||
echo "${CURL_RESPONSE}" | ||
} | ||
|
||
dkim_select_method() { | ||
METHOD="dkim_create" | ||
while read -r LINE; do | ||
# shellcheck disable=SC2001 | ||
PARSED_NAME=$(sed "s/^\([^\t]*\)\t\(.*\)$/\1/" <<< "${LINE}") | ||
# shellcheck disable=SC2001 | ||
PARSED_ID=$(sed "s/^\([^\t]*\)\t\(.*\)$/\2/" <<< "${LINE}") | ||
if [[ "${PARSED_NAME}" == "mail._domainkey.${PRIMARY_DOMAIN}" ]]; then | ||
METHOD="dkim_update" | ||
break | ||
fi | ||
done < <(jq -r '.result[] | .name + "\t" + .id' <<< "$(dkim_get)") | ||
} | ||
|
||
DKIM_CONTENT="$(cut -d"(" -f2 "/etc/opendkim/keys/${PRIMARY_DOMAIN}/mail.txt" | cut -d")" -f1 | tr -d ' "\n\t')" | ||
PAYLOAD=$(jq -r ".name = \"mail._domainkey.${PRIMARY_DOMAIN}\" | .content = \"${DKIM_CONTENT}\" | .type = \"TXT\"" <<< '{}') | ||
|
||
dkim_select_method | ||
|
||
RESPONSE="$(eval "${METHOD}" "${PARSED_ID}")" | ||
|
||
echo "${RESPONSE}" | jq | ||
if [[ $(jq -r '.success' <<< "${RESPONSE}") != "true" ]]; then | ||
return 1 | ||
fi | ||
return 0 | ||
} | ||
|
||
provider_renew() { | ||
write_credential_file | ||
certbot renew "${TEST_MODE}" --dns_cloudflare --dns-cloudflare-credentials /tmp/cloudflare --dns-cloudflare-propagation-seconds "${DNS_PROPAGATION_DELAY}" | ||
} | ||
|
||
write_credential_file() { | ||
echo "dns_cloudflare_api_token = ${CLOUDFLARE_API_TOKEN}" >> /tmp/cloudflare | ||
chmod 600 /tmp/cloudflare | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/bin/bash | ||
|
||
create() { | ||
echo "CONTAINER > 'create' function has been called." | ||
pushd "certbot" > /dev/null || exit 127 | ||
echo "CONTAINER > Attempting to create certificates ..." | ||
provider_create | ||
install_certificates | ||
popd > /dev/null || exit 127 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/bin/bash | ||
|
||
function dkim() { | ||
echo "CONTAINER > 'dkim' function has been called." | ||
sleep "${DKIM_DELAY}" | ||
echo "CONTAINER > Attempting to update the DNS dkim key ..." | ||
provider_dkim | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/bin/bash | ||
|
||
function install_certificates() { | ||
echo "CONTAINER > 'install_certificates' function has been called." | ||
echo "CONTAINER > Attempting to install certificates ..." | ||
cp -v /etc/letsencrypt/live/"${PRIMARY_DOMAIN}"/fullchain.pem /run/secrets/fullchain.pem | ||
cp -v /etc/letsencrypt/live/"${PRIMARY_DOMAIN}"/privkey.pem /run/secrets/privkey.pem | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#!/bin/bash | ||
|
||
function renew() { | ||
echo "CONTAINER > 'renew' function has been called." | ||
while true; do | ||
echo "CONTAINER > 'renew' is waiting ${RENEW_INTERVAL_IN_DAYS} days before attempting the next certificate renewal ..." | ||
sleep $((3600*RENEW_INTERVAL_IN_DAYS)) | ||
echo "CONTAINER > Attempting to renew certificates ..." | ||
pushd "certbot" || exit 127 | ||
if provider_renew; then | ||
install_certificates | ||
fi | ||
popd || exit 127 | ||
|
||
echo "CONTAINER > Reloading dovecot and postfix ..." | ||
dovecot reload | ||
postfix reload | ||
done | ||
} |