OpenVPN Server憑證配置與連線設定 === ###### tags: `Linux` ## 憑證配置 ### OpenVPN憑證模型 OpenVPN的憑證模型算簡單的, 憑證鍊最少只需要兩層 基本的憑證結構是由CA簽署出server端跟client端的憑證 ![ca_single_no_chain](https://hackmd.io/_uploads/HysXd0Qjxe.png) 因此實作上最少只需要三份憑證(ca, server, client), 以及各自的私鑰 ### 憑證的用途 舉例來說, server要如何判斷client是可連入的對象呢? 雖然server跟client沒有信任關係, 但是server信任CA(server的憑證是由CA簽發), 所以只要client的憑證是由相同的CA簽發的, 那就可以認為這個client是可信任的對象(允許連入) 反過來說, 如果client的憑證使用CA的公鑰驗證簽章失敗, 代表該client不是可信任對象(禁止連入) 因此: CA的腳色就是server與client共同信任的對象, 只負責簽發憑證, 但會提供ca.crt讓其他腳色可以驗證要驗的對象是否被CA信任 對server來說, 需要的檔案是ca.crt, server.crt, server.key 對client來說, 需要的檔案是ca.crt, client.crt, client.key ![certs](https://hackmd.io/_uploads/HJ4sEA7sex.png) 不過在這邊會以兩份CA憑證當作範例, 其實結構也是類似的 只是CA鍊多了一張中繼憑證(intermediate), 當要簽發憑證時並不直接使用rootCA, 而是讓下一層的signingCA代為簽署 ![ca_signing_chain_dashed](https://hackmd.io/_uploads/SJFdUCXjgx.png) 為求使用簡單且兼顧安全性跟高效, 憑證配置會以ECDSA取代RSA, 而且資料加密只使用AEAD算法 環境配置以TLS 1.3為目標, OpenVPN限定2.5/2.6, 實作環境為Ubuntu 24.04 且為了高度客製憑證(三層鍊+ECDSA, 還有自定義的X509v3 extensions), 因此接下來使用的憑證產生工具不使用easy-rsa, 而是使用彈性較高的openssl ### 建立憑證鏈 首先先建立一張自簽憑證rootCA, 這邊要注意的兩點: 1. basicConstraints的CA必須為TRUE 2. keyUsage必須有keyCertSign跟cRLSig兩項 ``` # 建立rootCA的key-pair $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out root.key # 建立自簽憑證root.crt $ openssl req -x509 -new -key root.key -sha384 -days 3650 -out root.crt \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ -addext "subjectKeyIdentifier=hash" \ -addext "authorityKeyIdentifier=keyid:always,issuer" ``` 再來建立中繼憑證signingCA, 由rootCA簽發, 這邊的注意點跟rootCA相同, 但可以在basicConstraints多加pathlen=0, 代表這張憑證無法再往下簽發CA憑證 ``` # 建立signingCA的key-pair $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out signing.key # 建立CSR $ openssl req -new -key signing.key -sha384 -out signing.csr # 使用root的私鑰簽署CSR, 得到signing.crt $ openssl x509 -req -in signing.csr -CA root.crt -CAkey root.key -CAcreateserial \ -days 3650 -sha384 \ -extfile <(printf "[v3_ca]\n\ basicConstraints=critical,CA:TRUE,pathlen:0\n\ keyUsage=critical,keyCertSign,cRLSign\n\ subjectKeyIdentifier=hash\n\ authorityKeyIdentifier=keyid:always,issuer\n") \ -extensions v3_ca -out signing.crt ``` rootCA跟中繼憑證都必須提供出去, 這樣才算是完整的CA chain 我們可以把兩份CA憑證都放在同一個檔案, 方便使用 順序的話, 中繼憑證先, 再接根憑證 ``` # 將rootCA與signingCA合併成一個CA憑證鏈ca.crt $ cat signing.crt root.crt > ca.crt ``` 接下來要建立OpenVPN server需要的憑證, 由server端產生CSR後丟給CA簽署 注意簽署時必須加入keyUsage=digitalSignature跟extendedKeyUsage=serverAuth這兩項, 以及basicConstraints的CA必須為FALSE (不能是CA憑證) ``` # 建立server的key-pair $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out server.key # 建立CSR $ openssl req -new -key server.key -sha384 -out server.csr # 使用signingCA的私鑰簽署CSR, 得到server.crt $ openssl x509 -req -in server.csr -CA signing.crt -CAkey signing.key -CAcreateserial \ -days 825 -sha384 \ -extfile <(printf "[v3_server]\n\ basicConstraints=critical,CA:FALSE\n\ keyUsage=critical,digitalSignature\n\ extendedKeyUsage=serverAuth\n\ subjectKeyIdentifier=hash\n\ authorityKeyIdentifier=keyid,issuer\n") \ -extensions v3_server -out server.crt ``` 接下來要建立OpenVPN client需要的憑證, 由client端產生CSR後丟給CA簽署 注意簽署時必須加入keyUsage=digitalSignature跟extendedKeyUsage=clientAuth這兩項, 以及basicConstraints的CA必須為FALSE (不能是CA憑證) ``` # 建立client的key-pair $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out client.key # 建立CSR $ openssl req -new -key client.key -sha384 -out client.csr # 使用signingCA的私鑰簽署CSR, 得到client.crt $ openssl x509 -req -in client.csr -CA signing.crt -CAkey signing.key -CAcreateserial \ -days 825 -sha384 \ -extfile <(printf "[v3_client]\n\ basicConstraints=critical,CA:FALSE\n\ keyUsage=critical,digitalSignature\n\ extendedKeyUsage=clientAuth\n\ subjectKeyIdentifier=hash\n\ authorityKeyIdentifier=keyid,issuer\n") \ -extensions v3_client -out client.crt ``` 到這邊所需要的憑證就都準備完成了 保險一點可以加驗憑證的信任鍊 ``` # 驗證server與CA的信任鍊 $ openssl verify -show_chain -CAfile ca.crt server.crt # 驗證client與CA的信任鍊 $ openssl verify -show_chain -CAfile ca.crt client.crt ``` ## 伺服器配置 ### OpenVPN server configuration OpenVPN Server的配置文件預設是放在/etc/openvpn/server/ 在設定之前,我們可以考慮使用TLS Authentication 我們先來產生一把ta.key, 這把key可以讓我們把控制通道的封包整個加密, 如果沒有也沒關係, 只是在握手階段時比較容易被掃到是使用OpenVPN通訊 如果有加密, 那握手階段的封包會長得很像隨機資料, 會更難辨識或是阻擋 ``` $ openvpn --genkey secret ta.key ``` 接下來產生一個server.conf放在預設路徑下, conf的內容如下 值得一提的是我們使用了secp384r1, 這種ECDHE算法可以在交換金鑰後直接算出shared secret, 因此並不需要dh檔, 所以在這邊dh就設為none data-ciphers我們也只留下AEAD算法, AEAD本身就有HMAC可以做驗證, 因此不需額外的設置 不加入AES-CBC是因為這種算法缺少了HMAC做驗證, 會需要額外設置auth的方式 (AEAD具備完整的加解密與資料驗證的功能, CBC只能加解密) ``` port 1194 proto udp4 dev tun server 10.8.0.0 255.255.255.0 topology subnet # CA鏈:Intermediate(中繼) + Root ca ca.crt cert server.crt key server.key # 資料通道(AEAD),相容 2.5/2.6 data-ciphers AES-256-GCM:CHACHA20-POLY1305 data-ciphers-fallback AES-256-GCM dh none tls-version-min 1.3 # 控制通道(TLS Auth)要使用的key tls-crypt ta.key # 連線穩定性 & 安全性 keepalive 10 60 persist-key persist-tun user nobody group nogroup # 推送路由,可選,需看網路環境做更改 push "route 192.168.50.0 255.255.255.0" # UDP 關閉通知(Windows/RouterOS 客戶端友好) explicit-exit-notify 1 # 用 systemd journal 看 log;除錯時再調整 verb verb 3 ``` 接下來把需要的憑證跟key也複製到這個路徑下: ``` $ ls ca.crt server.conf server.crt server.key ta.key ``` 這樣server端的OpenVPN配置就完成了 ### Linux服務與網路配置 首先安裝openvpn套件 ``` sudo apt install openvpn ``` systemd service相關的指令 ``` sudo systemctl start openvpn-server@server sudo systemctl enable openvpn-server@server sudo systemctl status openvpn-server@server ``` Linux預設不會幫你轉發封包, 如果client要送的封包對象不是server, 那傳到server時封包就會被直接丟棄 因此如果client想要存取server區網上的其他設備, 那就要讓server開啟路由的功能 以Ubuntu 24.04來說, 我們需要設定net.ipv4.ip_forward=1, 方法如下: 建立以下檔案 ``` $ sudo vi /etc/sysctl.d/99-ipforward.conf ``` 之後在檔案內增加以下一行 ``` net.ipv4.ip_forward=1 ``` 存檔後重載設定讓新設定生效 ``` $ sudo sysctl -p /etc/sysctl.d/99-ipforward.conf ``` 可以用以下指令驗證是否開啟, 有開啟的話回傳值會是1 ``` $ sudo sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1 ``` 現在封包轉發是可以了, 但還有個問題 VPN client的ip網段是10.8.0.0/24, 去程沒問題 但回程的話如果沒有特別設定路由, 當封包要往10.8.0.0/24送的時候就會不知道要往哪送了 一種解法是設定路由器的靜態路由, 在server的上層router增加對10.8.0.0/24的路由規則, 假設server的private IP是192.168.50.101, 那麼規則如下: ``` Host IP: 10.8.0.0 Netmask: 255.255.255.0 Gateway: 192.168.50.101 Interface: LAN ``` 另一種解法是不修改上層router, 而是設定iptables把VPN client(10.8.0.0/24)發往區網的封包, 全部偽裝成從server的private IP(192.168.50.101)發出 ``` $ sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j SNAT --to-source 192.168.50.101 ``` 透過這種方式, 區網其他電腦以為"是server要連我", 所以封包就會回覆給server(192.168.50.101), 然後server再把回覆轉給VPN client 不過這樣設定重開機後就會揮發掉, 要永久生效的話還需要做幾個步驟: ``` $ sudo apt install iptables-persistent $ sudo iptables-save | sudo tee /etc/iptables/rules.v4 ``` 下次開機時, iptables-persistent就會自動載入/etc/iptables/rules.v4 ## 使用者配置 ### OpenVPN client profile 先將需要的檔案準備好, 檔案如下 ca.crt跟ta.key都是必須要與server使用的相同 ``` ca.crt client.crt client.key ta.key ``` 接下建立一個.ovpn的OpenVPN profile, 內容如下 檔案開頭的x.x.x.x需更換成server的public ip 檔案下方的內嵌憑證區塊就直接把對應的檔案內容複製貼上 ``` client dev tun proto udp4 remote x.x.x.x 1194 resolv-retry infinite nobind persist-key persist-tun keepalive 10 60 remote-cert-tls server tls-version-min 1.3 data-ciphers AES-256-GCM:CHACHA20-POLY1305 data-ciphers-fallback AES-256-GCM tls-crypt ta.key verb 3 <ca> -----BEGIN CERTIFICATE----- # ca.crt -----END CERTIFICATE----- </ca> <cert> -----BEGIN CERTIFICATE----- # client.crt -----END CERTIFICATE----- </cert> <key> -----BEGIN PRIVATE KEY----- # client.key -----END PRIVATE KEY----- </key> <tls-crypt> -----BEGIN OpenVPN Static key V1----- # ta.key -----END OpenVPN Static key V1----- </tls-crypt> ``` 設定檔配置完成後若client是Linux可以使用 ``` $ sudo openvpn --config client.ovpn ``` 若是Windows則是使用OpenVPN Connect for Windows將.ovpn import進去後就可連線 :diamond_shape_with_a_dot_inside:Cyui