[SOLVED] WebUI PAM Zugang Einschränken / WebUI restrict PAM (root) Login

NojuHD

Member
May 1, 2022
39
7
13
Hallo zusammen,

Ich möchte die WebUI über VPN für andere Erreichbar machen, damit diese Ihre VMs selbst verwalten können.

Nun hab ich mir die Frage gestellt, ob es denn möglich ist den Root Zugriff auf die WebUI nur in einem bestimmten Netzwerk zuzulassen?

Heimnetz: Root kann sich anmelden.
EXT Netz: Root kann sich nicht anmelden, sondern nur pve Accounts

Danke im Voraus.
 
Ich möchte die WebUI über VPN für andere Erreichbar machen, damit diese Ihre VMs selbst verwalten können.
Die API (welche ja die Web UI bedient) nur auf bestimmten Netzwerken verfügbar machen ist relativ einfach, siehe:
https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_pvedaemon_proxmox_ve_api_daemon

Nun hab ich mir die Frage gestellt, ob es denn möglich ist den Root Zugriff auf die WebUI nur in einem bestimmten Netzwerk zuzulassen?
Nein, das ist nicht einfach möglich. Evtl. könnte man sich mit komplexeren fail2ban config o.ä. helfen, aber meiner Meinung nach ist es einfacher, und in der Praxis nicht wirklich weniger sicher, für den root@pam Account ein starkes, einzigartiges Password zu verwenden und Zweifaktor Authentifizierung (TFA) aufzusetzen.
 
Die API (welche ja die Web UI bedient) nur auf bestimmten Netzwerken verfügbar machen ist relativ einfach, siehe:
https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_pvedaemon_proxmox_ve_api_daemon


Nein, das ist nicht einfach möglich. Evtl. könnte man sich mit komplexeren fail2ban config o.ä. helfen, aber meiner Meinung nach ist es einfacher, und in der Praxis nicht wirklich weniger sicher, für den root@pam Account ein starkes, einzigartiges Password zu verwenden und Zweifaktor Authentifizierung (TFA) aufzusetzen.

Hallo @t.lamprecht,

es ist jetzt etwas Zeit vergangen und ich wollte mich noch für die schnelle Hilfe bedanken.
Nun habe ich gestern in der Roadmap für PVE 8 gelesen, dass es nun möglich sein soll, den Zugang via pam_rhost einzuschränken. Wie setze ich dies am besten um?

Danke im Voraus.
 
Man kann dafür ein Script erstellen welches mit dem pam_exec plugin in ein Proxmox VE spezifischen PAM service config integriert wird.

Also, /usr/local/lib/pam/pve.sh mit dem folgenden Inhalt erstellen:

Bash:
#!/bin/bash

if [[ "$PAM_USER" != "root" ]]; then
    # only care about root user
    exit 0;
fi

logger "Testing PAM_RHOST ($PAM_RHOST) via $0"
case "$PAM_RHOST" in
    '127.0.0.1') exit 0 ;;
    '::ffff:127.0.0.1') exit 0 ;;
    # or whichever IPs you want to add
    '10.9.2.1') exit 0 ;;
    '::ffff:10.9.2.1') exit 0 ;;
    *)
        logger "Rejecting unknown PAM_RHOST ($PAM_RHOST) via $0"
        exit 1
        ;;
esac

Und mit chmod +x /usr/local/lib/pam/pve.sh ausführbar machen.

Dafür kann man natürlich auch perl/python/... verwenden, wäre sogar empfehlenswert, bash ist ja Teilweise etwas haarig.

Dann /etc/pam.d/proxmox-ve-auth mit dem Folgenden Inhalt erstellen:

Code:
auth [success=ok default=bad] pam_exec.so /usr/local/lib/pam/pve.sh

@include common-auth
account required pam_nologin.so
@include common-account

Damit wir zuerst das Script aufgerufen, wenn das mit OK (exit-code 0) endet, werden die restlichen PAM module verwendet um password etc. zu prüfen, wenn das Script sich aber mit exit-code 1 (nicht OK) beendet, bricht PAM an der stelle ab (wegen default=bad).

Das kann man auch so für PMG (proxmox-mailgateway-auth) und PBS (proxmox-backup-auth) konfigurieren, man muss nur den PAM Config Filename anpassen.
 
Last edited:
Hallo @t.lamprecht,

