Initial project commit

This commit is contained in:
2026-02-22 16:18:53 +01:00
parent 6e859c0b5b
commit 0f840bb58e
6 changed files with 5132 additions and 2 deletions

42
LICENSE Normal file
View File

@@ -0,0 +1,42 @@
MIT License
Copyright (c) 2016-2026 CB-601 - the open tec Elevator <mail@opensource-technology.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## Third-party code
This repository bundles external ACME client scripts under 'contrib/acme/':
- 'letsencrypt_master.sh'
Upstream ACME client script (ght-acme.sh) by Attila Bruncsak and others,
licensed under the GNU General Public License, version 2 (or any later
version). The full license text is included in the script header.
- 'letsencrypt_master_local.sh'
Locally adapted variant of the same upstream client, with modifications for
dynTLS integration (ACME server selection, dns-01 handling, http-01 token
permissions). It remains under the GNU GPLv2-or-later, as required by the
original project.
These third-party files are clearly marked with their original copyright
and license information in their respective headers and are **not** covered
by the MIT license above.

219
README.md
View File

@@ -1,3 +1,218 @@
# dyntls
# 🔐 dynTLS
Granular Lets Encrypt certificate management for multiple services and hosts. Uses external ACME backends (dns-01/http-01) and maps issued certs cleanly to system services.
**Granular Let's Encrypt certificate management for multi-service environments**
Provides flexible ACME backend integration, DNS/http challenges and per-service certificate deployment.
This utility simplifies issuing and managing TLS certificates via Lets Encrypt for multiple services and hosts (e.g. Postfix, Dovecot, web servers and others). It separates ACME handling from deployment logic, supports HTTP-01 and DNS-01 challenges and maps issued certificates cleanly to individual services.
Key features:
- 🔁 Automated issuance and renewal of Lets Encrypt certificates
- 🌐 Support for HTTP-01 and DNS-01 challenges via external ACME client
- 🧩 Flexible ACME backend selection (e.g. customized `letsencrypt_master_local.sh`)
- 🎯 Per-host and per-service certificate mapping with backup handling
- 📜 Central configuration via `vars` file
- 📂 Plain repository layout with optional contrib scripts under `./contrib/acme`
---
## Outline
1. [Features](#features)
2. [Installation](#installation)
3. [Configuration](#configuration)
4. [Directory Layout](#directory-layout)
5. [CRON Job Example](#cron-job-example)
6. [ACME Backend](#acme-backend)
7. [License](#license)
8. [Authors](#authors)
9. [Project Home](#project-home)
---
## Features
- Manages Lets Encrypt certificates for multiple domains and services
- Uses a configurable ACME backend script (e.g. `letsencrypt_master_local.sh`)
- Supports HTTP-01 and DNS-01 challenges (e.g. via Bind/nsupdate, TSIG, `rndc`)
- Validates hostnames, SAN lists and certificate expiration before deployment
- Creates backups of replaced certificates and can prune old backups
- Logs operations with log levels and domain-set IDs for better traceability
## Installation
The following steps describe a simple, source-based installation.
1. Clone the repository
```bash
git clone https://dev.town-square.de/cb601/dyntls.git /opt/dyntls
cd /opt/dyntls
```
2. Install required packages
Make sure you have at least:
- bash
- openssl
- nsupdate / bind-utils (for DNS-01 with Bind, if used)
- curl or wget (depending on your ACME script)
On RPM-based systems, for example:
```bash
sudo dnf install openssl bind-utils curl -y
```
3. Prepare configuration
Copy the example vars file and adapt it to your environment:
```bash
sudo cp vars.example vars
sudo chmod 640 vars
```
4. Test a dry run
Before using dynTLS in production, run a dry or staging-mode test (example):
```bash
./dyntls.sh help
```
## Configuration
dynTLS is configured via a `vars` file (e.g. `vars` or `vars.example`) which defines:
- Base directories for PKI and HTTP token storage
- ACME backend script path (`DYNTLS_LE_PROGRAM`)
- Certificate renewal thresholds and backup behavior
- Domain lists and service mappings
Key options include, for example:
- `DYNTLS_LE_PROGRAM` path to the external ACME client script
- `DYNTLS_PKI` root PKI directory (certificates, keys, backups)
- `DYNTLS_PKI_CERT_EXPIRE` minimum remaining validity (days) before renewal
- `DYNTLS_DOMAIN_LIST` list of domains (CN + SANs) for a certificate
- `DYNTLS_DOMAINSERVICE_LIST` mapping from certificate CN to service target(s)
You can keep `vars.example` under version control and use a separate, local `vars` file (ignored by Git) for host-specific secrets and paths.
## Directory Layout
Minimal plain layout in the repository:
```text
./
├── dyntls # main dynTLS script
├── vars.example # example configuration
├── LICENSE.md # project license
├── README.md # this file
└── contrib/
└── acme/
├── letsencrypt_master.sh
└── letsencrypt_master_local.sh
```
Suggested runtime layout on a host (example):
| Path | Purpose |
|-----------------------------|---------------------------------|
| /opt/dyntls/dyntls.sh | Main script |
| /opt/dyntls/vars.example | Example config (reference) |
| /opt/dyntls/vars | User config (never overwritten) |
| /etc/pki/httpd/certs | Issued certificates |
| /etc/pki/httpd/private | Private keys |
| /etc/pki/httpd/certs/backup | Certificate backups |
| /var/log/dyntls/dyntls.log | dynTLS log file |
| /etc/cron.daily/dyntls | Cron job script (optional) |
Adjust these paths in your `vars` file according to your distribution and service layout.
## CRON Job Example
To run the key generator daily, you have two options:
1. Crontab
You can add a daily cron job directly to the root user's crontab:
Open the root crontab for editing:
```bash
sudo crontab -e
```
Add the following line to run the script daily at 3:30 AM:
```bash
# Staging mode
30 3 * * * /opt/dyntls/dyntls.sh update-cert
# Productive mode
#30 3 * * * /opt/dyntls/dyntls.sh -P update-cert
```
*(Adjust the schedule as needed. This example runs the script daily.)*
2. System Cron Daily Directory
Create a script as `/etc/cron.daily/dyntls`:
```bash
#!/bin/sh
# Staging mode
/opt/dyntls/dyntls.sh update-cert
# Productive mode
#/opt/dyntls/dyntls.sh -P update-cert
exit 0
```
Ensure the script is executable:
```bash
sudo chmod 750 /etc/cron.daily/dyntls
```
## ACME Backend
dynTLS does not implement the ACME protocol itself. Instead, it delegates all ACME communication to an external client script configured via `DYNTLS_LE_PROGRAM`.
In this repository, ACME-related scripts are placed under:
- `contrib/acme/letsencrypt_master.sh`
Upstream ACME client script (reference), unchanged or only minimally modified.
- `contrib/acme/letsencrypt_master_local.sh`
Local variant with customized `dns-01` challenge handling, for example using Bind/nsupdate, TSIG and `rndc` to manage internal DNS zones.
Example configuration in `vars`:
```bash
# Use local ACME backend
setvar DYNTLS_LE_PROGRAM "contrib/acme/letsencrypt_master_local.sh"
# Pass DNS parameters to the ACME script (exported to its environment)
set_var DYNTLS_DNS_SERVER "root-dns.example365.tld"
set_var DYNTLS_DNS_TSIG "/opt/dyntls/private/tsig.key"
```
You can replace this with other ACME clients (e.g. acme.sh, lego) by pointing DYNTLS__LE_PROGRAM to your preferred script and ensuring its CLI options match your setup.
## License
[MIT](https://dev.town-square.de/cb601/dyntls/src/branch/main/LICENSE)
See `LICENSE.md` for details and third-party license information.
## Authors
CB-601 - the open tec Elevator
- [Stephan Düsterhaupt](xmpp:me@jabber.stephanduesterhaupt.de)
- [Ivo Noack](xmpp:me@jabber.ivonoack.de) aka Insonic
## Project Home
Project Home: <https://dev.town-square.de/cb601/dyntls>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1627
dyntls.sh Normal file

File diff suppressed because it is too large Load Diff

155
vars.example Normal file
View File

@@ -0,0 +1,155 @@
# dynTLS configuration example
# Copy this file to 'vars' and adjust as needed.
# Warning: do not edit vars.example directly!
# ------------------------------------------------------------------
# GENERAL
# ------------------------------------------------------------------
# Base directory of dynTLS configuration (defaults to script directory)
#set_var DYNTLS "${0%/*}"
# Main ACME client program used for certificate operations
# You can set this to any compatible wrapper script or binary
# https://github.com/bruncsak/ght-acme.sh
#DYNTLS_LE_PROGRAM="contrib/acme/letsencrypt.sh"
# OpenSSL binary (path if not in $PATH)
#set_var DYNTLS_OPENSSL "openssl"
# Productive mode:
# 0 = staging (test certs from Let's Encrypt staging server)
# 1 = production (real certs)
#set_var DYNTLS_PRODUCTIVE 0
# Use symlinks for server key: 1 = link all domains to base key
#set_var DYNTLS_PKI_KEY_LNS 0 # 0=per-domain key, 1=symlink all to base server key
# ------------------------------------------------------------------
# PKI DIRECTORIES
# ------------------------------------------------------------------
# Root PKI folder (contains httpd structure /certs /private etc.)
#set_var DYNTLS_PKI "/etc/pki"
# Temporary working dir
#set_var DYNTLS_TMP "$DYNTLS/tmp"
# HTTP service PKI directories
#set_var DYNTLS_PKI_HTTP_DIR "$DYNTLS_PKI/httpd"
#set_var DYNTLS_PKI_HTTP_CERT_DIR "$DYNTLS_PKI_HTTP_DIR/certs"
#set_var DYNTLS_PKI_HTTP_KEY_DIR "$DYNTLS_PKI_HTTP_DIR/private"
#set_var DYNTLS_PKI_HTTP_CERT_BACKUP_DIR "$DYNTLS_PKI_HTTP_CERT_DIR/backup"
# Cert/key naming suffixes
#set_var DYNTLS_PKI_CERT_SUFFIX "cert.pem"
#set_var DYNTLS_PKI_FULLCHAIN_SUFFIX "fullchain.pem"
#set_var DYNTLS_PKI_KEY_SUFFIX "key.pem"
# Base server key file and path
#set_var DYNTLS_PKI_SERVER_BASEKEY_FILE "base.$DYNTLS_PKI_KEY_SUFFIX"
#set_var DYNTLS_PKI_SERVER_BASEKEY "$DYNTLS_PKI_HTTP_KEY_DIR/$DYNTLS_PKI_SERVER_BASEKEY_FILE"
# Key algorithm and size
#set_var DYNTLS_PKI_KEY_ALGO rsa
#set_var DYNTLS_PKI_KEY_SIZE 2048
#set_var DYNTLS_PKI_KEY_CURVE secp384r1
# Certificate expiration threshold in days before renewal
#set_var DYNTLS_PKI_CERT_EXPIRE 30
# Force regenerating keys on renewal (0=no, 1=yes)
#set_var DYNTLS_PKI_KEY_FORCE_RENEW 0
# Days to keep backuped certificates before removal
# Set to 0 to disable automatic deletion of backups
#DYNTLS_BACKUP_EXPIRATION=720
# ------------------------------------------------------------------
# LET'S ENCRYPT / ACME
# ------------------------------------------------------------------
# Account key used to register with Let's Encrypt
#set_var DYNTLS_ENCRYPT_ACCOUNTKEY "$DYNTLS/private/letsencrypt_account.key"
# Token directory for http-01 challenges
#set_var DYNTLS_HTTPD_DEFAULT_DIR "/var/www/public_html/default"
#set_var DYNTLS_ENCRYPT_TOKEN_DIR "$DYNTLS_HTTPD_DEFAULT_DIR/.well-known/acme-challenge"
#set_var DYNTLS_HTTPD_DEFAULT_OWNER "apache."
# Chain CA files for fullchains
#set_var DYNTLS_PKI_LECA_CHAIN_FILE "LE_CA.chain.pem"
#set_var DYNTLS_PKI_LECA_CHAIN "$DYNTLS_PKI_HTTP_CERT_DIR/$DYNTLS_PKI_LECA_CHAIN_FILE"
#set_var DYNTLS_PKI_LECA_R12_CHAIN_FILE "LE_CA-R12.chain.pem"
#set_var DYNTLS_PKI_LECA_R12_CHAIN "$DYNTLS_PKI_HTTP_CERT_DIR/$DYNTLS_PKI_LECA_R12_CHAIN_FILE"
#set_var DYNTLS_PKI_LECA_R13_CHAIN_FILE "LE_CA-R13.chain.pem"
#set_var DYNTLS_PKI_LECA_R13_CHAIN "$DYNTLS_PKI_HTTP_CERT_DIR/$DYNTLS_PKI_LECA_R13_CHAIN_FILE"
# DNS validation (dns-01 challenge): server address and TSIG key file name
#set_var DYNTLS_DNS_SERVER "root-dns.example365.tld"
#set_var DYNTLS_DNS_TSIG "tsig.key"
#set_var DYNTLS_DNS_ZONE ""
# ------------------------------------------------------------------
# LOGGING
# ------------------------------------------------------------------
#set_var DYNTLS_LOG_DIR "/var/log/dyntls"
#set_var DYNTLS_LOG_FILE "$DYNTLS_LOG_DIR/dyntls.log"
# Log level controls verbosity of logging output:
# 0 = off : Disable all logging output.
# 1 = debug : Detailed diagnostic information for troubleshooting and development.
# Includes variable values, function calls, and detailed execution flow.
# 2 = info : Informational messages about normal operations and milestones.
# Useful for understanding general system behavior without noise.
# 3 = warn : Warnings about potential problems or unusual situations that are not critical.
# Indicates areas that may require attention to prevent errors.
# 4 = error : Errors indicating failures that impact functionality and require investigation.
# 5 = critical : Severe, critical failures that cause system malfunction and need immediate action.
#set_var DYNTLS_LOG_LEVEL "3"
# ------------------------------------------------------------------
# DOMAIN LISTS (CN + SANs)
# Each set_list line defines one certificate (CN + optional SANs separated by ':')
# ------------------------------------------------------------------
# Example multi-domain certificate (CN + SANs separated by :)
#set_list DYNTLS_DOMAIN_LIST "example365.tld:sub1.example365.tld:sub2.example365.tld"
# ------------------------------------------------------------------
# SERVICE LISTS (map CN to a service)
# Format: CN:pki_dir:user.group:chmod:service:owner:restartflag:restart|reload:displayname
#
# pki_dir behavior:
# - If pki_dir does NOT contain a '/' character, it is treated as a
# relative service name and will be created below DYNTLS_PKI.
# Example: pki_dir='postfix' with DYNTLS_PKI='/etc/pki'
# → effective PKI path: /etc/pki/postfix
#
# - If pki_dir contains at least one '/' character, it is treated as
# an absolute path and used as-is without prefixing DYNTLS_PKI.
# Example: pki_dir='/var/opt/container/mosquitto'
# → effective PKI path: /var/opt/container/mosquitto
# ------------------------------------------------------------------
# Example service mapping (format: CN:pki_dir:user.group:chmod:service:owner:restartflag:restart|reload:displayname)
#set_list DYNTLS_DOMAINSERVICE_LIST "mail02.example365.tld:postfix:root.root:444:postfix:root:1:restart:Postfix"
# ------------------------------------------------------------------
# OPTIONAL COMMAND HOOKS
# ------------------------------------------------------------------
# Commands to run before issuing/renewing a cert
#set_list DYNTLS_CMD_PRE_LIST ""
# Commands to run after successfully issuing/renewing a cert
#set_list DYNTLS_CMD_POST_LIST ""
# ------------------------------------------------------------------
# BACKUP AND EXPIRATION
# ------------------------------------------------------------------
# Days to keep backuped certificates before removal
# Set to 0 to disable automatic deletion of backups
#set_var DYNTLS_BACKUP_EXPIRATION 360