Skip to content

Instantly share code, notes, and snippets.

@dlangille
Last active May 22, 2017 02:10
Show Gist options
  • Save dlangille/f55307a52733920337f248ebcc128c1d to your computer and use it in GitHub Desktop.
Save dlangille/f55307a52733920337f248ebcc128c1d to your computer and use it in GitHub Desktop.
Description of a centralized LetsEncrypt strategy
I'm basing this on https://blog.crashed.org/letsencrypt-in-freebsd-org/
I'll create a new jail: certs.int.unixathome.org
get this port installed: https://reviews.freebsd.org/D10308
based on https://github.com/Neilpang/acme.sh/wiki/How-to-install
In my testing, I saw this:
root@certs:/usr/local/sbin # chown acme:acme /var/db/acme
root@certs:/usr/local/sbin # sudo -u acme /usr/local/sbin/acme.sh --install --home /var/db/acme --config-home /var/db/acme/data --certhome /var/db/acme/mycerts --accountemail "dan@langille.org" --accountkey /var/db/acme/myaccount.key --accountconf /var/db/acme/myaccount.conf --useragent "BSD Cabal HQ"
[Sat May 20 01:07:08 UTC 2017] Installing to /var/db/acme
[Sat May 20 01:07:08 UTC 2017] Installed to /var/db/acme/acme.sh
[Sat May 20 01:07:08 UTC 2017] No profile is found, you will need to go into /var/db/acme to use acme.sh
[Sat May 20 01:07:08 UTC 2017] Installing cron job
2 0 * * * "/var/db/acme"/acme.sh --cron --home "/var/db/acme" --config-home "/var/db/acme/data" > /dev/null
[Sat May 20 01:07:08 UTC 2017] Good, bash is found, so change the shebang to use bash as preferred.
/usr/local/sbin/acme.sh: cannot create /var/db/acme/acme.sh: Permission denied
/usr/local/sbin/acme.sh: cannot create /var/db/acme/acme.sh: Permission denied
[Sat May 20 01:07:08 UTC 2017] OK
root@certs:/var/db/acme # ls -l
total 178
-r-xr-xr-x 1 acme acme 142724 May 20 01:07 acme.sh
-rw-r--r-- 1 acme acme 150 May 20 01:07 acme.sh.env
drwx------ 2 acme acme 3 May 20 01:07 data
-rw-r--r-- 1 acme acme 237 May 20 01:07 myaccount.conf
root@certs:/var/db/acme # rm acme.sh
root@certs:/var/db/acme # cat acme.sh.env
export LE_WORKING_DIR="/var/db/acme"
export LE_CONFIG_HOME="/var/db/acme/data"
alias acme.sh="/var/db/acme/acme.sh --config-home '/var/db/acme/data'"
root@certs:/var/db/acme #
root@certs:~/tmp # acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
[Sat May 20 01:13:11 UTC 2017] Registering account
[Sat May 20 01:13:15 UTC 2017] Registered
[Sat May 20 01:13:16 UTC 2017] Update success.
[Sat May 20 01:13:16 UTC 2017] ACCOUNT_THUMBPRINT='zCArdwbAFZW_hSVMG4UPxaVSF0wBohkDh9qlXXVm8cw'
[Sat May 20 01:13:16 UTC 2017] Creating domain key
[Sat May 20 01:13:16 UTC 2017] Multi domain='DNS:www.example.com,DNS:cp.example.com'
[Sat May 20 01:13:16 UTC 2017] Getting domain auth token for each domain
[Sat May 20 01:13:16 UTC 2017] Getting webroot for domain='example.com'
[Sat May 20 01:13:16 UTC 2017] Getting new-authz for domain='example.com'
[Sat May 20 01:13:17 UTC 2017] The new-authz request is ok.
[Sat May 20 01:13:17 UTC 2017] Getting webroot for domain='www.example.com'
[Sat May 20 01:13:17 UTC 2017] Getting new-authz for domain='www.example.com'
[Sat May 20 01:13:17 UTC 2017] The new-authz request is ok.
[Sat May 20 01:13:17 UTC 2017] Getting webroot for domain='cp.example.com'
[Sat May 20 01:13:17 UTC 2017] Getting new-authz for domain='cp.example.com'
[Sat May 20 01:13:18 UTC 2017] The new-authz request is ok.
[Sat May 20 01:13:18 UTC 2017] Add the following TXT record:
[Sat May 20 01:13:18 UTC 2017] Domain: '_acme-challenge.example.com'
[Sat May 20 01:13:18 UTC 2017] TXT value: 'ckfmEefyLnJHsh7QB9R__KbBHIlyqt5wUl-v9kkOEtY'
[Sat May 20 01:13:18 UTC 2017] Please be aware that you prepend _acme-challenge. before your domain
[Sat May 20 01:13:18 UTC 2017] so the resulting subdomain will be: _acme-challenge.example.com
[Sat May 20 01:13:18 UTC 2017] Add the following TXT record:
[Sat May 20 01:13:18 UTC 2017] Domain: '_acme-challenge.www.example.com'
[Sat May 20 01:13:18 UTC 2017] TXT value: '2ALopkCJmIZiI11_2Ut7s729U0mpVkgl8GfTtT_ZitU'
[Sat May 20 01:13:18 UTC 2017] Please be aware that you prepend _acme-challenge. before your domain
[Sat May 20 01:13:18 UTC 2017] so the resulting subdomain will be: _acme-challenge.www.example.com
[Sat May 20 01:13:18 UTC 2017] Add the following TXT record:
[Sat May 20 01:13:18 UTC 2017] Domain: '_acme-challenge.cp.example.com'
[Sat May 20 01:13:18 UTC 2017] TXT value: 'n4fxVLPwuis5pPHkGkhB6qPuktsSYuRqjEY0JpCh-1k'
[Sat May 20 01:13:18 UTC 2017] Please be aware that you prepend _acme-challenge. before your domain
[Sat May 20 01:13:18 UTC 2017] so the resulting subdomain will be: _acme-challenge.cp.example.com
[Sat May 20 01:13:18 UTC 2017] Please add the TXT records to the domains, and retry again.
[Sat May 20 01:13:18 UTC 2017] Please add '--debug' or '--log' to check more details.
[Sat May 20 01:13:18 UTC 2017] See: https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
root@certs:~/tmp #
I deleted all files in /var/db/acme
cd /usr/local/sbin
acme.sh --install \
--home /var/db/acme \
--config-home /var/db/acme/data \
--certhome /var/db/acme/mycerts \
--accountemail "dan@langille.org" \
--accountkey /var/db/acme/myaccount.key \
--accountconf /var/db/acme/myaccount.conf \
--useragent "BSD Cabal HQ"
[Sat May 20 01:29:48 UTC 2017] Installing to /var/db/acme
[Sat May 20 01:29:48 UTC 2017] Installed to /var/db/acme/acme.sh
[Sat May 20 01:29:49 UTC 2017] No profile is found, you will need to go into /var/db/acme to use acme.sh
[Sat May 20 01:29:49 UTC 2017] Installing cron job
2 0 * * * /usr/local/sbin/acme.sh --cron --home "/var/db/acme" --config-home "/var/db/acme/data" > /dev/null
[Sat May 20 01:29:49 UTC 2017] Good, bash is found, so change the shebang to use bash as preferred.
/usr/local/sbin/acme.sh: cannot create /var/db/acme/acme.sh: Permission denied
/usr/local/sbin/acme.sh: cannot create /var/db/acme/acme.sh: Permission denied
That permission denied is:
[acme@certs ~]$ ls -l
total 178
-r-xr-xr-x 1 acme acme 142724 May 20 01:30 acme.sh
-rw-r--r-- 1 acme acme 150 May 20 01:29 acme.sh.env
drwx------ 2 acme acme 3 May 20 01:29 data
-rw-r--r-- 1 acme acme 237 May 20 01:29 myaccount.conf
We don't want acme.sh in there anyway
rm acme.sh
Then alter the alias in this file to point to /usr/local/sbin/acme.sh
$ cat acme.sh.env
export LE_WORKING_DIR="/var/db/acme"
export LE_CONFIG_HOME="/var/db/acme/data"
alias acme.sh="/usr/local/sbin/acme.sh --config-home '/var/db/acme/data'"
At this point, we have:
[acme@certs ~]$ find .
.
./myaccount.conf
./acme.sh.env
./data
./data/account.conf
[acme@certs ~]$ acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
[Sat May 20 01:36:05 UTC 2017] Registering account
[Sat May 20 01:36:08 UTC 2017] Registered
[Sat May 20 01:36:09 UTC 2017] Update success.
[Sat May 20 01:36:09 UTC 2017] ACCOUNT_THUMBPRINT='cKk2PPACeTh1jkpSXl5DM0SLi4UXY6sbQBkagDjat00'
[Sat May 20 01:36:09 UTC 2017] Creating domain key
[Sat May 20 01:36:09 UTC 2017] Multi domain='DNS:www.example.com,DNS:cp.example.com'
[Sat May 20 01:36:09 UTC 2017] Getting domain auth token for each domain
[Sat May 20 01:36:09 UTC 2017] Getting webroot for domain='example.com'
[Sat May 20 01:36:09 UTC 2017] Getting new-authz for domain='example.com'
[Sat May 20 01:36:10 UTC 2017] The new-authz request is ok.
[Sat May 20 01:36:10 UTC 2017] Getting webroot for domain='www.example.com'
[Sat May 20 01:36:10 UTC 2017] Getting new-authz for domain='www.example.com'
[Sat May 20 01:36:10 UTC 2017] The new-authz request is ok.
[Sat May 20 01:36:10 UTC 2017] Getting webroot for domain='cp.example.com'
[Sat May 20 01:36:10 UTC 2017] Getting new-authz for domain='cp.example.com'
[Sat May 20 01:36:11 UTC 2017] The new-authz request is ok.
[Sat May 20 01:36:11 UTC 2017] Add the following TXT record:
[Sat May 20 01:36:11 UTC 2017] Domain: '_acme-challenge.example.com'
[Sat May 20 01:36:11 UTC 2017] TXT value: 'R2ZzeVpVgxJP1dXrHdEhGcvglpUNFD0ZRJrlrgmLe2o'
[Sat May 20 01:36:11 UTC 2017] Please be aware that you prepend _acme-challenge. before your domain
[Sat May 20 01:36:11 UTC 2017] so the resulting subdomain will be: _acme-challenge.example.com
[Sat May 20 01:36:11 UTC 2017] Add the following TXT record:
[Sat May 20 01:36:11 UTC 2017] Domain: '_acme-challenge.www.example.com'
[Sat May 20 01:36:11 UTC 2017] TXT value: 'gyHQMoVzLOnOUG8GsVNOS_OGdMvPYxz43yYUuntM7_k'
[Sat May 20 01:36:11 UTC 2017] Please be aware that you prepend _acme-challenge. before your domain
[Sat May 20 01:36:11 UTC 2017] so the resulting subdomain will be: _acme-challenge.www.example.com
[Sat May 20 01:36:11 UTC 2017] Add the following TXT record:
[Sat May 20 01:36:11 UTC 2017] Domain: '_acme-challenge.cp.example.com'
[Sat May 20 01:36:11 UTC 2017] TXT value: 'fuoqbQhAZwSqt_4P7l3GCsqsJ1s08JNkPlBbOCjj620'
[Sat May 20 01:36:11 UTC 2017] Please be aware that you prepend _acme-challenge. before your domain
[Sat May 20 01:36:11 UTC 2017] so the resulting subdomain will be: _acme-challenge.cp.example.com
[Sat May 20 01:36:11 UTC 2017] Please add the TXT records to the domains, and retry again.
[Sat May 20 01:36:11 UTC 2017] Please add '--debug' or '--log' to check more details.
[Sat May 20 01:36:11 UTC 2017] See: https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
Now we have:
[acme@certs ~]$ find .
.
./mycerts
./mycerts/example.com
./mycerts/example.com/example.com.csr
./mycerts/example.com/example.com.csr.conf
./mycerts/example.com/example.com.key
./mycerts/example.com/example.com.conf
./myaccount.key
./.rnd
./myaccount.conf
./acme.sh.env
./data
./data/account.conf
./data/http.header
./data/ca
./data/ca/acme-v01.api.letsencrypt.org
./data/ca/acme-v01.api.letsencrypt.org/account.json
./data/ca/acme-v01.api.letsencrypt.org/ca.conf
The hint about dns_myapi.sh came from the bottom of https://github.com/Neilpang/acme.sh/tree/master/dnsapi
The clue for dns_myapi_rm came when running the test without that function defined.
[acme@certs ~]$ cat ~/.acme.sh/dns_myapi.sh
#!/bin/sh
dns_myapi_add() {
fulldomain="$1"
txtvalue="$2"
_info "Adding DNS ${fulldomain} ${txtvalue}"
echo "${fulldomain}. 60 IN TXT \"${txtvalue}\"" > "${LE_WORKING_DIR}/dnsextra/${fulldomain}"
return $?
}
dns_myapi_rm() {
return $?
}
[acme@certs ~]$
also, moved dnsextra from ~/ to ~/.~/.acme.sh
Now when running, I get this:
[acme@certs ~]$ acme.sh --issue --dns dns_myapi -d www.example.com
[Sat May 20 02:10:10 UTC 2017] Single domain='www.example.com'
[Sat May 20 02:10:10 UTC 2017] Getting domain auth token for each domain
[Sat May 20 02:10:10 UTC 2017] Getting webroot for domain='www.example.com'
[Sat May 20 02:10:10 UTC 2017] Getting new-authz for domain='www.example.com'
[Sat May 20 02:10:12 UTC 2017] The new-authz request is ok.
[Sat May 20 02:10:12 UTC 2017] Found domain api file: /var/db/acme/.acme.sh/dns_myapi.sh
[Sat May 20 02:10:12 UTC 2017] Adding DNS _acme-challenge.www.example.com nmz6gMTrW3nhDg1aofnI64FjSn5cMDm5VgsTvVkwtPk
[Sat May 20 02:10:12 UTC 2017] Sleep 120 seconds for the txt records to take effect
### <-- you see a countdown here
[Sat May 20 02:12:15 UTC 2017] Verifying:www.example.com
[Sat May 20 02:12:18 UTC 2017] www.example.com:Verify error:DNS problem: NXDOMAIN looking up TXT for _acme-challenge.www.example.com
[Sat May 20 02:12:18 UTC 2017] Please add '--debug' or '--log' to check more details.
[Sat May 20 02:12:18 UTC 2017] See: https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
[acme@certs ~]$
Note what it created by using the dns_myapi script:
[acme@certs ~/.acme.sh]$ cat dnsextra/_acme-challenge.www.example.com
_acme-challenge.www.example.com. 60 IN TXT "nmz6gMTrW3nhDg1aofnI64FjSn5cMDm5VgsTvVkwtPk"
[acme@certs ~/.acme.sh]$
NOTE that that the string on line 46 comes from line 32
OK, next step, start pushing that stuff into DNS zone files. I will most likely do this from another jail,
where the dnsextra diretory is nullfs mounted into it.
I have created another jail, dns-publish, on the same host and installed svn into it.
Now I have to figure out how to automate the push and pull of those DNS entries.
on the jail host:
sudo zfs create system/data/dnsextra
sudo zfs set compression=lz4 system/data/dnsextra
to get the permissions right, from within the jail:
cd /var/db/acme/.acme.sh
mv dnsextra dnsextra.tmp
mkdir dnsextra
on the host:
sudo zfs set mountpoint=/usr/jails/certs//var/db/acme/.acme.sh/dnsextra system/data/dnsextra
back in the jail:
chown acme:acme dnsextra
cp -a dnsextra.tmp/* dnsextra/
In the dns-publish jail, I create this directory for a mount point:
sudo mkdir /var/db/dnsextras
I added this entry to /etc/fstab.dns-publish and restarted that jail:
/usr/jails/certs//var/db/acme/.acme.sh/dnsextra /usr/jails/dns-publish/var/db/dnsextras nullfs ro,nosuid,noexec 0 0
BOOM, we have dnsextras from the certs jail magically available (READ-ONL) in the dns-publish jail.
On a typical day, we might want to renew multiple domains. What does that look like?
NOTE: I realized I should be using --staging here. i.e. not real certs. I've also started using my own domains.
$ acme.sh --staging --issue --dns dns_myapi -d www.langille.org -d dan.langille.org -d www.freebsddiary.org
[acme@certs ~]$ acme.sh --staging --issue --dns dns_myapi -d www.langille.org -d dan.langille.org -d www.freebsddiary.org
[Sat May 20 13:02:25 UTC 2017] Using stage api:https://acme-staging.api.letsencrypt.org
[Sat May 20 13:02:25 UTC 2017] Multi domain='DNS:dan.langille.org,DNS:www.freebsddiary.org'
[Sat May 20 13:02:25 UTC 2017] Getting domain auth token for each domain
[Sat May 20 13:02:25 UTC 2017] Getting webroot for domain='www.langille.org'
[Sat May 20 13:02:25 UTC 2017] Getting new-authz for domain='www.langille.org'
[Sat May 20 13:02:28 UTC 2017] The new-authz request is ok.
[Sat May 20 13:02:28 UTC 2017] Getting webroot for domain='dan.langille.org'
[Sat May 20 13:02:28 UTC 2017] Getting new-authz for domain='dan.langille.org'
[Sat May 20 13:02:29 UTC 2017] The new-authz request is ok.
[Sat May 20 13:02:29 UTC 2017] Getting webroot for domain='www.freebsddiary.org'
[Sat May 20 13:02:29 UTC 2017] Getting new-authz for domain='www.freebsddiary.org'
[Sat May 20 13:02:29 UTC 2017] The new-authz request is ok.
[Sat May 20 13:02:29 UTC 2017] Found domain api file: /var/db/acme/.acme.sh/dns_myapi.sh
[Sat May 20 13:02:29 UTC 2017] Adding DNS _acme-challenge.www.langille.org [REDACTED]
[Sat May 20 13:02:30 UTC 2017] Found domain api file: /var/db/acme/.acme.sh/dns_myapi.sh
[Sat May 20 13:02:30 UTC 2017] Adding DNS _acme-challenge.dan.langille.org [REDACTED]
[Sat May 20 13:02:30 UTC 2017] Found domain api file: /var/db/acme/.acme.sh/dns_myapi.sh
[Sat May 20 13:02:30 UTC 2017] Adding DNS _acme-challenge.www.freebsddiary.org [REDACTED]
[Sat May 20 13:02:30 UTC 2017] Sleep 120 seconds for the txt records to take effect
Looking on dns-publish, I see:
[dan@dns-publish:~] $ ls -l /var/db/dnsextras/
total 2
-rw-r--r-- 1 169 169 90 May 20 13:02 _acme-challenge.dan.langille.org
-rw-r--r-- 1 169 169 89 May 20 02:10 _acme-challenge.www.example.com
-rw-r--r-- 1 169 169 94 May 20 13:02 _acme-challenge.www.freebsddiary.org
-rw-r--r-- 1 169 169 90 May 20 13:02 _acme-challenge.www.langille.org
[dan@dns-publish:~] $
Good, so the domain name can be found, sort of, from that.
How do I get a list of domain names I'm handling? I already have the zone files, so how about this.
Assuming the directory contains:
[dan@dns-publish:~/DNS] $ ls bsdcan* freebsddiary.* fresh*
bsdcan.ca.db bsdcan.org.db freebsddiary.org.db freshports.org.db
bsdcan.com.db freebsddiary.com.db freshports.net.db freshsource.org.db
Then this script:
#!/bin/sh
DNSDIR=${HOME}/DNS
ZONEFILES=`/bin/ls ${DNSDIR}/*.db`
ZONEFILESUFFIX=".db"
DOMAINS=""
for domain in ${ZONEFILES}
do
echo processing ${domain}
# remove the leading directory name
FILENAME=`/usr/bin/basename ${domain}`
echo "got filename = '${FILENAME}'"
# NOTE the space at the end of the string to separate the domains.
# The final result will have a trailing space.
DOMAINS="${DOMAINS}`/usr/bin/basename ${FILENAME} ${ZONEFILESUFFIX}` "
done
echo ${DOMAINS}
produced this:
[dan@dns-publish:~/DNS] $ ~/bin/list-domains
processing /usr/home/dan/DNS/bsdcan.ca.db
got filename = 'bsdcan.ca.db'
processing /usr/home/dan/DNS/bsdcan.com.db
got filename = 'bsdcan.com.db'
processing /usr/home/dan/DNS/bsdcan.org.db
got filename = 'bsdcan.org.db'
processing /usr/home/dan/DNS/freebsddiary.com.db
got filename = 'freebsddiary.com.db'
processing /usr/home/dan/DNS/freebsddiary.org.db
got filename = 'freebsddiary.org.db'
processing /usr/home/dan/DNS/freshports.net.db
got filename = 'freshports.net.db'
processing /usr/home/dan/DNS/freshports.org.db
got filename = 'freshports.org.db'
processing /usr/home/dan/DNS/freshsource.org.db
got filename = 'freshsource.org.db'
bsdcan.ca bsdcan.com bsdcan.org freebsddiary.com freebsddiary.org freshports.net freshports.org freshsource.org
From there, it's easy to then see what file name extensions to look for.
From this script:
[dan@dns-publish:~/DNS] $ cat ~/bin/list-waiting-requests
#!/bin/sh
DNS_DIR=${HOME}/DNS
CHALLENGE_DIR="/var/db/dnsextras"
ZONE_FILES=`/bin/ls ${DNS_DIR}/*.db`
# for specific testing
#ZONE_FILES=`/bin/ls ${DNS_DIR}/bsdcan* ${DNS_DIR}/freebsddiary.* ${DNS_DIR}/fresh*`
ZONE_FILE_SUFFIX=".db"
DOMAINS=""
for domain in ${ZONE_FILES}
do
# remove the leading directory name
FILENAME=`/usr/bin/basename ${domain}`
# NOTE the space at the end of the string to separate the domains.
# The final result will have a trailing space.
DOMAINS="${DOMAINS}`/usr/bin/basename ${FILENAME} ${ZONE_FILE_SUFFIX}` "
done
# echo ${DOMAINS}
for domain in ${DOMAINS}
do
CHALLENGES=`find ${CHALLENGE_DIR} -name _acme-challenge.*${domain}`
if [ ! -z "${CHALLENGES}" ]
then
for challenge in ${CHALLENGES}
do
challenge_file=`/usr/bin/basename ${challenge}`
echo ${challenge} is a request for domain ${domain}
done
fi
done
I get:
$ ~/bin/list-waiting-requests
/var/db/dnsextras/_acme-challenge.www.freebsddiary.org is a request for domain freebsddiary.org
/var/db/dnsextras/_acme-challenge.dan.langille.org is a request for domain langille.org
/var/db/dnsextras/_acme-challenge.www.langille.org is a request for domain langille.org
Next step, append the data to the zone files. The amended script is:
$ cat ~/bin/append-to-zone-files
#!/bin/sh
DNS_DIR=${HOME}/DNS
CHALLENGE_DIR="/var/db/dnsextras"
CHALLENGE_PREFIX="_acme-challenge"
ZONE_FILES=`/bin/ls ${DNS_DIR}/*.db`
BASENAME="/usr/bin/basename"
FIND="/usr/bin/find"
GREP="/usr/bin/grep"
LS="/bin/ls"
# for specific testing
#ZONE_FILES=`${LS} ${DNS_DIR}/bsdcan* ${DNS_DIR}/freebsddiary.* ${DNS_DIR}/fresh*`
ZONE_FILE_SUFFIX=".db"
DOMAINS=""
for domain in ${ZONE_FILES}
do
# remove the leading directory name
FILENAME=`${BASENAME} ${domain}`
# NOTE the space at the end of the string to separate the domains.
# The final result will have a trailing space.
DOMAINS="${DOMAINS}`${BASENAME} ${FILENAME} ${ZONE_FILE_SUFFIX}` "
done
# echo ${DOMAINS}
for domain in ${DOMAINS}
do
CHALLENGES=`${FIND} ${CHALLENGE_DIR} -name ${CHALLENGE_PREFIX}.*${domain}`
if [ ! -z "${CHALLENGES}" ]
then
for challenge in ${CHALLENGES}
do
challenge_file=`${BASENAME} ${challenge}`
echo ${challenge_file} is a request for domain ${domain}
# append the challenge to the one file
# grep 'IN TXT' /var/db/dnsextras/_acme-challenge.www.freebsddiary.org
${GREP} 'IN TXT' ${challenge} >> ${DNS_DIR}/${domain}.db
done
fi
done
[dan@dns-publish:~/bin] $
The results are:
[dan@dns-publish:~/bin] $ svn di ~/DNS/
Index: /usr/home/dan/DNS/freebsddiary.org.db
===================================================================
--- /usr/home/dan/DNS/freebsddiary.org.db (revision 4165)
+++ /usr/home/dan/DNS/freebsddiary.org.db (working copy)
@@ -55,3 +55,4 @@
;
freebsddiary.org. IN TXT "v=spf1 ip4:64.90.182.103 ip4:162.208.116.77 ?all"
freebsddiary.org. IN SPF "v=spf1 ip4:64.90.182.103 ip4:162.208.116.77 ?all"
+_acme-challenge.www.freebsddiary.org. 60 IN TXT "9gfHxsSn1ajI1hAf7T8K6DMBRALYefbto0kRKQuhSbY"
Index: /usr/home/dan/DNS/langille.org.db
===================================================================
--- /usr/home/dan/DNS/langille.org.db (revision 4165)
+++ /usr/home/dan/DNS/langille.org.db (working copy)
@@ -57,3 +57,5 @@
; clavin1 clavin2 zuul supernew tallboy gelt
langille.org. IN TXT "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
langille.org. IN SPF "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
+_acme-challenge.dan.langille.org. 60 IN TXT "Yf8YKvyrrPR6j-PmXttsfLtKqmeT7nxMs8cTdrw8Axg"
+_acme-challenge.www.langille.org. 60 IN TXT "xa4I5BUsvkxYiIr9TRITOCt7yjlx8bftKuTwu23iHq8"
This looks right to me.
The problem is removing challenge records which are no longer required. That could be a simple sed command.
At http://www.linuxquestions.org/questions/programming-9/shell-script-bind-related-654894-print/ I found this script:
$ cat ~/bin/bump_serial.sh
#!/bin/sh
/usr/bin/perl -i.bak -pe 'BEGIN {chomp ($now=qx/date +%Y%m%d/)};
/(\d{8})(\d{2})/ and do {
$serial = ($1 eq $now ? $2+1 : 0);
s/\d{8}(\d{2})/sprintf "%8d%02d",$now,$serial/e;
}' $1
Feel free to do this without perl...
Running it the first time, it changed the serial to today's date:
[dan@dns-publish:~/DNS] $ ~/bin/bump_serial.sh langille.org.db
[dan@dns-publish:~/DNS] $ svn di langille.org.db
Index: langille.org.db
===================================================================
--- langille.org.db (revision 4165)
+++ langille.org.db (working copy)
@@ -1,7 +1,7 @@
$TTL 600
$ORIGIN org.
langille IN SOA muster.unixathome.org. soa.unixathome.org. (
- 2017042100 ; Serial
+ 2017052000 ; Serial
10800 ; Refresh
1800 ; Retry
1209600 ; Expire
@@ -57,3 +57,5 @@
; clavin1 clavin2 zuul supernew tallboy gelt
langille.org. IN TXT "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
langille.org. IN SPF "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
+_acme-challenge.dan.langille.org. 60 IN TXT "Yf8YKvyrrPR6j-PmXttsfLtKqmeT7nxMs8cTdrw8Axg"
+_acme-challenge.www.langille.org. 60 IN TXT "xa4I5BUsvkxYiIr9TRITOCt7yjlx8bftKuTwu23iHq8"
running it the second time, it increments by one:
[dan@dns-publish:~/DNS] $ ~/bin/bump_serial.sh langille.org.db
[dan@dns-publish:~/DNS] $ ~/bsvn dingille.org.db
Index: langille.org.db
===================================================================
--- langille.org.db (revision 4165)
+++ langille.org.db (working copy)
@@ -1,7 +1,7 @@
$TTL 600
$ORIGIN org.
langille IN SOA muster.unixathome.org. soa.unixathome.org. (
- 2017042100 ; Serial
+ 2017052001 ; Serial
10800 ; Refresh
1800 ; Retry
1209600 ; Expire
@@ -57,3 +57,5 @@
; clavin1 clavin2 zuul supernew tallboy gelt
langille.org. IN TXT "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
langille.org. IN SPF "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
+_acme-challenge.dan.langille.org. 60 IN TXT "Yf8YKvyrrPR6j-PmXttsfLtKqmeT7nxMs8cTdrw8Axg"
+_acme-challenge.www.langille.org. 60 IN TXT "xa4I5BUsvkxYiIr9TRITOCt7yjlx8bftKuTwu23iHq8"
This seems to do what is required.
At present, my DNS is in stand alone zone files.
With acme DNS challenges for LetsEncrypt, a new approach is needed.
Peter Wemm did this:
cat soa/freebsd.org primary/freebsd.org extra/freebsd.org.txt > unsigned/freebsd.org
I think my new approach will involve two repos. One where I do manual changes to the
zones. The other will be what is automatically pushed to DNS servers after assembly
of serial and extra bits.
I won't have a separate SOA file like above, but just the extra bit ...
This should just work.
I have branched my main DNS into a dns-01 branch. Updates to this branch will be exported to DNS servers.
With this slightly modified code, I copy from main DNS, append challenges, then bump serial:
[dan@dns-publish:~] $ cat ~/bin/append-to-zone-files
#!/bin/sh
# where we get pure DNS files
DNS_SRC_DIR="${HOME}/DNS"
# where we put the dns stuff for the servers to svn up later
DNS01_DST_DIR="${HOME}/dns-01"
# where do we find the acme challegne files
CHALLENGE_DIR="/var/db/dnsextras"
# what do the challenge files look like?
CHALLENGE_PREFIX="_acme-challenge"
ZONE_FILE_SUFFIX=".db"
# list of zone files we support
ZONE_FILES=`/bin/ls ${DNS_SRC_DIR}/*${ZONE_FILE_SUFFIX}`
# various tools we use
BASENAME="/usr/bin/basename"
FIND="/usr/bin/find"
GREP="/usr/bin/grep"
LS="/bin/ls"
BUMP_SERIAL="${HOME}/bin/bump_serial.sh"
# for specific testing
#ZONE_FILES=`${LS} ${DNS_SRC_DIR}/bsdcan* ${DNS_SRC_DIR}/freebsddiary.* ${DNS_SRC_DIR}/fresh*`
DOMAINS=""
for domain in ${ZONE_FILES}
do
# remove the leading directory name
FILENAME=`${BASENAME} ${domain}`
# NOTE the space at the end of the string to separate the domains.
# The final result will have a trailing space.
DOMAINS="${DOMAINS}`${BASENAME} ${FILENAME} ${ZONE_FILE_SUFFIX}` "
done
# echo ${DOMAINS}
for domain in ${DOMAINS}
do
zone_file="${domain}${ZONE_FILE_SUFFIX}"
CHALLENGES=`${FIND} ${CHALLENGE_DIR} -name ${CHALLENGE_PREFIX}.*${domain}`
if [ ! -z "${CHALLENGES}" ]
then
for challenge in ${CHALLENGES}
do
challenge_file=`${BASENAME} ${challenge}`
echo ${challenge_file} is a request for domain ${domain}
# append the challenge to the one file
# grep 'IN TXT' /var/db/dnsextras/_acme-challenge.www.freebsddiary.org
cat ${DNS_SRC_DIR}/${zone_file} > ${DNS01_DST_DIR}/${zone_file}
${GREP} 'IN TXT' ${challenge} >> ${DNS01_DST_DIR}/${zone_file}
${BUMP_SERIAL} ${DNS01_DST_DIR}/${zone_file}
done
fi
done
See the diffs here:
[dan@dns-publish:~/dns-01] $ ~/bin/append-to-zone-files
_acme-challenge.www.freebsddiary.org is a request for domain freebsddiary.org
_acme-challenge.dan.langille.org is a request for domain langille.org
_acme-challenge.www.langille.org is a request for domain langille.org
[dan@dns-publish:~/dns-01] $ cd -
/usr/home/dan/DNS
[dan@dns-publish:~/DNS] $ svn di
[dan@dns-publish:~/DNS] $ cd -
/usr/home/dan/dns-01
[dan@dns-publish:~/dns-01] $ svn di
Index: freebsddiary.org.db
===================================================================
--- freebsddiary.org.db (revision 4166)
+++ freebsddiary.org.db (working copy)
@@ -1,7 +1,7 @@
$TTL 600
$ORIGIN org.
freebsddiary IN SOA muster.unixathome.org. soa.unixathome.org. (
- 2015123800 ; Serial
+ 2017052000 ; Serial
10800 ; Refresh
1800 ; Retry
1209600 ; Expire
@@ -55,3 +55,4 @@
;
freebsddiary.org. IN TXT "v=spf1 ip4:64.90.182.103 ip4:162.208.116.77 ?all"
freebsddiary.org. IN SPF "v=spf1 ip4:64.90.182.103 ip4:162.208.116.77 ?all"
+_acme-challenge.www.freebsddiary.org. 60 IN TXT "9gfHxsSn1ajI1hAf7T8K6DMBRALYefbto0kRKQuhSbY"
Index: langille.org.db
===================================================================
--- langille.org.db (revision 4166)
+++ langille.org.db (working copy)
@@ -1,7 +1,7 @@
$TTL 600
$ORIGIN org.
langille IN SOA muster.unixathome.org. soa.unixathome.org. (
- 2017042100 ; Serial
+ 2017052000 ; Serial
10800 ; Refresh
1800 ; Retry
1209600 ; Expire
@@ -57,3 +57,4 @@
; clavin1 clavin2 zuul supernew tallboy gelt
langille.org. IN TXT "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
langille.org. IN SPF "v=spf1 ip4:162.208.116.86 ip4:199.233.228.197 ip4:162.208.116.64/26 ip4:206.127.23.226/26 ip4:199.233.228.194/29 ip4:64.90.182.98/27 include:_spf.google.com ~all"
+_acme-challenge.www.langille.org. 60 IN TXT "xa4I5BUsvkxYiIr9TRITOCt7yjlx8bftKuTwu23iHq8"
[dan@dns-publish:~/dns-01] $
I think I have a better idea for dns-01.
At present the flow is:
certs jail
- runs acme.sh script to create requests for new certs and dns-01 _acme-challenge TXT records
dns-publish jail
- has nullfs mounted directory with _acme-challenge TXT records
- svn up's the DNS files to make sure it has the latest
- adds in any _acme-challenge TXT records
- puts results in DSTDIR
dns-rsync jail
- has nullfs mounted directory with DNS records including _acme-challenge TXT records from previous step
- DNS master ryncs from this directory, notices any changes, and issues a reload/restart
I decided not to go with svn because it was getting awkward/difficult to secure it well enough.
With this solution, rsync is the only tool which can run, and the source data cannot be altered as it's nullfs mounted.
@koobs suggested this (http://twitter.com/koobs/status/866118344587358208)
$ mkdir test
$ cd test
$ touch a b
$ ls -l
total 0
-rw-r--r-- 1 dan staff 0 May 21 10:04 a
-rw-r--r-- 1 dan staff 0 May 21 10:04 b
# point current at a
$ ln -s a current
$ ls -l
total 4
-rw-r--r-- 1 dan staff 0 May 21 10:04 a
-rw-r--r-- 1 dan staff 0 May 21 10:04 b
lrwxr-xr-x 1 dan staff 1 May 21 10:05 current -> a
# When time comes to create a new set of zone files
# create a new directory
# copy zone files there
# update the symlink
$ touch c
$ ln -sf c current
$ ls -l
total 4
-rw-r--r-- 1 dan staff 0 May 21 10:04 a
-rw-r--r-- 1 dan staff 0 May 21 10:04 b
-rw-r--r-- 1 dan staff 0 May 21 10:07 c
lrwxr-xr-x 1 dan staff 1 May 21 10:07 current -> c
# BOOM
# This step is atomic. Thank you koobs
I was wondering how to detect which files are updated during an rsync. I found the solution on the man page: --out-format
$ rsync -a --out-format="updated: %n" -zz -e "ssh -i ~/.ssh/id_ed25519" muster@10.55.0.54: .
updated: bsdcan.ca.db
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment