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 검증, 로깅, 타임아웃/재시도, 고가용성 구성 등 추가 작업이 필요합니다.
요점: 이 예제는 빠른 통합을 위한 출발점입니다. 실제 서비스에 적용하기 전 보안·운영 요구사항을 채워 넣으세요.