Letsencrypt Skript ohne Reverse-Proxy

nick81

Premier Customer
Advanced Certified
Mitglied seit
3. Mai 2024
Beiträge
29
Hi zusammen,

falls jemand, wie wir, keinen Reverse-Proxy (http, tcp, udp) vor die 3CX schalten will, für den wollte ich kurz meine Lösung zeigen um trotzdem an LetsEncrypt Zertifikate zu kommen und diese automtisch auf dem 3CX server zu erneuern. Mir war es wichtig, möglichst wenig auf dem 3CX Server anzupassen, aber ganz ohne Anpassung klappt es leider nicht.
Der Text dient auch mir zur Dokumentation.

1. .well-known weiterleiten, damit LetsEncrypt die Validierung über einen externen Server durchführen kann
Der Text zwischen den # muss VOR der Prüfung "http" eingefügt werden.
nano /var/lib/3cxpbx/Bin/nginx/conf/nginx.conf
Code:
        access_log off;
        error_log /dev/null crit;

        # --- Letsencrypt Proxy bypass http-block
        if ($request_uri ~ "^/\.well-known/acme-challenge/") {
            set $is_remote "";
        }
        # --- END: Letsencrypt Proxy bypass http-block

        if ($scheme = 'http'){
            set $block_remote "1";
        }

nano /var/lib/3cxpbx/Bin/nginx/conf/60-letsencrypt-proxy.conf
Code:
location ^~ /.well-known/acme-challenge/ {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://acmeserver.domain.com/.well-known/acme-challenge/;
}

2. Externen ACME Server vorbereiten. Ich nutze Docker mit dem nging-proxy und acme-companion. Da könnt ihr auch etwas anders nutzen.
Code:
services:
  proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    networks:
      - proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf:/etc/nginx/conf.d
      - ./vhost:/etc/nginx/vhost.d
      - ./certs:/etc/nginx/certs:ro
      - ./html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: unless-stopped
  acme-companion:
    image: nginxproxy/acme-companion
    container_name: proxy-acme
    networks:
      - proxy
    environment:
      - [email protected]
    volumes_from:
      - proxy
    volumes:
      - ./certs:/etc/nginx/certs:rw
      - ./acme:/etc/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped
networks:
  proxy:
    external: true
Ich habe die Container getrennt, man könnte die container auch in eine compose schreiben
Code:
services:
  leproxy:
    image: nginx
    ports:
      - 80
    networks:
      - proxy
    environment:
      - VIRTUAL_HOST=acmeserver.domain.com,3cxfqdn.domain.com
      - HTTPS_METHOD=noredirect
      - LETSENCRYPT_HOST=3cxfqdn.domain.com
    restart: unless-stopped

networks:
  proxy:
    external: true

Nun sollten die Zertifikate erstellt werden. Der nächste Schritt ist, ein Shell-Script zu schreiben, welches prüft ob neue Zertifikate vorhanden sind und diese auf den 3CX Server zu kopieren und nginx neu zu laden.


3. Upload und Deployment der Zertifikate.
Pfade und Domain evtl anpassen
Bash:
#!/bin/bash
set -euo pipefail

DOMAIN="3cxfqdn.domain.com"

SRC_DIR="/opt/docker/proxy/certs/${DOMAIN}"
CACHE_DIR="/opt/docker/3cxcert/${DOMAIN}"

WORK_DIR="${CACHE_DIR}/work"
STATE_DIR="${CACHE_DIR}/state"
CURRENT_DIR="${CACHE_DIR}/current"

DST_HOST="[B]3cxfqdn.domain.com[/B]"
DST_DIR="/root/cert-import/${DOMAIN}"
SSH_DEPLOY_KEY="/root/.ssh/3cx_cert_deploy"
SSH_UPLOAD_KEY="/root/.ssh/3cx_cert_upload"

FULLCHAIN_SRC="${SRC_DIR}/fullchain.pem"
PRIVKEY_SRC="${SRC_DIR}/key.pem"

mkdir -p "$WORK_DIR" "$STATE_DIR" "$CURRENT_DIR"

# --- 1. Quelle prüfen ---
if [[ ! -f "$FULLCHAIN_SRC" || ! -f "$PRIVKEY_SRC" ]]; then
    echo "[ERROR] Zertifikatsdateien nicht gefunden"
    exit 1
fi

# --- 2. in work kopieren ---
cp "$FULLCHAIN_SRC" "$WORK_DIR/fullchain.pem"
cp "$PRIVKEY_SRC" "$WORK_DIR/privkey.pem"

# --- 3. Hash berechnen ---
sha256sum "$WORK_DIR/fullchain.pem" "$WORK_DIR/privkey.pem" > "$WORK_DIR/new.sha256"

# --- 4. Vergleich ---
if [[ -f "$STATE_DIR/deployed.sha256" ]]; then
    if cmp -s "$WORK_DIR/new.sha256" "$STATE_DIR/deployed.sha256"; then
        echo "[INFO] Zertifikat unverändert – nichts zu tun"
        exit 0
    fi
fi

echo "[INFO] Neues Zertifikat erkannt – starte Deploy"

# --- 5. Upload ---
scp -4 -i "$SSH_UPLOAD_KEY" \
    "$WORK_DIR/fullchain.pem" \
    "$WORK_DIR/privkey.pem" \
    root@"$DST_HOST":"$DST_DIR"/

