Technologieführer

FreeRADIUS mit LinOTP 2: Zwei-Faktor-Authentifizierung per Einmalpasswort

4 min read Authentifizierung Aktualisiert 01 Oct 2025
FreeRADIUS + LinOTP 2: OTP 2FA einrichten
FreeRADIUS + LinOTP 2: OTP 2FA einrichten

Kurzüberblick

LinOTP ist ein Backend für One-Time-Passwords (OTP), das zahlreiche Hardware-Token, Software-Token und SMS-Token unterstützt. Die Community Edition stellt eine einfache Web-API zur Verfügung, über die FreeRADIUS die Gültigkeit eines Einmalpassworts prüfen kann.

API

Während die Enterprise Edition ein C-Modul für FreeRADIUS liefert, bietet die Community Edition (AGPLv3) dieses nicht. Stattdessen stellt LinOTP einfache HTTP(S)-APIs bereit, z. B. zur Authentifizierung von Benutzern:

https://yourServer/validate/check?user=....&pass=....

oder

https://yourServer/validate/simplecheck?user=...&pass=...

Diese Endpunkte geben eine einfache Rückmeldung, ob ein OTP für einen Benutzer gültig ist. Die Integration erfolgt über einen HTTP-Aufruf vom FreeRADIUS-Server zur LinOTP-Instanz.

FreeRADIUS-Integration

Die einfache LinOTP-API kombiniert mit einem FreeRADIUS-Modul (rlm_perl) ermöglicht eine unkomplizierte Lösung für OTP via RADIUS. Alternativ wäre rlm_exec möglich, um ein externes Programm aufzurufen, oder ein C-basiertes Modul in der Enterprise Edition. In dieser Anleitung verwenden wir rlm_perl.

Die Dokumentation des rlm_perl-Moduls enthält ein Beispiel, das wir nur in der authenticate-Funktion anpassen müssen. Dort wird die LinOTP-API aufgerufen und die Antwort ausgewertet.

Die Lösung (Perl-Modul)

Nachfolgend das Perl-Beispiel, wie es in rlm_perl eingebunden werden kann. Dieses Skript ist weitgehend unverändert aus dem Beispiel; Sie müssen vor dem Einsatz $URL an Ihre LinOTP-Instanz anpassen.

#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
#  Copyright 2002  The FreeRADIUS server project
#  Copyright 2002  Boian Jordanov <[email protected]>
#  Copyright 2011  linotp project <[email protected]>

#
# Based on the Example code for use with rlm_perl
#
#

=head1 NAME

freeradius_perl - Perl module for use with FreeRADIUS rlm_perl, to authenticate against 
 LinOTP  http://www.linotp.org

