Skip to content

Commit

Permalink
Develop (#1)
Browse files Browse the repository at this point in the history
Initial package commit.
  • Loading branch information
kyse authored Jun 5, 2017
1 parent 8841f40 commit d0abefa
Show file tree
Hide file tree
Showing 11 changed files with 2,055 additions and 0 deletions.
Empty file added .gitignore
Empty file.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "dehydrated"]
path = dehydrated
url = https://github.com/lukas2511/dehydrated
[submodule "utm-update-certificate"]
path = utm-update-certificate
url = https://github.com/mbunkus/utm-update-certificate.git
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# letsencrypt-sophosutm-dns
Let's Encrypt ssl cert management via Dehydrated with tsig dns-01 verification and Sophos UTM update hooks.
## Disclaimer
USE AT YOUR OWN RISK!

This package is not meant to be used on production servers or by inexperienced users. I assume no liability if something goes wrong while you use this package. I am not responsible for any damages you may incur using these scripts. I suggest you read through the scripts dehydrated, hook.sh, and utm-update-certificate.pl to know what they are doing.
## Contents
- [Description](#description)
- [Usage](#usage)
- [Setup](#setup)
- [Automate](#automate)
- [Notes](#notes)
- [Dependencies](#dependencies)
- [Contributing](#contributing)
- [Development Setup](#development-setup)
## Description
This package is setup to provide an automated way to keep updated Let's Encrypt ssl certs on your UTM without dealing with SSH key's, SCP file transfers, etc. Everything happens on the UTM and stays on the UTM. It will work well in scenarios where you intend to perform SSL termination at the UTM WAF and intend to use DNS-01 acme-challenge verifications of your domains. Some modifications have been made to Dehydrated and the hooks to ensure things work properly when running in the UTM environment.
## Usage
### UTM Environment
- You need to ensure you have the Let's Encrypt intermediate verification CA imported in your UTM. It can be found [here](https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem).
### Setup
1. SSH into your UTM shell: `ssh -l loginuser utm.domain.local`
2. Become root: `su`, enter root password
3. Change directory to root home or wherever you intend to host this package: `cd ~`
4. Grab the package: `wget https://github.com/kyse/letsencrypt-sophosutm-dns/raw/develop/dist/leutmdns.tar.gz`
5. Unzip the package: `tar -xzvf leutmdns.tar.gz`
6. Edit ~/leutmdns/config: `vi ~/leutmdns/config`
- To start with, ensure you are using the LE staging servers until you've tested everything. Then switch the commeted lines for CA and CA_TERMS.
- Update CONTACT_EMAIL to your LE account email.
7. Edit ~/leutmdns/hook.sh: `vi ~/leutmdns/hook.sh`
- Update SERVER to your dns tsig update endpoint.
- If your UTM is behind a split brain DNS, uncomment EXTERNALNS to point to a name server on the outside. This will allow the script to ensure external name servers have received the updated TXT challenge records before asking LE to validate.
8. Edit ~/leutmdns/domains.txt: `vi ~/leutmdns/domains.txt`
- Standard Dehydrated proecdure here... enter primary domain with any additional SAN domains space seperated. 1 line per certificatee.
9. Create tsig key files in the ~/leutmdns/tsig/ folder.
- File name format: K_acme-challenge.zone.tld.+157+random.private - zone.tld = your DNS zone your updating, no need for 1 file per FQDN, just the zone being targeted for that FQDN. Random can be anything.
- File content format (the keyname and secret will come from your DNS provider):
```
key "keyname" {
algorithm hmac-md5;
secret "secret";
};
```
10. Create ref files in the ~/leutmdns/refs/ folder.
- First, you'll need to ensure you have existing certificates created that you want to target for updates from the LE cert renewals.
```
cc
OBJS
ca
host_key_cert
tab tab (hit it twice to list existing REF_* for each cert).
exit
```
- Create a file named after the primary domain (first domein on each line of ~/leutmdns/domains.txt). If your domains.txt file contains domain.com www.domain.com on line 1, and www.domain.net www2.domain.net on line 2:
```bash
cd ~/leutmdns/refs
echo REF_123456789 >> domain.com
echo REF_987654321 >> www.domain.net
```
11. Register an account.
- `./dehydrated --register --accept-terms`
12. Run a test!
- Again ensure you're targeting the staging LE servers.
- Probably a good idea not to target any active certs in the UTM, so create a fake one to test with.
- Kick off the proces (in ~/leutmdns folder): `./dehydrated -c`
13. Update domains.txt, REF_ files, and switch staging urls to prod urls in the config file and go live with it.
### Automate
TODO: There's bound to be a better way to achieve this. Research and update this section. Also ned to update output, possibly figure out how to get it to email output & errors through the UTM notification system.
1. Add a link to dehydrated to your bin path: `ln -s /root/leutmdns/dehydrated /usr/local/bin/dehydrated`
2. Add a line to the bottom of your /etc/crontab-static file: `@monthly root /usr/local/bin/dehydrated -c`
3. Make a change in the UTM web admin site to get the crontab file updated.
- In web admin site, click the management menu item.
- Select up2date sub menu item.
- Select the configuration tab.
- Change one of the dropdowns to a different value, save, then change back to your desired value and save again.
4. Confirm /etc/crontab contains the new entry.
### Notes
- UTM uses a customized openssl.cnf file in /etc/ssl that doesn't work well unless provided proper ENV variables. Dehydrated stock script didn't provide the --cert flag during the certificate request which caused openssl to try and load up the UTM openssl.cnf file. I've updated the dehydrated script on line 619 to include the flag to the openssl.cnf file path provided in the ~/leutmdns/config file to resolve.
- Ensure you have a file for each DNS zone you will be updating using the proper naming scheme in the tsig folder.
- Ensure you have a file for each certificate named after the domain (the first domain per line/cert in domains.txt file) containing the REF_* to your UTM certificate object.
## Dependencies
### SubModules
Making use of the following submodule dependencies so as not to reinvent the wheel:
- [Dehydrated](https://github.com/lukas2511/dehydrated) - Modified
- [utm-update-certificate](https://github.com/mbunkus/utm-update-certificate.git)
### Other Imports
Also directly imported and modified the followng:
- [Dehydrated Hook Example](https://ente.limmat.ch/ftp/pub/software/bash/letsencrypt/letsencrypt_acme_dns-01_challenge_hook) - Modified
## Contributing
### Development Setup
Download the git repo to your local environment and load the submodules.
```bash
git clone --recursive https://github.com/kyse/letsencrypt-sophosutm-dns.git leutmdns
```

To get a new .tar.gz package built in the dist folder, just run build.sh.
87 changes: 87 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash

# build script for letsencrypt-sophosutm-dns

set -e
set -u
set -o pipefail
[[ -n "${ZSH_VERSION:-}" ]] && set -o SH_WORD_SPLIT && set +o FUNCTION_ARGZERO

umask 077

# Find directory in which this script is stored by traversing all symbolic links
SOURCE="${0}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"

BASEDIR="${SCRIPTDIR}"
BUILDDIR="build"
DISTDIR="dist"
SRCDIR="src"

PROJNAME="leutmdns"

_exiterr() {
echo "ERROR: ${1}" >&2
exit 1
}

command_clean() {
[[ ! -d ${BASEDIR}/${BUILDDIR} ]] || rm -r ${BASEDIR}/${BUILDDIR} || _exiterr "Unable to clean ${BASEDIR}/${BUILDDIR} directory."
[[ ! -d ${BASEDIR}/${PROJNAME} ]] || rm -r ${BASEDIR}/${PROJNAME} || _exiterror "Unable to clean ${BASEDIR}/${PROJNAME} directory."
}

command_build() {
command_clean

git submodule update --init --recursive

mkdir -p ${BASEDIR}/${BUILDDIR}/{accounts,certs,refs,tsig} || _exiterr "Unable to create ${BASEDIR}/${BUILDDIR} directory."
cp -t ${BASEDIR}/${BUILDDIR}/ ${BASEDIR}/dehydrated/docs/examples/{config,domains.txt} ${BASEDIR}/utm-update-certificate/utm_update_certificate.pl || _exiterr "An error occured copying submodule files. Make sure you run the command 'git submodule update --init --recursive' before building."
cp -t ${BASEDIR}/${BUILDDIR}/ ${BASEDIR}/${SRCDIR}/{dehydrated,hook.sh,openssl.cnf} || _exiterr "An error occured copying src files."

# Create the package
[[ -d ${BASEDIR}/${DISTDIR} ]] || mkdir -p ${BASEDIR}/${DISTDIR} || _exiterr "Unable to create ${BASEDIR}/${DISTDIR} directry."
[[ ! -f ${BASEDIR}/${DISTDIR}/leutmdns.tar.gz ]] || rm ${BASEDIR}/${DISTDIR}/${PROJNAME}.tar.gz
printf "Creating distribution package: ${PROJNAME}.tar.gz\nLocation: ${BASEDIR}/${DISTDIR}/"
tar -czvf ${BASEDIR}/${DISTDIR}/${PROJNAME}.tar.gz -C ${BASEDIR} --transform "s/^${BUILDDIR}/${PROJNAME}/" ${BUILDDIR}

command_clean
}

main() {
COMMAND=""
set_command() {
[[ -z "${COMMAND}" ]] || _exiterr "Only one command can be executed at a time. See help for more information."
COMMAND="${1}"
}

while (( ${#} )); do
case "${1}" in
build|-b)
set_command build
;;

clean|-c)
set_command clean
;;

*)
set_command build
;;
esac

shift 1
done

case "${COMMAND}" in
clean) echo "Cleaning build."; command_clean;;
*) command_build;;
esac
}

main "${@:-}"
1 change: 1 addition & 0 deletions dehydrated
Submodule dehydrated added at 116386
Binary file added dist/leutmdns.tar.gz
Binary file not shown.
94 changes: 94 additions & 0 deletions src/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
########################################################
# This is the main config file for dehydrated #
# #
# This file is looked for in the following locations: #
# $SCRIPTDIR/config (next to this script) #
# /usr/local/etc/dehydrated/config #
# /etc/dehydrated/config #
# ${PWD}/config (in current working-directory) #
# #
# Default values of this config are in comments #
########################################################

# Resolve names to addresses of IP version only. (curl)
# supported values: 4, 6
# default: <unset>
#IP_VERSION=

# Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory)
#CA="https://acme-v01.api.letsencrypt.org/directory"
CA="https://acme-staging.api.letsencrypt.org/directory"

# Path to certificate authority license terms redirect (default: https://acme-v01.api.letsencrypt.org/terms)
#CA_TERMS="https://acme-v01.api.letsencrypt.org/terms"
CA_TERMS="https://acme-staging.api.letsencrypt.org/terms"

# Path to license agreement (default: <unset>)
#LICENSE=""

# Which challenge should be used? Currently http-01 and dns-01 are supported
#CHALLENGETYPE="http-01"
CHALLENGETYPE="dns-01"

# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# default: <unset>
#CONFIG_D=

# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
#BASEDIR=$SCRIPTDIR

# File containing the list of domains to request certificates for (default: $BASEDIR/domains.txt)
#DOMAINS_TXT="${BASEDIR}/domains.txt"

# Output directory for generated certificates
#CERTDIR="${BASEDIR}/certs"

# Directory for account keys and registration information
#ACCOUNTDIR="${BASEDIR}/accounts"

# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/dehydrated)
#WELLKNOWN="/var/www/dehydrated"

# Default keysize for private keys (default: 4096)
#KEYSIZE="4096"

# Path to openssl config file (default: <unset> - tries to figure out system default)
OPENSSL_CNF=${BASEDIR}/openssl.cnf

# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: <unset>
HOOK=${BASEDIR}/hook.sh

# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
#HOOK_CHAIN="no"

# Minimum days before expiration to automatically renew certificate (default: 30)
#RENEW_DAYS="30"

# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
#PRIVATE_KEY_RENEW="yes"

# Create an extra private key for rollover (default: no)
#PRIVATE_KEY_ROLLOVER="no"

# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
#KEY_ALGO=rsa

# E-mail to use during the registration (default: <unset>)
#[email protected]

# Lockfile location, to prevent concurrent access (default: $BASEDIR/lock)
#LOCKFILE="${BASEDIR}/lock"

# Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no)
#OCSP_MUST_STAPLE="no"
Loading

0 comments on commit d0abefa

Please sign in to comment.