# --- 6. Remote Deploy triggern ---
ssh -i "$SSH_DEPLOY_KEY" root@"$DST_HOST" -4

echo "[INFO] Deploy erfolgreich – aktualisiere lokalen Cache"

# --- 7. Cache aktualisieren ---
cp "$WORK_DIR/fullchain.pem" "$CURRENT_DIR/fullchain.pem"
cp "$WORK_DIR/privkey.pem" "$CURRENT_DIR/privkey.pem"
cp "$WORK_DIR/new.sha256" "$STATE_DIR/deployed.sha256"

echo "[OK] Zertifikat erfolgreich synchronisiert"
Damit der Upload funktioniert wird vorher noch ein SSH Schlüsselpaar erstellt werden und auf dem 3CX Server eingetragen werden.
Code:
ssh-keygen -t ed25519 -f /root/.ssh/3cx_cert_upload -N ""
ssh-keygen -t ed25519 -f /root/.ssh/3cx_cert_deploy -N ""
cat /root/.ssh/3cx_cert_upload.pub
cat /root/.ssh/3cx_cert_deploy.pub


Der erste Eintrag ist für den deploy Schlüssel, der zweite ist für den Upload Schlüssel
nano /root/.ssh/authorized_keys
Code:
from="IP-ACME-Server",command="/usr/local/sbin/deploy-3cx-cert.sh 3cxfqdn.domain.com",no-port-forwarding,no-X11-forwardinwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA....
from="IP-ACME-Server",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA....
nano /usr/local/sbin/deploy-3cx-cert.sh
Bash:
#!/bin/bash
set -euo pipefail

DOMAIN="$1"

IMPORT_DIR="/root/cert-import/${DOMAIN}"
FULLCHAIN="${IMPORT_DIR}/fullchain.pem"
PRIVKEY="${IMPORT_DIR}/privkey.pem"

TARGET_DIR="/var/lib/3cxpbx/Bin/nginx/conf/Instance1"
TARGET_CERT="${TARGET_DIR}/domain_cert_${DOMAIN}.pem"
TARGET_KEY="${TARGET_DIR}/domain_key_${DOMAIN}.pem"

BACKUP_DIR="${TARGET_DIR}/backup-$(date +%F-%H%M%S)"

log() {
    echo "[deploy-3cx-cert] $1"
}

if [[ ! -f "$FULLCHAIN" ]]; then
    log "FEHLER: fullchain.pem nicht gefunden: $FULLCHAIN"
    exit 1
fi

if [[ ! -f "$PRIVKEY" ]]; then
    log "FEHLER: privkey.pem nicht gefunden: $PRIVKEY"
    exit 1
fi

CERT_MOD="$(openssl x509 -noout -modulus -in "$FULLCHAIN" | openssl md5)"
KEY_MOD="$(openssl rsa  -noout -modulus -in "$PRIVKEY"   | openssl md5)"

if [[ "$CERT_MOD" != "$KEY_MOD" ]]; then
    log "FEHLER: Zertifikat und Key passen nicht zusammen"
    exit 1
fi

mkdir -p "$BACKUP_DIR"

if [[ -f "$TARGET_CERT" ]]; then
    cp -a "$TARGET_CERT" "$BACKUP_DIR/"
fi

if [[ -f "$TARGET_KEY" ]]; then
    cp -a "$TARGET_KEY" "$BACKUP_DIR/"
fi

cp "$FULLCHAIN" "${TARGET_CERT}.new"
cp "$PRIVKEY" "${TARGET_KEY}.new"

chmod 600 "${TARGET_CERT}.new" "${TARGET_KEY}.new"
chown root:root "${TARGET_CERT}.new" "${TARGET_KEY}.new"

mv "${TARGET_CERT}.new" "$TARGET_CERT"
mv "${TARGET_KEY}.new" "$TARGET_KEY"

if nginx -t; then
    nginx -s reload
    log "ERFOLG: Zertifikat für ${DOMAIN} aktualisiert und nginx neu geladen"
else
    log "FEHLER: nginx -t fehlgeschlagen, versuche Rollback"

    if [[ -f "${BACKUP_DIR}/$(basename "$TARGET_CERT")" ]]; then
        cp -a "${BACKUP_DIR}/$(basename "$TARGET_CERT")" "$TARGET_CERT"
    fi

    if [[ -f "${BACKUP_DIR}/$(basename "$TARGET_KEY")" ]]; then
        cp -a "${BACKUP_DIR}/$(basename "$TARGET_KEY")" "$TARGET_KEY"
    fi

    nginx -t && nginx -s reload || true
    exit 1
fi
chmod 700 /usr/local/sbin/deploy-3cx-cert.sh

Nun kann auf dem ACME Server das Skript getestet werden.

4. Cronjob erstellen.
 
Zuletzt bearbeitet:
Verstehe ich nicht... ohne Reverseproxy macht das die 3c doch schon alleine!?
 
  • Like
Reaktionen: mbehrens
Seit wann das denn? Geht um einen custom fqdn.
Dafür gab es doch von fxbastler ein shellscript im Zusammenhang mit pfsense und haproxy.

 
Das musst du dann auch dazu sagen... :-D
 
Ja hast Recht, die Info wäre sinnvoll gewesen
 
  • Like
Reaktionen: bitn2

Statistik des Forums

Themen
44.751
Beiträge
233.643
Mitglieder
78.493
Neuestes Mitglied
arming