This commit is contained in:
2026-03-12 12:41:28 +01:00
parent 87755ef08e
commit 64c7b6c310
12 changed files with 539 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
# filepath: /usr/local/bin/certbot_parse_facts.py
import sys
import yaml
def parse_certbot_output(lines):
certs = []
cert = {}
for line in lines:
if line.startswith(" Certificate Name:"):
if cert:
certs.append(cert)
cert = {}
cert["name"] = line.split(":", 1)[1].strip()
elif line.strip().startswith("Serial Number:"):
cert["serial"] = line.split(":", 1)[1].strip()
elif line.strip().startswith("Key Type:"):
cert["key_type"] = line.split(":", 1)[1].strip()
elif line.strip().startswith("Domains:"):
cert["domains"] = line.split(":", 1)[1].strip()
elif line.strip().startswith("Expiry Date:"):
cert["expiry"] = line.split(":", 1)[1].strip()
elif line.strip().startswith("Certificate Path:"):
cert["cert_path"] = line.split(":", 1)[1].strip()
elif line.strip().startswith("Private Key Path:"):
cert["key_path"] = line.split(":", 1)[1].strip()
if cert:
certs.append(cert)
return {"certificates": certs}
def sort_certificates_by_name(facts):
facts["certificates"].sort(key=lambda c: c.get("name", "").lower())
if __name__ == "__main__":
# Read lines from stdin
lines = [line.rstrip("\n") for line in sys.stdin]
facts = parse_certbot_output(lines)
# Write facts but sorted by certificate name
sort_certificates_by_name(facts)
# Output to YAML file for Ansible facts
with open("/etc/ansible/facts.d/certbot.certificates.yml", "w") as f:
yaml.dump(facts, f, default_flow_style=False)

View File

@@ -0,0 +1,35 @@
{
"certificates": [
{% set cert = {} %}
{% for line in certbot_certificates %}
{% if line.startswith(' Certificate Name:') %}
{% if cert %}
{{ cert | to_nice_json }},{% set cert = {} %}
{% endif %}
{% set cert = cert.copy() %}
{% set cert['name'] = line.split(':', 1)[1].strip() %}
{% elif line.startswith(' Serial Number:') %}
{% set cert = cert.copy() %}
{% set cert['serial'] = line.split(':', 1)[1].strip() %}
{% elif line.startswith(' Key Type:') %}
{% set cert = cert.copy() %}
{% set cert['key_type'] = line.split(':', 1)[1].strip() %}
{% elif line.startswith(' Domains:') %}
{% set cert = cert.copy() %}
{% set cert['domains'] = line.split(':', 1)[1].strip() %}
{% elif line.startswith(' Expiry Date:') %}
{% set cert = cert.copy() %}
{% set cert['expiry'] = line.split(':', 1)[1].strip() %}
{% elif line.startswith(' Certificate Path:') %}
{% set cert = cert.copy() %}
{% set cert['cert_path'] = line.split(':', 1)[1].strip() %}
{% elif line.startswith(' Private Key Path:') %}
{% set cert = cert.copy() %}
{% set cert['key_path'] = line.split(':', 1)[1].strip() %}
{% endif %}
{% if loop.last and cert %}
{{ cert | to_nice_json }}
{% endif %}
{% endfor %}
]
}

View File

@@ -0,0 +1,4 @@
{
"installed":"true",
"version":"{{ __certbot_version | regex_replace('^.*certbot ([\\w\\.\\-]+).*?$', '\\1') }}"
}

43
templates/certbot.md.j2 Normal file
View File

@@ -0,0 +1,43 @@
{{ certbot_message | default (admin_message ) | comment }}
# Certbot
Certbot {{ __certbot_version | regex_replace('^.*certbot ([\\w\\.\\-]+).*?$', '\\1') }} is installed on host {{ inventory_hostname }}
For all certs helper scripts (/usr/local/bin/certbot-[domainname].sh) and a cronjob (to update the certs) are installed.
{% if certbot_webserver is defined %}
The Webserver "{{ certbot_webserver }}" is defined as the default one which interacts with the certbot plugin.
{% endif %}
## Certs
{{ __certbot_certificates.stdout }}
## Facts
{% if certbot_facts | default(false) %}
Facts are saved on the host in these files:
- /etc/ansible/facts.d/certbot.json
- /etc/ansible/facts.d/certbot.certificates.yml
### Update facts without Ansible
To update the facts run:
certbot certificates | /opt/ansible-facts-venv/bin/python3 /usr/local/bin/ansible_certbot_parse_facts.py
{% else %}
Facts are not saved on the host. To enable this set certbot_facts to true in your inventory.
{% endif %}
---
Any Questions?
**Linux-Server-Admin.com**

View File

@@ -0,0 +1,56 @@
#!/bin/bash
#
# Create Lets Encrypt Cert for {{ item.item.name }}
# If the Cert is already created, it will just perform a quiet "certbot renew".
#
# Linux-Server-Admin.com
#
{{ certbot_message | default (admin_message ) | comment }}
#
#
# /usr/local/bin/certbot-{{ item.item.name }}.sh
#
CERT="/etc/letsencrypt/live/{{ item.item.name }}/"
if [ ! -d "$CERT" ]; then
{% if certbot_webserver is defined and certbot_webserver_plugin_install | default(true) | bool %}
echo "### Start Creating Cert {{ item.item.name }} or renew it";
certbot certonly --{{ certbot_webserver }} --noninteractive --agree-tos --expand \
--email {{ certbot_admin_email | default('root@localhost') }} \
-d {{ item.item.name }} {% if item.item.alias is defined %}\
{% for altname in item.item.alias %} -d {{ altname }} {% endfor %}
{% endif %}
{% else %}
{% if certbot_freeipa %}
systemctl stop httpd
{% endif %}
echo "### Start Creating Cert {{ item.item.name }} or renew it";
certbot certonly --standalone --noninteractive --agree-tos --expand \
--email {{ certbot_admin_email | default('root@localhost') }} \
-d {{ item.item.name }} {% if item.item.alias is defined %}\
{% for altname in item.item.alias %} -d {{ altname }} {% endfor %}
{% endif %}
{% if certbot_freeipa %}
systemctl start httpd
{% endif %}
{% endif %}
else
{% if certbot_webserver is defined %}
certbot renew --quiet --{{ certbot_webserver }}
{% else %}
certbot renew --quiet
{% endif %}
fi
exit

