openvpn을 사용하여 인프라에 접근하는 시스템을 구축하였을 경우, 유저가 할당받는 ip를 고정해야 할 수 있다. 예를 들어 리눅스 서버에 접속한 IP를 확인해야 할 때 클라이언트들의 IP가 고정되어 있지 않다면, 로그를 보더라도 어떤 유저가 ssh를 통해서 접근하였는지 구별할 수가 없기 때문이다. 이런 경우를 어떻게 해결할 수 있는지 알아보자.

ifconfig-push

--ifconfig-push local remote-netmask [alias]

이 옵션은 유저에게 ip를 push하는 역할을 한다. 설정은 서버 설정에서 진행하지만 실행은 openvpn 클라이언트 사이드에서 진행된다. 그래서 이 명령 뒤에 따르는 localremote-netmask는 클라이언트 입장에서 필요한 내용을 입력해 주어야 한다.

ifconfig-push의 실행

그러면 ifconfig-push를 실행하는 시점은 어떤 것이 있을지를 알아보면 그것은 다음 내용이 순서대로 진행된다.

1 -- Use --client-connect script generated file for static IP (first choice).
2 -- Use --client-config-dir file for static IP (next choice).
3 -- Use --ifconfig-pool allocation for dynamic IP (last choice).

여기서는 --ifconfig-pool을 제외한 1,2번에 대해서만 기록해 본다.

client-connect cmd

클라이언트와의 연결이 이루어지는 시점에 특정 명령을 실행할 수 있다. 이를 통해 ifconfig-push명령을 실행하게 하면 되는데, 예를 들어 접속하는 유저에게 무조건 192.168.255.6의 ip를 할당하여야 하는 경우, 다음과 같은 설정을 만들 수 있다.

$ cat openvpn.conf
# 서버사이드 설정
...
client-connect /etc/openvpn/get_ip.sh
script-security 2
...
$ cat /etc/openvpn/get_ip.sh
#!/bin/bash
ifconfig-push 192.168.255.6 255.255.255.0

openvpn이 외부 스크립트를 실행하기 위해서 --script-security 2를 추가한 것을 확인할 수 있다. 참고로 --script-security의 경우 레벨에 따라 다음과 같은 실행이 가능해진다.

0 -- 외부 프로그램을 전혀 사용하지 않는다.
1 -- (Default) 기본 제공 실행파일, 즉 ifconfig, ip, route, netsh등의 스크립트 이외의 것들은 사용하지 않는다.
2 -- 1과 더불어 유저 스크립트도 실행 가능해진다.
3 -- 환경 변수에 담긴 password등까지도 사용 가능해진다.(안전하지 않음)

이와는 반대로, 연결이 끊어지는 시점에 명령을 실행할 수도 있는데 이는 --client-disconnect를 사용한다.

client-config-dir dir

두 번째 방법은 ccd설정을 이용하는 것이다. 설정 파일에 ccd 경로를 명시하면, 클리이언트와의 연결이 이루어질 때 클라이언트의 common name과 동일한 이름의 파일을 디렉토리 ccd에서 찾는다. 찾은 경우 해당 파일의 내용을 실행하는데, 이를 통하여 고정 IP를 할당할 수 있다.

$ cat openvpn.conf
# 서버사이드 설정
...
client-config-dir ccd
...

client-config-dir의 인자 ccd는 디렉토리 이름이므로 ccd라는 디렉토리를 만들어서 그 안에 클라이언트의 common name과 같은 파일을 만들어 둔다.

$ ls -la ccd/
total 32
drwxr-xr-x 2 root root 4096 Oct 15 11:21 .
drwxr-xr-x 6 root root 4096 Oct 15 11:35 ..
-rw-r--r-- 1 root root   44 Jul 19  2016 cublr
-rw-r--r-- 1 root root   44 Jun 30 06:09 cublr-home

$ cat ccd/cublr
ifconfig-push 192.168.0.254  255.255.255.0

