OpenVPN으로 VPN 구성 및 활용 6편_인증서 활용한 공개키 이용방법
작성자 정보
- 관리자 작성
- 작성일
컨텐츠 정보
- 3,948 조회
- 0 추천
- 목록
본문
OpenVPN으로 VPN 구성 및 활용 6편_인증서 활용한 공개키 이용방법
인증서를 활용한 공개키(PKI-Public Key Infrastructure)를 이용하는 방법
(1) PKI를 활용한 서버와 PC간 VPN 연동
지금까지 앞에서 살펴본 공유키(static key)를 이용하는 방법으로는 별도의 데몬을 띄우지 않는 한 여러 PC에서 동시 접속할 수 없다는 심각한 한계가 있었다. 따라서 이를 위해서는 지금부터 살펴볼 RSA키를 이용한 SSL/TLS(소위 SSL-VPN)을 이용하여야 한다.
이는 가장 안전한 방법으로 알려져 있는데, 여기에서는 openssl을 이용하여 RSA 인증서 및 키를 빌드할 것이다.
여기에서 .crt 확장자는 인증서(certificate)파일이고, .key 파일은 개인키(private key)라고 생각하면 된다.
물론 개인키는 안전하게 보관하여야 하며 인증서 파일은 자유롭게 공개해도 된다.
여기에서 SSL이나 PKI의 원리에 대해서는 별도로 설명하지 않으므로 관심 있는 분은 관련 자료를 참고하기 바란다.
|
먼저 openssl의 설정파일은 기본적으로 /usr/share/ssl/openssl.cnf에 있는데, 이 파일을 열어 아래와 같이 몇 가지 설정을 변경해 주도록 한다.
dir = /usr/share/ssl |
openssl의 기본 경로를 지정한다.
certificate = $dir/my-ca.crt |
CA 파일의 위치를 지정한다.
private_key = $dir/private/my-ca.key |
개인키 파일의 위치를 지정한다.
이후 현재 디렉토리에서 아래와 같이 실행하여 serial과 index.txt 파일을 생성한다.
serial 과 index.txt에는 각각 root CA에서 인증한 횟수와 인증한 내용이 기록되는데, 추가로 인증할 때마다 자동으로 갱신된다.
특히 이는 기존에 발급한 인증서를 취소하거나 폐기하고자 할 때 사용된다.
# echo "01" | cat > serial # touch index.txt |
다음으로는 root CA인증서와 개인키를 생성할 차례인데, 아래 명령어로 10년 동안 유효한 CA 및 개인키를 생성한다.
# openssl req -nodes -new -x509 -keyout my-ca.key -out my-ca.crt -days 3650 |
참고로, 유효기간이 만료되면 정상적으로 작동하지 않으므로 특정 인증서 이를테면 my-ca.crt의 만기일을 확인하려면 다음과 같이 실행해 보면 된다.
# openssl x509 -in my-ca.crt -noout -text
Not Before: Jan 24 06:52:54 2008 GMT
Not After : Jan 22 06:52:54 2018 GMT
위의 경우 2018년까지는 유효한 것을 알 수 있다.
다음은 서버 쪽에서 사용할 공개키와 개인키를 생성하도록 한다.
# openssl req -nodes -new -keyout server.key -out server.csr |
그리고 root CA로 서버쪽 인증서에 사인한다.
실행이 완료되면 index.txt와 serial 정보가 업데이트 된다.
# openssl ca -out server.crt -in server.csr |
마찬가지로 클라이언트에서 사용할 키를 생성하도록 한다.
# openssl req -nodes -new -keyout client.key -out client.csr |
그리고 root CA로 클라이언트쪽 인증서에 사인한다.
실행이 완료되면 index.txt와 serial 정보가 업데이트 된다.
# openssl ca -out client.crt -in client.csr |
만약 .key 생성 시 -nodes를 생략하면 각각의 키는 암호(password)로 암호화되어 설사 개인키가 노출되어도 암호를 알지 못하면 아무 소용이 없으므로 더욱 안전하게 키 관리를 할 수 있다.
그러나 이러한 경우 OpenVPN을 실행할 때마다 암호를 입력해야 하는 번거로움이 있는데, 특히 OpenVPN 서버가 재부팅 될 때에도 수동으로 암호를 입력해야만 데몬이 뜨게 된다.
따라서 이 방법은 안전하기는 하지만 번거로움 때문에 그다지 권장하지 않는다. |
다음으로 아래와 같이 실행하여 암호화 협상 과정에서 필요한 Diffe Hellman 파라미터를 생성한다.
이 파일은 서버 쪽에서만 필요하며 생성 시 약 2-3분 정도 소요될 것이다.
# openssl dhparam -out dh1024.pem 1024 |
이제 나머지는 서버에 그대로 두고, client.crt와 client.key 그리고 my-ca.crt 파일을 클라이언트 PC의 C:\Program Files\OpenVPN\config 디렉토리에 복사하면 된다.
먼저 서버 쪽 설정을 살펴보자. 크게 변한 것은 없는데, 전체 설정에서 변경된 부분만 잠시 살펴보도록 하자. config 파일은 server.conf라고 가정한다.
port 5000 proto udp dev tun0 |
여기까지는 기존의 설정과 동일하다.
tls-server |
SSL/TLS 키 교환시에 서버역할을 할 것이므로 tls-server로 지정한다.
dh key/dh1024.pem |
tls-server에서만 정의한다.
현재 디렉토리는 server.conf 파일이 있는 위치를 뜻한다.
ca key/my-ca.crt |
CA 파일을 지정한다.
cert key/server.crt |
인증서의 공개키를 지정한다.
key key/server.key |
개인키를 지정한다.
ifconfig 10.1.0.1 10.1.0.2 route 10.1.0.0 255.255.255.0 |
ifconfig-pool 10.1.0.4 10.1.0.251 |
이는 개별적으로 접속하는 각 PC나 서버에 마치 DHCP 서버처럼 dynamic하게 IP 주소를 할당할 때 할당할 대역을 지정하는 것인데, 위의 경우 10.1.0.4에서부터 10.1.0.251까지 할당하도록 한 것을 알 수 있다.
duplicate-cn |
각 클라이언트 유저마다 동일한 인증서 사용을 허가할 것인지 지정한다.
만약 허가하지 않으려면 삭제하거나 주석처리하면 되고 각 유저마다 별도의 인증서파일(client.key, client.crt)을 생성하여야 한다.
만약 이 설정이 없는 상태에서 동일한 인증서를 사용한 유저가 접속하게 되면 기존의 접속이 끊기게 되므로 한 VPN 서버에 여러 클라이언트의 접속을 허용하려면 이 설정을 반드시 하여야 한다.
client-to-client |
기본적으로는 VPN에 연결된 서버와 클라이언트와만 통신할 수 있지만 VPN에 접속된 클라이언트끼리도 상호 통신할 수 있도록 허용할 것인지 설정하는 것이다.
이를테면 만약 내 PC는 VPN을 통해 10.1.0.3을 할당받고, 미국지사에서 접속한 PC는 10.1.0.100을 할당받았다면 내 PC에서 10.1.0.100으로 터미널 접속하면 미국지사의 PC로 VPN을 통해 접속이 되는 것이다.
이는 매우 편리하지만 보안상 문제가 될 수도 있으므로 주의하기 바란다.
만약 이 기능을 사용하지 않으려면 주석처리하면 된다.
max-clients 100 |
서버에서 동시에 접속을 받아들일 클라이언트 개수를 지정한다.
별도로 지정하지 않으면 1024가 된다.
keepalive 10 60 comp-lzo user nobody group nobody persist-key persist-tun status openvpn-status.log log openvpn.log log-append openvpn.log verb 4 |
나머지는 앞에서와 동일하다.
이후 서버에서 아래와 같이 --mode server를 추가하여 실행하면 된다.
./openvpn --config server.conf --daemon --mode server |
참고로 여기에서 --mode server로 사용할 경우 server에서는 몇 가지 옵션을 별도로 지정하지 않고도 사용할 수 있는데, 만약 “server 10.1.0.0 255.255.255.0"라고 하였다면 아래와 동일한 의미가 된다.
즉 아래의 내용은 별도로 지정하지 않아도 되는 것이다.
mode server tls-server
if dev tun: ifconfig 10.1.0.1 10.1.0.2 ifconfig-pool 10.1.0.4 10.1.0.251 route 10.1.0.0 255.255.255.0 if client-to-client: push "route 10.1.0.0 255.255.255.0" else push "route 10.1.0.1"
if dev tap: ifconfig 10.1.0.1 255.255.255.0 ifconfig-pool 10.1.0.2 10.1.0.254 255.255.255.0 push "route-gateway 10.1.0.1" |
특별한 에러가 없다면 정상적으로 포트를 리슨하면서 openvpn.log 파일을 보면 아래와 같이 동시접속 모드로 가동하였음을 알 수 있다.
Tue Oct 19 15:32:18 us=556826 MULTI: multi_init called, r=256 v=256 |
다음으로 클라이언트 쪽 설정파일 차례이다.
클라이언트 설정파일은 .ovpn 확장자이기만 하면 된다.
아래의 설정을 각각의 Windows PC에서 설정하여 서버에 접속하면 앞에서와는 달리 동시에 접속해도 기존의 접속이 끊어지지 않는다는 것을 알 수 있다.
client |
클라이언트 역할을 하므로 client라고 정의한다.
이 설정을 하게 되면 서버의 설정을 따라 몇 가지 내용을 읽어오게 된다.
여기에서 client는 tls-client와 pull의 의미를 포함한다.
dev tun0 proto udp remote 211.47.68.21 port 5000 nobind |
클라이언트는 특별한 포트로 bind할 필요가 없다.
ca my-ca.crt cert client.crt key client.key |
서버에서 살펴본 바와 같이 CA 및 클라이언트의 공개키, 개인키를 지정한다.
물론 이 파일들은 설정파일이 있는 Windows PC로 복사하여야 한다.
comp-lzo verb 4 dev-node "로컬 영역 연결 3" |
나머지는 앞에서 살펴본 바와 동일하다.
만약 클라이언트가 Windows PC가 아니라 리눅스라면 dev-node 부분만 빼고 실행하면 된다.
이후 각각의 PC에서 아래와 같이 실행하면 된다.
openvpn --config config.ovpn |
실행한 후 아래의 로그를 보면 암호화 키 교환 후에는 임의의 IP인 10.1.0.6을 클라이언트 PC에 할당하고 있는 것을 알 수 있다.
PC에서 “ipconfig” 또는 “ipconfig /all”을 실행해 보면 10.1.0.6이 IP 주소이고, 10.1.0.5는 DHCP Server로 설정된 것을 알 수 있다.
즉, 서버로부터 dynamic하게 IP 주소를 할당받으므로 클라이언트의 Windows PC에는 DHCP Client 서비스가 가동 중이어야 한다.
Wed Oct 20 15:50:28 us=243131 211.219.171.185:2006 VERIFY OK: depth=1, /C=KR/ST=Seoul/L=KURO/O=TTIDC/OU=NETWORK/CN=cofw.tt.co.kr /emailAddress=secure@tt.co.kr Wed Oct 20 15:50:28 us=244693 211.219.171.185:2006 VERIFY OK: depth=0,/C=KR/ST=Seoul /O=TTIDC/OU=NETWORK/CN=client.tt.co.kr/emailAddress=client@tt.co.kr Wed Oct 20 15:50:28 us=332968 211.219.171.185:2006 Data Channel Decrypt: Using 160 bit message hash 'SHA1' for HMAC authentication Wed Oct 20 15:50:28 us=349000 211.219.171.185:2006 Control Channel: TLSv1, cipher TLSv1/SSLv3 DHE-RSA-AES256-SHA, 1024 bit RSA Wed Oct 20 15:50:28 us=349102 211.219.171.185:2006 [client.tt.co.kr] Peer Connection Initiated with 211.219.171.185:2006 Wed Oct 20 15:50:28 us=349247 client.tt.co.kr/211.219.171.185:2006 MULTI: Learn: 10.1.0.6 -> client.tt.co.kr/211.219.171.185:2006 Wed Oct 20 15:50:28 us=349294 client.tt.co.kr/211.219.171.185:2006 MULTI: primary virtual IP for client.tt.co.kr/211.219.171.185:2006: 10.1.0.6 Wed Oct 20 15:50:29 us=549942 client.tt.co.kr/211.219.171.185:2006 PUSH: Received control message: 'PUSH_REQUEST' Wed Oct 20 15:50:29 us=550074 client.tt.co.kr/211.219.171.185:2006 SENT CONTROL [client.tt.co.kr]: 'PUSH_REPLY,route 10.1.0.0 255.255.255.0,ping 10,ping-restart 60,ifconfig 10.1.0.6 10.1.0.5' (status=1) |
(2) PKI를 활용한 NAT 사무실과 서버 간 VPN 연동
비슷한 방법으로 NAT 방화벽으로 사용 중인 사무실과 특정 서버와의 VPN 연동은 어떻게 하여야 할까? 이렇게 구성할 경우 사무실의 NAT 방화벽과 특정 서버에만 각각 VPN 설정을 하면 되며 사무실 내부의 유저들은 일일이 자신의 PC에 VPN 설정을 할 필요가 없게 되어 VPN이 설치되어 있는지 조차 모르게 된다.
즉, NAT 게이트웨이 자체와 특정 서버간에 별도의 터널이 구성되는 것이다.
복잡할 것 같지만 아래 설정을 보면 그다지 특별한 설정이 필요하지 않는데, 이 경우 아래와 같이 설정하면 된다.
여기에서는 사무실의 NAT 방화벽을 client, vpn.server.com서버를 server로 하기로 한다.
VPN은 항상 쌍으로 작동한다.
따라서 게이트웨이인 방화벽에 VPN을 설치했다고 해서 사무실에서 나가는 모든 트래픽이 암호화되는 것은 아니며 사무실과 특정 서버간 트래픽만 암호화되는 것이다.
|
사무실 내부 IP 대역: 192.168.1.0/24
NAT 방화벽 공인 IP : 고정 IP나 유동 IP이거나 무방
원격지 서버 IP : vpn.server.com
[사무실의 NAT 방화벽 설정 예]
tls-client |
클라이언트 역할을 하므로 tls-client로 지정한다.
dev tun0 proto udp port 5000 remote vpn.server.com ifconfig 10.1.0.2 10.1.0.1 |
10.1.0.2는 방화벽의 IP, 10.1.0.1은 원격지 서버의 IP이다.
이하는 앞에서 언급한 바와 동일하다.
ca my-ca.crt cert office.crt key office.key user nobody group nobody comp-lzo verb 4 status openvpn-status.log log openvpn.log log-append openvpn.log |
[서버의 설정 예]
tls-server |
서버 역할을 하므로 tls-server로 지정한다.
dev tun0 proto udp port 5000 ifconfig 10.1.0.1 10.1.0.2 |
클라이언트와는 반대로 10.1.0.1이 서버의 IP, 10.1.0.2는 사무실의 방화벽 IP이다.
up ./up-script |
VPN 연동이 되었을 때 라우팅 테이블을 추가하기 위해 필요하다.
아래 설정은 앞과 동일하다.
dh key/dh1024.pem ca key/my-ca.crt cert key/office.crt key key/office.key comp-lzo user nobody group nobody persist-key persist-tun status openvpn-status.log log openvpn.log log-append openvpn.log verb 4 |
추가적으로 up-script 파일은 아래와 같이 정의한다.
#!/bin/sh route add -net 192.168.1.0 netmask 255.255.255.0 gw $5 |
설정이 끝난 후 아래와 같이 각각
./openvpn --config client.conf --daemon ./openvpn --config server.conf --daemon |
위와 같이 실행하면 정상적으로 VPN 연동이 되며 사무실 내부의 어떤 PC에서든 10.1.0.1 로 접속하면 서버로 접속이 되는 것을 알 수 있다.
이때 서버의 로그에는 NAT 방화벽의 공인 IP가 아닌 192.168.1.x 대역에서 직접 접속한 것으로 보이게 된다.
아래의 경우 4곳에서 telnet이나 ssh등으로 원격 접속한 것을 알 수 있는데, 211.47.65.57 은 공인 IP로서 VPN이 아닌 공용 네트워크를 통해 접속한 것이고, 10.1.0.5와 10.1.0.9는 VPN을 통해 개별적으로 접속한 것이며 192.168.1.222는 NAT를 사용하는 사무실에서 VPN 게이트웨이를 통해 서버에 접속한 것임을 알 수 있다.
즉, 이때는 NAT 방화벽의 공인 IP가 아닌 PC에서 사용하는 사설IP가 직접 보이게 된다는 것을 알 수 있다.
[그림] VPN 접속 화면
????[유용한 팁]
PKI를 이용하여 서버와 클라이언트 간에 VPN이 연동된 상황에서 클라이언트쪽에서 openvpn을 재가동하여도 서버 쪽에서는 재가동할 필요가 없다.
그러나 서버 쪽에서 openvpn 프로세스를 재가동하였다면 클라이언트 쪽에서도 프로세스를 재가동하여야 접속이 되게 되는데, 이 때문에 가끔 불편함을 느낄 때가 있다.
이는 프로세스를 재가동할 때 키교환이 필요하기 때문인데, 클라이언트의 경우 remote로 키를 교환할 원격지의 IP를 알고 있지만 서버는 클라이언트의 IP를 알 수 없기 때문이다.
따라서 이러한 경우 몇 가지 방안이 있을 수 있는데, 먼저 양 구간사이의 연결이라면 양 구간 모두 상호 IP를 remote로 지정하여 사용하도록 할 수 있으나 클라이언트가 다수라면 불가능할 것이다.
이러한 경우라면 ping과 ping-restart를 사용하여 일정 시간동안 터널을 통한 통신이 안 될 경우에는 강제적으로 키 교환을 하도록 하는 방법도 있다.
그리고 장시간 사용하지 않아 PC를 잠가놓았을 때 VPN이 끊기지 않도록 하려면 시작->실행에서 REGEDIT를 입력 후 HKEY_LOCAL_MACHINE\SOFTWARE\OpenVPN-GUI\disconnect_on_suspend 값을 0으로 변경하면 된다.
기타 다른 값도 레지스트리에서 수정할 수 있다.
|
(3) username/password로 VPN 연동
앞에서는 static 키와 공개키 인증 방식을 이용하여 VPN을 연동하는 방법에 대해 알아보았는데, 이외에도 최근 버전에서는 접속하고자 하는 서버의 PAM을 활용하여 username/password로 인증하는 방법을 제공하고 있다.
이 기능은 계속적으로 개발 중인데, 기존의 공개키 인증 방식과 함께 사용해도 되고 별도로 이 방식만으로 인증을 하여도 된다.
서버 쪽에서는 별도의 설정파일을 수정할 필요 없이 아래와 같이 실행하면 된다.
./openvpn --config server.conf --auth-user-pass-verify /usr/local/vpn/auth-pam.pl via-file --tmp-dir /tmp --mode server --daemon |
여기에서 두 가지 옵션이 추가되었는데, 각각에 대해 알아보도록 하자.
“--auth-user-pass-verify /usr/local/vpn/auth-pam.pl via-file” |
이는 서버에 접속하는 클라이언트들에게 username/password로 인증하겠다는 의미로서 /usr/local/vpn/auth-pam.pl을 실행하여 클라이언트가 제공하는 username/password를 인증하여 만약 인증에 성공하면 VPN을 연동하고 그렇지 않으면 연결을 종료하게 된다.
이때 auth-pam.pl 파일은 직접 만들 필요 없이 압축 해제 후 /sample-scripts/ 디렉토리에 있는 auth-pam.pl을 복사하여 사용하면 되며 username/password로 인증하는 계정은 해당 서버에 존재하는 아무런 계정이나 관계없다.
즉 서버에 존재하는 모든 계정으로 인증이 가능하다.
그리고, 그 뒤에서는 via-env와 via-file이 올 수 있는데, via-file로 지정하면 인증시 클라이언트가 제공하는 username/password를 임시로 파일에 저장을 하며 인증이 끝나면 자동으로 삭제하게 된다.
“--tmp-dir /tmp” |
인증 과정에서 필요로 하는 임시 파일을 어떤 디렉토리에 저장할 것인지 지정하는 것으로
통상적으로 /tmp로 하면 무난하다.
추가적으로 “--client-cert-not-required”를 추가하면 클라이언트의 인증서는 인증하지 않고 username/password로만 인증하게 된다.
???? [필자경험담]
username/password 방법을 이용할 때 추가적으로 빼놓을 수 없는 설정은 바로 서버쪽에서의 설정파일에서 user와 group을 반드시 root로 설정하여야 한다는 것이다.
왜냐하면 입력받은 유저에 대한 암호 인증을 하려면 /etc/shadow 파일을 읽어야 하는데, nobody 는 읽을 수 있는 권한이 없기 때문이며 따라서 읽을 수 있는 root 권한이어야 한다.
필자는 이 사실을 깜빡 잊고 root로만 프로세스를 실행하면 되는지 알고 여러 차례 시행착오를 거쳐야 했다. 아래는 nobody로 user와 group을 nobody로 설정한 후 openvpn을 가동했을 때의 /var/log/messages로서 권한이 없어 에러가 발생한 것을 알 수 있다.
|
이렇게 해서 서버 쪽에서의 설정은 끝났고, 다음으로 클라이언트에서는 구동시 시작 스크립트에 아래의 라인을 추가하면 된다.
“--auth-user-pass ./pass” |
이는 인증시 pass라는 파일에 저장되어 있는 username/password로 인증하겠다는 의미로서 직접 config 파일에 지정할 때는 auth-user-pass ./pass로 하면 된다.
이때 ./pass 파일을 지정하지 않으면 실행 과정에서 username/password를 묻게 되고 ./pass 파일은 아래와 같이 두 줄에 각각 username(test)과 password(12345)를 지정하면 된다.
test 12345 |
이제 서버와 클라이언트에서 각각 구동한 후 서버쪽의 로그 파일을 보면 아래와 같이 211.xxx.xxx.xx로부터 test라는 유저로 인증이 허용되었음을 알 수 있다.
Tue Nov 9 18:19:37 us=636890 211.xxx.xxx.xx:5000 TLS: Username/Password authentication succeeded for username 'test' |
만약 암호를 잘못 입력하여 인증이 거부되면 아래와 같이 접속을 거부하게 된다.
Auth 'anti' failed, PAM said: Authentication failure Tue Nov 9 18:21:20 us=265275 211.xxx.xxx.xx:5000 TLS Auth Error: Auth Username/Password verification failed for peer |
그리고, 클라이언트가 Windows일 경우에는 OpenVPN-GUI를 실행할 때 config.ovpn 파일에 auth-user-pass만 지정하면 아래와 같이 암호를 묻는 창이 나오게 되는데, 정확하게 입력하여 인증이 되어야 VPN 연동이 된다.
[그림] username/password 인증 화면
기본적으로 OpenVPN에서는 서버 내에 존재하는 모든 유저에 대한 username/password로 인증할 수 있다.
그러나 sendmail에서 제공하는 SMTP AUTH처럼 특정한 유저에 대해서만 제공하고자 한다면 어떻게 하여야 할까? 간단하게 설정할 수 있는데, auth-pam.pl 파일을 열어 아래와 같이 되어 있는 부분에 $username = special;를 추가하면 입력받은 변수와 관계없이 username을 special이라는 유저로 치환하기 때문에 이 유저(special)에 대해서만 인증을 처리하게 된다.
따라서 special 부분을 각자 인증 시 사용할 유저명으로 적당히 설정하면 될 것이다.
chomp $username; chomp $password; $username = special; |
관련자료
-
이전
-
다음