Гид по технологиям

FreeRADIUS + LinOTP 2: двухфакторная аутентификация OTP

4 min read Безопасность Обновлено 01 Oct 2025
FreeRADIUS + LinOTP 2: OTP двухфакторная аутентификация
FreeRADIUS + LinOTP 2: OTP двухфакторная аутентификация

О чём этот материал

Это пошаговое руководство объясняет, как использовать LinOTP 2 (Community Edition) как backend для одноразовых паролей и подключить его к FreeRADIUS. LinOTP поддерживает множество аппаратных токенов, программных токенов и SMS. Вариант ориентирован на быструю и простой интеграцию с помощью HTTP API LinOTP и модуля rlm_perl FreeRADIUS.

API LinOTP

Enterprise-версия LinOTP поставляется с C-модулем для FreeRADIUS, но Community Edition (AGPLv3) такого модуля не содержит. Зато LinOTP предоставляет простой WEB API для проверок одноразовых паролей.

Пример запроса проверки пароля:

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

или короткий вариант:

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

Полный список API можно посмотреть в документации LinOTP.

FreeRADIUS и подход через rlm_perl

Самый простой подход — использовать rlm_perl и сделать HTTP-запрос к LinOTP из функции authenticate. Можно было бы применить rlm_exec и запускать внешний скрипт, но rlm_perl даёт более прямую интеграцию и меньшую задержку.

Модуль rlm_perl имеет пример, который нужно адаптировать в функции authenticate. Там мы формируем запрос к LinOTP и возвращаем ответ FreeRADIUS в зависимости от результата проверки.

Пример Perl-модуля для rlm_perl

Ниже приведён пример Perl-модуля. Важно: этот блок кода сохранён в оригинале и должен быть вставлен в файл модуля без изменений в синтаксисе.

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

Примечание: вам нужно изменить переменную $URL в модуле Perl на адрес вашего LinOTP-сервера.

Настройка FreeRADIUS

Типичная настройка включает:

  • В файле /etc/freeradius/users установить DEFAULT Auth-Type := perl для нужных пользователей.
  • В /etc/freeradius/modules/perl указать модуль (файл с приведённым выше кодом).
  • В /etc/freeradius/sites-enabled/<ваш_сайт> добавить вызов perl в секции authenticate.

Проверьте права доступа к файлу модуля и что FreeRADIUS может читать/исполнять его.

Безопасность и надёжность

Важно учитывать несколько моментов безопасности:

  • SSL: пример кода не проверяет сертификат сервера LinOTP. В продакшене обязательно валидируйте сертификаты HTTPS и используйте надежные CA. Без проверки сертификата возможны MITM-атаки.
  • Доступность: Single Point of Failure — если LinOTP недоступен, аутентификация по OTP провалится. Подумайте о резервировании и балансировке.
  • Логирование: не записывайте в логи чистые пароли и одноразовые коды.
  • Ошибки: добавьте обработку ошибок HTTP и таймауты, чтобы FreeRADIUS не зависал.

Совет: используйте системные средства проверки сертификатов (например, LWP::Protocol::https с корректной конфигурацией SSL) и ограничьте число повторных запросов.

Когда этот подход не подходит

  • Если у вас строгие требования по задержкам и пакетной обработке, HTTP-запросы в authenticate могут не подойти.
  • Если нужна высокая производительность при тысячах запросов в секунду, лучше использовать нативный C-модуль или локальное кеширование состояний.
  • Если требуется централизованная поддержка и коммерческая поддержка, рассмотрите Enterprise-версию LinOTP.

Альтернативы

  • Использовать rlm_exec и запускать внешний бинарник/скрипт (может быть проще с существующим кодом).
  • Настроить нативный модуль для FreeRADIUS (если доступен в вашей сборке или в Enterprise-сборке LinOTP).
  • Промежуточный прокси с кешированием ответов LinOTP для снижения нагрузки на сеть.

Мини-методология внедрения

  1. Тестирование в изолированной среде: настройте тестовую FreeRADIUS-сервер и LinOTP.
  2. Подключите rlm_perl с модулем, измените $URL на тестовый LinOTP.
  3. Протестируйте положительные и отрицательные сценарии.
  4. Добавьте обработку ошибок, таймауты и проверку SSL.
  5. Перенесите в продакшн с мониторингом и резервированием.

Рольовые чеклисты

Администратор FreeRADIUS:

  • Убедиться, что rlm_perl подключён и файл модуля доступен.
  • Проверить права и SELinux/AppArmor политики.

Оператор LinOTP:

  • Убедиться, что API /validate доступен.
  • Проверить сертификаты HTTPS и цепочку доверия.

DevOps:

  • Настроить таймауты, алерты и метрики доступности.
  • Организовать резервирование LinOTP и балансировку.

Критерии приёмки

  • FreeRADIUS успешно аутентифицирует пользователя с корректным OTP.
  • При неверном OTP запрос отклоняется и возвращается понятное сообщение.
  • Логи не содержат открытых паролей или OTP.
  • Сервер LinOTP валидирует TLS-сертификат и проходит интеграционные тесты.

Короткое заключение

Это простой и быстрый способ интегрировать LinOTP Community Edition с FreeRADIUS через HTTP API и модуль rlm_perl. Подойдёт для тестовых сред и небольших production-сценариев при условии доработки по безопасности, обработки ошибок и резервирования.

Важно: до ввода в эксплуатацию внедрите проверку сертификатов, обработку ошибок и планы отказоустойчивости.

Ключевые выводы:

  • LinOTP предоставляет простой API для проверки OTP.
  • rlm_perl позволяет выполнять HTTP-запросы в процессе аутентификации.
  • Требуется доработка безопасности и высокодоступности перед продакшн-внедрением.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Закрепить верхние 2 строки в Excel — быстро
Excel

Закрепить верхние 2 строки в Excel — быстро

Восстановить исчезнувшие каналы Microsoft Teams
Поддержка

Восстановить исчезнувшие каналы Microsoft Teams

Как обновить Windows 11: 3 быстрых способа
Windows

Как обновить Windows 11: 3 быстрых способа

Настройка терминального сервера RDS на Server 2008 R2
IT

Настройка терминального сервера RDS на Server 2008 R2

Вредоносное ПО уровня ядра: защита и восстановление
Кибербезопасность

Вредоносное ПО уровня ядра: защита и восстановление

Как посмотреть дизлайки на YouTube — вернуть счётчик
Руководство

Как посмотреть дизлайки на YouTube — вернуть счётчик