時常在開發任何應用服務時,會利用 HTTP 協定來串接服務與服務之間的通訊,然而這樣的方式可能存在著一定的風險,因此就有人會想是否可以在傳輸過程中加入驗證機制,來提高資料的安全性,進而衍伸出 TLS 雙向驗證機制。
筆者在了解其中運作原理時,意外在網路上看到一篇文章 ,用故事方式來描述這之間的關係,覺得還不錯分享給讀者們。
大意如下:
有一天 A 公司的業務要去 B 公司進行業務接洽,當業務到達 B 公司時,被 B 公司的警衛攔下,並要求業務出示身份。不過 B 公司沒有人認識這位業務,因此業務只能拿出蓋有 A 公司印章的文件來證明自己是 A 公司的員工。
聽完以上的敘述雖然可能大致上沒什麼問題,不過有人可能會想到,如果這樣 B 公司有 100 的合作廠商,不就需要記住 100 個公司的印章才能身任身份驗證的任務。
因此,C 公司看準了這個商機,成為了第三方的驗證機構。在 B 公司信任 C 公司與 C 公司信任 A 的的基礎上,只要 A 公司向 B 公司提出自己與 C 公司的印章,則為合法。如此一來 B 公司只要記得 C 公司印章即可。
上述 C 公司就是 CA 的角色,驗證的過程如故事情境,接著我們就實際來建置完整的驗證流程。
cfssl 簡介 我們使用 cfssl 這個工具來產生金鑰與憑證,cfssl 是 CloudFlare 公司是基於 Golang 開源的 PKI/TLS 工具。當前僅支援 ECDSA
與 RSA
兩種方式。
安裝 首先要先安裝 cfssl
工具
1 2 3 4 $ export CFSSL_URL=https://pkg.cfssl.org/R1.2 $ wget ${CFSSL_URL}/cfssl_linux-amd64 -O /usr/local/bin/cfssl $ wget ${CFSSL_URL}/cfssljson_linux-amd64 -O /usr/local/bin/cfssljson $ chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson
安裝完成後,利用指令檢查當前安裝版本。
1 2 3 4 5 $ cfssl version Version: 1.2.0 Revision: dev Runtime: go1.6
配置 在建立服務憑證前,我們需要先建立 ca/ca-csr.json
檔案來產生 CA 憑證與金鑰。ca/ca-csr.json
大致上會描述如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ mkdir ca $ vim ca-csr.json { "CN": "james-blog.com", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "TW", "ST": "Taipei", "L": "Taipei", "O": "james company", "OU": "james company" } ] }
CN(Common Name) 用來識別使用的 Domain。
Key 描述用 RSA 進行加密,並且長度為 2048(當前僅支援 2048 - 8192)
Name.C 表示國家
Name.ST 與 L 表示城市
Name.O 表示公司
Name.OU 表示公司部門
建立完成後,初始化 CA 憑證與金鑰。
1 2 3 4 5 6 7 8 $ cfssl gencert -initca ca-csr.json | cfssljson -bare ca/ca 2021/02/08 07:49:22 [INFO] generating a new CA key and certificate from CSR 2021/02/08 07:49:22 [INFO] generate received request 2021/02/08 07:49:22 [INFO] received CSR 2021/02/08 07:49:22 [INFO] generating key: rsa-2048 2021/02/08 07:49:23 [INFO] encoded CSR 2021/02/08 07:49:23 [INFO] signed certificate with serial number 512432585453518655479838519048416241349248297204
檢視當前 ca/
目錄內的檔案,即 ca-key.pem
為金鑰,ca.pem
為憑證。
1 2 3 $ ls ca/ ca.csr ca-key.pem ca.pem
完成後,接著利用 CA 來建立服務的金耀與憑證。我們利用 nginx
作為示範,建立 nginx/nginx-csr.json
檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ mkdir nginx $ vim nginx/nginx-csr.json { "CN": "james-blog.com", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "TW", "ST": "Taipei", "L": "Taipei", "O": "james company", "OU": "james company" } ] }
這邊我們就用與 CA 相同的檔案內容。
建立 ca-config.json
檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ vim ca-config.json { "signing": { "default": { "expiry": "87600h" }, "profiles": { "nginx": { "usages": [ "signing", "key encipherment", "server auth", "client auth" ], "expiry": "87600h" } } } }
expiry 表示可用時間為一年
signing 表示可用於其他簽名證書
server auth 表示 Client 可以 CA 對 Server 提供的憑證進行驗證
client auth 表示 Server 可以 CA 對 Client 提供的憑證進行驗證
基於 CA 的憑證與金鑰,產生 nginx
服務憑證與金鑰。
1 2 3 4 5 6 7 8 9 10 11 12 13 $ cfssl gencert \ -ca=ca/ca.pem \ -ca-key=ca/ca-key.pem \ -config=ca-config.json \ -hostname=james-blog.com \ -profile=nginx \ nginx/nginx-csr.json | cfssljson -bare nginx/nginx 2021/02/08 08:03:09 [INFO] generate received request 2021/02/08 08:03:09 [INFO] received CSR 2021/02/08 08:03:09 [INFO] generating key: rsa-2048 2021/02/08 08:03:09 [INFO] encoded CSR 2021/02/08 08:03:09 [INFO] signed certificate with serial number 67599991771298537291786410610924862791459550062
檢視當前 nginx/
目錄內的檔案,即 nginx-key.pem
為金鑰,nginx.pem
為憑證。
1 2 3 $ ls nginx/ nginx.csr nginx-csr.json nginx-key.pem nginx.pem
接著將 nginx/nginx.pem
與 nginx/nginx-key.pem
內容合成一份名為 tls.pem
檔案以提供 HAProxy 配置使用。
1 2 $ cat nginx/nginx.pem > tls.pem $ cat nginx/nginx-key.pem >> tls.pem
檢視當前 tls.pem
內容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 $ cat tls.pem -----BEGIN CERTIFICATE----- MIIEGjCCAwKgAwIBAgIUC9dJ2RtjdZYV/EvQvYWwUrl0M24wDQYJKoZIhvcNAQEL BQAweDELMAkGA1UEBhMCVFcxDzANBgNVBAgTBlRhaXBlaTEPMA0GA1UEBxMGVGFp cGVpMRYwFAYDVQQKEw1qYW1lcyBjb21wYW55MRYwFAYDVQQLEw1qYW1lcyBjb21w YW55MRcwFQYDVQQDEw5qYW1lcy1ibG9nLmNvbTAeFw0yMTAyMDgwNzU4MDBaFw0z MTAyMDYwNzU4MDBaMHgxCzAJBgNVBAYTAlRXMQ8wDQYDVQQIEwZUYWlwZWkxDzAN BgNVBAcTBlRhaXBlaTEWMBQGA1UEChMNamFtZXMgY29tcGFueTEWMBQGA1UECxMN amFtZXMgY29tcGFueTEXMBUGA1UEAxMOamFtZXMtYmxvZy5jb20wggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxLh+7I7avapikDuiP9JQhHJu4hVlxrcvK trMcXP9QcqiRkrgJ/Z2kWxM1KRq1TEFpArBCaLBxDpRVDuH3zG5b+gbZSF+uAK1d WBy7ANFnSjj49/XtD54gU9mu0DUoGgQFU0zfI7RDOKwXbVmL04WBVI1avS88qxVa jjaKCwvz4ubvIHrDhJRzVIzKrGuC5F7lRoahQTfL6Qu6kD89AgpiunPnAeMaQULU LKt4ScdPrZsbnd6hlRBt8SsFx6ain/Z9J2FG/J27XwtV78nO409owZ4Is/ZwANQC oI3BdvEO4i2p5B0f5517FKM8p13ZQ02WbFPHpch47XDUgpJWqQ8BAgMBAAGjgZsw gZgwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSLQt4fZ6N7eBFxFRfGrb/Yy8sTOjAf BgNVHSMEGDAWgBTgpE193bdCcTryuM5SoEVqxDpUFDAZBgNVHREEEjAQgg5qYW1l cy1ibG9nLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEATsIqEmyayWvrfapcEMnzrirr r1uIPRHlWYjzWx0HRtaxnWzk/mgtDSDUTzAVM7+unQFqy7ZsJoI7zsDuhfidv8WG 3ChdSs773grz5m6GB3Wy8n+P5LNmqp3uKN80e1A0fjccqoKcec29YdMjlCRWj/El 7OF9vOQECROFiGDiXvz9Ou0BsAB79GgTjMRQfwIVTjgVhh9rVR5Q4eDtaUqnOF5e Wxo8erHBw8wIhfjrSI7Mfa09MWwF4ohC3U1tHnkb44gjdz6wfZ1xFv/7rw6LA1bG bD5mj2FUKoMKnzL9FkQhoe8H5YBchdB5IUS6MZygmwAgQ4L89dByLes3QJAT6Q== -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA8S4fuyO2r2qYpA7oj/SUIRybuIVZca3LyrazHFz/UHKokZK4 Cf2dpFsTNSkatUxBaQKwQmiwcQ6UVQ7h98xuW/oG2UhfrgCtXVgcuwDRZ0o4+Pf1 7Q+eIFPZrtA1KBoEBVNM3yO0QzisF21Zi9OFgVSNWr0vPKsVWo42igsL8+Lm7yB6 w4SUc1SMyqxrguRe5UaGoUE3y+kLupA/PQIKYrpz5wHjGkFC1CyreEnHT62bG53e oZUQbfErBcemop/2fSdhRvydu18LVe/JzuNPaMGeCLP2cADUAqCNwXbxDuItqeQd H+edexSjPKdd2UNNlmxTx6XIeO1w1IKSVqkPAQIDAQABAoH/QuOH6V7+S6hErTt1 RHeQnZ5Rkdtp8x1AZ/hDtJoWNTmXhsgqQpsUHYEk0pmTzrRXb8hPGhvu02w3t19p TFBmSxwMyjZIUvndGwZU8YhIi10KcAJVMmoicNTQiqs0EuskMlXn+/rrA7m0AMtT CnSfkj7g9UmC0FCim3rVpXNq3TiG9H+tFPnOFadTbRJi0CkADgMRrmywliBe/7Dd B6Xcp/2jxHeoMiqqOcszDgIxG5CoE03mghqqfxalJXzioMexaMyMH4epoYFaKVsI KfOIo1Fie8JPCtOP0DEi3BOolaFd9Nrj3RwgMIoQQ0ZeOdkJUvZRklOxJHwG3hEQ 8sNRAoGBAPJM9SueSnDqqhofjcRl8a5CKY9y7zwTiXy92VQFhsK21fG8EviEgs50 577EHE4Cb5gxQxsPSK54gxOOZ2ZbP11ynaKbI2ZbPUUgNppiaPc+ERG6xBL4e0/r VzAqd5kAZZUov5oi2RW6cBtQeONhgdRGN3Fq0an1QCMaJe9TRcRlAoGBAP7Q8vLp L7rAs8jqllD+Ijx7aDzLMDHD2k2iWatSe6uIXghyqOD7VmSI4goYYqtcipRv53iH dwWm/c6BtqwHrsOgM12ZiqotDDOUhgJIe29IEdGHG+fJ9rfefAbeV53I8CadL+G/ wtmaMDzq9h6FZmMPT2lzvYj9oeBKQAfqw7BtAoGBAIjD5583Oc2Cp4EXTm4NHN2/ erX9qgu++1vtzT4f4HEHwrsv7YVZRnxCgIytJUzjExpUtAwSFHRmkZX4S2T7HEki 6NdfuhuMZIkgJbH+2kC0R+45/XK3zuLNc+k8D0XNc4k99uiJwv8AvUatpY6y+xVW jPT31mCYjhtCJydvWXIBAoGBAMC0ECc3xgq7lLsK/WZ+6jFHOotPNkFMVhmD/8Aa fsA4PrSw0ZpjOPCKvDbaPjRNpdef0TNLbu1tXl//pL/wh3AWBQJyDXWo36NaXQX4 /rAnlqIYRThDejuPG8it+SCwRz1MfluBA8BAZN6M6lgmlkmv2GRtTRb+iJ7wSAA0 wIz5AoGBAJhIzuMCMuUpywBPJr6L4zwWBswhqds/KZX31EcYCFVxL/rtkNQhjy/b mL+JGNARMbYHadqP/wobwqhY2WI9ZtBb+A+mVKl/kO38q+hwUCigOumo2P3U76SR 9ofrV/klBKDE8Az+D5M0zwWvmosutoIQDpgiif20qlySdX1N9U7g -----END RSA PRIVATE KEY-----
最後,編輯 /etc/haproxy/haproxy.cfg
檔案,加入 TLS 配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ vim /etc/haproxy/haproxy.cfg ... global ... tune.ssl.default-dh-param 2048 frontend f_nginx mode http bind *:8443 ssl crt /home/vagrant/tls.pem default_backend b_nginx backend b_nginx server nginx-1 192.168.10.10:80 check
在 global 加入 tune.ssl.default-dh-param 2048
並在最底部加入上述配置,其原因為 RSA 為長度 2048,預設 HAProxy 為 1024,若無調整則啟動時會產生警告。
重新啟動 HAProxy 服務。
1 $ systemctl restart haproxy
檢視當前 HAProxy 狀態。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ systemctl status haproxy ● haproxy.service - HAProxy Load Balancer Loaded: loaded (/lib/systemd/system/haproxy.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2021-02-08 08:11:44 UTC; 1s ago Docs: man:haproxy(1) file:/usr/share/doc/haproxy/configuration.txt.gz Process: 15325 ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS (code=exited, status=0/SUCCESS) Main PID: 15336 (haproxy) Tasks: 2 (limit: 1074) Memory: 6.6M CGroup: /system.slice/haproxy.service ├─15336 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock └─15337 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock Feb 08 08:11:44 ubuntu systemd[1]: Starting HAProxy Load Balancer... Feb 08 08:11:44 ubuntu haproxy[15336]: Proxy f_nginx started. Feb 08 08:11:44 ubuntu haproxy[15336]: Proxy f_nginx started. Feb 08 08:11:44 ubuntu haproxy[15336]: Proxy b_nginx started. Feb 08 08:11:44 ubuntu haproxy[15336]: Proxy b_nginx started. Feb 08 08:11:44 ubuntu haproxy[15336]: [NOTICE] 038/081144 (15336) : New worker #1 (15337) forked Feb 08 08:11:44 ubuntu systemd[1]: Started HAProxy Load Balancer.
結果 我們需要在 /etc/hosts
中加入 192.168.10.10 james-blog.com
讓系統認得這個 Domain。
1 2 3 4 $ vim /etc/hosts ... 192.168.10.10 james-blog.com
利用 cUrl
指令並帶入憑證進行驗證。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $ curl --cacert nginx/nginx.pem https://james-blog.com:8443 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
若想要透過瀏覽器進行測試,在 MacOS 系統就需要將憑證加入到”鑰匙圈存取”中,並且開啟”永遠信任”。
若成功,則利用 Chrome 瀏覽器可以看到網址左邊有一個”鎖”的 icon。
問題 因為我們在建立 nginx
服務憑證時,有註明使用的 hostname 為 james-blog.com
,因此若使用其他們 domain 會發生錯誤。
1 2 3 4 5 6 7 8 $ curl --cacert nginx/nginx.pem https://james1-blog.com:8443 curl: (60) SSL: no alternative certificate subject name matches target host name 'james1-blog.com' More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above.
參考