기술 가이드

FreeRADIUS와 LinOTP 2로 OTP 기반 이중 인증 설정 방법

4 min read 인증 업데이트됨 01 Oct 2025
FreeRADIUS + LinOTP 2로 OTP 이중 인증 구성
FreeRADIUS + LinOTP 2로 OTP 이중 인증 구성

목표와 주요 변형 검색어

이 문서는 FreeRADIUS와 LinOTP 2를 연동해 일회용 비밀번호(OTP)를 이용한 이중 인증을 구성하는 방법을 설명합니다. 관련 변형 키워드: FreeRADIUS LinOTP 설정, LinOTP RADIUS 연동, OTP 이중 인증, rlm_perl LinOTP, RADIUS OTP 구성

개요

LinOTP는 다양한 하드웨어 토큰, 소프트웨어 토큰, SMS를 지원하는 일회용 비밀번호(OTP) 백엔드입니다. Enterprise 에디션은 FreeRADIUS용 C 모듈을 제공하지만, Community Edition(AGPLv3)은 제공하지 않습니다. 대신 LinOTP는 간단한 웹 API를 제공하므로 FreeRADIUS에서 HTTP 호출을 통해 인증 여부를 확인할 수 있습니다.

중요: 이 가이드는 개념 증명(pre-beta) 수준의 예제입니다. 운영 환경에서는 SSL 인증서 검증, 중복성, 상세 로깅 및 예외 처리 등을 반드시 구현해야 합니다.

LinOTP 인증 API

LinOTP에서 인증을 확인하는 기본 URL 예시:

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

또는

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

이 API는 특정 사용자에 대해 전달된 OTP가 유효한지 여부를 간단히 응답합니다. Community Edition은 이 HTTP 기반 API를 통해 쉽게 통합됩니다.

FreeRADIUS 쪽 접근 방식

FreeRADIUS에서는 외부 프로그램 실행(rlm_exec) 대신 perl 통합(rlm_perl)을 사용해 LinOTP API를 호출하는 방식이 간단하고 유연합니다. rlm_perl 예제를 인증 흐름(authenticate 함수)만 수정해 LinOTP에 질의하고, 반환 결과에 따라 RADIUS 응답을 결정하면 됩니다.

예제 perl 모듈

아래 perl 스크립트는 rlm_perl 예제를 LinOTP 검증용으로 수정한 것입니다. 실제 운영 전 $URL을 LinOTP 서버로 변경하고 SSL/TLS 검증, 타임아웃, 에러 로그를 강화하세요.

#
#  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 변수를 실제 LinOTP 서버의 검증 엔드포인트(예: https://linotp.example/validate/simplecheck)로 변경하세요. 또한 LWP::UserAgent 기본 설정으로는 SSL 검증을 하지 않을 수 있으니, 운영 환경에서는 SSL 검증을 명시적으로 활성화하세요.

FreeRADIUS 설정 요약

  • /etc/freeradius/users에서 DEFAULT Auth-Type := perl 설정
  • /etc/freeradius/modules/perl에서 module 경로를 예제 perl 파일로 지정
  • 사이트 설정(/etc/freeradius/sites-enabled/)의 authenticate 섹션에서 perl 호출 추가

운영에서 고려할 보안 및 신뢰성 강화

  • SSL/TLS 검증: LWP::UserAgent에 SSL 인증서 검증을 강제하세요. 서버 인증서가 자체 서명일 경우 CA로 신뢰하도록 구성하세요.
  • 타임아웃 및 재시도: 네트워크 장애에 대비해 HTTP 타임아웃과 재시도를 구현하세요. 무한 대기나 블로킹 호출을 피하세요.
  • 로깅: 실패 원인(네트워크, 500 응답, 파싱 실패 등)을 상세히 로깅하되, OTP 자체나 민감한 패스워드는 로그에 남기지 마세요.
  • 고가용성: LinOTP 서버를 다중화하거나 로드밸런서를 통해 중단 위험을 줄이세요.
  • 권한: FreeRADIUS와 perl 스크립트 파일의 파일 권한을 최소화하세요.