nochmals danke für die schnelle Hilfe, es hat wie beschrieben funktioniert, nach beheben von 2 kleinen Fehlern im Bash Skript ;)

Ich habe nun ein Python Skript erstellt, welches die Funktion des Bash Skripts etwas erweitert. (Ich bin nicht wirklich erfahren was Programmierung an geht)

unter /etc/proxmox-ve-auth.conf kann man eine Datei mit den beschränkten Benutzern und den erlaubten IPs ablegen, die Datei kann wie folgt aussehen:

Code:
root 127.0.0.1,192.168.0.0/24
user 127.0.0.1

/etc/pam.d/proxmox-ve-auth sieht wie folgt aus:

Code:
auth [success=ok default=bad] pam_exec.so /bin/python3 /usr/local/lib/pam/pve.py

@include common-auth
account required pam_nologin.so
@include common-account

Das Python Skript sieht wie folgt aus:


Python:
import sys
import os
import ipaddress

config_path = '/etc/proxmox-ve-auth.conf'

def ipisinlist(host, ip):
    if '/' in ip:
        if host in ipaddress.ip_network(ip, strict=False):
            return True
        else:
            return False
    else:
        if host == ipaddress.ip_address(ip):
            return True
        else:
            return False

acl = {}
with open(config_path, 'r') as config_file:
    for line in config_file:
        parts = line.strip().split()
        if len(parts) >= 2:
            username = parts[0]
            ip_list = parts[1].split(',')
            acl[username] = ip_list

PAM_USER = os.environ.get('PAM_USER')
PAM_RHOST = os.environ.get('PAM_RHOST')

# Check if PAM_USER is Restricted
if PAM_USER in acl:
    for ip in acl[PAM_USER]:
        try:
            PAM_RHOST = ipaddress.ip_address(PAM_RHOST)
            if PAM_RHOST.version == 6 and PAM_RHOST.ipv4_mapped:
                if ipisinlist(PAM_RHOST.ipv4_mapped, ip):
                    sys.exit(0)
            else:
                if ipisinlist(PAM_RHOST, ip):
                    sys.exit(0)
        except ValueError:
            logger_message = f"Invalid RAM_RHOST address: ({PAM_RHOST}) via {sys.argv[0]})"
    logger_message = f"Rejecting unknown PAM_RHOST ({PAM_RHOST}) via {sys.argv[0]})"
else:
    sys.exit(0)
sys.exit(1)

FYI:
Das Skript übersetzt in PAM_RHOST enthaltene IPv4-mapped Adressen in normale IPv4 Adressen. Z.B.: ::ffff:192.168.0.1 -> 192.168.0.1 damit bekommt der Host auch Zugriff wenn nur der IPv4 Adressbereich in /etc/proxmox-ve-auth.conf freigegeben ist.
 
Last edited:
  • Like
Reactions: Johannes S
nach beheben von 2 kleinen Fehlern im Bash Skript
Hab nur einen gefunden (} statt fi in der if-Bedingung, blödes "muscle memory" ;) ) und auch editiert, was war der Zweite?

Ich habe nun ein Python Skript erstellt, welches die Funktion des Bash Skripts etwas erweitert. (Ich bin nicht wirklich erfahren was Programmierung an geht)
Nice, und danke fürs Teilen!

Was mir auffällt, dein logging scheint verkehrt herum zu sein, bei exit 0 wird ja akzeptiert, nicht "rejected".
Kein verhaltens bug, aber kann wohl verwirrend sein. IPv4-inIPv6 handlet deine regex wohl halbwegs richtig, aber geht sicher auch bissl schöner, sonst sehe ich nur paar stilistische Sachen, aber da ist vieles auch einfach Persönliche Geschmackssache.

Hab dann auch noch was in Perl geschrieben, das behandelt auch IPv4-mapped-in-IPv6 Addressen, ist etwa für den Proxmox Backup Server relevant, aber für neuere Proxmox VE und Proxmox Mail Gateway haben wir die LISTEN_IP auch auf "any" umgestellt, und da kann man dann hier auch IPv4 als IPv6 erhalten. Sowas würde bei deinem Script die Regex wohl auch unnötig machen.

