Guida alle tecnologie

Come usare FreeRADIUS con LinOTP 2 per l'autenticazione a due fattori con password monouso

5 min read Sicurezza Aggiornato 01 Oct 2025
FreeRADIUS + LinOTP 2: Autenticazione 2FA
FreeRADIUS + LinOTP 2: Autenticazione 2FA

Importante: la versione Community usa l’API web di LinOTP e non il modulo C disponibile nell’Enterprise. Verificate sempre i certificati SSL e aggiungete logging e ridondanza in produzione.

Panoramica

LinOTP è un backend per password monouso che permette l’autenticazione a due fattori con vari dispositivi hardware, token software e SMS. FreeRADIUS può interrogare LinOTP tramite le sue semplici API web per validare le OTP.

Questa guida copre:

  • come funziona l’API di LinOTP;
  • come usare rlm_perl per chiamare LinOTP da FreeRADIUS;
  • uno script perl di esempio pronto da adattare;
  • considerazioni operative, sicurezza e alternative.

L’API di LinOTP

L’API di LinOTP consente di verificare se una OTP è valida per un utente. Esempi di URL di validazione:

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

oppure

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

La risposta è testuale; lo script qui sotto controlla se il contenuto contiene “:-)” per determinare l’esito positivo.

Note: verificate la versione della vostra istanza LinOTP e i percorsi dell’API prima di mettere in produzione.

FreeRADIUS e rlm_perl

È possibile usare rlm_exec per eseguire un esterno, ma usare rlm_perl permette di integrare la chiamata a LinOTP direttamente nel flusso di autenticazione di FreeRADIUS. Adattate la funzione authenticate dello script di esempio per interrogare l’API di LinOTP.

Configurazione minima richiesta (esempi):

  • in /etc/freeradius/users impostare DEFAULT Auth-Type := perl
  • in /etc/freeradius/modules/perl puntare al modulo perl creato
  • in /etc/freeradius/sites-enabled/ aggiungere perl nella sezione authenticate

Lo script perl di esempio

Copiate e incollate il seguente modulo perl nel file indicato dalla configurazione di rlm_perl. Adattate la variabile $URL al vostro server LinOTP. Il blocco di codice qui sotto è ripreso dall’originale e deve rimanere esattamente come mostrato se lo utilizzate così com’è.

#
#  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;

Modificate la variabile $URL nello script perl con l’endpoint corretto della vostra istanza LinOTP. Aggiungete inoltre il controllo del certificato SSL e timeout adeguati.

Quando questa soluzione può fallire

  • Se l’API LinOTP non è raggiungibile (network, firewall o DNS).
  • Se la risposta dell’API cambia formato rispetto all’aspettativa (non contiene “:-)”).
  • Mancanza di validazione SSL espone a attacchi Man-in-the-Middle.
  • Scalabilità: chiamate sincrone verso LinOTP possono creare colli di bottiglia.

Approcci alternativi

  • Usare il modulo C fornito con LinOTP Enterprise per integrazione più stretta e performance superiori.
  • Implementare una cache TTL delle risposte OTP per ridurre latenza (con attenzione alla sicurezza).
  • Usare rlm_exec per chiamare un wrapper esterno (in Python/Go) che gestisce retry, logging avanzato e metriche.

Modello mentale e best practice

Mental model: FreeRADIUS delega la validazione delle credenziali a LinOTP. Trattate LinOTP come un servizio esterno: autenticarne e verificarne l’integrità e la disponibilità sono compiti chiave.

Heuristics:

  • Validazione SSL obbligatoria in produzione.
  • Timeout corti e circuit breaker per evitare di bloccare RADIUS.
  • Logging strutturato e monitoraggio SLI/SLO per le chiamate API.

Hardening della sicurezza

  • Abilitare e verificare i certificati TLS del server LinOTP.
  • Limitare l’accesso al server LinOTP via firewall e VPN.
  • Non loggare le OTP in chiaro.
  • Usare canali interni sicuri per le chiamate tra FreeRADIUS e LinOTP.

Privacy e GDPR

  • Le OTP sono dati sensibili; minimizzate la conservazione dei log che li contengono.
  • Se memorizzate eventi di autenticazione, pseudonimizzate o limitate i campi a quelli strettamente necessari.
  • Definite una retention policy per i log conformemente alle regole locali.

Checklist per ruoli

  • Amministratore RADIUS:
    • Impostare DEFAULT Auth-Type := perl e puntare al modulo.
    • Testare autenticazioni con utenti di prova.
  • Amministratore LinOTP:
    • Verificare endpoint validate/simplecheck e certificati TLS.
    • Monitorare la coda di richieste e carico.
  • Security engineer:
    • Configurare firewall, VPN e policy di logging.
    • Eseguire penetration test sulle chiamate API.

Mini-metodologia di rollout

  1. Preparare ambiente di staging con FreeRADIUS e LinOTP.
  2. Installare e configurare rlm_perl con il modulo perl di esempio.
  3. Testare casi positivi e negativi (vedere criteri di accettazione).
  4. Abilitare logging e monitoraggio.
  5. Rilasciare in produzione in orari a basso traffico e monitorare.

Criteri di accettazione (test cases)

  • Login utente valido con OTP corretto restituisce accesso.
  • Login utente con OTP errato viene respinto e genera evento di log.
  • Timeout verso LinOTP causa rifiuto con messaggio di errore controllato.
  • Nessuna OTP salvata in log in chiaro.

Conclusione

Questa soluzione è semplice e rapida da implementare: dimostra come FreeRADIUS possa delegare la verifica delle OTP a LinOTP tramite l’API web. Per l’uso in produzione è essenziale migliorare il controllo dei certificati, aggiungere logging, metriche, retry e ridondanza.

Riepilogo rapido:

  • Usate rlm_perl per chiamare l’API di LinOTP;
  • Modificate $URL nello script perl e controllate TLS;
  • Implementate hardening, monitoraggio e test prima del rollout.
Autore
Redazione

Materiali simili

Ripristinare canali scomparsi in Microsoft Teams
Microsoft Teams

Ripristinare canali scomparsi in Microsoft Teams

Aggiornare il desktop in Windows 11: 3 metodi rapidi
Guide Windows

Aggiornare il desktop in Windows 11: 3 metodi rapidi

Server Terminal su Server 2008 R2 — Guida RDS
Infrastruttura IT

Server Terminal su Server 2008 R2 — Guida RDS

Malware a livello kernel: come riconoscerlo e difendersi
Sicurezza

Malware a livello kernel: come riconoscerlo e difendersi

Come vedere i dislike su YouTube: browser e Android
Guide.

Come vedere i dislike su YouTube: browser e Android

FreeRADIUS + LinOTP 2: Autenticazione 2FA
Sicurezza

FreeRADIUS + LinOTP 2: Autenticazione 2FA