Guide des technologies

Utiliser FreeRADIUS avec LinOTP 2 pour l'authentification à deux facteurs (OTP)

5 min read Sécurité Mis à jour 01 Oct 2025
FreeRADIUS + LinOTP 2 : authentification 2FA (OTP)
FreeRADIUS + LinOTP 2 : authentification 2FA (OTP)

Objectif principal

Fournir une intégration simple entre FreeRADIUS et LinOTP 2 (édition communautaire) pour réaliser une authentification à deux facteurs basée sur OTP.

Variantes de recherche utiles

  • FreeRADIUS LinOTP
  • authentification RADIUS OTP
  • rlm_perl LinOTP
  • configuration FreeRADIUS OTP
  • LinOTP API

Aperçu de l’API LinOTP

LinOTP Community Edition fournit une API HTTP simple pour valider des OTP. L’appel d’authentification typique ressemble à :

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

ou

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

Ces endpoints retournent une réponse concise indiquant si l’OTP est valide. Adaptez l’URL selon votre serveur LinOTP et votre configuration TLS.

Important: vérifiez toujours la connectivité TLS et l’authenticité du serveur LinOTP (cf. section Sécurité et limites).

Intégration avec FreeRADIUS

La version Enterprise de LinOTP propose un module C pour FreeRADIUS. La Community Edition, sous AGPLv3, n’en dispose pas ; toutefois l’API web permet d’intégrer LinOTP via un module externe. Deux approches courantes :

  • Utiliser rlm_exec pour appeler un script externe.
  • Utiliser rlm_perl pour écrire un petit module Perl exécuté par FreeRADIUS (exemple ci‑dessous).

Cet article fournit un exemple pour rlm_perl basé sur l’exemple officiel, modifié pour consulter l’API LinOTP depuis la fonction authenticate.

Exemple de module Perl (rlm_perl)

Le module Perl suivant est un exemple pré‑bêta montrant comment interroger LinOTP depuis FreeRADIUS. Adaptez la variable $URL et améliorez la gestion d’erreurs et TLS avant production.

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

Remarque rapide : modifiez la variable $URL pour pointer vers votre serveur LinOTP et choisissez check ou simplecheck selon vos préférences d’API.

Fichiers FreeRADIUS à configurer

  • /etc/freeradius/users : ajouter une règle par défaut pour utiliser Perl
    • Exemple : DEFAULT Auth-Type := perl
  • /etc/freeradius/modules/perl : pointer module vers le fichier Perl créé ci‑dessus
  • /etc/freeradius/sites-enabled/ : dans la section authenticate appeler perl

Checklist de déploiement

  • [ ] Adapter $URL dans le module Perl.
  • Installer et activer rlm_perl (module Perl) dans FreeRADIUS.
  • Tester la connectivité HTTPS entre le serveur FreeRADIUS et LinOTP.
  • Vérifier que les attributs RADIUS (User-Name / User-Password) sont transmis correctement.
  • Activer le logging détaillé en environnement de test.
  • Préparer rollback : restaurer configuration FreeRADIUS antérieure.

Critères d’acceptation

  • Une requête avec un OTP valide retourne une autorisation RADIUS (Access-Accept).
  • Une requête avec un OTP invalide reçoit un refus (Access-Reject) et un message explicite dans Reply-Message.
  • Les erreurs HTTP vers LinOTP entraînent un échec contrôlé et des logs explicites.
  • Les modifications sont réversibles et testées en environnement de préproduction.

Sécurité et limites

Important: l’exemple ci‑dessous n’effectue pas de vérification stricte du certificat TLS du serveur LinOTP. En production, configurez LWP::UserAgent pour valider :

  • la chaîne de certificats (CA trust),
  • le nom d’hôte (verify_hostname),
  • et limitez les ciphers si nécessaire.

Limites à considérer :

  • tolérance et redondance : prévoyez un mécanisme de basculement si LinOTP devient indisponible ;
  • journalisation et alerting pour les échecs répétés ;
  • protections contre le rejeu d’OTP si nécessaire.

Alternatives et approches complémentaires

  • Utiliser le module C fourni avec LinOTP Enterprise pour une intégration native à FreeRADIUS.
  • Appeler un script externe via rlm_exec si vous préférez implémenter la logique dans un langage autre que Perl.
  • Intégrer un cache local de validation pour tolérer de courts isolats réseau (attention à la sécurité).

Méthodologie rapide (mini‑méthode)

  1. Préparer un environnement de test dédié (FreeRADIUS + LinOTP).
  2. Déployer le module Perl et configurer FreeRADIUS pour l’utiliser.
  3. Tester avec comptes d’essai et OTP valides/invalide.
  4. Activer la vérification TLS et durcir la configuration.
  5. Monter en production avec supervision.

Matrice de risques (qualitative)

  • Indisponibilité LinOTP : impact élevé — mitigation : redondance et bascule.
  • Connexions TLS non vérifiées : impact élevé — mitigation : activer la vérification de certificat.
  • Logs insuffisants : impact moyen — mitigation : augmenter le niveau de log et centraliser.

Test cases / Cas de test

  • Cas 1 : OTP valide -> Access-Accept attendu.
  • Cas 2 : OTP invalide -> Access-Reject attendu et message d’erreur.
  • Cas 3 : LinOTP indisponible -> comportement défini (fail-close ou fail-open selon votre politique), logs d’erreur.

Diagramme de décision (flux simplifié)

flowchart TD
  A[Requête d'authentification RADIUS] --> B{User-Password présent ?}
  B -- Non --> C[Procéder à la méthode d'auth existante]
  B -- Oui --> D[Appeler LinOTP via API HTTP]
  D --> E{Réponse OK ?}
  E -- Oui --> F[Return RLM_MODULE_OK 'Access-Accept']
  E -- Non --> G[Return RLM_MODULE_REJECT 'Access-Reject']
  D --> H{Erreur HTTP ?}
  H -- Oui --> I[Log erreur & appliquer politique d'échec]
  I --> G

Glossaire (1 ligne)

  • LinOTP : backend d’authentification par mots de passe à usage unique (OTP).
  • FreeRADIUS : serveur RADIUS open source pour authentification, autorisation et accounting.

Conclusion

Cette intégration montre une manière simple d’utiliser l’API LinOTP avec FreeRADIUS via rlm_perl. Avant la production, améliorez la gestion TLS, ajoutez la surveillance et testez la redondance. L’approche est flexible et vous permet d’adapter la logique (rlm_exec, module C Enterprise) selon vos contraintes opérationnelles.

Important: évitez toute mise en production sans validation TLS et sans tests en environnement contrôlé.

Résumé rapide :

  • Intégration possible via l’API HTTP de LinOTP ;
  • Exemple prêt à l’emploi avec rlm_perl ;
  • Nécessite durcissement TLS, logging et plan de haute disponibilité.
Auteur
Édition

Matériaux similaires

Installer et utiliser Podman sur Debian 11
Conteneurs

Installer et utiliser Podman sur Debian 11

Guide pratique : apt-pinning sur Debian
Administration système

Guide pratique : apt-pinning sur Debian

OptiScaler : activer FSR 4 dans n'importe quel jeu
Jeux PC

OptiScaler : activer FSR 4 dans n'importe quel jeu

Dansguardian + Squid NTLM sur Debian Etch
réseau

Dansguardian + Squid NTLM sur Debian Etch

Corriger l'erreur d'installation Android sur SD
Android, Dépannage

Corriger l'erreur d'installation Android sur SD

KNetAttach et remote:/ — Dossiers réseau KDE
Tutoriel

KNetAttach et remote:/ — Dossiers réseau KDE