From 0aa3cb530344a4111db48e6f54a3d594e288d4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Fra=C3=9F?= Date: Thu, 30 May 2024 22:56:56 +0200 Subject: [PATCH] [mod] role:tlscert_acme_inwx:eigene umsetzung und automatische erneuerung --- roles/tlscert_acme_inwx/files/tls-renew | 87 ++++++++---- roles/tlscert_acme_inwx/info.md | 5 + roles/tlscert_acme_inwx/tasks/main.json | 134 ++++++++---------- .../templates/tls-renew-conf.json.j2 | 2 +- 4 files changed, 125 insertions(+), 103 deletions(-) diff --git a/roles/tlscert_acme_inwx/files/tls-renew b/roles/tlscert_acme_inwx/files/tls-renew index 4ca7893..db85e2e 100755 --- a/roles/tlscert_acme_inwx/files/tls-renew +++ b/roles/tlscert_acme_inwx/files/tls-renew @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import sys as _sys import os as _os import json as _json import argparse as _argparse @@ -21,17 +22,12 @@ def main(): type = str, dest = "conf_path", metavar = "", - default = "./tls-renew-conf.json", + default = _os.path.join(_os.environ["HOME"], ".tls-renew-conf.json"), ) argument_parser.add_argument( type = str, - dest = "domain_base", - metavar = "", - ) - argument_parser.add_argument( - type = str, - dest = "domain_path", - metavar = "", + dest = "domain", + metavar = "", ) argument_parser.add_argument( "-t", @@ -59,45 +55,84 @@ def main(): metavar = "", help = "seconds to wait at end of certbot auth hook", ) + argument_parser.add_argument( + "-q", + "--dry-run", + dest = "dry_run", + action = "store_true", + default = False, + help = "whether to only print the command on stdout instead of executing it", + ) args = argument_parser.parse_args() ## vars conf = _json.loads(file_read(args.conf_path)) - domain = (args.domain_base + args.domain.path) + le_dir = "/etc/letsencrypt/live" ## exec command_hook_parts = [ ("/usr/local/bin/inwx"), - ("--username='%s'" % conf["inwx_account"]["username"]), - ("--password='%s'" % conf["inwx_account"]["password"]), - ("--challenge-prefix='%s'" % args.challenge_prefix), - ("--delay=%.4f" % args.delay), - ("save"), - (args.domain_base), - ("_acme-challenge.%s" % args.domain.path), - ("TXT"), - ("'\${CERTBOT_VALIDATION}'"), + ("--username=\"%s\"" % conf["inwx_account"]["username"]), + ("--password=\"%s\"" % conf["inwx_account"]["password"]), + ("certbot-hook") ] command_hook = " ".join(command_hook_parts) command_certbot_parts = [ ("certbot"), + ("certonly"), ("--email='%s'" % conf["acme_account"]["email"]), - ("--work-dir='%s'" % conf["misc"]["working_directory"]), + # ("--work-dir='%s'" % conf["misc"]["working_directory"]), ("--preferred-challenges='dns'"), ("--non-interactive"), - ("--key-path='%s'" % _os.path.join(args.target_directory, "private", "%s.pem" % domain)), - ("--cert-path='%s'" % _os.path.join(args.target_directory, "certs", "%s.pem" % domain)), - ("--chain-path='%s'" % _os.path.join(args.target_directory, "chains", "%s.pem" % domain)), - ("--fullchain-path='%s'" % _os.path.join(args.target_directory, "fullchains", "%s.pem" % domain)), - ("--domain='%s'" % domain), + ("--agree-tos"), + ("--domain='%s'" % args.domain), + ("--manual"), ("--manual-auth-hook='%s'" % command_hook), - ("renew"), + # ("--key-path='%s'" % _os.path.join(args.target_directory, "private", "%s.pem" % args.domain)), + # ("--cert-path='%s'" % _os.path.join(args.target_directory, "certs", "%s.pem" % args.domain)), + # ("--chain-path='%s'" % _os.path.join(args.target_directory, "chains", "%s.pem" % args.domain)), + # ("--fullchain-path='%s'" % _os.path.join(args.target_directory, "fullchains", "%s.pem" % args.domain)), ] command_certbot = " ".join(command_certbot_parts) - _os.system(command_certbot) + if (args.dry_run): + _sys.stdout.write(command_certbot + "\n") + else: + _os.system(command_certbot) + _os.system( + "mkdir --parents %s && cp --dereference %s %s" + % ( + _os.path.join(args.target_directory, "private"), + _os.path.join(le_dir, args.domain, "privkey.pem"), + _os.path.join(args.target_directory, "private", "%s.pem" % args.domain), + ) + ) + _os.system( + "mkdir --parents %s && cp --dereference %s %s" + % ( + _os.path.join(args.target_directory, "certs"), + _os.path.join(le_dir, args.domain, "cert.pem"), + _os.path.join(args.target_directory, "certs", "%s.pem" % args.domain), + ) + ) + _os.system( + "mkdir --parents %s && cp --dereference %s %s" + % ( + _os.path.join(args.target_directory, "chains"), + _os.path.join(le_dir, args.domain, "chain.pem"), + _os.path.join(args.target_directory, "chains", "%s.pem" % args.domain), + ) + ) + _os.system( + "mkdir --parents %s && cp --dereference %s %s" + % ( + _os.path.join(args.target_directory, "fullchains"), + _os.path.join(le_dir, args.domain, "fullchain.pem"), + _os.path.join(args.target_directory, "fullchains", "%s.pem" % args.domain), + ) + ) main() diff --git a/roles/tlscert_acme_inwx/info.md b/roles/tlscert_acme_inwx/info.md index 8a8baa7..7407def 100644 --- a/roles/tlscert_acme_inwx/info.md +++ b/roles/tlscert_acme_inwx/info.md @@ -1,3 +1,8 @@ +## Beschreibung + +- richtet die regelmäßige TLS-Zertifikats-Erstellung für eine Domäne und führt eine Erstellung direkt aus + + ## Verweise - [Digital Ocean | How To Acquire a Let's Encrypt Certificate Using Ansible](https://www.digitalocean.com/community/tutorials/how-to-acquire-a-let-s-encrypt-certificate-using-ansible-on-ubuntu-18-04) diff --git a/roles/tlscert_acme_inwx/tasks/main.json b/roles/tlscert_acme_inwx/tasks/main.json index 0e8a365..af0dcbb 100644 --- a/roles/tlscert_acme_inwx/tasks/main.json +++ b/roles/tlscert_acme_inwx/tasks/main.json @@ -6,17 +6,19 @@ "update_cache": true, "pkg": [ "openssl", - "python3-cryptography" + "python3-cryptography", + "certbot" ] } }, { - "name": "directories | ssl", + "name": "directories", "become": true, "loop": [ "{{var_tlscert_acme_inwx_ssl_directory}}/private", "{{var_tlscert_acme_inwx_ssl_directory}}/csr", "{{var_tlscert_acme_inwx_ssl_directory}}/certs", + "{{var_tlscert_acme_inwx_ssl_directory}}/chains", "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains" ], "ansible.builtin.file": { @@ -25,54 +27,7 @@ } }, { - "name": "directories | Let's Encrypt account key", - "become": true, - "ansible.builtin.file": { - "state": "directory", - "path": "{{var_tlscert_acme_inwx_acme_account_key_path | dirname}}" - } - }, - { - "name": "key", - "become": true, - "community.crypto.openssl_privatekey": { - "path": "{{var_tlscert_acme_inwx_ssl_directory}}/private/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem" - } - }, - { - "name": "csr", - "become": true, - "community.crypto.openssl_csr": { - "common_name": "{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}", - "privatekey_path": "{{var_tlscert_acme_inwx_ssl_directory}}/private/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "path": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem" - } - }, - { - "name": "acme | account key", - "become": true, - "ansible.builtin.shell": { - "cmd": "test -f {{var_tlscert_acme_inwx_acme_account_key_path}} || openssl genrsa 4096 > {{var_tlscert_acme_inwx_acme_account_key_path}}" - } - }, - { - "name": "acme | init", - "become": true, - "community.crypto.acme_certificate": { - "acme_version": 2, - "acme_directory": "https://acme-v02.api.letsencrypt.org/directory", - "account_email": "{{var_tlscert_acme_inwx_acme_account_email}}", - "account_key_src": "{{var_tlscert_acme_inwx_acme_account_key_path}}", - "terms_agreed": true, - "csr": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "challenge": "dns-01", - "dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem" - }, - "register": "temp_acme_data" - }, - { - "name": "dns challenge | place script", + "name": "tools | inwx", "become": true, "ansible.builtin.copy": { "src": "inwx", @@ -81,33 +36,60 @@ } }, { - "name": "dns challenge | execute", - "when": "'challenge_data' in temp_acme_data", - "ansible.builtin.command": { - "cmd": "/usr/local/bin/inwx --username={{var_tlscert_acme_inwx_inwx_account_username}} --password={{var_tlscert_acme_inwx_inwx_account_password}} save {{var_tlscert_acme_inwx_domain_base}} _acme-challenge.{{var_tlscert_acme_inwx_domain_path}} TXT {{temp_acme_data['challenge_data'][var_tlscert_acme_inwx_domain_path + '.' + var_tlscert_acme_inwx_domain_base]['dns-01']['resource_value']}}" - } - }, - { - "name": "dns challenge | wait", - "when": "'challenge_data' in temp_acme_data", - "ansible.builtin.pause": { - "seconds": 60 - } - }, - { - "name": "acme | finalize", + "name": "tools | tls-renew | script", "become": true, - "community.crypto.acme_certificate": { - "acme_version": 2, - "acme_directory": "https://acme-v02.api.letsencrypt.org/directory", - "account_email": "{{var_tlscert_acme_inwx_acme_account_email}}", - "account_key_src": "{{var_tlscert_acme_inwx_acme_account_key_path}}", - "terms_agreed": true, - "csr": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "challenge": "dns-01", - "dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem", - "data": "{{temp_acme_data}}" + "ansible.builtin.file": { + "src": "tls-renew", + "dest": "/usr/local/bin/tls-renew", + "mode": "a+x" + } + }, + { + "name": "tools | tls-renew | conf", + "become": true, + "ansible.builtin.template": { + "src": "tls-renew-conf.json.j2", + "dest": "/root/.tls-renew-conf.json" + } + }, + { + "name": "tools | pseudo queue | setup", + "become": true, + "ansible.builtin.cron": { + "state": "present", + "disabled": false, + "name": "pseudo queue", + "special_time": "reboot", + "job": "bash -c \"test -p /var/pseudoqueue || (mkfifo --mode=0600 /var/pseudoqueue && (while true ; do bash < /var/pseudoqueue ; done))\"" + } + }, + { + "name": "tools | pseudo queue | run", + "become": true, + "ansible.builtin.shell": { + "cmd": "bash -c \"test -p /var/pseudoqueue || (mkfifo --mode=0600 /var/pseudoqueue && (while true ; do bash < /var/pseudoqueue ; done))\"" + } + }, + { + "name": "setup auto renewal", + "become": true, + "ansible.builtin.cron": { + "state": "present", + "disabled": false, + "name": "TLS certificate for {{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}", + "minute": "0", + "hour": "2", + "day": "1", + "month": "*", + "weekday": "*", + "job": "echo '/usr/local/bin/tls-renew --conf-path=/root/.tls-renew-conf.json {{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}} --target-directory={{var_tlscert_acme_inwx_ssl_directory}}' > /var/pseudoqueue" + } + }, + { + "name": "run", + "become": true, + "ansible.builtin.shell": { + "cmd": "echo '/usr/local/bin/tls-renew --conf-path=/root/.tls-renew-conf.json {{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}} --target-directory={{var_tlscert_acme_inwx_ssl_directory}}' > /var/pseudoqueue" } } ] diff --git a/roles/tlscert_acme_inwx/templates/tls-renew-conf.json.j2 b/roles/tlscert_acme_inwx/templates/tls-renew-conf.json.j2 index 4ffa03e..24a0bac 100644 --- a/roles/tlscert_acme_inwx/templates/tls-renew-conf.json.j2 +++ b/roles/tlscert_acme_inwx/templates/tls-renew-conf.json.j2 @@ -2,7 +2,7 @@ "acme_account": { "email": "{{var_tlscert_acme_inwx_acme_account_email}}" }, - "inwx_account: { + "inwx_account": { "username": "{{var_tlscert_acme_inwx_inwx_account_username}}", "password": "{{var_tlscert_acme_inwx_inwx_account_password}}" },