View File

@@ -0,0 +1,120 @@
#!/bin/bash
#
# LetsEncrypt Integration for FreeIPA with Backup and Recovery
#
# Linux-Server-Admin.com
#
{{ certbot_message | default (admin_message ) | comment }}
#
# This script obtains a Lets Encrypt certificate for FreeIPA and integrates it.
# It also creates backups and provides instructions for recovery.
#
# USAGE:
# sudo ./letsencrypt-freeipa.sh
#
# REQUIREMENTS:
# - FreeIPA server with ipa-cacert-manage, ipa-certupdate, ipa-server-certinstall, and ipactl available.
# - Certbot certificates must already be present in /etc/letsencrypt/live/$IPADOMAIN/.
#
set -euo pipefail
### VARIABLES ###
IPADOMAIN="{{ certbot_ipa_domain | default(inventory_hostname) }}"
TEMP_DIR="{{ certbot_temp_dir | default('/tmp/letsencrypt/') }}"
ADMIN_EMAIL="{{ certbot_admin_email | default('root@localhost') }}"
CERTS_URL="https://letsencrypt.org/certs/"
CERTS_BASE=("isrgrootx1.pem" "isrg-root-x2.pem")
CERTS_EXTRA_URL="https://letsencrypt.org/certs/2024/"
CERTS_EXTRA=("e5.pem" "e6.pem" "r10.pem" "r11.pem")
CERTDIR="/etc/letsencrypt/live/$IPADOMAIN/"
TIMESTAMP=$(date +%Y%m%d%H%M%S)
BACKUP_DIR="/var/lib/ipa-backups/${TIMESTAMP}"
### FUNCTIONS ###
# If something fails after we start modifying certs, you may need to restore.
restore_instructions() {
echo "### ERROR DETECTED ###"
echo "To restore from backups:"
echo "- If only certs and private directories changed, restore from backups:"
echo " cp -r /var/lib/ipa/certs.bak.${TIMESTAMP} /var/lib/ipa/certs"
echo " cp -r /var/lib/ipa/private.bak.${TIMESTAMP} /var/lib/ipa/private"
echo " ipa-certupdate"
echo " ipactl restart"
echo ""
echo "- If the FreeIPA state is more severely disrupted, use ipa-restore:"
echo " ipa-restore --from-backup /var/lib/ipa/backup/${TIMESTAMP}"
echo "You will need to confirm the restore when prompted."
exit 1
}
trap restore_instructions ERR
### MAIN SCRIPT ###
echo "### Creating working directory"
mkdir -p "${TEMP_DIR}"
cd "${TEMP_DIR}"
echo "### Creating timestamped backups for IPA certs and private keys"
mkdir -p "${BACKUP_DIR}"
# Back up existing certs and keys
cp -r /var/lib/ipa/certs "/var/lib/ipa/certs.bak.${TIMESTAMP}"
cp -r /var/lib/ipa/private "/var/lib/ipa/private.bak.${TIMESTAMP}"
# Optional: Create a full FreeIPA backup for complete rollback if needed.
# Note: This can be commented out if you do not want a full backup.
echo "### Performing a full FreeIPA backup"
ipa-backup
echo "### Downloading Lets Encrypt root certificates"
for CERT_BASE in "${CERTS_BASE[@]}"; do
curl -fSLo "${TEMP_DIR}${CERT_BASE}" "${CERTS_URL}${CERT_BASE}"
done
echo "### Downloading additional Lets Encrypt certificates"
for CERT_EXTRA in "${CERTS_EXTRA[@]}"; do
curl -fSLo "${TEMP_DIR}${CERT_EXTRA}" "${CERTS_EXTRA_URL}${CERT_EXTRA}"
done
echo "### Installing Root Certificates into IPA CA Store"
for CERT_BASE in "${CERTS_BASE[@]}"; do
ipa-cacert-manage install "${TEMP_DIR}${CERT_BASE}"
done
echo "### Installing Additional Certificates into IPA CA Store"
for CERT_EXTRA in "${CERTS_EXTRA[@]}"; do
ipa-cacert-manage install "${TEMP_DIR}${CERT_EXTRA}"
done
echo "### Updating CA certificates in IPA"
ipa-certupdate
echo "### Installing Lets Encrypt server certificates"
# Ensure that fullchain.pem and privkey.pem exist
if [[ ! -f "${CERTDIR}fullchain.pem" || ! -f "${CERTDIR}privkey.pem" ]]; then
echo "ERROR: The Let's Encrypt certificates are not present in ${CERTDIR}"
exit 1
fi
ipa-server-certinstall -w -d \
"${CERTDIR}privkey.pem" \
"${CERTDIR}fullchain.pem" \
--pin=''
echo "### Restarting IPA services"
ipactl restart
echo "### Cleanup"
rm -rf "${TEMP_DIR}"
echo "### Done!"
echo "The Lets Encrypt certificates have been installed successfully."
echo "If you need to restore at any point, follow the instructions in the error handler above."