defmod web2 FHEMWEB 8085 global
defmod allowed4web2 allowed
attr allowed4web2 basicAuth {radius($user,$password)}
attr allowed4web2 validFor web2
sub radius {
use Authen::Radius; #apt install libauthen-radius-perl
my ($user,$password) = @_;
my $clientPassword = getKeyValue("clientPassword");
my $r = new Authen::Radius(Host => '192.168.123.152', Secret => $clientPassword);
return $r->check_pwd($user,$password);
}
- 192.168.123.152 ist die IP Adresse des Radius Servers
- das Client-Password wurde mit setKeyValue("clientPassword","geheimesPassword") in den FHEM-keystore abgelegt
Warum? Weil in meinem Netzwerk eh ein Radius Server läuft.
Mich hatte einfach interessiert, ob das funktioniert und wie aufwändig es wird.
Dank der perl Library Authen::Radius ist das Ganze aber sehr einfach umzusetzen.
Hallo betateilchen,
ja ist denn heute schon Weihnachten ? 😃
Die Idee ist echt prima, das werde ich über die Feiertage definitiv mal ausprobieren.
Ich hatte vor vielen Weihnachten mal das LDAP-Modul (keine Ahnung ob das im SVN ist) ausprobiert, aber dazu hätte ich Änderungen am LDAP-Schema vornehmen müssen, an die ich nicht ran wollte.
So kann ich nun die Auth gegen mein AD machen.
hab Dank für Deinen Vorschlag und ein frohes Fest,
JudgeDredd
Schöne Lösung. Auch nice wäre ein OAuth2 oder besser gleich OIDC. Mein Keycloak würde sich da freuen ;D
Beschreibe mir doch mal das Szenario, das Dir da vorschwebt.
So ganz allgemein. So wie bisher BasicAuth funktioniert nur eben per SSO mittels OIDC. In meinem Fall an Keycloak.
Wenn der Token abgelaufen ist wird an Keycloak weiter geleitet um sich zu Authentifizieren.
Also im Prinzip sowas ähnliches, wie wir es bei der Anmeldung mit einem OTP gegen den Google-Authenticator bereits haben? Wenn die Anmeldung erfolgreich war, wird das für maximal 86400 Sekunden gecached und nicht mehr neu gefragt. Danach muss man sich neu anmelden.
Das sollte doch eigentlich nicht allzu schwierig umzusetzen sein. Das allowed-device muss ja nur einmal ein "true" zurückbekommen und sich das merken.
Zitat von: CoolTux am 23 Dezember 2025, 16:37:46Schöne Lösung. Auch nice wäre ein OAuth2 oder besser gleich OIDC. Mein Keycloak würde sich da freuen ;D
Ich nutze oauth2-proxy (https://oauth2-proxy.github.io/oauth2-proxy/) und nginx auth_request (https://nginx.org/en/docs/http/ngx_http_auth_request_module.html). Nginx sendet dann, bei erfolgreicher Anmeldung über den oauth2-proxy, einfach den Basic Auth header mit (also user:pass in base64; ähnlich wie in diesem Beispiel (https://oauth2-proxy.github.io/oauth2-proxy/configuration/integration#configuring-for-use-with-the-nginx-auth_request-directive)
X-Access-Token nur eben hardcoded). In FHEM muss man also gar nichts umstellen.
oauth2-proxy funktioniert auch mit Keycloak als IDP (https://oauth2-proxy.github.io/oauth2-proxy/configuration/providers/keycloak_oidc/). (Ich nutze aktuell Nextcloud als IDP, weil das hier eh läuft.)
Mein FHEM läuft in einem Kubernetes Cluster und ist über Traefik Ingress aufrufbar. Muss mal schauen ob man Traefik diesbezüglich konfigurieren kann.
Laut Doku geht da irgendwas mit Traefik: https://oauth2-proxy.github.io/oauth2-proxy/configuration/integration#configuring-for-use-with-the-traefik-v2-forwardauth-middleware
Zitat von: CoolTux am 23 Dezember 2025, 16:37:46Schöne Lösung. Auch nice wäre ein OAuth2 oder besser gleich OIDC. Mein Keycloak würde sich da freuen ;D
Es gibt doch für keycloak ein radius-plugin.
Würde das Deine Aufgabenstellung nicht abdecken?
(von keycloak habe ich keine Ahnung, ich habe nur ein bisschen im Internet rumgelesen, nachdem Du mit Deinem Wunsch kamst)
Zitat von: betateilchen am 26 Dezember 2025, 11:23:11Zitat von: CoolTux am 23 Dezember 2025, 16:37:46Schöne Lösung. Auch nice wäre ein OAuth2 oder besser gleich OIDC. Mein Keycloak würde sich da freuen ;D
Es gibt doch für keycloak ein radius-plugin.
Würde das Deine Aufgabenstellung nicht abdecken?
(von keycloak habe ich keine Ahnung, ich habe nur ein bisschen im Internet rumgelesen, nachdem Du mit Deinem Wunsch kamst)
Das muss ich mir mal in Ruhe anschauen. Wird sicher nicht Standard sein sondern muss man als Zusatz einbinden. Danke Dir auf jeden Fall.
https://github.com/vzakharchenko/keycloak-radius-plugin/blob/master/README.md
So, die Feiertage sind vorbei und ich habe mich mit der RADIUS-Auth mal beschäftigt.
Die erste Euphorie war allerdings schnell verflogen, da mein RADIUS nicht für PlainText-Passwort (PAP) konfiguriert ist.
Die Begeisterung für die Idee von betateilchen war aber ungebrochen.
Daher habe ich mir eine Sub für die RADIUS-Auth mittels MSCHAPv2 gebaut.
Die Response-Challenge ist hier manuell nachgebaut, da es dafür kein debian Paket gibt und ich von cpan absehen wollte.
Vielleicht gibt es ja weitere User, die ebenfalls MSCHAPv2 nutzen und Interesse daran haben.
use Authen::Radius; #Debian Paket libauthen-radius-perl
use Digest::MD4 qw(md4); #Debian Paket libdigest-md4-perl
use Digest::SHA qw(sha1); # " "
use Crypt::DES; #Debian Paket libcrypt-des-perl
sub radius_mschapv2($$) {
# -----------------------------
# Konfiguration
# -----------------------------
my ($username, $password ) = @_;
Log3 undef, 4, "Starting Auth for $username";
my $radius_server = 'radius.mydomain.intranet'; #IP/FQDN of RADIUS
my $radius_secret = getKeyValue('RADIUSclientPassword') || ''; #Client Password (clients.conf) keepair set via setKeyValue('RADIUSclientPassword','password')
my $radius_port = 1812; #Listen Port
my $radius_nas_Identifier = 'fhem-server'; #optional Attribut
# -----------------------------
# MS-CHAPv2 Parameter
# -----------------------------
my $auth_challenge = random_bytes(16); # AuthenticatorChallenge
my $peer_challenge = random_bytes(16); # PeerChallenge
# NT-Password-Hash
my $nt_hash = md4(encode_unicode($password));
# MS-CHAPv2 Response berechnen
my $nt_response = mschapv2_response(
$peer_challenge,
$auth_challenge,
$username,
$nt_hash
);
Authen::Radius->load_dictionary( '/usr/share/freeradius/dictionary' );
# -----------------------------
# RADIUS Initialisierung
# -----------------------------
my $radius = Authen::Radius->new(
Host => $radius_server,
Secret => $radius_secret,
Port => $radius_port,
TimeOut => 5,
);
$radius->clear_attributes;
# -----------------------------
# RADIUS Attribute setzen
# -----------------------------
$radius->add_attributes(
{ Name => 'Service-Type', Value => 'Login-User' },
{ Name => 'User-Name', Value => $username },
{ Name => 'MS-CHAP-Challenge', Value => $auth_challenge },
{ Name => 'MS-CHAP2-Response', Value => pack("C C a24 a24", 1, 0, $peer_challenge, $nt_response ), Type => 'octets' },
{ Name => 'NAS-Identifier', Value => $radius_nas_Identifier },
);
# -----------------------------
# Access-Request senden
# -----------------------------
$radius->send_packet(ACCESS_REQUEST) and my $res = $radius->recv_packet();
# -----------------------------
# Ergebnis
# -----------------------------
if ( defined($res) ) {
if ($res == ACCESS_ACCEPT) {
Log3 undef, 4, "Auth for $username successfull";
return $res;
} elsif ($res == ACCESS_REJECT) {
Log3 undef, 4, "Auth for $username failed";
} else {
Log3 undef, 4, "Auth for $username unknown ($res)";
}
} else {
Log3 undef, 4, "No reply from $radius_server"
}
# =========================================================
# Hilfsfunktionen
# =========================================================
sub random_bytes {
my ($len) = @_;
return join '', map { chr(int(rand(256))) } 1 .. $len;
}
sub encode_unicode {
my ($str) = @_;
return join('', map { $_ . "\x00" } split //, $str);
}
sub mschapv2_response {
my ($peer_challenge, $auth_challenge, $user, $nt_hash) = @_;
# ChallengeHash = SHA1(PeerChallenge | AuthChallenge | Username)[0..7]
my $challenge = substr(
sha1($peer_challenge . $auth_challenge . $user),
0, 8
);
return des_encrypt_challenge($challenge, $nt_hash);
}
sub des_encrypt_challenge {
my ($challenge, $hash) = @_;
my $response = '';
for my $i (0..2) {
my $key = substr($hash . ("\0" x 5), $i * 7, 7);
$key = expand_des_key($key);
my $des = Crypt::DES->new($key);
$response .= $des->encrypt($challenge);
}
return $response;
}
sub expand_des_key {
my ($key7) = @_;
my @k = unpack("C7", $key7);
my @key8;
$key8[0] = $k[0] & 0xFE;
$key8[1] = (($k[0] << 7) | ($k[1] >> 1)) & 0xFE;
$key8[2] = (($k[1] << 6) | ($k[2] >> 2)) & 0xFE;
$key8[3] = (($k[2] << 5) | ($k[3] >> 3)) & 0xFE;
$key8[4] = (($k[3] << 4) | ($k[4] >> 4)) & 0xFE;
$key8[5] = (($k[4] << 3) | ($k[5] >> 5)) & 0xFE;
$key8[6] = (($k[5] << 2) | ($k[6] >> 6)) & 0xFE;
$key8[7] = ($k[6] << 1) & 0xFE;
return pack("C8", @key8);
}
}
Es freut mich, dass mein "einfacher" Ansatz (natürlich zum Testen nur mit PAP) dazu angeregt hat, das Thema "Radius" ein ganzes Stück weiter zu denken.
Dann nimm bitte weiterhin "gute Ideen liefern" in Deine guten Vorsätze für 2026 auf 😉
So oder so ist der Ansatz es Wert, Dir einen guten Rutsch zu wünschen.