Eduroam
RadSecProxy
- RADIUS 프로토콜을 TLS 기반으로 확장한 RadSec 서비스를 제공하는 프록시
- RADIUS 요청을 암호화하여 안전하게 전송할 수 있게 해주는 역할
- 무선 AP 등에서 발생하는 RADIUS 요청을 Radsec을 통해 안전하게 전달하는 중개 서버 역할을 하며, RADIUS 인증 트래픽의 보안 강화에 사용
FreeRADIUS
- RADIUS 프로토콜을 구현한 오픈소스 AAA 서버로 네트워크 접속 제어를 위한 사용자 인증 및 권한 부여, 계정 정보 관리
- 주로 VPN, 무선 네트워크, 스위치 등 클라이언트 인증을 담당하며 다양한 인증 메커니즘과 외부 데이터베이스, LDAP 연동 기능 등을 지원
RadSecProxy와 FreeRADIUS의 차이점
- RadSecProxy는 RADIUS 프로토콜을 TLS로 감싸서 보안 전송을 지원하는 프록시 서버이며, RADIUS 요청을 중계하고 암호화 함
- FreeRADIUS는 RADIUS 서버로서 사용자 인증과 권한 부여, 계정 관리 기능을 수행하는 인증 서버
- RadSecProxy는 RADIUS 프로토콜을 TLS로 감싸서 보안 전송을 지원하는 프록시 서버이며, FreeRADIUS는 실제 인증 서버스와 사용자 관리 기능을 제공하는 서버
Clickhouse
- 컬럼 지향의 고성능 데이터베이스 관리 시스템으로서, 대용량 데이터에 대해 빠른 분석과 실시간 쿼리를 지원
- 초당 수억 건의 데이터를 효율적으로 처리할 수 있어 실시간 분석과 통계 모니터링 등 대규모 데이터 처리에 최적화
Logstash
- 오픈소스 데이터 수집 및 처리 엔진
- 다양한 데이터 소스를 수집하여 실시간으로 필터링, 변환, 정규화 등의 가공 작업을 수행
- 입력, 필터, 출력 플러그인 구조를 갖추고 있어 다양한 데이터 유형을 처리하고, 최종적으로 저장소 혹은 다른 시스템으로 전달하는 역할
Filebeat
- Elastic Stack의 경량 로그 수집기
- 서버나 시스템에서 생성되는 로그 파일을 모니터링하여 실시간으로 로그 데이터를 수집하고 이를 중앙 시스템에 전송하는 역할
- 운영 중인 여러 서버에서 발생하는 로그 파일을 읽어들여 Logstash나 Elasticsearch 같은 로그 처리 및 저장 시스템으로 전달
Filebeat, Logstash, Clickhouse의 관계
- Filebeat는 분산된 서버에서 로그를 경량 수집해 Logstash로 전송 ( 데이터 초기 수집)
- Logstash는 수집된 로그를 필터링, 가공하여 분석에 적합한 형태로 변환 후 저장소로 보냄 (데이터 전처리 및 파이프라인 역할)
- Clickhouse는 Logstash가 보낸 데이터를 고성능으로 저장하고 대용량 실시간 분석 수행 (저장 및 고속 분석 역할)
인증서 소유
- RadSecProxy가 RADIUS 인증 요청을 다른 서버로 프록시할 때, 인증서 검사 및 TLS 연결 설정은 인증 요청을 받는 서버에서 관리된다.
- 즉, 프록시 요청을 보내는 서버는 다른 서버의 인증서를 직접 보유할 필요가 없다.
인증서 생성
# 루트 인증서 # ca.key openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:2048 # ca.pem openssl req -new -x509 -days 365 -key ca.key -out ca.pem -subj "/C=KR/ST=chanegeme/L=changeme/O=changeme/OU=changeme/CN=changeme" # 서버 인증서 # server.key openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048 # server.csr openssl req -new -key server.key -out server.csr -subj "/C=KR/ST=changeme/L=chanegme/O=changeme/OU=changeme/CN=server.domain.or.ip" # CA로 CSR 서명 openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 365 -sha256
Eduroam 인증서 만료시 발생 로그
Mon Nov 17 08:05:56 2025 : ERROR: (0) ERROR: (TLS) RADIUS/TLS - Alert read:fatal:certificate expired Mon Nov 17 08:05:56 2025 : Info: ... shutting down socket auth from client (0.0.0.0, 44409) -> (*, 2083, virtual-server=default) Mon Nov 17 08:05:58 2025 : Info: ... adding new socket auth from client (0.0.0.0, 38253) -> (*, 2083, virtual-server=default) Mon Nov 17 08:05:58 2025 : ERROR: (0) ERROR: (TLS) RADIUS/TLS - Alert read:fatal:certificate expired Mon Nov 17 08:05:58 2025 : Info: ... shutting down socket auth from client (0.0.0.0, 38253) -> (*, 2083, virtual-server=default) Mon Nov 17 08:06:00 2025 : Info: ... adding new socket auth from client (0.0.0.0, 34935) -> (*, 2083, virtual-server=default) Mon Nov 17 08:06:00 2025 : ERROR: (0) ERROR: (TLS) RADIUS/TLS - Alert read:fatal:certificate expired Mon Nov 17 08:06:00 2025 : Info: ... shutting down socket auth from client (0.0.0.0, 34935) -> (*, 2083, virtual-server=default) Mon Nov 17 08:06:04 2025 : Info: ... adding new socket auth from client (0.0.0.0, 37521) -> (*, 2083, virtual-server=default) Mon Nov 17 08:06:04 2025 : ERROR: (0) ERROR: (TLS) RADIUS/TLS - Alert read:fatal:certificate expired Mon Nov 17 08:06:04 2025 : Info: ... shutting down socket auth from client (0.0.0.0, 37521) -> (*, 2083, virtual-server=default)
RadSecProxy Config
#radsecproxy.conf
tls default {
CACertificateFile /your/path/ca.pem
CertificateFile /your/path/server.pem
CertificateKeyFile /your/path/server.key
}
# 2. 로컬 FreeRADIUS 정의 (RadSecProxy가 받은 요청을 넘겨줄 곳)
client local-radius {
host 127.0.0.1`
type udp
secret changeme
fticksVISCOUNTRY KR # F-ticks Country Code
fticksVISINST Server1 # F-ticks Your Private Name
}
client nro-client {
host Your NRO Server IP
type tls
secret changeme
fticksVISCOUNTRY KR # F-ticks Country Code
fticksVISINST NRO # F-ticks Your Private Name
}
client test-monitor {
host Your Monitoring Server IP
type udp
secret changeme
fticksVISCOUNTRY KR # F-ticks Country Code
fticksVISINST MONITOR # F-ticks Your Private Name
}
server nro-server {
host Yout NRO Server IP
port 2083
type tls
tls default
secret changeme
}
server local-radius {
host 127.0.0.1
port 1812
type udp
secret changeme
}
# 5. 라우팅 (Realm) 설정
realm Your realm {
server local-radius
}
realm * {
server nro-server
}
# 포트 개방
ListenTLS *:2083
ListenUDP *:18121
#F-Ticks
FTicksReporting Basic
FTicksPrefix F-TICKS/eduroam/1.0
FTicksKey C9GovB1Lt1mjYxrp
FTicksMAC VendorKeyHashed
FTicksSyslogFacility = LOG_LOCAL1
SQL Config
## mods-available/sql -- SQL modules
sql {
dialect = "mysql"
driver = "rlm_sql_mysql"
sqlite {
filename = "/tmp/freeradius.db"
busy_timeout = 200
bootstrap = "${modconfdir}/${..:name}/main/sqlite/schema.sql"
}
mysql {
warnings = auto
}
postgresql {
send_application_name = yes
}
mongo {
appname = "freeradius"
tls {
certificate_file = /path/to/file
certificate_password = "password"
ca_file = /path/to/file
ca_dir = /path/to/directory
crl_file = /path/to/file
weak_cert_validation = false
allow_invalid_hostname = false
}
}
server = "localhost"
port = 3306
login = "radius"
password = "changeme"
radius_db = "radius"
acct_table1 = "radacct"
acct_table2 = "radacct"
postauth_table = "radpostauth"
authcheck_table = "radcheck"
groupcheck_table = "radgroupcheck"
authreply_table = "radreply"
groupreply_table = "radgroupreply"
usergroup_table = "radusergroup"
delete_stale_sessions = yes
pool {
start = ${thread[pool].start_servers}
min = ${thread[pool].min_spare_servers}
max = ${thread[pool].max_servers}
spare = ${thread[pool].max_spare_servers}
uses = 0
retry_delay = 30
lifetime = 0
idle_timeout = 60
max_retries = 5
}
client_table = "nas"
group_attribute = "SQL-Group"
$INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
}
Clickhouse Logstash Config
input {
beats {
port => 5044
}
}
filter {
# Syslog 헤더 분리
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:syslog_timestamp} %{HOSTNAME:syslog_host} %{DATA:syslog_program}: %{GREEDYDATA:fticks_data}" }
}
# F-TICKS 로그만 처리
if [fticks_data] =~ /F-TICKS\/eduroam/ {
dissect {
mapping => {
"fticks_data" => "F-TICKS/%{federation}/%{version}#%{kv_data}"
}
}
kv {
source => "kv_data"
field_split => "#"
value_split => "="
trim_key => "#"
}
mutate {
rename => {
"REALM" => "realm"
"VISCOUNTRY" => "viscountry"
"VISINST" => "visinst"
"CSI" => "csi"
"RESULT" => "result"
}
}
date {
match => [ "syslog_timestamp", "ISO8601" ]
target => "@timestamp"
}
# ClickHouse용 포맷 문자열로 변환하여 'timestamp_str' 필드에 저장
ruby {
code => "event.set('timestamp_str', event.get('@timestamp').time.localtime.strftime('%Y-%m-%d %H:%M:%S'))"
}
mutate {
add_tag => ["fticks_success"]
copy => { "message" => "original_message" }
remove_field => ["kv_data", "fticks_data", "syslog_program", "syslog_host", "message", "syslog_timestamp"]
}
}
else {
drop { }
}
}
output {
if "fticks_success" in [tags] {
clickhouse {
http_hosts => ["http://127.0.0.1:8123"]
table => "fticks"
mutations => {
"timestamp" => "timestamp_str"
"federation" => "federation"
"version" => "version"
"realm" => "realm"
"viscountry" => "viscountry"
"visinst" => "visinst"
"csi" => "csi"
"result" => "result"
"original_message" => "original_message"
}
}
}
}
Test-Command
#basic echo "User-Name=testuser@kisti.re.kr,User-Password=changeme" | radclient -x 127.0.0.1:18121 auth changeme #include MAC echo "User-Name=testuser@kisti.re.kr,User-Password=changeme,,Calling-Station-Id=02-00-00-00-00-01" | radclient -x 127.0.0.1:18121 auth changeme
