| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /proc/self/root/tmp/ |
Upload File : |
#!/usr/bin/env bash
#
# ArNi CRM – OpenVPN Root-Wrapper
# ===============================
# EINZIGE Schnittstelle, über die das CRM (Webserver-Benutzer) mit OpenVPN/PKI
# interagiert. Aufruf ausschließlich per `sudo` als root. Der Webserver-Benutzer
# erhält dadurch NIEMALS direkten Schreibzugriff auf /etc/openvpn oder die PKI.
#
# Installation (als root):
# cp bin/openvpn-wrapper.sh /usr/local/sbin/arni-openvpn-wrapper.sh
# chown root:root /usr/local/sbin/arni-openvpn-wrapper.sh
# chmod 750 /usr/local/sbin/arni-openvpn-wrapper.sh
# cp bin/openvpn-sudoers /etc/sudoers.d/arni-openvpn (Benutzer/Pfad anpassen!)
# chmod 440 /etc/sudoers.d/arni-openvpn
# visudo -c
# Pfade unten ggf. an die lokale OpenVPN-Installation anpassen (oder via
# /etc/default/arni-openvpn überschreiben).
#
# Sicherheits-Prinzipien:
# * feste Subkommando-Allowlist (case-Block) – keine freie Befehlsausführung
# * jeder Parameter wird streng validiert (Benutzername/Zahl)
# * keine Shell-Interpolation von Eingaben (kein eval, kein `sh -c "$input"`)
# * PKI-Schreibzugriff nur über easy-rsa im definierten Verzeichnis
# * Ausgabe von Schlüsseln nur für existierende, im CRM verwaltete Clients
#
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
umask 027
# ---- Konfiguration (überschreibbar via /etc/default/arni-openvpn) ----
SYSTEMCTL=/usr/bin/systemctl
OPENVPN_BIN=/usr/sbin/openvpn
SERVICE_UNIT="openvpn-server@server" # systemd-Unit der Server-Instanz
SERVER_CONF=/etc/openvpn/server/server.conf # Server-Konfiguration
STATUS_FILE=/run/openvpn-server/status-server.log
MAIN_LOG=/var/log/openvpn/openvpn.log
EASYRSA_DIR=/etc/openvpn/server/easy-rsa # easy-rsa-Arbeitsverzeichnis
PKI_DIR="$EASYRSA_DIR/pki"
CCD_DIR=/etc/openvpn/server/ccd # client-config-dir (für Sperren)
TA_KEY=/etc/openvpn/server/ta.key # tls-auth/crypt Schlüssel (optional)
MGMT_SOCK=/run/openvpn-server/mgmt-server.sock # Management-Socket (für kill, optional)
# Werte für die generierte .ovpn (remote host/port/proto):
OVPN_REMOTE_HOST="vpn.example.com"
OVPN_REMOTE_PORT=1194
OVPN_PROTO=udp
[ -r /etc/default/arni-openvpn ] && . /etc/default/arni-openvpn
[ -x "$SYSTEMCTL" ] || SYSTEMCTL="$(command -v systemctl || echo /usr/bin/systemctl)"
[ -x "$OPENVPN_BIN" ] || OPENVPN_BIN="$(command -v openvpn || echo /usr/sbin/openvpn)"
die() { echo "ERROR: $*" >&2; exit 2; }
# ---- Validierungen ----
valid_user() { [[ "${1:-}" =~ ^[A-Za-z0-9._-]{1,64}$ ]] || die "ungültiger Benutzername"; }
valid_int() { [[ "${1:-}" =~ ^[0-9]{1,6}$ ]] || die "ungültige Zahl"; }
easyrsa() {
[ -d "$EASYRSA_DIR" ] || die "easy-rsa-Verzeichnis nicht gefunden ($EASYRSA_DIR)"
# easy-rsa legt Temp-Dateien standardmäßig im PKI-Verzeichnis ab; das schlägt im
# eingeschränkten Web-/sudo-Environment fehl ("Failed to create temporary file").
# Deshalb ein frisch erzeugtes, garantiert beschreibbares /tmp-Verzeichnis erzwingen.
local td rc=0
td="$(mktemp -d /tmp/arni-easyrsa.XXXXXX)" || die "Temp-Verzeichnis nicht erstellbar"
( cd "$EASYRSA_DIR" && EASYRSA_BATCH=1 EASYRSA_TEMP_DIR="$td" HOME="$td" TMPDIR="$td" ./easyrsa "$@" ) || rc=$?
rm -rf "$td"
return $rc
}
# easyrsa ausführen; bei Fehler mit der echten (letzten) easy-rsa-Meldung abbrechen.
easyrsa_or_die() {
local label="$1"; shift
local out tail_line
if ! out="$(easyrsa "$@" 2>&1)"; then
tail_line="$(printf '%s\n' "$out" | grep -v '^[[:space:]]*$' | tail -n1)"
die "$label fehlgeschlagen: ${tail_line:-unbekannter easy-rsa-Fehler}"
fi
}
cmd="${1:-}"; shift || true
case "$cmd" in
ping)
echo "ok"
;;
version)
"$OPENVPN_BIN" --version 2>/dev/null | head -n1 || true
;;
service-status)
"$SYSTEMCTL" is-active "$SERVICE_UNIT" 2>/dev/null || true
;;
service-start) "$SYSTEMCTL" start "$SERVICE_UNIT" ;;
service-stop) "$SYSTEMCTL" stop "$SERVICE_UNIT" ;;
service-restart) "$SYSTEMCTL" restart "$SERVICE_UNIT" ;;
server-info)
# Maschinell lesbare Eckdaten der Serverkonfiguration.
if [ -r "$SERVER_CONF" ]; then
echo "conf=$SERVER_CONF"
grep -E '^[[:space:]]*(port|proto|dev|server|server-ipv6|cipher|data-ciphers|max-clients)[[:space:]]' "$SERVER_CONF" 2>/dev/null || true
fi
echo "remote_host=$OVPN_REMOTE_HOST"
echo "remote_port=$OVPN_REMOTE_PORT"
echo "remote_proto=$OVPN_PROTO"
;;
status-file)
# Rohinhalt der OpenVPN-Statusdatei (aktive Sitzungen). PHP parst.
[ -r "$STATUS_FILE" ] && cat "$STATUS_FILE" || true
;;
cert-list)
# easy-rsa index.txt: Status<TAB>Expiry<TAB>...<TAB>/CN=name
[ -r "$PKI_DIR/index.txt" ] && cat "$PKI_DIR/index.txt" || true
;;
tail-log)
valid_int "${1:-200}"; n="${1:-200}"; [ "$n" -gt 10000 ] && n=10000
[ -r "$MAIN_LOG" ] && tail -n "$n" "$MAIN_LOG" || true
;;
pki-create)
valid_user "${1:-}"; cn="$1"
[ -f "$PKI_DIR/issued/$cn.crt" ] && die "Zertifikat existiert bereits"
easyrsa_or_die "Zertifikatserstellung" build-client-full "$cn" nopass
echo "OK"
;;
pki-revoke)
valid_user "${1:-}"; cn="$1"
easyrsa revoke "$cn" >/dev/null 2>&1 || true
easyrsa gen-crl >/dev/null 2>&1 || true
echo "OK"
;;
pki-renew)
valid_user "${1:-}"; cn="$1"
# altes Zertifikat zurückziehen, Reste entfernen, neues ausstellen
if [ -f "$PKI_DIR/issued/$cn.crt" ]; then
easyrsa revoke "$cn" >/dev/null 2>&1 || true
easyrsa gen-crl >/dev/null 2>&1 || true
fi
rm -f "$PKI_DIR/issued/$cn.crt" "$PKI_DIR/private/$cn.key" "$PKI_DIR/reqs/$cn.req" 2>/dev/null || true
easyrsa_or_die "Erneuerung" build-client-full "$cn" nopass
easyrsa gen-crl >/dev/null 2>&1 || true
echo "OK"
;;
cert-expiry)
# Ablaufdatum eines Client-Zertifikats (notAfter), maschinenlesbar.
valid_user "${1:-}"; cn="$1"
[ -r "$PKI_DIR/issued/$cn.crt" ] || die "Zertifikat nicht gefunden"
openssl x509 -enddate -serial -noout -in "$PKI_DIR/issued/$cn.crt" 2>/dev/null || true
;;
get-ovpn)
# Komplette inline-.ovpn (ca, cert, key, tls-auth) auf STDOUT.
valid_user "${1:-}"; cn="$1"
[ -r "$PKI_DIR/issued/$cn.crt" ] || die "Zertifikat nicht gefunden"
[ -r "$PKI_DIR/private/$cn.key" ] || die "Schlüssel nicht gefunden"
[ -r "$PKI_DIR/ca.crt" ] || die "CA nicht gefunden"
echo "client"
echo "dev tun"
echo "proto $OVPN_PROTO"
echo "remote $OVPN_REMOTE_HOST $OVPN_REMOTE_PORT"
echo "resolv-retry infinite"
echo "nobind"
echo "persist-key"
echo "persist-tun"
echo "remote-cert-tls server"
echo "verb 3"
echo "<ca>"
cat "$PKI_DIR/ca.crt"
echo "</ca>"
echo "<cert>"
openssl x509 -in "$PKI_DIR/issued/$cn.crt" 2>/dev/null || cat "$PKI_DIR/issued/$cn.crt"
echo "</cert>"
echo "<key>"
cat "$PKI_DIR/private/$cn.key"
echo "</key>"
if [ -r "$TA_KEY" ]; then
echo "<tls-crypt>"
cat "$TA_KEY"
echo "</tls-crypt>"
fi
;;
get-ca) [ -r "$PKI_DIR/ca.crt" ] && cat "$PKI_DIR/ca.crt" || die "CA nicht gefunden" ;;
get-cert) valid_user "${1:-}"; [ -r "$PKI_DIR/issued/$1.crt" ] && cat "$PKI_DIR/issued/$1.crt" || die "Zertifikat nicht gefunden" ;;
get-key) valid_user "${1:-}"; [ -r "$PKI_DIR/private/$1.key" ] && cat "$PKI_DIR/private/$1.key" || die "Schlüssel nicht gefunden" ;;
kill)
# Aktive Sitzung über das Management-Interface trennen (falls vorhanden).
valid_user "${1:-}"; cn="$1"
[ -S "$MGMT_SOCK" ] || die "Management-Socket nicht verfügbar"
if command -v socat >/dev/null 2>&1; then
printf 'kill %s\nquit\n' "$cn" | socat - "UNIX-CONNECT:$MGMT_SOCK" || die "kill fehlgeschlagen"
echo "OK"
else
die "socat nicht installiert (für kill benötigt)"
fi
;;
*) die "unbekanntes Kommando" ;;
esac