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

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
Автор
Редакция

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

Отключить рекламу на экране блокировки Windows 10
Windows

Отключить рекламу на экране блокировки Windows 10

Исправление ошибок Microsoft Store
Windows

Исправление ошибок Microsoft Store

Удаление старых точек восстановления Windows
Windows

Удаление старых точек восстановления Windows

Расширенный поиск Google: практическое руководство
Поиск

Расширенный поиск Google: практическое руководство

Изменять размер изображений из контекстного меню
Windows

Изменять размер изображений из контекстного меню

Изменить частоту резервных копий Time Machine
Резервное копирование

Изменить частоту резервных копий Time Machine