이번 시간에는 wireguard를 기반으로하는 흥미로운 project인, wiretap과 netbird를 소개해 보고자 한다.
wiretap: WireGuard + Application Proxy
NetBird : WireGuard 기반의 Overlay networks VPN
and
본 blog는 embedded system, single board computer, linux kernel & device driver, RTOS 등에 관한 내용 소개가 주된 목적이지만, 본 저자의 최근 관심 사항이 wireguard이다 보니, 어쩔 수 없이 blog의 주제에서 다소 벗어난 내용이 계속 연재되고 있는 상황이다. 독자 여러분의 이해를 바란다. 😂
목차
1. WireGuard 기반의 awesome 프로젝트 소개
2. Wiretap 프로젝트
3. NetBird 프로젝트 #당초 계획보다 내용 분석이 좀 부실하다.
4. DefGuard 프로젝트 #TODO
5. References
<WireGuard 관련 이전 posting 모음>
그 동안 wireguard protocol을 분석하고, kernel wireguard, wireguard-go, android, ios, windows version의 코드를 살펴 보았다. kernel 버젼 적용이 어려운 embedded linux용으로 lightweight wireguard-c를 만들기도 했다. 또한 noise ik protocol을 변형하여 PQC 기능을 넣어 보기도 했다. EspressoBin, NanoPi, OrangePi, Gl-iNet, ESP32, Intel Appliance 등 다양한 router 장치에서 돌려도 보았다. 오늘 살펴볼 부분은 gVisor와 같이 application kernel 위에서 wireguard를 돌려 보는 방법에 관한 것이다.
______________________________________________
WireGuard는 아주 경량화되어 있어, TLS를 대신하여 아주 많은 곳에서 활용성이 매우 높은 protocol이다. 차기 제품을 만든다면, Wireguard & PQC & ZTNA로 무장한 Overlay VPN이 되어야 하지 않을까 ? 😎
1. WireGuard 기반의 awesome 프로젝트 소개
아래 site에는 wireguard 관련 아주 따끈 따끈하고(?) 흥미로운 project들이 많이 정리되어 있다. 가끔씩 머리를 식히고 싶을 때 마다 한번씩 들러 보는 곳이다.
이번 장에서는 이 site에 소개된 프로젝트 중에서 wiretap과 netbird project를 소개해 보고자 한다. 😉
a) Wiretap 프로젝트
Wiretap은 Go언어로 구현한 wireguard 기반의 transparent, VPN like proxy 프로젝트이다. Wireguard를 이용해 vpn service를 하면서도 gateway 형태로 배치할 필요 없이, standalone server 형태로도 구성이 가능한 특징을 갖고 있다. 일전에 잠시 운용해 보았는데, 나름 쓸만하여 여기에 소개해 본다.
Wiretap은 server 쪽에 설치하며, wireguard가 장착된 client와 연동한다. Client는 wiretap을 통해 server와 같은(동일한) network에 위치하는 다른 device와 자연스럽게 연결될 수 있다. 즉, 아래 구성과 같이 Server는 물론이고, Server와 동일한(local) network에 존재하는 다른 device 들에 접근이 가능하다.
📌 Client에서는 wiretap configure 명령을 한 차례 수행하여, 실제 wireguard 설정에 필요한 정보를 생성해 내야 한다.
Client(wireguard) ---------> internet --------> Server(wiretap) ----- Neighbor devices
아래 내용은 wiretap의 동작 방식을 한눈에 보여주는 그림인데, Wiretap site에 가보면 동적으로 움직이는 그림을 볼 수 있다~ 🏂
[그림 1.1] wiretap의 개요 [그림 출처 - 참고문헌 1]
Wiretap은 CLI 용 framework인 Cobra와 application kernel framework인 gVisor 등을 활용하여 만들어 졌다. 따라서 wiretap code에 대한 원활한 분석이 이루어지려면, 아래 2개의 Go package에 대한 사전 이해가 필수적이다. 왜 이리 배울게 많냐 ? 갑자기 반만을 ...😋
[그림 1.2] Cobra project 로고 [그림 출처 - 참고문헌 4]
📌 Cobra는 CLI를 만들 때 사용하는 GoLang 기반의 framework로, NetBird project에서도 사용되고 있다. 명령행 option 처리(CLI)가 필요한 경우는 대부분 Cobra를 사용할 정도로 유명세를 자랑한다.
[그림 1.3] gVisor의 개념 [그림 출처 - 참고문헌 2]
📌 gVisor는 Google이 Golang으로 만든 container를 위한 application kernel project이다. Google은 참으로 훌륭한 회사이다. ***** 별 다섯개다. 👍
b) netbird 프로젝트
NetBird를 한마디로 쉽게 말하면 wireguard를 기반으로 하는 P2P or mesh VPN(or overlay network VPN)이라고 말할 수 있겠다. Tailscale사의 제품과 유사한 것으로 말하면 이해가 더 쉬울지도 모르겠다. NetBird는 요즘 대세 concept인 Zero Trust 모델을 도입했다는 점과 PQC(Post Quantum Cryptography) 즉 양자내성 암호(Rosenpass와 연동하고 있음)가 고려되었다는 점, full open source라는 점 등에서 주목할만하다.
📌 아직까지는 Tailscale 제품이 좀 더 나은것 같다는 평가다. 하지만, NetBird의 발전 가능성에 한표를 던지고 싶다. 😍
[그림 1.4] Netbird의 개요 1 [출처 - 참고 문헌 11]
[그림 1.5] Netbird의 개요 2 [출처 - 참고 문헌 11]
📌 Wiretap이나 NetBird를 분석하는 이유는 개인적으로는 GoLang에 좀 더 익숙해 지기 위한 목적도 있다. 😅
2. Wiretap 프로젝트
자, 그럼 지금부터 wiretap code를 download 받아 build 한 후, 아래 환경에서 돌려 보도록 하자.
<시험환경>
Client ========== Internet ========= Server, Another Server
a) wiretap build & 동작확인
$ git clone https://github.com/sandialabs/wiretap
$ cd wiretap/src
$ make
$ cd ../bin
$ ls -la
합계 14860
drwxrwxr-x 2 chyi chyi 4096 5월 31 18:05 .
drwxrwxr-x 7 chyi chyi 4096 5월 30 16:08 ..
-rw-rw-r-- 1 chyi chyi 0 5월 18 16:45 .gitkeep
-rwxrwxr-x 1 chyi chyi 15200408 5월 30 17:27 wiretap_linux_amd64
<Client>
$ ./wiretap_linux_amd64 configure --port 51820 --endpoint x.x.x.x:51820 -r 192.168.2.0/24
📌 endpoint ip 주소인 x.x.x.x는 위의 그림에서 Client 쪽(예: Access Point)의 공인 ip 주소이다.
$ ls -la
total 14868
drwxrwxr-x 2 ubuntu ubuntu 4096 May 31 09:04 .
drwxrwxr-x 7 ubuntu ubuntu 4096 May 30 12:57 ..
-rw-rw-r-- 1 ubuntu ubuntu 0 May 30 12:57 .gitkeep
-rw------- 1 ubuntu ubuntu 276 May 31 09:04 wiretap.conf
-rwxrwxr-x 1 ubuntu ubuntu 15200408 May 30 13:01 wiretap_linux_amd64
-rw------- 1 ubuntu ubuntu 239 May 31 09:04 wiretap_relay.conf
-rw------- 1 ubuntu ubuntu 351 May 31 09:04 wiretap_server.conf
$ sudo wg-quick up ./wiretap.conf
$ sudo wg-quick up ./wiretap_relay.conf
$ ifconfig wiretap
wiretap: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1340
inet 172.19.0.1 netmask 255.255.255.255 destination 172.19.0.1
inet6 fd:19::1 prefixlen 128 scopeid 0x0<global>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 1000 (UNSPEC)
RX packets 47 bytes 3524 (3.5 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 21 bytes 2412 (2.4 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
$ ifconfig wiretap_relay
wiretap_relay: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 8921
inet 172.16.0.1 netmask 255.255.255.255 destination 172.16.0.1
inet6 fd:16::1 prefixlen 128 scopeid 0x0<global>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 1000 (UNSPEC)
RX packets 59 bytes 7540 (7.5 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 50 bytes 5004 (5.0 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
📌 앞서서 wg-quick 명령을 두번 실행했으므로, wiretap 및 wiretap_relay 라는 2개의 wireguard interface가 생성되었다.
$ netstat -nr
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
0.0.0.0 172.31.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.255.0 U 0 0 0 wiretap_relay
172.31.0.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0
172.31.0.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 wiretap
$ ping 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=11.1 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64 time=10.4 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=64 time=9.73 ms
^C
--- 192.168.2.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 9.737/10.454/11.197/0.607 ms
$ sudo wg show
interface: wiretap
public key: TzoQryVxyw4+B6x7L7tVYQYtFpbq9YPZeudKz74sFAs=
private key: (hidden)
listening port: 51821
peer: JqaHDAnOu427ufk1mGazmEtSogRfasAIT0kPJiIXdhg=
endpoint: 172.17.0.2:51821
allowed ips: 192.168.2.0/24, ::2/128
latest handshake: 9 seconds ago
transfer: 5.35 KiB received, 3.21 KiB sent
interface: wiretap_relay
public key: ZU68Y4aIZyI3TYZ1BFS3ug7KREygmA+Hih34ayZYUkQ=
private key: (hidden)
listening port: 51820
peer: EiGsMPnZe7e99KRXW1nOnM7xKoq5RawWNnvKThymeB0=
endpoint: x.x.x.x:51820
allowed ips: 172.17.0.0/24, fd:17::/48
latest handshake: 9 seconds ago
transfer: 12.20 KiB received, 7.40 KiB sent
<Server>
$ ./wiretap_linux_amd64 serve -f ./wiretap_server.conf
📌 wiretap_server.conf 파일의 내용은 client에서 wiretap configure 명령 수행시 자동 생성되는 것으로, 이 파일을 server로 전달(혹은 파일 내용만 긁어서 사용)하여 사용한다.
Relay configuration:
────────────────────────────────
[Peer]
PublicKey = EiGsMPnZe7e99KRXW1nOnM7xKoq5RawWNnvKThymeB0=
AllowedIPs = 0.0.0.0/32
────────────────────────────────
E2EE configuration:
────────────────────────────────
[Peer]
PublicKey = JqaHDAnOu427ufk1mGazmEtSogRfasAIT0kPJiIXdhg=
AllowedIPs = 0.0.0.0/32
────────────────────────────────
private_key=18e4036a10bf1fe1540f0aa082fa067984d5c5002c5dce32cb3f85befd001462
listen_port=51820
public_key=654ebc6386886722374d86750454b7ba0eca444ca0980f878a1df86b26585244
endpoint=y.y.y.y:51820
allowed_ip=172.16.0.1/32
allowed_ip=fd:16::1/128
persistent_keepalive_interval=25
private_key=50d4eb37a4102bd1070c7f7ccc7422d37a68a9f4a1fa62bb6efbd75d0dfad76b
listen_port=51821
public_key=4f3a10af2571cb0e3e07ac7b2fbb5561062d1696eaf583d97ae74acfbe2c140b
endpoint=172.16.0.1:51821
allowed_ip=172.19.0.1/32
allowed_ip=fd:19::1/128
persistent_keepalive_interval=25
WIRETAP: 2024/05/31 18:05:37 API: API listener up
//Client에서 ping을 시도하면, 아래 log가 출력된다.
WIRETAP: 2024/05/31 18:05:46 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:47 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:48 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:49 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:50 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:51 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:52 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:53 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
WIRETAP: 2024/05/31 18:05:54 (client 172.19.0.1) - Transport: ICMP -> 192.168.2.1
📌 여기까지의 시험에서는 Relay interface는 사용되지 않고 있다. 즉, E2E(Peer to Peer) 간의 통신만 테스트한 것이다.
희한하게도 Server(wiretap 서버 모드)가 설치된 PC에서는 아래와 같이 wireguard 관련 내용(wireguard interface, peer 설정 상태, routing 항목 등)이 보이질 않는다. 그런데, tcpdump를 해 보면, wireguard 연결(noise handshaking 및 transport packet)이 정상인 것 처럼 보인다. 어떻게 이게 가능한 것일까 ?
$ sudo wg show
-> 아무런 내용 출력 안됨.
$ netstat -nr
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
0.0.0.0 192.168.2.1 0.0.0.0 UG 0 0 0 enp4s0
169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 enp4s0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 enp4s0
-> wireguard interface가 보이지 않음.
$ sudo tcpdump -i enp4s0 port 51820
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp4s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:27:40.843047 IP xxxclient.51820 > earth.lan.51820: UDP, length 32 //keepalive packet
18:27:43.402758 IP xxxclient.51820 > earth.lan.51820: UDP, length 192 //ping packet
18:27:43.405397 IP earth.lan.51820 > xxxclient.51820: UDP, length 192
18:27:44.407053 IP xxxclient.51820 > earth.lan.51820: UDP, length 192
18:27:44.409686 IP earth.lan.51820 > xxxclient.51820: UDP, length 192
18:27:45.406847 IP xxxclient.51820 > earth.lan.51820: UDP, length 192
18:27:45.409648 IP earth.lan.51820 > xxxclient.51820: UDP, length 192
18:28:02.603060 IP xxxclient.51820 > earth.lan.51820: UDP, length 32
18:28:02.603143 IP xxxclient.51820 > earth.lan.51820: UDP, length 96
18:28:12.608433 IP earth.lan.51820 > xxxclient.51820: UDP, length 32
18:28:27.604872 IP earth.lan.51820 > xxxclient.51820: UDP, length 96
18:28:37.675602 IP xxxclient.51820 > earth.lan.51820: UDP, length 32
18:28:52.614842 IP earth.lan.51820 > xxxclient.51820: UDP, length 96
18:28:52.615183 IP earth.lan.51820 > xxxclient.51820: UDP, length 148 //initiation packet
18:28:52.615206 IP earth.lan.51820 > xxxclient.51820: UDP, length 208
18:28:52.623391 IP xxxclient.51820 > earth.lan.51820: UDP, length 92 //response packet
18:28:52.623452 IP xxxclient.51820 > earth.lan.51820: UDP, length 160
-> wireguard 통신이 정상적으로 이루어지고 있음.
b) wiretap의 동작 원리
앞서의 tcpdump 결과를 보면 분명히 wireguard protocol이 정상적으로 동작하는 것이 맞다. 그런데, wireguard interface 정보는 어디에도 보이질 않는다. 그렇다면 linux kernel의 tunnel interface를 생성하지 않고 wireguard 통신을 한다는 얘긴데, 이게 어떻게 가능할까 ? 답은 gVisor code에 있다.
[그림 2.2] gVisor application kernel [그림 출처 - 참고 문헌 2]
아래 site는 wiretap에 관한 내용은 아니지만, gVisor를 이용하여 user space에서 동작하며, root 권한이 필요치 않는 wireguard 및 proxy를 구현하는 방법이 구체적으로 정리되어 있다. 이 내용을 읽고 나서, wiretap code를 살펴 본다면, wiretap의 동작 원리가 쉽게 이해될 수 있을 듯 하다. So Cool~ 😙
위의 site의 내용을 요약하자면,
- gVisor는 application level에서 tcp/ip stack(Netstack이라고 함) 등을 구현한 것인데,
- 이를 기반으로 tun device를 만든 후,
- tun interface를 통해 wireguard packet을 만들어 linux kernel stack을 거치지 않고, 곧 바로 peer에게로 (packet을) 내보내게 되므로,
- kernel에서 제공하는 tun driver를 사용할 필요가 없게된다(더불어 root 권한도 필요 없게됨).
뭐 ... 이런 정도의 내용이 될 것 같다. 헉헉... 😛
그렇다면, 실제 코드를 따라가 보도록 하자.
<gVisor를 이용하여 wireguard device 생성 후, link up 시키는 절차 요약>
[1] tun, tnet, err := netstack.CreateNetTUN(....)
=> tun interface 생성. 단, gVisor 기반으로 application level에서 생성됨.
[2] dev := device.NewDevice(tun, ....)
=> 이 함수 내에서 3개의 queue(handshaking, encryption, decryption) 생성,
=> 3개의 go routine(handshaking, encryption, decryption worker) 호출
[3] dev.IpcSet(....)
=> wireguard 설정
[4] err = dev.Up()
=> wireguard interface(tun interface) link up
📌 이렇게 4개의 함수만 호출해 주면, gVisor 환경에서 wireguard를 구동시킬 수 있다.
아, 그런데 위의 내용이 이미 wireguard-go code 안에 example code 형태로 존재한다. 왜 이걸 이제야 보았을까 ... 위의 site 내용도 이걸 참조로 만든 것이 분명해 보인다. 😓
📌 위의 예제를 잘 활용하면 WebAssembly 환경에서 wireguard tunnel 구현이 가능할 듯 보인다. 추후에 시도해 보도록 하자. ☕
지금까지의 내용을 숙지한 상태에서 wiretap code(src/cmd/serve.go)를 들여다 보도록 하자. 어떤까 ? 코드 흐름이 동일하지 않은가 ?
<wiretap code 분석 - wireguard device 생성 후, link up 시키는 절차 요약>
____________________________________________________________________
func init() { //GoLang 패키지 초기화 함수이다.
var err error
// Usage info.
cmd := &cobra.Command{ //Cobra CLI code이다.
Use: "serve",
Short: "Listen and proxy traffic into target network",
Long: `Listen and proxy traffic into target network`,
Run: func(cmd *cobra.Command, args []string) {
serveCmd.Run() // 앞 부분은 생략하고... 여기가 궁극적인 server의 시작 point~
},
}
rootCmd.AddCommand(cmd) //root CLI에 serve command 추가
...
}
func (c serveCmdConfig) Run() {
...
tunRelay, tnetRelay, err := netstack.CreateNetTUN(....)
...
tunE2EE, tnetE2EE, err = netstack.CreateNetTUN(....)
...
devRelay := device.NewDevice(tunRelay, ....)
err = devRelay.IpcSet(configRelay.AsIPC())
err = devRelay.Up()
...
devE2EE = device.NewDevice(tunE2EE, ....)
err = devE2EE.IpcSet(configE2EE.AsIPC())
err = devE2EE.Up()
....
}
📌 Client의 경우 처럼, server에서도 relay와 e2ee용 wireguard interface 2개가 생성됨을 알 수 있다.
____________________________________________________________________
c) wiretap에 PQC 알고리즘 적용해 보기
wiretap/src/cmd/serv.go 파일의 아래 import 구문을 다음과 같이 변경해 보도록 하자.
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/tun/netstack"
=>
"github.com/ChunghanYi/wireguard-go/wireguard/conn"
"github.com/ChunghanYi/wireguard-go/wireguard/device"
"github.com/ChunghanYi/wireguard-go/wireguard/tun"
"github.com/ChunghanYi/wireguard-go/wireguard/tun/netstack"
📌 PQC 알고리즘이 적용된 파일은 wireguard-go/wireguard/device/noise_protocol.go 파일이 전부이다(아래 blog post 1장 참조).
d) wiretap의 proxy code 분석
wiretap은 wireguard 터널(A)을 통해 peer와 안전하게 패킷을 교환한 후, 이를 최종 서버로 전달해 주기 위해 최종 서버와 tcp, udp 혹은 icmp connection(혹은 session)(B)을 별도로 맺는다. 최종 서버와의 연결이 확립된 이후에는 (A) => (B) 및 (A) <= (B)의 패킷 전달 과정을 반복한다.
Peer <==== (A)wireguard tunnel ====> Wiretap Proxy ===> (B)tcp/udp/icmp session ====> Server
[그림 2.3] wiretap proxy의 개념
그렇다면, 실제 코드 상으로도 위와 같이 되어 있는지 살펴 보도록 하자. 어떤가 ? 위의 그림과 비슷하지 않은가 ?
<tcp connection의 경우>
func (c serveCmdConfig) Run() { //wiretap/src/cmd/serve.go
...
s := transportHandler.Stack()
...
tcpForwarder := gtcp.NewForwarder(s, 0, 65535, tcp.Handler(tcpConfig))
s.SetTransportProtocolHandler(gtcp.ProtocolNumber, tcpForwarder.HandlePacket)
...
}
// Handler manages a single TCP flow.
func Handler(c Config) func(*tcp.ForwarderRequest) { //wiretap/src/transport/tcp/tcp.go
...
srcConn, err := accept(&c, req)
transport.Proxy(srcConn, dstConn)
}
func Proxy(src net.Conn, dst net.Conn) { //wiretap/src/transport/transport.go
go func() {
_, err := io.Copy(src, dst)
...
} ()
// Copy from peer to new connection.
_, nerr := io.Copy(dst, src)
}
📌 위의 코드는 tcp connection의 경우이고, udp의 경우는 코드 모양이 좀 다르다. 궁금하다면, wiretap/src/transport/udp/udp.go 파일을 분석해 보기 바란다.
이 상으로 대략적으로 Wiretap의 동작 원리를 살펴 보았다. 다음 장에서는 NetBird로 넘어가도록 하자.
3. NetBird 프로젝트
이번 장에서는 Overlay VPN의 개념과 NetBird open source project를 소개하고자 한다.
a) Overlay network VPN 이란 ?
지금까지의 전통적인(Traditional or Legacy) VPN은 중앙에 Gateway를 두고, 외부의 Client에서 VPN Gateway를 통해 내부 서버에 연결하는 방식을 취하였다. 아래 그림에서 보듯이 이러한 방식을 hub-and-spoke model이라고 한다.
[그림 3.1] Hub-and-spoke VPN 방식 [그림 출처 - 참고문헌 10]
반면, 중앙에 VPN 서버(or Gateway)가 없는 상태에서, Client 간에 1:1 직접 통신이 가능한 방식이 요즘 유행하는 p2p or mesh vpn 방식이다.
[그림 3.2] Mesh VPN 방식 [그림 출처 - 참고문헌 10]
📌 예전 것은 틀리고, 요즘 것은 맞다는 얘기가 절대 아니다. 필요성에 맞게 적용하는 것이 정답이다. 전통적인 VPN 방식이 필요한 곳이 있고, Overlay VPN 방식이 필요한 경우가 있는 것이다.
📌 위와 같은 구분 방식 외에도, 최근의 trend(?)인 Zero Trust라는 concept에 입각하여, Legacy VPN과 ZTNA(Zero Trust Network Access)로 제품을 분류하기도 한다. 이 경우는 Legacy VPN은 문제가 있고, ZTNA가 대안이라는 주장이다.
중앙의 VPN Gateway가 없는 상태에서, Client 간에 1:1 직접 통신이 가능하기 위해서는 몇가지 해결해야 할 숙제가 있다(아래 link 참조).
- NAT Traversal
- STUN
- Relay(TURN)
- ICE
- ...
아래 site에 위와 관련한 내용이 잘 정리되어 있으니, 참고하기 바란다.
국내 site(Netmania)인데, (예전에 많이 참조했었는데) 아주 쉽게 잘 정리되어 있다. 👍
b) Overlay VPN 제품 비교
최근들어 (개인적인 느낌일 수도 있지만) 급격히 Overlay VPN(P2P VPN 혹은 Mesh VPN)이 늘어가고 있는 것 같다. 아래 표는 최근 유행하는 대표적인 Overlay VPN을 비교한 것인데, NetBird와 이들 간에 어떠한 차이가 있는지 유심히 따져 볼 필요가 있다.
[그림 3.3] Overlay VPN 프로젝트 비교 [출처 - 참고 문헌 9]
📌 이번 posting에서는 시간 관계상 각 제품의 특징을 상세히 비교 분석하지는 못하였다. 관련해서는 참고 문헌 9를 참조해 보기 바란다.
<여기서 잠깐! - n2n open source project>
===============================
참고로, 이전 blog에서 n2n이라는 P2P open source project를 소개한 바 있는데, 이 또한 overlay network VPN solution으로 볼 수 있다.
n2n이 상용 제품으로 가기 위해 넘어야 할 것들로는 ...
- 암호 key 교환 방식 추가 or wireguard와 같은 공식 protocol 채용
- rosenpass 같은 protocol과 연계하는 것은 어떨까 ?
- vpn ip 자동 할당 문제
- 사용자 인증 기능 보강
- 다양한 OS 지원
- ...
c) NetBird의 주요 아키텍쳐
NetBird는 open source project인 WireGuard®, Pion ICE (WebRTC), Coturn 등과 netbird에서 자체 개발한 software를 사용하고 있으며, 1개의 Client application (or agent)와 3개의 서비스(Management, Signal and Relay), 그리고 dashboard(web 기반 UI)로 구성되어 있다.
아래 그림이 netbird의 전체 구조를 하나의 그림으로 요약해 보여준다.
[그림 3.4] Netbird Architecture1 [출처 - 참고 문헌 11]
NetBird를 사용하게 되면 각각의 client agent(or peer) 간에 1:1 직접 통신이 가능해지므로, 전형적인 Mesh 구조의 VPN을 만들 수가 있다.
[그림 3.5] Netbird Architecture2 - Mesh [출처 - 참고 문헌 11]
아래 그림은 client(peer)를 등록하고, 인증하는 역할을 담담하는 Management 서비스(서버)의 개념을 보여준다. Management 서버는 Coordination Server라고도 부른다.
[그림 3.6] Netbird Architecture3 - Management 서비스 [출처 - 참고 문헌 11]
Signal 서비스는 peer 간에 direct 연결을 하기에 적절한 후보를 선정하는 과정에서 이를 알려주는 역할을 담당한다.
[그림 3.7] Netbird Architecture3 - Signal 서비스 [출처 - 참고 문헌 11]
아래 그림은 TURN 서버를 이용한 relay service를 보여준다. Relay 서비스는 p2p direct 통신이 불가한 경우 마지막으로 선택하는 방법이다.
[그림 3.8] Netbird Architecture4 - Relay 서비스 [출처 - 참고 문헌 11]
NetBird에서는 TURN 서버를 위해 아래 coturn이라는 open source project를 이용한다.
📌 여기까지의 내용은 NetBird site에서 언급하고 있는 내용을 앵무새(?) 처럼 다시 반복하는 것에 지나지 않는다. 보다 심도 있는 설명이 필요한데, 아무래도 (코드 분석 후) 다음 기회로 넘겨야 할 듯 하다. 😓😈
끝으로, NetBird는 OIDC(OpenID Connect) 표준을 따르는 많은 IdP(ID Provider)를 지원한다.
Self-hosted options
Managed options
[그림 3.9] OpenID Connect와 OAuth2.0 관계도 [그림 출처 - 참고문헌 16]
📌 일전에 ZTNA의 concept를 파악하기 위해 인증 기능 관련하여 여러 site의 내용을 유심히 살펴 보았던 적이 있는데, 그 때 무슨 얘기인지 잘 이해가 안가던 내용이 이제 좀 이해가 되는 듯도 하다. 😤
d) 주요 코드 build해 보기
자, 그럼 netbird source code를 내려 받아 build를 진행해 보도록 하자.
$ git clone https://github.com/netbirdio/netbird
$ cd netbird
$ ls -la
합계 300
drwxrwxr-x 20 chyi chyi 4096 5월 24 17:21 .
drwxrwxr-x 16 chyi chyi 4096 6월 6 16:57 ..
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 .devcontainer
drwxrwxr-x 8 chyi chyi 4096 6월 7 15:40 .git
-rw-rw-r-- 1 chyi chyi 17 5월 24 17:21 .gitattributes
drwxrwxr-x 4 chyi chyi 4096 5월 24 17:21 .github
-rw-rw-r-- 1 chyi chyi 815 5월 24 17:21 .gitignore
-rw-rw-r-- 1 chyi chyi 5749 5월 24 17:21 .golangci.yaml
-rw-rw-r-- 1 chyi chyi 12718 5월 24 17:21 .goreleaser.yaml
-rw-rw-r-- 1 chyi chyi 2593 5월 24 17:21 .goreleaser_ui.yaml
-rw-rw-r-- 1 chyi chyi 635 5월 24 17:21 .goreleaser_ui_darwin.yaml
-rw-rw-r-- 1 chyi chyi 128 5월 24 17:21 AUTHORS
-rw-rw-r-- 1 chyi chyi 5485 5월 24 17:21 CODE_OF_CONDUCT.md
-rw-rw-r-- 1 chyi chyi 11932 5월 24 17:21 CONTRIBUTING.md
-rw-rw-r-- 1 chyi chyi 9686 5월 24 17:21 CONTRIBUTOR_LICENSE_AGREEMENT.md
-rw-rw-r-- 1 chyi chyi 1516 5월 24 17:21 LICENSE
-rw-rw-r-- 1 chyi chyi 12859 5월 24 17:21 README.md
-rw-rw-r-- 1 chyi chyi 513 5월 24 17:21 SECURITY.md
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 base62
drwxrwxr-x 14 chyi chyi 4096 6월 7 15:40 client
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 dns
drwxrwxr-x 3 chyi chyi 4096 5월 24 17:21 docs
drwxrwxr-x 3 chyi chyi 4096 5월 24 17:21 encryption
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 formatter
-rw-rw-r-- 1 chyi chyi 9464 5월 24 17:21 go.mod
-rw-rw-r-- 1 chyi chyi 107459 5월 24 17:21 go.sum
drwxrwxr-x 5 chyi chyi 4096 5월 24 17:21 iface
drwxrwxr-x 4 chyi chyi 4096 5월 24 17:21 infrastructure_files
drwxrwxr-x 6 chyi chyi 4096 5월 24 17:21 management
drwxrwxr-x 4 chyi chyi 4096 5월 24 17:21 release_files
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 route
drwxrwxr-x 3 chyi chyi 4096 5월 24 17:21 sharedsock
drwxrwxr-x 7 chyi chyi 4096 5월 24 17:21 signal
drwxrwxr-x 4 chyi chyi 4096 5월 24 17:21 util
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 version
📌 NetBird dashboard(WebUI) 코드는 아래 github에서 download 받을 수 있다.
$ cd client$ CGO_ENABLED=0 go build .
$ ls -la
합계 51284
drwxrwxr-x 14 chyi chyi 4096 6월 7 15:40 .
drwxrwxr-x 20 chyi chyi 4096 5월 24 17:21 ..
-rw-rw-r-- 1 chyi chyi 184 5월 24 17:21 Dockerfile
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 android
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 anonymize
-rwxrwxr-x 1 chyi chyi 52424736 6월 7 15:40 client // 이 파일이 생성된다.
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 cmd
drwxrwxr-x 7 chyi chyi 4096 5월 24 17:21 firewall
-rw-rw-r-- 1 chyi chyi 6060 5월 24 17:21 installer.nsis
drwxrwxr-x 16 chyi chyi 4096 5월 24 17:21 internal
drwxrwxr-x 3 chyi chyi 4096 5월 24 17:21 ios
-rw-rw-r-- 1 chyi chyi 147 5월 24 17:21 main.go
-rw-rw-r-- 1 chyi chyi 605 5월 24 17:21 manifest.xml
-rw-rw-r-- 1 chyi chyi 3624 5월 24 17:21 netbird.wxs
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 proto
-rw-rw-r-- 1 chyi chyi 223 5월 24 17:21 resources.rc
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 server
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 ssh
drwxrwxr-x 4 chyi chyi 4096 5월 24 17:21 system
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 testdata
drwxrwxr-x 3 chyi chyi 4096 5월 24 17:21 ui
<실행하기>
$ sudo ./client up --log-level debug --log-file console
📌 Windows용 client를 build 하기 위해서는 아래의 명령을 사용한다.
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o netbird.exe ./client/
$ cd ../signal
$ go build
$ ls -l
합계 17128
drwxrwxr-x 7 chyi chyi 4096 6월 7 15:45 .
drwxrwxr-x 20 chyi chyi 4096 5월 24 17:21 ..
-rw-rw-r-- 1 chyi chyi 153 5월 24 17:21 Dockerfile
-rw-rw-r-- 1 chyi chyi 2804 5월 24 17:21 README.md
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 client
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 cmd
-rw-rw-r-- 1 chyi chyi 146 5월 24 17:21 main.go
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 peer
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 proto
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 server
-rwxrwxr-x 1 chyi chyi 17496543 6월 7 15:45 signal // 이 파일이 생성된다.
<실행하기>
$ ./signal run --log-level debug --log-file console
$ cd ../management
$ go build
$ ls -la
합계 53576
drwxrwxr-x 6 chyi chyi 4096 6월 7 15:44 .
drwxrwxr-x 20 chyi chyi 4096 5월 24 17:21 ..
-rw-rw-r-- 1 chyi chyi 210 5월 24 17:21 Dockerfile
-rw-rw-r-- 1 chyi chyi 233 5월 24 17:21 Dockerfile.debug
-rw-rw-r-- 1 chyi chyi 4597 5월 24 17:21 README.md
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 client
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 cmd
-rw-rw-r-- 1 chyi chyi 150 5월 24 17:21 main.go
-rwxrwxr-x 1 chyi chyi 54813824 6월 7 15:44 management // 이 파일이 생성된다.
drwxrwxr-x 2 chyi chyi 4096 5월 24 17:21 proto
drwxrwxr-x 19 chyi chyi 4096 5월 24 17:21 server
<실행하기>
$ ./management management --log-level debug --log-file console --config ./management.json
여기까지 간단히 build를 진행해 보았고, 그 결과로 client(peer agent), managment(Coordination server), signal(Signal server) 등의 binary를 얻었다.
📌 위와 관련 내용은 netbird/CONTRIBUTING.md 파일을 참조로하여 작성하였다. 앞서 설명한 내용에는 dashboard build 절차와 coturn build 방법이 아직 빠져 있다.
e) Self-hosted NetBird 구축해 보기
이번 절에서는 자체 서비스 가능한 NetBird를 구축하는 과정을 소개해 보고자 한다. 실제로 돌려 보아야 NetBird가 어떻게 동작하는지를 파악할 수 있을 것이다.
<shell script download 후, 실행>
$ curl -sSLO https://github.com/netbirdio/netbird/releases/latest/download/getting-started-with-zitadel.sh
$ cat getting-started-with-zitadel.sh
$ export NETBIRD_DOMAIN=netbird.example.com // domain 명은 적절히 수정해 주어야 함.
$ bash getting-started-with-zitadel.sh
...
...
📌 당초 계획은 위의 내용을 실 환경에서 돌려 보고, 설치된 내용을 토대로 이후 설명을 진행하는 것이었으나, 아쉽게도 환경이 갖추어지지 못하여, 요 정도 선에서 1차 마무리하고자 한다. 추후 환경이 갖추어 지는 대로 다시 시도해 볼 생각이다. 😓
f) NetBird의 source code 분석
netbird source code 내의 주요 디렉토리의 용도(의미)를 파악해 보는 것도 의미가 있을 것 같다.
<CONTRIBUTING.md 파일에서 발췌>
- /client - NetBird agent code
- /client/cmd - NetBird agent cli code
- /client/internal - NetBird agent business logic code
- /client/proto - NetBird agent daemon GRPC proto files
- /client/server - NetBird agent daemon code for background execution
- /client/ui - NetBird agent UI code
- /encryption - Contain main encryption code for agent communication
- /iface - Wireguard® interface code
- /infrastructure_files - Getting started files containing docker and template scripts
- /management - Management service code
- /management/client - Management service client code which is imported by the agent code
- /management/proto - Management service GRPC proto files
- /management/server - Management service server code
- /management/server/http - Management service REST API code
- /management/server/idp - Management service IDP management code
- /release_files - Files that goes into release packages
- /signal - Signal service code
- /signal/client - Signal service client code which is imported by the agent code
- /signal/peer - Signal service peer message logic
- /signal/proto - Signal service GRPC proto files
- /signal/server - Signal service server code
📌 역시 source code 분석에는 시간이 좀 필요하다. 😂
NetBird를 분석하는 최종 목적은 NetBird를 기반(수정/보강)으로 Overlay VPN 제품을 개발하는 것이다.
4. DefGuard 프로젝트
요즘에 Rust를 study 중에 있다. Rust로 작성된 WireGuard code를 찾던 중, Rust와 TypeScript로 작성된 (아직은 완성도면에서 어떨지 모르지만) 외관상 아주 훌륭한 project가 있어서, 여기에 언급해 보고자 한다. 이름하여 데프가드(DefGuard)~
Open Source Enterprise SSO & VPN
The only open-source solution with real WireGuard MFA/2FA & integrated OpenID Connect SSO.
어찌보면 NetBird랑 비슷한 것 같기도 하고, 좀 더 나은 것 같기도 하고 ... 암튼 재밌는 기능이 많이 보인다. 😍
자세한 내용 분석은 Rust study가 어느 정도 끝나는 대로 진행해 보도록 하겠다. 😂
______________________________________________
이번 posting은 이 정도 선에서 마무리해야 할 듯 싶다. 당초 계획보다 내용이 많이 부족한데, "끝날 때까지 끝난 것이 아니다"라는 말을 남기며 이번 posting을 마무리하고자 한다. 끝까지 읽어 주셔서 감사 드린다. 😎
To be continued ...
5. References
[1] https://github.com/sandialabs/wiretap
[2] https://gvisor.dev/
[3] https://pkg.go.dev/github.com/spf13/cobra#Command
[4] https://cobra.dev/
[5] https://dev.to/aurelievache/series/13751
[6] https://github.com/scraly/learning-go-by-examples/
[7] https://nangman14.tistory.com/97
[8] https://pkg.go.dev/github.com/spf13/cobra#Command
---> wiretap 관련 내용
[9] https://franklinetech.com/comparative-analysis-of-top-overlay-vpn-networks/
[10] https://tailscale.com/blog/how-tailscale-works
[11] https://docs.netbird.io/about-netbird/how-netbird-works
[12] https://www.netmanias.com/ko/post/blog/6263/nat-network-protocol-p2p/p2p-nat-nat-traversal-technic-rfc-5128-part-2-udp-hole-punching
[13] https://tailscale.com/blog/how-nat-traversal-works
[14] https://docs.netbird.io/selfhosted/identity-providers#auth0
[15] https://www.google.com/search?q=netbird+server&oq=netbird+server&gs_lcrp=EgZjaHJvbWUyCwgAEEUYExg5GIAEMgoIARAAGAgYExgeMggIAhAAGBMYHjIKCAMQABiABBiiBDIKCAQQABiABBiiBDIGCAUQRRg8MgYIBhBFGDwyBggHEEUYPNIBCDI4ODlqMGo0qAIAsAIB&sourceid=chrome&ie=UTF-8#fpstate=ive&vld=cid:a952312e,vid:_Fgwap-sl3A,st:0
[16] https://velog.io/@wlsh44/OpenID-Connect%EC%99%80-OAuth2.0
---> netbird 관련 내용
[17] And Google~
Slowboot
댓글 없음:
댓글 쓰기