대체 접근법

  • rlm_exec 사용: 외부 바이너리나 스크립트를 호출해 LinOTP API를 호출하는 방식. 디버깅이 수월하지만 프로세스 생성 비용이 큼.
  • Enterprise C 모듈: LinOTP Enterprise가 제공하는 C 모듈을 사용하면 더 낮은 레이턴시와 안정성을 얻을 수 있음(상업적 옵션).
  • 프록시 서비스: 내부에 작은 인증 프록시를 두고 FreeRADIUS는 로컬 프록시와 통신하게 하여 복잡도를 분리.

실패 사례와 주의점

  • 네트워크 단절 시 모든 인증 요청이 실패할 수 있음(서비스 가용성 저하). 대비책: 캐시 기반 예비 인증, 또는 이중화.
  • SSL 미검증: 중간자 공격(MITM)에 취약. 운영에서는 항상 인증서를 검증하세요.
  • 응답 포맷 변화: LinOTP의 응답 패턴이 바뀌면 정규식 매칭이 실패할 수 있음. API 스펙을 확인하고 견고한 파싱을 구현하세요.

역할별 체크리스트

  • 시스템 관리자:
    • $URL을 실제 서버로 설정하고 파일 권한 확인
    • 방화벽에서 FreeRADIUS와 LinOTP 간 통신 허용
  • 보안 책임자:
    • SSL 인증서 정책 검토 및 모니터링
    • OTP/로그 보관 정책 결정
  • 네트워크 운영자:
    • LinOTP 서버에 대한 가용성(로드밸런서/백업) 구성
    • 모니터링 임계치 및 알람 설정

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

  • FreeRADIUS가 LinOTP에 질의하여 유효한 OTP에 대해 ACCESS-ACCEPT를 반환한다.
  • 잘못된 OTP에 대해 ACCESS-REJECT를 반환한다.
  • 네트워크 실패 시 적절한 로그가 수집되고, 재시도 정책이 동작한다.
  • SSL 인증서 검증이 적용되어 중간자공격으로부터 보호된다.

간단한 점검(테스트 케이스)

  • 정상 케이스: 올바른 사용자명과 유효한 OTP를 전송해 인증 성공 확인
  • 오류 케이스: 잘못된 OTP로 인증 실패 확인 및 Reply-Message 확인
  • 네트워크 오류: LinOTP 서비스 차단 시 FreeRADIUS가 적절히 실패 처리(로그/거부)하는지 확인

요약

  • LinOTP Community는 간단한 HTTP API로 FreeRADIUS와 쉽게 통합됩니다.
  • rlm_perl을 사용하면 Perl에서 LinOTP API를 호출해 인증 흐름을 제어할 수 있습니다.
  • 운영 환경에서는 SSL 검증, 로깅, 타임아웃/재시도, 고가용성 구성 등 추가 작업이 필요합니다.

요점: 이 예제는 빠른 통합을 위한 출발점입니다. 실제 서비스에 적용하기 전 보안·운영 요구사항을 채워 넣으세요.

공유하기: X/Twitter Facebook LinkedIn Telegram
저자
편집

유사한 자료

Debian 11에 Podman 설치 및 사용하기
컨테이너

Debian 11에 Podman 설치 및 사용하기

Apt-Pinning 간단 소개 — Debian 패키지 우선순위 설정
시스템 관리

Apt-Pinning 간단 소개 — Debian 패키지 우선순위 설정

OptiScaler로 FSR 4 주입: 설치·설정·문제해결 가이드
그래픽 가이드

OptiScaler로 FSR 4 주입: 설치·설정·문제해결 가이드

Debian Etch에 Dansguardian+Squid(NTLM) 구성
네트워크

Debian Etch에 Dansguardian+Squid(NTLM) 구성

안드로이드 SD카드 설치 오류(Error -18) 완전 해결
안드로이드 오류

안드로이드 SD카드 설치 오류(Error -18) 완전 해결

KNetAttach로 원격 네트워크 폴더 연결하기
네트워킹

KNetAttach로 원격 네트워크 폴더 연결하기