=head1 SYNOPSIS

   use with freeradius:  
   
   Configure rlm_perl to work with LinOTP:
   in /etc/freeradius/users 
    set:
     DEFAULT Auth-type := perl

  in /etc/freeradius/modules/perl
     point
     perl {
         module = 
  to this file

  in /etc/freeradius/sites-enabled/
  set
  authenticate{
    perl
    [....]

=head1 DESCRIPTION

This module enables freeradius to authenticate using LinOTP.

   TODO: 
     * checking of server certificate


=head2 Methods

   * authenticate

=head1 AUTHOR

Cornelius Koelbel ([email protected])

=head1 COPYRIGHT

Copyright 2011 

This library is free software; you can redistribute it 
under the GPLv2.

=head1 SEE ALSO

perl(1).

=cut

use strict;
use LWP 5.64;


# use ...
# This is very important ! Without this script will not get the filled  hashesh from main.
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK $URL);
use Data::Dumper;

$URL = "https://localhost/validate/simplecheck";

# This is hash wich hold original request from radius
#my %RAD_REQUEST;
# In this hash you add values that will be returned to NAS.
#my %RAD_REPLY;
#This is for check items
#my %RAD_CHECK;

#
# This the remapping of return values
#

       use constant    RLM_MODULE_REJECT=>    0;#  /* immediately reject the request */
       use constant    RLM_MODULE_FAIL=>      1;#  /* module failed, don't reply */
       use constant    RLM_MODULE_OK=>        2;#  /* the module is OK, continue */
       use constant    RLM_MODULE_HANDLED=>   3;#  /* the module handled the request, so stop. */
       use constant    RLM_MODULE_INVALID=>   4;#  /* the module considers the request invalid. */
       use constant    RLM_MODULE_USERLOCK=>  5;#  /* reject the request (user is locked out) */
       use constant    RLM_MODULE_NOTFOUND=>  6;#  /* user not found */
       use constant    RLM_MODULE_NOOP=>      7;#  /* module succeeded without doing anything */
       use constant    RLM_MODULE_UPDATED=>   8;#  /* OK (pairs modified) */
       use constant    RLM_MODULE_NUMCODES=>  9;#  /* How many return codes there are */

# Function to handle authorize
sub authorize {
       # For debugging purposes only
#       &log_request_attributes;

       # Here's where your authorization code comes
       # You can call another function from here:
       &test_call;

       return RLM_MODULE_OK;
}

# Function to handle authenticate
sub authenticate {
       # For debugging purposes only
#       &log_request_attributes;

    
    my $ua = LWP::UserAgent->new();
    my $req = HTTP::Request->new(GET => $URL . "?user=" .
        $RAD_REQUEST{'User-Name'} . "&pass=" . 
        $RAD_REQUEST{'User-Password'} );
    my $response = $ua->request( $req );

    die "Error at $URL\n ", $response->status_line, "\n Aborting"
      unless $response->is_success;
      
    if($response->content =~ m/:\-\)/i) {
               return RLM_MODULE_OK;
      } else {
        $RAD_REPLY{'Reply-Message'} = "LinOTP server denied access!";
               return RLM_MODULE_REJECT;
    }
}

