Merge branch 'master' of ssh://git-ssh.notandy.de:2222/ansible/roles/certificates into HEAD
This commit is contained in:
commit
20b2f78de6
5 changed files with 122 additions and 20 deletions
78
files/acme-primitives.py
Executable file
78
files/acme-primitives.py
Executable file
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import datetime
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography import x509
|
||||||
|
from acme import client, messages, crypto_util, challenges
|
||||||
|
from acme.errors import ConflictError, ValidationError
|
||||||
|
import josepy
|
||||||
|
import click
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def cli():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@cli.command(name="extract_domains")
|
||||||
|
@click.argument('csr_file', type=click.File('rb'))
|
||||||
|
def extract_domains(csr_file):
|
||||||
|
csr = x509.load_pem_x509_csr(csr_file.read(), default_backend())
|
||||||
|
san_ext = csr.extensions.get_extension_for_oid(x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
|
||||||
|
sans = san_ext.value.get_values_for_type(x509.DNSName)
|
||||||
|
print(' '.join(sans))
|
||||||
|
|
||||||
|
@cli.command(name="remaining_days")
|
||||||
|
@click.argument('crt_file', type=click.File('rb'))
|
||||||
|
def remaining_days(crt_file):
|
||||||
|
crt = x509.load_pem_x509_certificate(crt_file.read(), default_backend())
|
||||||
|
print((crt.not_valid_after - datetime.datetime.now()).days)
|
||||||
|
|
||||||
|
@cli.command(name="get_cert")
|
||||||
|
@click.option('--directory', default="https://acme-staging-v02.api.letsencrypt.org/directory")
|
||||||
|
@click.option('--acc', required=True, type=click.File('rb'))
|
||||||
|
@click.option('--csr', required=True, type=click.File('rb'))
|
||||||
|
@click.argument('challenge_hook', nargs=-1)
|
||||||
|
def get_cert(directory, acc, csr, challenge_hook):
|
||||||
|
acc_key = serialization.load_pem_private_key(acc.read(), None, default_backend())
|
||||||
|
jose = josepy.JWKRSA(key=acc_key)
|
||||||
|
net = client.ClientNetwork(jose, user_agent="acme-primitives")
|
||||||
|
directory = messages.Directory.from_json(net.get(directory).json())
|
||||||
|
client_acme = client.ClientV2(directory, net=net)
|
||||||
|
try:
|
||||||
|
regr = client_acme.new_account(messages.NewRegistration.from_data(terms_of_service_agreed=True))
|
||||||
|
except ConflictError as e:
|
||||||
|
regr = client_acme.update_registration(messages.RegistrationResource(body=messages.Registration.from_data(terms_of_service_agreed=True)))
|
||||||
|
csr_data = csr.read()
|
||||||
|
orderr = client_acme.new_order(csr_data)
|
||||||
|
for authz in orderr.authorizations:
|
||||||
|
domain = authz.body.identifier.value
|
||||||
|
for i in authz.body.challenges:
|
||||||
|
if isinstance(i.chall, challenges.DNS01):
|
||||||
|
response, validation = i.response_and_validation(jose)
|
||||||
|
subprocess.run([*challenge_hook, i.validation_domain_name(domain), validation], env=os.environ.copy(), check=True)
|
||||||
|
client_acme.answer_challenge(i, response)
|
||||||
|
try:
|
||||||
|
finalized_orderr = client_acme.poll_and_finalize(orderr)
|
||||||
|
except ValidationError as e:
|
||||||
|
for auth in e.failed_authzrs:
|
||||||
|
msg += '\n Authorization for identifier %s failed.' % (
|
||||||
|
auth.body.identifier)
|
||||||
|
msg += '\n Here are the challenges that were not fulfilled:'
|
||||||
|
for challenge in auth.body.challenges:
|
||||||
|
msg += \
|
||||||
|
'\n Challenge Type: %s' \
|
||||||
|
'\n Error information: ' \
|
||||||
|
'\n Type: %s' \
|
||||||
|
'\n Details: %s \n\n' % (
|
||||||
|
challenge.chall.typ,
|
||||||
|
challenge.error.typ if challenge.error else '',
|
||||||
|
challenge.error.detail if challenge.error else '',
|
||||||
|
)
|
||||||
|
print(msg, file=sys.stderr, flush=True)
|
||||||
|
sys.exit(1)
|
||||||
|
print(finalized_orderr.fullchain_pem)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cli()
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CHALLENGE_RECORD="$1"
|
||||||
|
CHALLENGE_VALUE="$2"
|
||||||
|
|
||||||
|
logger -t letsencrypt "deploying challenge for record ${CHALLENGE_RECORD} with value ${CHALLENGE_VALUE}"
|
||||||
|
|
||||||
for i in $LETSENCRYPT_CHALLENGE_SERVERS; do
|
for i in $LETSENCRYPT_CHALLENGE_SERVERS; do
|
||||||
ssh -i /etc/letsencrypt/renewkey -o "StrictHostKeyChecking no" letsencrypt@$i $(< $LETSENCRYPT_TOKEN ) $1 $2
|
logger -t letsencrypt "deploying to ${i}"
|
||||||
|
{ ssh -i /etc/letsencrypt/renewkey -o "StrictHostKeyChecking no" letsencrypt@$i "$(cat "$LETSENCRYPT_TOKEN")" "${CHALLENGE_RECORD}" "${CHALLENGE_VALUE}" | logger -t letsencrypt -e; } ||
|
||||||
|
{ logger -t letsencrypt "deploying failed with exit code $?"; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ set -euo pipefail
|
||||||
|
|
||||||
source $1
|
source $1
|
||||||
|
|
||||||
daysleft=$(/usr/local/bin/acme-primitives.py remaining_days "$LETSENCRYPT_CRT" || echo "0") 2>/dev/null
|
logger -t letsencrypt "Checking certificate ${LETSENCRYPT_CRT}"
|
||||||
[ "$daysleft" -lt "$LETSENCRYPT_REMAININGDAYS" ] || exit 0
|
daysleft=$(/usr/local/bin/acme-primitives.py remaining_days "${LETSENCRYPT_CRT}" || echo "0") 2>/dev/null
|
||||||
|
[ "$daysleft" -lt "$LETSENCRYPT_REMAININGDAYS" ] || { logger -t letsencrypt "Cert has ${LETSENCRYPT_REMAININGDAYS} days remaining, not renewing" exit 0; }
|
||||||
|
|
||||||
folder="$(mktemp -d)"
|
folder="$(mktemp -d)"
|
||||||
cd "$folder"
|
cd "${folder}"
|
||||||
/usr/local/bin/acme-primitives.py get_cert --directory 'https://acme-v02.api.letsencrypt.org/directory' --acc /etc/ssl/letsencrypt_account.key --csr $LETSENCRYPT_CSR /usr/local/bin/letsencrypt_deploy_challenge.sh > chained.pem
|
logger -t letsencrypt "Renewing certificate"
|
||||||
|
/usr/local/bin/acme-primitives.py get_cert --directory 'https://acme-v02.api.letsencrypt.org/directory' --acc /etc/ssl/letsencrypt_account.key --csr "${LETSENCRYPT_CSR}" /usr/local/bin/letsencrypt_deploy_challenge.sh > chained.pem
|
||||||
|
|
||||||
cat chained.pem "$LETSENCRYPT_KEY" > full.pem
|
cat chained.pem "$LETSENCRYPT_KEY" > full.pem
|
||||||
openssl x509 -in chained.pem > cert.pem
|
openssl x509 -in chained.pem > cert.pem
|
||||||
|
|
@ -25,6 +27,10 @@ mv full.pem "$LETSENCRYPT_FULL"
|
||||||
cd
|
cd
|
||||||
rm -r "$folder"
|
rm -r "$folder"
|
||||||
|
|
||||||
for i in $LETSENCRYPT_SERVICES; do
|
logger -t letsencrypt "Success, restarting services ( ${LETSENCRYPT_SERVICES} )..."
|
||||||
/bin/systemctl "$i" restart
|
|
||||||
|
for i in ${LETSENCRYPT_SERVICES}; do
|
||||||
|
/bin/systemctl "${i}" restart
|
||||||
done
|
done
|
||||||
|
|
||||||
|
logger -t letsencrypt "done"
|
||||||
|
|
|
||||||
|
|
@ -36,23 +36,26 @@
|
||||||
src: "/etc/letsencrypt/cert_{{ certname }}.token"
|
src: "/etc/letsencrypt/cert_{{ certname }}.token"
|
||||||
register: tokenfile
|
register: tokenfile
|
||||||
- name: add renew ssh key to backend server
|
- name: add renew ssh key to backend server
|
||||||
delegate_to: "{{ item }}"
|
delegate_to: "{{ challengeserver }}"
|
||||||
loop: "{{ cert_backend.challengeserver }}"
|
loop: "{{ cert_backend.challengeserver }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: challengeserver
|
||||||
authorized_key:
|
authorized_key:
|
||||||
user: letsencrypt
|
user: letsencrypt
|
||||||
key: "{{ letsencrypt_renewkey.public_key }}"
|
key: "{{ letsencrypt_renewkey.public_key }}"
|
||||||
- name: add server token to record whitelist on backend server
|
- name: add server token to record whitelist on backend server
|
||||||
when:
|
when:
|
||||||
- challenge is changed
|
- challenge is changed
|
||||||
delegate_to: "{{ item.0 }}"
|
delegate_to: "{{ serverchallengepair.0 }}"
|
||||||
loop: "{{ cert_backend.challengeserver|product(challenge.challenge_data.keys()|list)|list }}"
|
loop: "{{ cert_backend.challengeserver|product(challenge.challenge_data.keys()|list)|list }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: serverchallengepair
|
||||||
command:
|
command:
|
||||||
argv:
|
argv:
|
||||||
- "/usr/local/bin/pdns.py"
|
- "/usr/local/bin/pdns.py"
|
||||||
- "add_token"
|
- "add_token"
|
||||||
- "--"
|
|
||||||
- "{{ tokenfile.content | b64decode }}"
|
- "{{ tokenfile.content | b64decode }}"
|
||||||
- "{{ challenge.challenge_data[item.1]['dns-01'].record }}"
|
- "{{ challenge.challenge_data[serverchallengepair.1]['dns-01'].record }}"
|
||||||
- name: create cert renew config
|
- name: create cert renew config
|
||||||
template:
|
template:
|
||||||
src: letsencrypt_renew_config.j2
|
src: letsencrypt_renew_config.j2
|
||||||
|
|
@ -71,23 +74,27 @@
|
||||||
when:
|
when:
|
||||||
- challenge is changed
|
- challenge is changed
|
||||||
- cert_backend.challenge == "dns-01"
|
- cert_backend.challenge == "dns-01"
|
||||||
delegate_to: "{{ item.0 }}"
|
delegate_to: "{{ serverchallengepair.0 }}"
|
||||||
loop: "{{ cert_backend.challengeserver|product(challenge.challenge_data.keys()|list)|list }}"
|
loop: "{{ cert_backend.challengeserver|product(challenge.challenge_data.keys()|list)|list }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: serverchallengepair
|
||||||
command:
|
command:
|
||||||
argv:
|
argv:
|
||||||
- "/usr/local/bin/pdns.py"
|
- "/usr/local/bin/pdns.py"
|
||||||
- "add_challenge"
|
- "add_challenge"
|
||||||
- "--"
|
- "--"
|
||||||
- "{{ challenge.challenge_data[item.1]['dns-01'].record }}"
|
- "{{ challenge.challenge_data[serverchallengepair.1]['dns-01'].record }}"
|
||||||
- "{{ challenge.challenge_data[item.1]['dns-01'].resource_value }}"
|
- "{{ challenge.challenge_data[serverchallengepair.1]['dns-01'].resource_value }}"
|
||||||
|
|
||||||
- name: "setup challenge server for {{ certname }} (manual dns challenge)"
|
- name: "setup challenge server for {{ certname }} (manual dns challenge)"
|
||||||
when:
|
when:
|
||||||
- challenge is changed
|
- challenge is changed
|
||||||
- cert_backend.challenge == "dns-01-manual"
|
- cert_backend.challenge == "dns-01-manual"
|
||||||
loop: "{{ challenge.challenge_data_dns|d({})|dict2items }}"
|
loop: "{{ challenge.challenge_data_dns|d({})|dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: challengedata
|
||||||
debug:
|
debug:
|
||||||
msg: "add the following dns record: '{{ item.key }}.': { TXT: {{ item.value }} }"
|
msg: "add the following dns record: '{{ challengedata.key }}.': { TXT: {{ challengedata.value }} }"
|
||||||
|
|
||||||
- name: wait for challenges in dns (manual dns challenge)
|
- name: wait for challenges in dns (manual dns challenge)
|
||||||
pause:
|
pause:
|
||||||
|
|
@ -100,11 +107,14 @@
|
||||||
when:
|
when:
|
||||||
- challenge is changed
|
- challenge is changed
|
||||||
- cert_backend.challenge == "http-01"
|
- cert_backend.challenge == "http-01"
|
||||||
delegate_to: "{{ item.0 }}"
|
delegate_to: "{{ serverchallengepair.0 }}"
|
||||||
loop: "{{ cert_backend.challengeserver|product(challenge.challenge_data.keys()|list)|list }}"
|
loop: "{{ cert_backend.challengeserver|product(challenge.challenge_data.keys()|list)|list }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: serverchallengepair
|
||||||
copy:
|
copy:
|
||||||
dest: "/var/www/letsencrypt/{{ challenge.challenge_data[item.1]['http-01'].resource | basename }}"
|
dest: "/var/www/letsencrypt/{{ challenge.challenge_data[serverchallengepair.1]['http-01'].resource | basename }}"
|
||||||
content: "{{ challenge.challenge_data[item.1]['http-01'].resource_value }}"
|
content: "{{ challenge.challenge_data[serverchallengepair.1]['http-01'].resource_value }}"
|
||||||
|
mode: 0666
|
||||||
|
|
||||||
- name: "get certificate {{ certname }}"
|
- name: "get certificate {{ certname }}"
|
||||||
acme_certificate:
|
acme_certificate:
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,9 @@
|
||||||
mode: 0755
|
mode: 0755
|
||||||
|
|
||||||
- name: copy acme primitives
|
- name: copy acme primitives
|
||||||
get_url:
|
copy:
|
||||||
|
src: acme-primitives.py
|
||||||
dest: /usr/local/bin/acme-primitives.py
|
dest: /usr/local/bin/acme-primitives.py
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: 0755
|
mode: 0755
|
||||||
url: "https://git.notandy.de/ansible/acme-primitives/-/raw/master/acme-primitives.py"
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue