[keycloak] RADIUS 서버 설치 및 Keycloak 연동 설정
ruinnel
3519 Words CHANGE ME READ TIME 15 Minutes, 59 Seconds
2019-06-11 15:36 +0000
RADIUS 서버 용도에 대해서
- Wifi 접속 인증으로 사용하기 위해서는 RADIUS 서버가 EAP를 지원해야 합니다.
- 단말에서 사용자가 입력한 ID/PW를 RADIUS 서버에서 복호화해서 평문으로 추출 할수 있어야 Keycloak에 로그인 요청을 할 수 있습니다.
- EAP 규격중
EAP-TTLS
를 사용하고 2차인증으로PAP
를 사용합니다. PEAP, CHAP 등의 방식은 hash를 이용해 handshake하는 방식이라 RADIUS 서버에서 사용자가 입력한 Password를 알아 낼수 없습니다.
Wifi 접속 인증이 필요없고, SSH 접속인증을 Keycloak과 연동하기 RADIUS 서버를 사용하는 경우 EAP 관련 설정은 불필요합니다.
구성
- Docker Hub에 공개되어 있는 이미지를 기반으로 python 사용이 가능하도록 수정한 이미지를 생성해 사용합니다.
- FreeRADIUS는 다양한 모듈을 제공합니다. 그중에 Python 모듈(rlm_python)을 이용해 keycloak과 연동합니다.
많은 모듈을 제공하는데 Keycloak 모듈은 지원 안해요 ㅠ_ㅠ- Perl 모듈(rlm_perl)도 있는데 전 Perl은 쓸 자신이 없네요.
- REST 모듈(rlm_rest)로도 할 수 있을거 같은데.. keycloak 연동시에 API 요청을 여러번 해야 해서 이걸로 하면 처리가 힘들 수 있을거 같아서 검토하지 않았습니다.
- 처리 흐름은 아래와 같습니다. {{ }} graph LR; CLIENT[시작: Client] –>|1. RADIUS 요청| FREE_RADIUS(FreeRADIUS) subgraph Radius-Server FREE_RADIUS –>|2| PYTHON_MODULE(Python모듈) end PYTHON_MODULE –>|3. 권한확인 요청| KEYCLOAK[Keycloak] KEYCLOAK –>|4. 권한확인 응답| PYTHON_MODULE PYTHON_MODULE –>|5| FREE_RADIUS FREE_RADIUS –>|6. RADIUS 응답| CLIENT {{ }}
설치시 주의사항
- Radius는
UDP
Port2개를 사용합니다.(default: 1812, 1813) 얼마 전부터 지원시작UDP
는 AWS에서 Load Balancer를 사용할 수 없습니다.- Kubernetes에 설치시 ingress의 유형에 따라 UDP를 지원하지 않을 수 있습니다.
- 그냥 NodePort로 노출 시키고, Route53에서 Kubernetes에 사용된 각 Node(EC2 Instance)의 Public IP로 Round Robin으로 분산 시켜도 됩니다.(참고)
- Kubernetes에서 NodePort를 사용 할 경우 사용가능한 Port Range는
30000-32767
입니다.(Radius의 기본 Port번호 사용 불가)
FreeRADIUS 설정 및 Docker Image 생성
- Docker Hub에 공개되어 있는 이미지를 그대로 이용하면 좋지만… 기본 이미지는
python
을 지원하지 않습니다. - Python 모듈에서 암복호화 기능도 사용할 예정이므로 pycrypto도 필요합니다.
- FreeRADIUS 설정 파일들은 굳이 Docker Image에 넣지 않고 실행시 mount 해도 상관없습니다만, 저는 Docker Image에 설정파일을 수정해서 포함 시켰습니다.
- FreeRADIUS에서 사용 할 인증서 파일도 생성해서 이미지에 포함시킵니다.(이것도.. 실행시 mount 해도 무방합니다.)
1. 설정 디렉토리 가져오기.
- 먼저 Docker Hub에 공개되어 있는 이미지를 실행시킨 후 설정 디렉토리(
/etc/freeradius
)를 추출 합니다. - 추출한 설정 파일을 적절히 수정하고, 수정한 파일을 적용해서 Docker Image를 새롭게 생성할 예정입니다.
$ docker run -d --name freeradius-server freeradius/freeradius-server
$ docker cp freeradius-server:/etc/freeradius freeradius-config
$ docker kill freeradius-server
- 설정 디렉토리 구조(수정 불필요 파일은 생략..)
.
├── certs
│ ├── Makefile
│ ├── README
│ ├── ca.cnf
│ ├── client.cnf
│ ├── dh
│ ├── inner-server.cnf
│ ├── passwords.mk
│ └── server.cnf
├── clients.conf
├── mods-available
│ ├── ...
│ └── python
├── mods-config
│ └── python
│ ├── example.py
│ └── radiusd.py
├── mods-enabled
│ ├── ...
│ └── utf8
├── radiusd.conf
├── sites-enabled
│ ├── default
│ └── inner-tunnel
├── templates.conf
├── trigger.conf
└── users
2. 인증서 생성(Wifi 인증)
Wifi 인증
에EAP-TLS
,EAP-TTLS
등을 사용해야 할 경우 필요합니다. 아닐 경우 다음 단게로…- 추출한 설정 디렉토리중
certs/README
를 참조해서 진행합니다. - 추가로 이곳도 참조하시면 좋습니다.
- 먼저 기존 파일을 제거합니다. (
아래 작업들은 cert 디렉토리에서 수행합니다.
)
$ rm -f *.pem *.der *.csr *.crt *.key *.p12 serial* index.txt*
ca.cnf
,server.cnf
,client.cnf
를 열고 수정합니다.(저는 아래 항목들을 수정했습니다.)default_days
: 인증서 유효기간countryName ~ commonName
: 인증서에 포함시킬 정보input_password & output_password
: 인증서 암호
$ make ca.pem
$ make ca.der
$ make server.pem
$ make server.csr
$ make client.pem
$ openssl dhparam -check -text -5 4096 -out dh # 오래 걸릴 수 있음... 몇분~몇십분
3. FreeRADIUS 기본 설정
...
부분은 기본 설정 유지한 부분.- client.conf
SHARED_SECRET
실행시 환경변수로 지정합니다.
client localhost {
...
secret = $ENV{SHARED_SECRET}
...
}
- sites-enabled/default
- 기본 리퀘스트 처리를 위한 설정.
- eap 처리 설정 + authorize와 authenticate 단계에서 python 모듈을 사용하도록 추가
- authorize 단계에서 실행된 python script에서
Auth-Type=KEYCLOAK
를 설정 할 예정.
server default {
authorize {
...
eap {
ok = return
updated = return
}
...
python
}
authenticate {
...
Auth-Type KEYCLOAK {
python
}
...
eap
}
...
}
- sites-enabled/inner-tunnel
mods-enabled/eap
에서 EAP-TTLS의virtual-server
로 inner-tunnel이 설정됨.- 들어온 RADIUS요청이 EAP-TTLS일 경우
sites-enabled/default
에서 eap 관련 처리가 되고 inner-tunnel로 전달 됨.
server inner-tunnel {
authorize {
...
pap
python
}
authenticate {
Auth-Type KEYCLOAK {
python
}
...
eap
}
}
- mods-enabled/eap
default_eap_type
은ttls(EAP-TTLS)
로 지정.- tls, ttls 관련 설정을 빼고 다른 EAP Type 들은 주석처리.
private_key_password
는 인증서 생성시에 만들때 쓴 암호 입력.
eap {
...
default_eap_type = ttls
ignore_unknown_eap_types = yes
#md5 {
#}
#pwd {
#}
#leap {
#}
#gtc {
#}
tls-config tls-common {
private_key_password = <private_key_password>
...
}
tls {
...
tls = tls-common
...
}
ttls {
...
tls = tls-common
...
virtual_server = "inner-tunnel"
...
}
#peap {
#}
#mschapv2 {
#}
#fast {
#}
}
- mods-enabled/python
- python 모듈 실행 설정
python_path
에:
로 구분해서 참조 할 모듈의 path를 지정.- command line에서 python을 실행하는 경우 기본 python module 및 추가 설치된 module 들의 path가 기본 설정된 채로 실행되지만 FreeRADIUS에서 python을 실행해 줄때는 그렇지 않으므로 스크립트에서 사용 할(import 하는..) 모듈 들이 있는 path를 추가해 주어야함.
- Dockerfile에서 이미지 생성시 추가하는 모듈들의 docker image 내 경로를 확인해서 추가해 주어야함.
- 아래는
pycrypto
가 설치된 경로를 추가한 설정임. (아래 Docker file 참조)
python {
python_path="/usr/lib/python2.7/:/usr/local/lib/python2.7/dist-packages:${modconfdir}/python/:/usr/lib/python2.7/lib-dynload/"
module = keycloak
pass_all_vps = no
pass_all_vps_dict = yes
...
mod_authorize = ${.module}
func_authorize = authorize
mod_authenticate = ${.module}
func_authenticate = authenticate
...
}
- mods-config/python/keycloak.py
- 원래 해당 디렉토리에는
radiusd.py
/example.py
두개 파일이 있습니다. radiusd.py
는 반환값 상수 및 로그 출력을 위한 함수가 선언되어 있습니다.(자동생성된 파일로 수정해도 반영되지 않습니다.)example.py
->keycloak.py
로 변경(위 mods-enabled/python 파일에서도 미리 변경해두었음.)- 각 단계별 함수에 넘어오는
p
는tuple
이며, 일반적으로(('User-Name', 'ruinnel'), ('User-Password', 'password'), ('NAS-Identifier', 'client-identifier'), ...)
으로 전달 됩니다. - 인증 방식이
PAP
가 아닐 경우User-Password
는 전달되지 않습니다. authorize
단계에서는 username / password가 있는 경우Auth-Type=KEYCLOAK
으로 설정하며,authenticate
단계에서 실제 인증을 처리.- sites-enabled 설정의 authenticate에서 Auth-Type 분기처리가 되어있어서 username / password가 없으면 authenticate 함수는 호출되지 않음.
- 실제 인증처리 로직은 keycloak API 참고 하셔서 구현하시면 됩니다.
회사에서 작업한 소스를 다 깔 순 없잖아요 ㅎㅎ- 꼭 keycloak과 연동 안하고 python으로 어떤 처리든 하고 결과만 반환하면 되니 원하는 서버 혹은 DB 베이스로 인증처리를 하시면 됩니다.
- 원래 해당 디렉토리에는
#! /usr/bin/env python2
import radiusd
...
def authorize(p):
username = getUserName(p)
password = getUserPassword(p)
if username and password:
# tuple로 반환시 `인증결과, response, FreeRADIUS 내부 처리 변수` 순으로 반환 가능
return (
radiusd.RLM_MODULE_UPDATED,
(),
(('Auth-Type', "KEYCLOAK"),)
)
else :
# 처리 결과 반환
return radius.RLM_MODULE_REJECT
def authenticate(p):
username = getUserName(p)
password = getUserPassword(p)
nasId = getNasIdentifier(p)
# ...<인증 처리>...
if authResult :
return radiusd.RLM_MODULE_OK
else :
return radiusd.RLM_MODULE_REJECT
...
def getNasIdentifier(p) :
for key, val in p :
if key.upper() == "NAS-Identifier".upper():
return val
return ""
def getUserName(p) :
for key, val in p :
if key.upper() == "User-Name".upper():
return val
return ""
def getUserPassword(p) :
for key, val in p :
if key.upper() == "User-Password".upper():
return val
return ""
4. Docker Image 생성
- Docker Hub에 공개되어 있는 이미지를 베이스.
- python 설치
- python 추가 모듈 설치(아래 예시는 pycrypto, pycrypto는 설치시 빌드가 필요해서 build-essential도 설치했다가 설치 후 제거)
- 위에서 수정한 설정파일들을 설정 디렉토리(
/etc/raddb
) 아래에 덮어씀.
FROM freeradius/freeradius-server:3.0.17
RUN \
apt-get update --force-yes -y && \
apt-get install -o Dpkg::Options::="--force-confold" --force-yes -y python python-dev curl build-essential
RUN curl -o pycrypto-2.6.1.tar.gz https://ftp.dlitz.net/pub/dlitz/crypto/pycrypto/pycrypto-2.6.1.tar.gz
RUN tar -xzf pycrypto-2.6.1.tar.gz
RUN rm -rf pycrypto-2.6.1.tar.gz
WORKDIR /pycrypto-2.6.1
RUN python setup.py install
WORKDIR /
RUN rm -rf pycrypto-2.6.1
RUN apt-get remove --force-yes -y --auto-remove build-essential
RUN apt-get purge --force-yes -y --auto-remove build-essential
COPY ./freeradius-config/radiusd.conf /etc/raddb/radiusd.conf
COPY ./freeradius-config/clients.conf /etc/raddb/clients.conf
COPY ./freeradius-config/sites-enabled/default /etc/raddb/sites-enabled/default
COPY ./freeradius-config/sites-enabled/inner-tunnel /etc/raddb/sites-enabled/inner-tunnel
COPY ./freeradius-config/mods-enabled/eap /etc/raddb/mods-enabled/eap
COPY ./freeradius-config/mods-enabled/python /etc/raddb/mods-enabled/python
COPY ./freeradius-config/certs /etc/raddb/certs
COPY ./freeradius-config/mods-config/python /etc/raddb/mods-config/python
WORKDIR /etc/raddb/mods-config/python
RUN rm -rf *.pyc
WORKDIR /
- docker build & push
$ docker build -t ruinnel/radius-server:0.9.1 .
$ docker push -t ruinnel/radius-server:0.9.1
설치
docker-compose
를 이용해 설치 하였습니다. (Kubernetes에 올리려 하였으나… 이런저런 사정상 docker-compose로 진행회사에서 다른 서버에 설치 하래요)docker-compose.yml
예시
version: "3"
services:
radius-server:
container_name: free-radius-server
image: ruinnel/radius-server:0.9.1
env_file:
- ./config.env
ports:
- 31812:1812/udp
- 31813:1813/udp
volumes: []
#- ./logs:/var/log/freeradius
config.env
예시
SHARED_SECRET=<my-shared-secret>
- 설치 (docker-compose.yml, config.env가 있는 경로에서 실행)
$ docker-compose up -d
정리
- 위에서 처리한 내용을 간단하게 정리하면 아래와 같습니다.
- FreeRADIUS에서는 authorize, authorize등 각 단계별로 사용 가능한 여러가지 모듈들이 있는데 그중 python 모듈을 사용하기 위한 설정 방법.
- Python 모듈을 사용 할 때 추가 python module 사용을 위한 설정 방법.
- Python 스크립트 작성 가이드.
- Docker Image 생성 및 docker-compose.yml 예시.