만약 cublr라는 유저가 접속하는 경우 ccd/cublr에 명시된 ifconfig-push 192.168.0.254 255.255.255.0가 클라이언트 사이드에서 실행된다. 딱히 뭔가를 수정할 필요도 없이 사용 가능한, 1번에 비해 상당히 우아한 방법이라고 할 수 있겠다.

예제: 고정 IP 할당

여기서는 client-connect를 통해 고정 IP를 할당하는 방법에 대해서 적는다. 사실 ccd를 사용하는 경우 딱히 적을만한 내용이 없이 매우 간단하다. 거기다 유저의 수가 많을 경우 파일들을 계속 지우거나 생성하여야 하므로 매우 귀찮기 때문에, 개인적으로도 이런 요구가 있을 경우는 client-connect를 더 선호하는 편이다.

client-connect를 사용하는 경우 진행되는 순서는 다음과 같다.

  1. 유저와의 연결이 이루어짐(client-connect)
  2. 서버에서는 접속 시도한 유저의 common name을 사용하여 그 유저가 사용하여야 할 fixed ip를 확인
  3. 클라이언트로 ifconfig-push를 하도록 명령 전송

설정

openvpn 서버 사이드 설정에 다음 내용을 추가한다.

# 서버사이드 설정
client-connect /etc/openvpn/get_ip.sh

Fixed IP List 작성

유저와 그 유저의 IP를 기록한 파일이 필요하다. 예를 들어 다음과 같은 내용이면 충분하다. 필요에 맞게 적당히 만들면 된다.

CN IP NETMASK 이름 용도
...
cublr 192.168.12.42 192.168.12.41 cublr 개발
cublr-home 192.168.12.46 192.168.12.45 cublr 긴급대응
cublr-notebook 192.168.12.50 192.168.12.49 cublr 긴급대응
cublr-phone 192.168.12.54 192.168.12.53 cublr 긴급대응
...

각 클라이언트는 /30의 서브넷을 할당받도록 지정하였다. 이는 Windows와의 호환성을 위해 이렇게 할당한다. 이 내용의 파일을 /etc/openvpn/iplist.txt로 저장하였다.

스크립트 작성

#!/bin/bash
GREPED=$(grep -P "^$common_name " iplist.txt)
IP=$(echo $GREPED iplist.txt | awk '{print $2}')
NETMASK=$(echo $GREPED iplist.txt | awk '{print $3}')
echo "ifconfig-push $IP $NETMASK" > $1

스크립트의 내용은 별 거 없는데, $common_nameiplist.txt에서 찾아서 ifconfig-push명령의 파라미터를 만드는 것이다. $common_name은 연결이 이루어진 시점에 자동으로 환경 변수로 전달되는데, 중요한 것들만 추리면 다음과 같다. 모든 환경변수의 내용은 man openvpn을 참고하자.

  • common name: 연결된 유저의 X509 common name을 말한다. 쉽게 말하면 키 이름.
  • dev: tun/tap 등의 디바이스 이름.
  • username: 유저가 인증에 사용한 아이디. 예를 들어 openvpn+pam 인증을 붙였을 경우, 이를 스크립트에 사용할 수 있다.
  • password: 유저가 인증에 사용한 패스워드. 이름부터가 왠지 보안적으로다가 사용하면 안될 것 같아 보인다.

client-connect의 장점은 스크립트를 실행한다는 것에 있다. 지금은 간단한 txt파일과 이를 파싱하도록 grep을 사용하여 고정된 얻도록 하였는데, 조금 더 발전된 형태를 생각해 보면 python3mysql/sqlite등을 통해 조금 더 우아하고 멋진 테이블 관리를 할 수 있을 것이다.

사용

이제 유저가 접속할 경우 get_ip.sh가 실행되어 해당 유저의 올바른 IP/NETMASK를 얻을 수 있다. 새로운 유저가 추가되면 iplist.txt에 해당 유저 정보를 추가하여 사용하면 된다.