# Function to handle preacct
sub preacct {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle accounting
sub accounting {
       # For debugging purposes only
#       &log_request_attributes;

       # You can call another subroutine from here
       &test_call;

       return RLM_MODULE_OK;
}

# Function to handle checksimul
sub checksimul {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle pre_proxy
sub pre_proxy {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle post_proxy
sub post_proxy {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle post_auth
sub post_auth {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle xlat
sub xlat {
       # For debugging purposes only
#       &log_request_attributes;

       # Loads some external perl and evaluate it
       my ($filename,$a,$b,$c,$d) = @_;
       &radiusd::radlog(1, "From xlat $filename ");
       &radiusd::radlog(1,"From xlat $a $b $c $d ");
       local *FH;
       open FH, $filename or die "open '$filename' $!";
       local($/) = undef;
       my $sub = ;
       close FH;
       my $eval = qq{ sub handler{ $sub;} };
       eval $eval;
       eval {main->handler;};
}

# Function to handle detach
sub detach {
       # For debugging purposes only
#       &log_request_attributes;

       # Do some logging.
       &radiusd::radlog(0,"rlm_perl::Detaching. Reloading. Done.");
} 

#
# Some functions that can be called from other functions
#

sub test_call {
       # Some code goes here
}

sub log_request_attributes {
       # This shouldn't be done in production environments!
       # This is only meant for debugging!
       for (keys %RAD_REQUEST) {
               &radiusd::radlog(1, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
       }
}

1;

Wichtig: Passen Sie in diesem Skript unbedingt die Variable $URL an Ihre LinOTP-Instanz an (z. B. “https://linotp.example.local/validate/simplecheck”). Außerdem müssen Sie FreeRADIUS so konfigurieren, dass rlm_perl dieses Modul lädt.

FreeRADIUS-Konfiguration (Kurz)

  • In /etc/freeradius/users: setzen Sie für die passenden Benutzer oder DEFAULT: Auth-Type := perl
  • In /etc/freeradius/modules/perl: verweisen Sie auf das obige Perl-Modul (module = /pfad/zum/modul)
  • In /etc/freeradius/sites-enabled/ innerhalb authenticate { perl }

Sicherheits- und Betriebs-Hinweise

  • TLS prüfen: Das Beispiel führt keinen Server-Zertifikat-Check durch. Aktivieren Sie in Produktion die Prüfung von Zertifikaten und erwägen Sie Zertifikat-Pinning.
  • Fehlerbehandlung: Das Skript beendet sich bei HTTP-Fehlern mit die(). Ersetzen Sie dies durch robustes Logging und Rückfallverhalten.
  • Redundanz: Planen Sie Hochverfügbarkeit für Ihre LinOTP-Instanzen ein (Load-Balancer, aktive/passive Replikation), sonst ist RADIUS abhängig von einer einzelnen API-Quelle.
  • Logging: Protokollieren Sie erfolgreiche und fehlgeschlagene Prüfungen, aber vermeiden Sie das Speichern kompletter OTPs im Log.

Wann diese Lösung nicht passend ist

  • Große Installationen mit sehr hohen RPS sollten ein nativeres, performanteres Modul (C-Modul oder Enterprise-Plugin) verwenden.
  • Wenn Sie strenge Offline-Authentifizierung benötigen (kein Netzwerk zum LinOTP-Server), ist diese HTTP-basierte Lösung ungeeignet.

Alternative Ansätze

  • rlm_exec: Ein externes Programm per rlm_exec starten (z. B. ein kleines Go-/Python-Programm für API-Aufrufe).
  • Enterprise-Modul: LinOTP Enterprise Edition mit C-basiertem FreeRADIUS-Modul verwenden.
  • Proxy-Layer: Einen lokalen Proxy zwischen FreeRADIUS und LinOTP einsetzen, der Caching, Fallbacks und TLS-Validation übernimmt.

Checkliste für Administratoren (Rollenspezifisch)

  • Administrator:
    • $URL im Perl-Modul anpassen
    • Module-Pfad in /etc/freeradius/modules/perl setzen
    • FreeRADIUS-Konfiguration testen und neu laden
  • Security Engineer:
    • TLS/Cert-Validierung sicherstellen
    • Logging- und Monitoring-Policy definieren
  • Operator:
    • Verfügbarkeit der LinOTP-API überwachen
    • Fallback-/Rollback-Plan für API-Ausfall testen

Kurze Methodik für Rollout

  1. Entwicklungsumgebung aufsetzen und $URL auf eine Test-LinOTP-Instanz zeigen lassen.
  2. FreeRADIUS mit rlm_perl konfigurieren und das Modul laden.
  3. Funktionstests mit Test-Token durchführen.
  4. TLS/Logging/Monitoring aktivieren.
  5. Stufenweise in Produktion ausrollen und Verfügbarkeit prüfen.

1-Zeilen-Glossar

  • LinOTP: Backend für One-Time-Passwords (OTP).
  • RADIUS: Remote Authentication Dial-In User Service, Protokoll für Netzwerk-Authentifizierung.
  • rlm_perl: FreeRADIUS-Modul zum Einbetten von Perl-Skripten.

Fazit

Diese Anleitung zeigt eine einfache, schnell realisierbare Integration von FreeRADIUS mit LinOTP 2 über dessen Web-API und ein rlm_perl-Beispiel. Für den produktiven Einsatz sollten Sie TLS-Validierung, robustes Fehlerhandling und Hochverfügbarkeit ergänzen.

Wichtig: Bevor Sie diese Lösung in Produktion nehmen, ersetzen Sie die einfachen Fehlerausgaben durch kontrolliertes Logging und sichern Sie die Kommunikation zwischen FreeRADIUS und LinOTP ab.

Autor
Redaktion

Ähnliche Materialien

Gelikte Beiträge auf Instagram finden
Soziale Medien

Gelikte Beiträge auf Instagram finden

Obere 2 Zeilen in Excel fixieren — schnelle Anleitung
Excel

Obere 2 Zeilen in Excel fixieren — schnelle Anleitung

Verschwundene Teams‑Kanäle wiederherstellen
Microsoft Teams

Verschwundene Teams‑Kanäle wiederherstellen

Windows 11 aktualisieren: 3 schnelle Methoden
Anleitung

Windows 11 aktualisieren: 3 schnelle Methoden

RDS unter Server 2008 R2 einrichten
IT-Administration

RDS unter Server 2008 R2 einrichten

Kernel‑Level‑Malware: Schutz, Erkennung, Wiederherstellung
Sicherheit

Kernel‑Level‑Malware: Schutz, Erkennung, Wiederherstellung