Das Perl script, mit statischer Config nur für root (erweitern auf per-user config, und/oder auslesen aus File wie du's hast wäre nicht eine allzu große Änderung):
Perl:
#!/usr/bin/perl

use strict;
use warnings;

use Net::IP qw($IP_B_IN_A_OVERLAP $IP_IDENTICAL);

# Define the list of allowed CIDRs
my @allowed_cidrs = (
    '127.0.0.0/8',        # IPv4 loopback
    '::1/128',            # IPv6 loopback
    'fe80::/10',          # IPv6 link-local addresses
    '10.0.0.0/8',         # LAN
    '172.16.0.0/12',      # LAN
    '192.168.0.0/16',     # LAN
    # ...
);

my $user = $ENV{'PAM_USER'} or die "PAM_USER not defined\n";
my $rhost = $ENV{'PAM_RHOST'} or die "PAM_RHOST not defined\n";

# only check the all-powerfull user, ignore all others
exit(0) if $user ne 'root';

if (my $embedded_ipv4 = Net::IP::ip_get_embedded_ipv4($rhost)) {
    $rhost = $embedded_ipv4; # normalize IPv4-mapped-in-IPv6 first
}

my $request_ip = Net::IP->new($rhost) or die "invalid IP address '$rhost' in PAM_RHOST\n";

for my $ip_range (map { Net::IP->new($_) } @allowed_cidrs) {
    my $overlap = $ip_range->overlaps($request_ip);
    next if !defined($overlap);

    if ($overlap == $IP_B_IN_A_OVERLAP || $overlap == $IP_IDENTICAL) {
        `logger "allowing login request for user '$user' from '$rhost' to proceed"`; # uncomment for debugging
        exit(0); # OK
    }
}

`logger "rejecting login for user '$user' from '$rhost' early"`;
exit(1); # no configured CIDR matched the IP, exit with failure

An jeden der sowas ausrollt: Es empfiehlt sich das nicht über die Shell im PVE/PBS/PMG web-interface zu konfigurieren, sondern lieber über SSH o.ä., und dann auch testen, sonst kann man sich auch mal leicht selbst aussperren.
 
Hab nur einen gefunden (} statt fi in der if-Bedingung, blödes "muscle memory" ;) ) und auch editiert, was war der Zweite?

Entschuldige es war natürlich nur der eine Fehler.

Rest war ein Layer 8 Problemchen :)
Ich hab das Skript beim manuellen Ausführen per sh und nicht per bash aufgerufen und der mag die double Brackets nicht.

Was mir auffällt, dein logging scheint verkehrt herum zu sein, bei exit 0 wird ja akzeptiert, nicht "rejected".
Kein verhaltens bug, aber kann wohl verwirrend sein. IPv4-inIPv6 handlet deine regex wohl halbwegs richtig, aber geht sicher auch bissl schöner, sonst sehe ich nur paar stilistische Sachen, aber da ist vieles auch einfach Persönliche Geschmackssache.

Das logging werde ich natürlich gleich anpassen, da war ich wohl zu müde um mein eigenes Skript noch zu verstehen :)

Was den Stil angeht bin ich mir sicher, dass es in jedem Fall besser geht.
Ist nur mein erstes Skript in Python, zudem wollte ich erst noch in die Programmierung einsteigen, daher habe ich quasi noch NULL Erfahrung.

Also fühlt euch frei das Skript von neu auf umzugestalten geschweige den neu zu erstellen, wenn es nicht sogar einfacher ist das Perl Skript zu verwenden :D
 
Last edited:
  • Like
Reactions: Johannes S
Hey,

ich find den Ansatz ziemlich cool! Allerdings ist pve bei mir hinter einem nginx reverse proxy. Jetzt wird kein Login Versuch geblockt, da ja die IP vom nginx Container innerhalb des erlaubten Netzwerks ist.
Hätte wer ne Idee, wie ich auf die eigentliche Client IP zugreifen könnte? Die steckt ha immerhin schon mal im Request Header, aber der wird nicht mitgeliefert :/
 
Der reverse proxy müsse die richtige IP weiterleiten, ich hab's nicht genau im Kopf und nur kurz gesucht, aber sowas wie folgende nginx config zeile könnte helfen:

proxy_set_header Host $host;

Da müsste ich beizeiten wiedermal selber kurz genauer nachlesen, um eine bessere Antwort zu haben.
 
das hab ich eigentlich gesetzt :/ Dabei liest das Skript dann nur die IP vom nginx

