From 4456f8c3eaa0232ea6f82a555af741e958a03124 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 20 Sep 2026 22:05:20 +0200 Subject: [PATCH] Extern geliefertes Zertifikat verwenden, statt es zu kopieren Liefert eine fremde Stelle das Zertifikat in ein Verzeichnis und ersetzt es dort beim Erneuern - gleicher Dateiname, neuer Inhalt -, passt der uebliche Weg nicht: der Proxy Manager kopiert hochgeladene Zertifikate nach /data/custom_ssl/npm-/ und bekommt von einer ersetzten Datei nichts mit. Das Verzeichnis wird deshalb schreibgeschuetzt als /certs in den Container gehaengt (neuer Mount in der compose-Datei, Vorgabe ./data/npm/external-certs), und "cert external" ersetzt die beiden Dateien unter custom_ssl durch Verweise darauf. Am Abbild jc21/nginx-proxy-manager:2 nachgemessen: nginx liefert danach das externe Zertifikat, und ein Austausch der Datei wirkt - allerdings erst nach einem Neuladen. Genau das ist der Haken, und er ist belegt: ohne Neuladen liefert nginx weiterhin das alte Zertifikat. Dafuer gibt es cert external --refresh vergleicht den SHA256-Fingerabdruck mit dem zuletzt gesehenen und laedt nur bei Aenderung neu cert external --watch on traegt das als taeglichen cron-Eintrag ein Vor dem Umstellen wird geprueft, ob beide Dateien lesbar sind, ob Zertifikat und Schluessel zusammengehoeren (Vergleich der oeffentlichen Schluessel - das faengt auch EC-Schluessel) und wie lange es noch gilt. Beide Faelle sind durchgespielt: ein fremder Schluessel wird abgelehnt, ein in fuenf Tagen ablaufendes Zertifikat gemeldet. "cert external --off --id N" nimmt es zurueck und legt wieder eigene Kopien an. In der Oberflaeche ist aus dem Menuepunkt "Zertifikat" ein kleines Menue geworden. Co-Authored-By: Claude Opus 5 (1M context) --- .env.example | 20 ++ .gitignore | 3 + README.md | 72 +++++++ README_en.md | 71 +++++++ data/npm/external-certs/.gitkeep | 0 docker-compose.yml | 5 + scripts/cmk-manage.sh | 351 +++++++++++++++++++++++++++++++ 7 files changed, 522 insertions(+) create mode 100644 data/npm/external-certs/.gitkeep diff --git a/.env.example b/.env.example index 4fbb76b..2f493c9 100644 --- a/.env.example +++ b/.env.example @@ -220,3 +220,23 @@ FW_AGENT=off # Anfuehrungszeichen (das erledigt "cmk-manage.sh config set" selbst). # FW_EXTRA_PORTS=3000/tcp=Grafana,5667/tcp=NSCA,514/udp=Syslog FW_EXTRA_PORTS= + +# --------------------------------------------------------------------- +# Von aussen geliefertes Zertifikat +# +# Wird das Zertifikat von einer fremden Stelle in ein Verzeichnis +# gelegt und beim Erneuern dort ersetzt (gleicher Dateiname, neuer +# Inhalt), dann zeigt der Proxy Manager auf diese Dateien, statt sie zu +# kopieren. Das Verzeichnis wird schreibgeschuetzt als /certs in den +# Container gehaengt - nach einer Aenderung ist ein "up" noetig. +# +# cmk-manage.sh cert external --domain kunde.example.com +# --------------------------------------------------------------------- +CERT_EXTERNAL_DIR=./data/npm/external-certs +CERT_EXTERNAL_FULLCHAIN=fullchain.pem +CERT_EXTERNAL_KEY=privkey.pem + +# Taeglich nachsehen, ob die Datei ausgetauscht wurde, und nginx dann +# neu laden. Ohne das liefert nginx bis zum naechsten Neustart weiter +# das alte Zertifikat. +CERT_EXTERNAL_WATCH=off diff --git a/.gitignore b/.gitignore index 2cccd90..c15bdfa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ data/npm/data/* data/npm/letsencrypt/* !data/npm/letsencrypt/.gitkeep data/npm/custom-certs/* +!data/npm/external-certs/.gitkeep +data/npm/external-certs/* !data/npm/custom-certs/.gitkeep backups/* !backups/.gitkeep @@ -17,3 +19,4 @@ backup-jobs.conf scripts/.h.sh scripts/.harness.sh firewall-opened.conf +cert-external.state diff --git a/README.md b/README.md index 76a20ff..b0f5e7e 100644 --- a/README.md +++ b/README.md @@ -557,6 +557,8 @@ Die wichtigsten Schalter: | `BACKUP_DIR` | Ablage für **manuelle** Backups (Vorgabe `./backups`); geplante Jobs haben eigene Pfade | | `BACKUP_AUTO` | `on`/`off` – sollen die Backup-Jobs per cron laufen? | | `CERT_DOMAIN`, `CERT_DAYS` | Vorgaben für selbstsignierte Zertifikate | +| `CERT_EXTERNAL_DIR`, `CERT_EXTERNAL_FULLCHAIN`, `CERT_EXTERNAL_KEY` | Verzeichnis und Dateinamen eines von außen gelieferten Zertifikats | +| `CERT_EXTERNAL_WATCH` | täglich prüfen, ob es ausgetauscht wurde | | `FW_ROLE` | `single`/`central`/`remote` – Rolle im verteilten Monitoring, steuert die Firewall-Prüfung | | `FW_EC_SYSLOG`, `FW_EC_SNMPTRAP`, `FW_EC_TCP` | Event Console: Syslog (514), SNMP-Traps (162/udp), Status über TCP (6558) | | `FW_AGENT` | läuft auf diesem Host ein Checkmk-Agent im Pull-Betrieb? (6556) | @@ -1747,6 +1749,75 @@ Ziele prüfen**. > Proxy erreicht, genau wie Checkmk auf 5000. `firewall close` bietet einen > versehentlich geöffneten Port wieder an. +### 10.13 Ein von außen geliefertes Zertifikat verwenden + +Wenn eine fremde Stelle das Zertifikat in ein Verzeichnis legt und es beim +Erneuern dort **ersetzt** – gleicher Dateiname, neuer Inhalt –, passt der +übliche Weg nicht: Der Proxy Manager kopiert hochgeladene Zertifikate nach +`/data/custom_ssl/npm-/` und bekommt von einer ersetzten Datei nichts mit. + +Deshalb wird das Verzeichnis **schreibgeschützt als `/certs` in den Container +gehängt**, und die beiden Dateien unter `custom_ssl` werden durch Verweise +darauf ersetzt: + +```bash +./scripts/cmk-manage.sh config set CERT_EXTERNAL_DIR /etc/ssl/kunde +./scripts/cmk-manage.sh up # Container neu erstellen, damit er /certs sieht +./scripts/cmk-manage.sh cert external --domain kunde.example.com +``` + +| Einstellung | Bedeutung | +|---|---| +| `CERT_EXTERNAL_DIR` | Verzeichnis auf dem Host (Vorgabe `./data/npm/external-certs`) | +| `CERT_EXTERNAL_FULLCHAIN` | Dateiname der Zertifikatskette (Vorgabe `fullchain.pem`) | +| `CERT_EXTERNAL_KEY` | Dateiname des privaten Schlüssels (Vorgabe `privkey.pem`) | +| `CERT_EXTERNAL_WATCH` | täglich auf Austausch prüfen (`on`/`off`) | + +Vor dem Umstellen wird geprüft, ob beide Dateien lesbar sind, **ob Zertifikat +und Schlüssel zusammengehören** (Vergleich der öffentlichen Schlüssel) und wie +lange es noch gilt: + +``` +[FEHLER] Zertifikat und Schluessel gehoeren nicht zusammen. +[ WARN ] Das Zertifikat laeuft in weniger als 14 Tagen ab (Sep 25 20:03:58 2026 GMT). +``` + +**Der Austausch allein genügt nicht.** nginx liest Zertifikatsdateien beim +Start und beim Neuladen – nachgemessen an einem ausgetauschten Zertifikat: + +| | ausgeliefert | +|---|---| +| Datei ersetzt, kein Neuladen | **altes** Zertifikat | +| nach `nginx -s reload` | neues Zertifikat | + +Dafür gibt es zwei Wege: + +```bash +./scripts/cmk-manage.sh cert external --refresh # einmalig, lädt nur bei Änderung neu +sudo ./scripts/cmk-manage.sh cert external --watch on # täglich per cron +``` + +`--refresh` vergleicht den SHA256-Fingerabdruck mit dem zuletzt gesehenen +(`cert-external.state`) und lädt nginx nur dann neu. Der Wächter schreibt dafür +`/etc/cron.d/checkmk-cert-external`. + +Zurück auf eine eigene Kopie – danach lebt der Proxy Manager wieder mit seinen +eigenen Dateien weiter: + +```bash +./scripts/cmk-manage.sh cert external --off --id 3 +``` + +`cert external` ohne Angabe zeigt den Stand: Verzeichnis, Ablaufdatum, +Aussteller, ob `/certs` im Container ankommt, welche Zertifikate darauf zeigen +und ob der Wächter eingetragen ist. In der Oberfläche liegt alles unter +**Selbstsigniertes Zertifikat erzeugen → Extern geliefertes Zertifikat +verwenden**. + +> Wird das Zertifikat im Proxy Manager über die **Weboberfläche** neu +> hochgeladen, ersetzt er die Verweise wieder durch Kopien. Dann einmal +> `cert external --domain …` nachziehen. + --- ## 11. Proxy Manager lokal einrichten (Terminal-Browser) @@ -2492,6 +2563,7 @@ Eine Dateiauswahl mit Verzeichnisnavigation ist eingebaut. | `npm-reset` | Datenbank beiseitelegen, damit das Konto neu entsteht | | `proxy status\|list\|certs\|add\|del` | Proxy-Hosts über die API anlegen und verwalten | | `cert …` | selbstsigniertes Zertifikat erzeugen | +| `cert external …` | von außen geliefertes Zertifikat verwenden (verweisen statt kopieren) | | `firewall [check\|open\|close]` | prüfen, öffnen und nicht mehr Benötigtes wieder schließen (firewalld, ufw, nftables, iptables); `close --keep-admin` lässt den Adminport offen | | `shell` | Root-Shell im Checkmk-Container | | `omd …` | beliebiges `omd`-Kommando im Container | diff --git a/README_en.md b/README_en.md index 926b404..036bc49 100644 --- a/README_en.md +++ b/README_en.md @@ -560,6 +560,8 @@ The most important settings: | `BACKUP_DIR` | location for **manual** backups (default `./backups`); scheduled jobs have their own paths | | `BACKUP_AUTO` | `on`/`off` – should the backup jobs run via cron? | | `CERT_DOMAIN`, `CERT_DAYS` | defaults for self-signed certificates | +| `CERT_EXTERNAL_DIR`, `CERT_EXTERNAL_FULLCHAIN`, `CERT_EXTERNAL_KEY` | directory and file names of an externally delivered certificate | +| `CERT_EXTERNAL_WATCH` | check daily whether it was replaced | | `FW_ROLE` | `single`/`central`/`remote` – role in distributed monitoring, drives the firewall check | | `FW_EC_SYSLOG`, `FW_EC_SNMPTRAP`, `FW_EC_TCP` | Event Console: syslog (514), SNMP traps (162/udp), status over TCP (6558) | | `FW_AGENT` | does a Checkmk agent run on this host in pull mode? (6556) | @@ -1736,6 +1738,74 @@ That is how you find a typo in a target address before the browser shows > through the proxy, just like Checkmk on 5000. `firewall close` offers an > accidentally opened port for closing again. +### 10.13 Using a certificate delivered from outside + +When an external party drops the certificate into a directory and **replaces** +it on renewal – same file name, new content – the usual route does not fit: the +Proxy Manager copies uploaded certificates to `/data/custom_ssl/npm-/` and +never notices a replaced file. + +The directory is therefore **mounted read-only as `/certs`** into the +container, and the two files under `custom_ssl` are replaced by links to it: + +```bash +./scripts/cmk-manage.sh config set CERT_EXTERNAL_DIR /etc/ssl/customer +./scripts/cmk-manage.sh up # recreate the container so it sees /certs +./scripts/cmk-manage.sh cert external --domain customer.example.com +``` + +| Setting | Meaning | +|---|---| +| `CERT_EXTERNAL_DIR` | directory on the host (default `./data/npm/external-certs`) | +| `CERT_EXTERNAL_FULLCHAIN` | file name of the chain (default `fullchain.pem`) | +| `CERT_EXTERNAL_KEY` | file name of the private key (default `privkey.pem`) | +| `CERT_EXTERNAL_WATCH` | check daily for a replacement (`on`/`off`) | + +Before switching, both files are checked for readability, **whether certificate +and key belong together** (public keys compared) and how long it is still +valid: + +``` +[FEHLER] Zertifikat und Schluessel gehoeren nicht zusammen. +[ WARN ] Das Zertifikat laeuft in weniger als 14 Tagen ab (Sep 25 20:03:58 2026 GMT). +``` + +**Replacing the file alone is not enough.** nginx reads certificate files at +start and on reload – measured with a replaced certificate: + +| | served | +|---|---| +| file replaced, no reload | the **old** certificate | +| after `nginx -s reload` | the new certificate | + +Two ways to handle that: + +```bash +./scripts/cmk-manage.sh cert external --refresh # once, reloads only on change +sudo ./scripts/cmk-manage.sh cert external --watch on # daily via cron +``` + +`--refresh` compares the SHA256 fingerprint with the last one seen +(`cert-external.state`) and reloads nginx only if it differs. The watcher writes +`/etc/cron.d/checkmk-cert-external`. + +Back to an own copy – afterwards the Proxy Manager lives with its own files +again: + +```bash +./scripts/cmk-manage.sh cert external --off --id 3 +``` + +`cert external` without arguments shows the state: directory, expiry date, +issuer, whether `/certs` arrives in the container, which certificates point +there, and whether the watcher is installed. The GUI has all of it under +**Selbstsigniertes Zertifikat erzeugen → Extern geliefertes Zertifikat +verwenden**. + +> If the certificate is uploaded again through the Proxy Manager's **web +> interface**, it replaces the links with copies. Just run +> `cert external --domain …` once more. + --- ## 11. Configuring the Proxy Manager locally (terminal browser) @@ -2475,6 +2545,7 @@ A file chooser with directory navigation is built in. | `npm-reset` | move the database aside so the account is recreated | | `proxy status\|list\|certs\|add\|del` | create and manage proxy hosts through the API | | `cert …` | create a self-signed certificate | +| `cert external …` | use a certificate delivered from outside (link instead of copy) | | `firewall [check\|open\|close]` | check, open, and close again what is no longer needed (firewalld, ufw, nftables, iptables); `close --keep-admin` keeps the admin port open | | `shell` | root shell in the Checkmk container | | `omd …` | any `omd` command inside the container | diff --git a/data/npm/external-certs/.gitkeep b/data/npm/external-certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml index f125485..a627957 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,5 +71,10 @@ services: volumes: - ${NPM_DATA_DIR:-./data/npm/data}:/data:Z - ${NPM_LE_DIR:-./data/npm/letsencrypt}:/etc/letsencrypt:Z + # Verzeichnis fuer ein von aussen geliefertes Zertifikat. Wird es + # dort ersetzt, zeigen die Dateien unter /data/custom_ssl darauf - + # siehe "cmk-manage.sh cert external". Schreibgeschuetzt, denn der + # Container hat dort nichts zu suchen. + - ${CERT_EXTERNAL_DIR:-./data/npm/external-certs}:/certs:ro depends_on: - checkmk diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index c7de3dd..aa361bf 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -221,6 +221,9 @@ KOMMANDOS - Proxy-Hosts (ueber die API des Proxy Managers) KOMMANDOS - Zertifikat cert [Optionen] selbstsigniertes Zertifikat fuer den Proxy erzeugen + cert external [...] ein von aussen geliefertes Zertifikat verwenden + (Verzeichnis wird als /certs eingehaengt, die + Dateien werden dorthin verwiesen statt kopiert) --domain FQDN Common Name / SAN --alt NAME zusaetzlicher SAN (mehrfach; DNS-Name oder IP) --days N Gueltigkeit in Tagen (Vorgabe 3650) @@ -2027,6 +2030,7 @@ EOF FW_STATE_FILE_NAME="firewall-opened.conf" JOBS_FILE_NAME="backup-jobs.conf" CRON_FILE="/etc/cron.d/checkmk-backup" +CERT_CRON_FILE="/etc/cron.d/checkmk-cert-external" jobs_file() { printf '%s/%s\n' "$PROJECT_DIR" "$JOBS_FILE_NAME"; } @@ -3090,6 +3094,10 @@ cert|CERT_DOMAIN|text|FQDN fuer selbstsignierte Zertifikate|| cert|CERT_DAYS|int|Gueltigkeit in Tagen|| cert|CERT_COUNTRY|text|Laenderkuerzel|| cert|CERT_ORG|text|Organisation|| +cert|CERT_EXTERNAL_DIR|path|Verzeichnis mit geliefertem Zertifikat||1 +cert|CERT_EXTERNAL_FULLCHAIN|text|Dateiname Zertifikatskette||1 +cert|CERT_EXTERNAL_KEY|text|Dateiname privater Schluessel||1 +cert|CERT_EXTERNAL_WATCH|choice|Taeglich auf Austausch pruefen|off:on|1 firewall|FW_ROLE|choice|Rolle im verteilten Monitoring (leer = single)|single:central:remote|1 firewall|FW_EC_SYSLOG|choice|Event Console: Syslog 514 (leer = aus)|off:udp:tcp:beide|1 firewall|FW_EC_SNMPTRAP|choice|Event Console: SNMP-Traps 162/udp (leer = aus)|off:on|1 @@ -3197,6 +3205,10 @@ settings_post_set() { cmd_npm_enable "$val" ;; BACKUP_AUTO) cmd_job auto "$val" ;; + CERT_EXTERNAL_WATCH) + cmd_cert_external --watch "$val" ;; + CERT_EXTERNAL_DIR) + log "Wirksam nach: $SELF up (das Verzeichnis wird erst dann eingehaengt)" ;; CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|NPM_IMAGE|NPM_CONTAINER_NAME|TZ|CMK_LIVESTATUS_TCP|NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR) log "Wirksam nach: $SELF up (Container werden neu erstellt)" ;; esac @@ -4828,10 +4840,278 @@ EOF esac } +# ===================================================================== +# Vorhandenes Zertifikat von aussen verwenden +# +# Bei manchen Kunden liefert eine fremde Stelle das Zertifikat in ein +# Verzeichnis und ersetzt es dort beim Erneuern - gleicher Dateiname, +# neuer Inhalt. Der Proxy Manager kopiert hochgeladene Zertifikate +# aber nach /data/custom_ssl/npm-/ und bekommt von einer ersetzten +# Datei nichts mit. +# +# Deshalb: das Verzeichnis wird schreibgeschuetzt in den Container +# gehaengt (/certs), und die beiden Dateien unter custom_ssl werden +# durch Verweise darauf ersetzt. nginx liest die Datei bei jedem +# Neuladen frisch ein - nachgemessen: ohne Neuladen liefert es +# weiterhin das alte Zertifikat, danach das neue. Genau dafuer gibt es +# "cert external --refresh" und den Waechter per cron. +# ===================================================================== + +CERT_STATE_FILE_NAME="cert-external.state" + +cert_ext_dir() { printf '%s' "${CERT_EXTERNAL_DIR:-$PROJECT_DIR/data/npm/external-certs}"; } +cert_ext_full() { printf '%s' "${CERT_EXTERNAL_FULLCHAIN:-fullchain.pem}"; } +cert_ext_key() { printf '%s' "${CERT_EXTERNAL_KEY:-privkey.pem}"; } +cert_state_file() { printf '%s/%s\n' "$PROJECT_DIR" "$CERT_STATE_FILE_NAME"; } + +# Fingerabdruck des Zertifikats - daran haengt, ob neu geladen werden muss +cert_ext_fingerprint() { + local d; d="$(cert_ext_dir)" + case "$d" in /*) : ;; *) d="$PROJECT_DIR/${d#./}" ;; esac + openssl x509 -in "$d/$(cert_ext_full)" -noout -fingerprint -sha256 2>/dev/null \ + | sed 's/.*=//' | tr -d ':' +} + +# Passen Zertifikat und Schluessel zusammen, und wie lange gilt es noch? +cert_ext_verify() { + local d; d="$(cert_ext_dir)" + case "$d" in /*) : ;; *) d="$PROJECT_DIR/${d#./}" ;; esac + local c="$d/$(cert_ext_full)" k="$d/$(cert_ext_key)" + + [ -r "$c" ] || { err "Nicht lesbar: $c"; return 1; } + [ -r "$k" ] || { err "Nicht lesbar: $k"; return 1; } + openssl x509 -in "$c" -noout >/dev/null 2>&1 \ + || { err "$c ist kein lesbares Zertifikat (PEM erwartet)."; return 1; } + + local pc pk + pc="$(openssl x509 -in "$c" -noout -pubkey 2>/dev/null)" + pk="$(openssl pkey -in "$k" -pubout 2>/dev/null)" + if [ -z "$pk" ]; then + err "$k ist kein lesbarer privater Schluessel."; return 1 + fi + if [ "$pc" != "$pk" ]; then + err "Zertifikat und Schluessel gehoeren nicht zusammen." + printf ' %s\n %s\n' "$c" "$k" >&2 + return 1 + fi + + if ! openssl x509 -in "$c" -noout -checkend 0 >/dev/null 2>&1; then + warn "Das Zertifikat ist bereits abgelaufen ($(openssl x509 -in "$c" -noout -enddate | sed 's/notAfter=//'))." + elif ! openssl x509 -in "$c" -noout -checkend 1209600 >/dev/null 2>&1; then + warn "Das Zertifikat laeuft in weniger als 14 Tagen ab ($(openssl x509 -in "$c" -noout -enddate | sed 's/notAfter=//'))." + fi + return 0 +} + +# Liegt das Verzeichnis im Container? Ohne Mount geht gar nichts. +cert_ext_mounted() { + $RUNTIME exec "$NPM_CONTAINER_NAME" test -r "/certs/$(cert_ext_full)" 2>/dev/null +} + +# Zeigen die Dateien des Zertifikats schon nach draussen? +cert_ext_linked() { # id + [ "$($RUNTIME exec "$NPM_CONTAINER_NAME" readlink "/data/custom_ssl/npm-$1/fullchain.pem" 2>/dev/null)" \ + = "/certs/$(cert_ext_full)" ] +} + # ===================================================================== # Selbstsigniertes Zertifikat fuer den NGINX Proxy Manager # ===================================================================== +cmd_cert_external() { + local sub="" cid="" dom="" watch="" + while [ $# -gt 0 ]; do + case "$1" in + --id) cid="$2"; shift 2 ;; + --domain) dom="$2"; shift 2 ;; + --off) sub=off; shift ;; + --refresh) sub=refresh; shift ;; + --watch) sub=watch; watch="$2"; shift 2 ;; + --help|-h) +cat <>/var/log/cmk-cert.log 2>&1\n' "$SELF" + } >"$tmp" + install -m 0644 "$tmp" "$CERT_CRON_FILE"; rm -f "$tmp" + env_set CERT_EXTERNAL_WATCH on + ok "Waechter eingetragen: $CERT_CRON_FILE (taeglich 4:17 Uhr)." + log "Er laedt nginx nur dann neu, wenn sich die Datei geaendert hat." + return 0 + fi + + # --- Nur nachsehen, ob sich etwas geaendert hat ------------------- + if [ "$sub" = refresh ]; then + local now old sf; sf="$(cert_state_file)" + now="$(cert_ext_fingerprint)" + if [ -z "$now" ]; then + err "Zertifikat nicht lesbar: $d/$(cert_ext_full)"; return 1 + fi + old="$(cat "$sf" 2>/dev/null || true)" + if [ "$now" = "$old" ]; then + log "Zertifikat unveraendert - nichts zu tun." + return 0 + fi + log "Das Zertifikat hat sich geaendert." + cert_ext_verify || return 1 + if cmd_proxy reload; then + printf '%s\n' "$now" > "$sf" + ok "Neues Zertifikat ist in Betrieb." + else + err "Neuladen fehlgeschlagen - der Stand bleibt vermerkt als offen." + return 1 + fi + return 0 + fi + + # --- Stand anzeigen ---------------------------------------------- + if [ -z "$sub" ] && [ -z "$cid" ] && [ -z "$dom" ]; then + printf '\n%sExtern geliefertes Zertifikat%s\n' "$C_BLD" "$C_RST" + printf ' Verzeichnis : %s\n' "$d" + printf ' Dateien : %s , %s\n' "$(cert_ext_full)" "$(cert_ext_key)" + if [ -r "$d/$(cert_ext_full)" ]; then + printf ' Gueltig bis : %s\n' "$(openssl x509 -in "$d/$(cert_ext_full)" -noout -enddate 2>/dev/null | sed 's/notAfter=//')" + printf ' Aussteller : %s\n' "$(openssl x509 -in "$d/$(cert_ext_full)" -noout -subject 2>/dev/null | sed 's/subject=//')" + else + test_result warn "Dort liegt (noch) kein lesbares Zertifikat." + fi + if npm_enabled && container_running "$NPM_CONTAINER_NAME"; then + if cert_ext_mounted; then + test_result ok "Das Verzeichnis ist im Container als /certs eingehaengt." + else + test_result warn "Im Container fehlt /certs" + printf ' Nach dem Setzen von CERT_EXTERNAL_DIR muss der Container + neu erstellt werden: %s up\n' "$SELF" + fi + local i used="" + for i in $(json_ids "$(NPM_TOKEN="$(npm_api_token 2>/dev/null)" npm_api GET /api/nginx/certificates 2>/dev/null)" 2>/dev/null); do + if cert_ext_linked "$i"; then used="$used $i"; fi + done + if [ -n "$used" ]; then + test_result ok "Verwendet von Zertifikat(en):$used" + else + test_result warn "Noch kein Zertifikat zeigt dorthin." + printf ' Umstellen mit: %s cert external --domain \n' "$SELF" + fi + fi + printf ' Waechter : %s\n' \ + "$([ -f "$CERT_CRON_FILE" ] && echo "eingetragen ($CERT_CRON_FILE)" || echo 'nicht eingetragen')" + echo + return 0 + fi + + # --- Umstellen ---------------------------------------------------- + npm_require_running || return 1 + NPM_TOKEN="$(npm_api_token)" || return 1 + + if [ -z "$cid" ]; then + [ -n "$dom" ] || die "--domain oder --id angeben." + local all i o + all="$(npm_api GET /api/nginx/proxy-hosts)" + for i in $(json_ids "$all"); do + o="$(npm_api GET "/api/nginx/proxy-hosts/$i")" + if ( set +o pipefail; printf '%s' "$o" | grep -qF "\"$dom\"" ); then + cid="$(json_num "$o" certificate_id)"; break + fi + done + [ -n "$cid" ] || die "Kein Proxy-Host fuer '$dom' gefunden." + if [ "$cid" = 0 ]; then + die "Der Proxy-Host '$dom' hat noch kein Zertifikat. + Erst eines zuweisen, dann umstellen: + $SELF proxy edit --domain $dom --cert self + $SELF cert external --domain $dom" + fi + fi + + if [ "$sub" = off ]; then + cert_ext_linked "$cid" || die "Zertifikat $cid zeigt nicht nach draussen - nichts zurueckzunehmen." + log "Ersetze die Verweise durch eigene Kopien ..." + $RUNTIME exec "$NPM_CONTAINER_NAME" sh -c " + cd /data/custom_ssl/npm-$cid || exit 1 + cp -L fullchain.pem f.tmp && cp -L privkey.pem k.tmp || exit 1 + rm -f fullchain.pem privkey.pem + mv f.tmp fullchain.pem && mv k.tmp privkey.pem" \ + || die "Zuruecknehmen fehlgeschlagen." + ok "Zertifikat $cid hat jetzt wieder eigene Dateien." + cmd_proxy reload || true + return 0 + fi + + cert_ext_verify || return 1 + cert_ext_mounted || die "Im Container gibt es /certs/$(cert_ext_full) nicht. + Das Verzeichnis wird erst beim Neuerstellen des Containers eingehaengt: + $SELF config set CERT_EXTERNAL_DIR '$d' + $SELF up" + + log "Setze die Dateien von Zertifikat $cid auf /certs ..." + $RUNTIME exec "$NPM_CONTAINER_NAME" sh -c " + d=/data/custom_ssl/npm-$cid + mkdir -p \"\$d\" && cd \"\$d\" || exit 1 + rm -f fullchain.pem privkey.pem + ln -s '/certs/$(cert_ext_full)' fullchain.pem + ln -s '/certs/$(cert_ext_key)' privkey.pem" \ + || die "Die Verweise liessen sich nicht anlegen." + + cmd_proxy reload || die "nginx hat die Aenderung nicht angenommen." + cert_ext_fingerprint > "$(cert_state_file)" 2>/dev/null || true + ok "Zertifikat $cid kommt jetzt aus $d." + printf ' Wird die Datei dort ersetzt, uebernimmt nginx sie erst beim + naechsten Neuladen. Dafuer gibt es: + %s cert external --refresh einmalig + sudo %s cert external --watch on taeglich per cron\n\n' "$SELF" "$SELF" + return 0 +} + cmd_cert() { + if [ "${1:-}" = external ]; then shift; cmd_cert_external "$@"; return $?; fi local domain="${CERT_DOMAIN:-checkmk.example.local}" days="${CERT_DAYS:-3650}" local -a alts=() while [ $# -gt 0 ]; do @@ -6477,6 +6757,77 @@ gui_proxy_del() { } gui_cert() { + local sel + sel="$(ui_menu "Zertifikate" \ +"Fuer den Proxy Manager. Ein selbstsigniertes Zertifikat erzeugt dieses +Werkzeug selbst; ein von aussen geliefertes wird nicht kopiert, sondern +aus seinem Verzeichnis heraus verwendet - dann wirkt ein Austausch +dort, ohne dass hier etwas nachgezogen werden muss." \ + selbst "Selbstsigniertes Zertifikat erzeugen" \ + extern "Extern geliefertes Zertifikat verwenden" \ + stand "Stand des externen Zertifikats anzeigen" \ + back "zurueck")" || return 0 + case "$sel" in + back) return 0 ;; + stand) local fs; fs="$(mktemp)" + ( NO_COLOR=1 cmd_cert external ) >"$fs" 2>&1 || true + ui_text "Extern geliefertes Zertifikat" "$fs"; rm -f "$fs"; return 0 ;; + extern) gui_cert_external; return 0 ;; + esac + gui_cert_self +} + +gui_cert_external() { + local fs; fs="$(mktemp)" + ( NO_COLOR=1 cmd_cert external ) >"$fs" 2>&1 || true + ui_text "Extern geliefertes Zertifikat" "$fs"; rm -f "$fs" + + local sel + sel="$(ui_menu "Extern geliefertes Zertifikat" \ +"Verzeichnis: $(cert_ext_dir) +Dateien : $(cert_ext_full) , $(cert_ext_key) + +Geaendert wird das Verzeichnis unter Einstellungen > Selbstsignierte +Zertifikate. Nach einer Aenderung muss der Container neu erstellt +werden (Stack starten), damit er es sieht." \ + umstellen "Zertifikat eines Proxy-Hosts darauf umstellen" \ + refresh "Jetzt nachsehen, ob die Datei ausgetauscht wurde" \ + watchon "Taeglich nachsehen (cron eintragen)" \ + watchoff "Taegliches Nachsehen abschalten" \ + zurueck "Zurueck auf eine eigene Kopie" \ + back "zurueck")" || return 0 + + local -a args=() + case "$sel" in + back) return 0 ;; + umstellen) local d + d="$(ui_input "Umstellen" "Name des Proxy-Hosts:" "${PROXY_DOMAIN:-}")" || return 0 + [ -n "$d" ] || return 0 + args=(--domain "$d") ;; + refresh) args=(--refresh) ;; + watchon) args=(--watch on) ;; + watchoff) args=(--watch off) ;; + zurueck) local i + i="$(ui_input "Zurueck" "Zertifikats-ID:" "")" || return 0 + [ -n "$i" ] || return 0 + args=(--off --id "$i") ;; + esac + + local f2 rc=0 + f2="$(mktemp)" + if [ "$sel" = watchon ] || [ "$sel" = watchoff ]; then + if [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1; then + gui_run_interactive sudo -- "$SELF" ${SELF_ENV_ARR[@]+"${SELF_ENV_ARR[@]}"} cert external "${args[@]}" + rm -f "$f2"; return 0 + fi + fi + ( NO_COLOR=1 cmd_cert external "${args[@]}" ) >"$f2" 2>&1 || rc=$? + if [ "$rc" -eq 0 ]; then ui_text "Zertifikat" "$f2"; else ui_text "Fehlgeschlagen" "$f2"; fi + rm -f "$f2" + return 0 +} + +gui_cert_self() { local domain alt days domain="$(ui_input "Zertifikat" "FQDN / Common Name:" "${CERT_DOMAIN:-checkmk.example.local}")" || return 0 [ -n "$domain" ] || return 0