Code:
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name host.cloud;

    access_log /var/log/nginx/host.cloud-access.log;
    error_log /var/log/nginx/host.cloud-error.log;

[...] ssl stuff [...]

    location / {
        proxy_pass http://<proxmox_ip>:8006/;
     proxy_ssl_server_name on;

    #include /etc/nginx/snippets/private.conf;
    
    include /etc/nginx/snippets/header_default.conf;
    include /etc/nginx/snippets/header_security.conf;
    include /etc/nginx/snippets/header_websocket.conf;   
    include /etc/nginx/snippets/header_xframe.conf;   
    }
}

mit

Code:
$ cat /etc/nginx/snippets/header_default.conf

proxy_set_header Host $http_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_set_header X-Test-Header "Testing123";
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_connect_timeout 60s;
 
Der reverse proxy müsse die richtige IP weiterleiten, ich hab's nicht genau im Kopf und nur kurz gesucht, aber sowas wie folgende nginx config zeile könnte helfen:

proxy_set_header Host $host;

Da müsste ich beizeiten wiedermal selber kurz genauer nachlesen, um eine bessere Antwort zu haben.

Ich denke, pam_rhost beachtet den HTTP-Header nicht, daher denke ich funktioniert das nicht ohne Weiteres.

Hier müsste also der pveproxy schon die X-Real-IP oder X-Forwarded-For an pam weitergeben und ob das schon der Fall ist, weiß ich leider nicht.
D.h. falls nicht, müsste hier eine Änderung im pveproxy vorgenommen werden.

Bitte korrigiert mich, falls ich falsch liege.
 
Last edited:
So vermute ich das bisher auch. Ich hatte im Python Skript mir die verfügbaren ENVs mittels `os.environ.items()` ausgeben lassen, da war die real IP/Header nicht dabei. Ich hatte dann auch versucht in der /usr/share/perl5/PVE/Service/pveproxy.pm die ENV zur verfügung zu stellen, aber ich kann kein perl... Das hatte nicht geklappt
 
Hier müsste also der pveproxy schon die X-Real-IP oder X-Forwarded-For an pam weitergeben und ob das schon der Fall ist, weiß ich leider nicht.
D.h. falls nicht, müsste hier eine Änderung im pveproxy vorgenommen werden.
Das klingt sehr wahrscheinlich, ob man das hier aber beachten darf, oder ob dann eine Sicherheitslücke aufgemacht wird bin ich grade nicht ganz sicher. Denn die HTTP header kann ja jeder client auch gezielt setzen, da muss man etwas aufpassen denke ich.
 
  • Like
Reactions: NojuHD
... Denn die HTTP header kann ja jeder client auch gezielt setzen, da muss man etwas aufpassen denke ich.
Als Standardverhalten sollte es aus Sicherheitsgründen wahrscheinlich nicht, implementiert werden, hier ist es schon gut, dass nur die tatsächliche IP des Pakets verwendet wird.

Man könnte jedoch eine "Trusted Proxys" config implementieren, welche das Verhalten dann verändert, ich weiß jedoch nicht, wie viel Aufwand das seitens Proxmox bedeuten würde und ob sich das bei der geringen Nachfrage lohnt.
 
Last edited:
Ja, beim Kosten/Nutzen bin ich mir grad auch nicht sicher.
Und potentiell kann man "böse" IPs ja auch bereits auf reverse proxy ebene abfangen und ein 401 oder so zurückgeben.

Sollte jemand trotzdem noch denken das wäre gut zu haben dann könnt ihr gerne ein enhancement request im https://bugzilla.proxmox.com/ aufmachen (in Englisch bitte) und auf diesen Thread verweisen, ich würde aber empfehlen nicht allzu große Hoffnungen für eine schnelle Umsetzung zu haben.
 
  • Like
Reactions: lz114 and NojuHD
jaa so wichtig ists nicht. Wäre nice to have, aber es gibt ja auch andere Möglichkeiten das "abzusichern" :)
 

About

The Proxmox community has been around for many years and offers help and support for Proxmox VE, Proxmox Backup Server, and Proxmox Mail Gateway.
We think our community is one of the best thanks to people like you!

Get your subscription!

The Proxmox team works very hard to make sure you are running the best software and getting stable updates and security enhancements, as well as quick enterprise support. Tens of thousands of happy customers have a Proxmox subscription. Get yours easily in our online shop.

Buy now!