This is an automated email from the ASF dual-hosted git repository. membphis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push: new 13ab490 chore: improve HTTPS doc (#3149) 13ab490 is described below commit 13ab4903b4581f60466d4563ed74443c90acf943 Author: 罗泽轩 <spacewander...@gmail.com> AuthorDate: Wed Dec 30 22:57:00 2020 +0800 chore: improve HTTPS doc (#3149) --- apisix/schema_def.lua | 3 +- doc/admin-api.md | 5 +- doc/https.md | 75 ++++++++++++++++---- doc/stand-alone.md | 3 +- doc/zh-cn/admin-api.md | 5 +- doc/zh-cn/https.md | 77 +++++++++++++++++---- doc/zh-cn/stand-alone.md | 3 +- t/admin/ssl2.t | 29 ++++++++ t/config-center-yaml/ssl.t | 168 +++++++++++++++++++++++++++++++++------------ 9 files changed, 293 insertions(+), 75 deletions(-) diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index 2732edb..322f289 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -621,7 +621,8 @@ _M.ssl = { items = { type = "string", pattern = [[^\*?[0-9a-zA-Z-.]+$]], - } + }, + minItems = 1, }, certs = { type = "array", diff --git a/doc/admin-api.md b/doc/admin-api.md index 94ea65c..ffa8df4 100644 --- a/doc/admin-api.md +++ b/doc/admin-api.md @@ -667,10 +667,11 @@ Return response from etcd currently. |key|True|Private key|https private key|| |certs|False|An array of certificate|when you need to configure multiple certificate for the same domain, you can pass extra https certificates (excluding the one given as cert) in this field|| |keys|False|An array of private key|https private keys. The keys should be paired with certs above|| -|sni|True|Match Rules|https SNI|| +|snis|True|Match Rules|a non-empty arrays of https SNI|| |labels|False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}| |create_time|False| Auxiliary|epoch timestamp in second, will be created automatically if missing | 1602883670| |update_time|False| Auxiliary|epoch timestamp in second, will be created automatically if missing | 1602883670| +|status|False|Auxiliary|enable this SSL, default `1`.|`1` to enable, `0` to disable| Config Example: @@ -679,7 +680,7 @@ Config Example: "id": "1", # id "cert": "cert", # Certificate "key": "key", # Private key - "sni": "sni" # https SNI + "snis": ["t.com"] # https SNI } ``` diff --git a/doc/https.md b/doc/https.md index 7e4bd7a..53092a5 100644 --- a/doc/https.md +++ b/doc/https.md @@ -29,14 +29,53 @@ It is most common for an SSL certificate to contain only one domain. We can crea * `cert`: PEM-encoded public certificate of the SSL key pair. * `key`: PEM-encoded private key of the SSL key pair. -* `sni`: Hostname to associate with this certificate as SNIs. To set this attribute this certificate must have a valid private key associated with it. +* `snis`: Hostname(s) to associate with this certificate as SNIs. To set this attribute this certificate must have a valid private key associated with it. + +We will use the python script below to simplify the example: +```python +#!/usr/bin/env python +# coding: utf-8 +# save this file as ssl.py +import sys +# sudo pip install requests +import requests + +if len(sys.argv) <= 3: + print("bad argument") + sys.exit(1) +with open(sys.argv[1]) as f: + cert = f.read() +with open(sys.argv[2]) as f: + key = f.read() +sni = sys.argv[3] +api_key = "edd1c9f034335f136f87ad84b625c8f1" +resp = requests.put("http://127.0.0.1:9080/apisix/admin/ssl/1", json={ + "cert": cert, + "key": key, + "snis": [sni], +}, headers={ + "X-API-KEY": api_key, +}) +print(resp.status_code) +print(resp.text) +``` ```shell -curl http://127.0.0.1:9080/apisix/admin/ssl/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +# create SSL object +./ssl.py t.crt t.key test.com + +# create Router object +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { - "cert": "...", - "key": "....", - "sni": "test.com" + "uri": "/hello", + "hosts": ["test.com"], + "methods": ["GET"], + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } }' # make a test @@ -66,14 +105,22 @@ curl --resolve 'test.com:9443:127.0.0.1' https://test.com:9443/hello -vvv Sometimes, one SSL certificate may contain a wildcard domain like `*.test.com`, that means it can accept more than one domain, eg: `www.test.com` or `mail.test.com`. -Here is an example, please pay attention on the field `sni`. +Here is an example, note that the value we pass as `sni` is `*.test.com`. ```shell -curl http://127.0.0.1:9080/apisix/admin/ssl/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +./ssl.py t.crt t.key '*.test.com' + +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { - "cert": "...", - "key": "....", - "sni": "*.test.com" + "uri": "/hello", + "hosts": ["*.test.com"], + "methods": ["GET"], + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } }' # make a test @@ -101,8 +148,12 @@ curl --resolve 'www.test.com:9443:127.0.0.1' https://www.test.com:9443/hello -v ### multiple domain If your SSL certificate may contain more than one domain, like `www.test.com` -and `mail.test.com`, then you can more ssl object for each domain, that is a -most simple way. +and `mail.test.com`, then you can add them into the `snis` array. For example: +```json +{ + "snis": ["www.test.com", "mail.test.com"] +} +``` ### multiple certificates for a single domain diff --git a/doc/stand-alone.md b/doc/stand-alone.md index 25fa1f0..9aa8fc6 100644 --- a/doc/stand-alone.md +++ b/doc/stand-alone.md @@ -223,6 +223,7 @@ ssl: IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz r8yiEiskqRmy7P7MY9hDmEbG -----END PRIVATE KEY----- - sni: "yourdomain.com" + snis: + - "yourdomain.com" #END ``` diff --git a/doc/zh-cn/admin-api.md b/doc/zh-cn/admin-api.md index cea7667..c6a83ae 100644 --- a/doc/zh-cn/admin-api.md +++ b/doc/zh-cn/admin-api.md @@ -675,10 +675,11 @@ HTTP/1.1 200 OK |key|必需|私钥|https 证书私钥|| |certs|可选|证书字符串数组|当你想给同一个域名配置多个证书时,除了第一个证书需要通过cert传递外,剩下的证书可以通过该参数传递上来|| |keys|可选|私钥字符串数组|certs 对应的证书私钥,注意要跟 certs 一一对应|| -|sni|必需|匹配规则|https 证书SNI|| +|snis|必需|匹配规则|非空数组形式,可以匹配多个 SNI|| |labels|可选|匹配规则|标识附加属性的键值对|{"version":"v2","build":"16","env":"production"}| |create_time|可选|辅助|单位为秒的 epoch 时间戳,如果不指定则自动创建|1602883670| |update_time|可选|辅助|单位为秒的 epoch 时间戳,如果不指定则自动创建|1602883670| +|status|可选 |辅助| 是否启用此SSL, 缺省 `1`。|`1` 表示启用,`0` 表示禁用| ssl 对象 json 配置内容: @@ -687,7 +688,7 @@ ssl 对象 json 配置内容: "id": "1", # id "cert": "cert", # 证书 "key": "key", # 私钥 - "sni": "sni" # host 域名 + "snis": ["t.com"] # HTTPS 握手时客户端发送的 SNI } ``` diff --git a/doc/zh-cn/https.md b/doc/zh-cn/https.md index d034f66..8bb2bc6 100644 --- a/doc/zh-cn/https.md +++ b/doc/zh-cn/https.md @@ -31,14 +31,53 @@ SNI(Server Name Indication)是用来改善 SSL 和 TLS 的一项特性,它允 * `cert`: SSL 密钥对的公钥,pem 格式 * `key`: SSL 密钥对的私钥,pem 格式 -* `sni`: SSL 证书所指定的域名,注意在设置这个参数之前,你需要确保这个证书对应的私钥是有效的。 +* `snis`: SSL 证书所指定的一个或多个域名,注意在设置这个参数之前,你需要确保这个证书对应的私钥是有效的。 + +为了简化示例,我们会使用下面的 python 脚本: +```python +#!/usr/bin/env python +# coding: utf-8 +# save this file as ssl.py +import sys +# sudo pip install requests +import requests + +if len(sys.argv) <= 3: + print("bad argument") + sys.exit(1) +with open(sys.argv[1]) as f: + cert = f.read() +with open(sys.argv[2]) as f: + key = f.read() +sni = sys.argv[3] +api_key = "edd1c9f034335f136f87ad84b625c8f1" +resp = requests.put("http://127.0.0.1:9080/apisix/admin/ssl/1", json={ + "cert": cert, + "key": key, + "snis": [sni], +}, headers={ + "X-API-KEY": api_key, +}) +print(resp.status_code) +print(resp.text) +``` ```shell -curl http://127.0.0.1:9080/apisix/admin/ssl/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +# 创建 SSL 对象 +./ssl.py t.crt t.key test.com + +# 创建 Router 对象 +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { - "cert": "...", - "key": "....", - "sni": "test.com" + "uri": "/hello", + "hosts": ["test.com"], + "methods": ["GET"], + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } }' # 测试一下 @@ -68,14 +107,22 @@ curl --resolve 'test.com:9443:127.0.0.1' https://test.com:9443/hello -vvv 一个 SSL 证书的域名也可能包含泛域名,如`*.test.com`,它代表所有以`test.com`结尾的域名都可以使用该证书。 比如`*.test.com`,可以匹配 `www.test.com`、`mail.test.com`甚至`a.b.test.com`。 -看下面这个例子,请注意 `sni` 这个属性: +看下面这个例子,请注意我们把 `*.test.com` 作为 sni 传递进来: ```shell -curl http://127.0.0.1:9080/apisix/admin/ssl/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +./ssl.py t.crt t.key '*.test.com' + +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' { - "cert": "...", - "key": "....", - "sni": "*.test.com" + "uri": "/hello", + "hosts": ["*.test.com"], + "methods": ["GET"], + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } }' # 测试一下 @@ -102,8 +149,14 @@ curl --resolve 'www.test.com:9443:127.0.0.1' https://www.test.com:9443/hello -v ### 多域名的情况 -如果一个 SSL 证书包含多个独立域名,比如`www.test.com`和`mail.test.com`,通配符方式又会导致匹配不严谨。 -所以针对不同域名,设置不同 SSL 证书对象即可。 +如果一个 SSL 证书包含多个独立域名,比如`www.test.com`和`mail.test.com`, +你可以把它们都放入 `snis` 数组中,就像这样: + +```json +{ + "snis": ["www.test.com", "mail.test.com"] +} +``` ### 单域名多证书的情况 diff --git a/doc/zh-cn/stand-alone.md b/doc/zh-cn/stand-alone.md index 03ce3e1..9355e30 100644 --- a/doc/zh-cn/stand-alone.md +++ b/doc/zh-cn/stand-alone.md @@ -224,6 +224,7 @@ ssl: IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz r8yiEiskqRmy7P7MY9hDmEbG -----END PRIVATE KEY----- - sni: "yourdomain.com" + snis: + - "yourdomain.com" #END ``` diff --git a/t/admin/ssl2.t b/t/admin/ssl2.t index a616bb0..6614eef 100644 --- a/t/admin/ssl2.t +++ b/t/admin/ssl2.t @@ -336,3 +336,32 @@ wzarryret/7GFW1/3cz+hTj9/d45i25zArr3Pocfpur5mfz3fJO8jg== --- error_code: 400 --- response_body {"error_msg":"failed to handle cert-key pair[1]: failed to parse key: PEM_read_bio_PrivateKey() failed"} + + + +=== TEST 10: empty snis +--- config + location /t { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + local ssl_cert = t.read_file("t/certs/apisix.crt") + local ssl_key = t.read_file("t/certs/apisix.key") + local data = {cert = ssl_cert, key = ssl_key, snis = {}} + local code, message, res = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + json.encode(data) + ) + + if code >= 300 then + ngx.status = code + ngx.print(message) + return + end + + ngx.say(res) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"snis\" validation failed: expect array to have at least 1 items"} diff --git a/t/config-center-yaml/ssl.t b/t/config-center-yaml/ssl.t index 715bc54..673cdc0 100644 --- a/t/config-center-yaml/ssl.t +++ b/t/config-center-yaml/ssl.t @@ -53,6 +53,53 @@ _EOC_ if (!$block->no_error_log) { $block->set_value("no_error_log", "[error]\n[alert]"); } + + if ($block->sslhandshake) { + my $sslhandshake = $block->sslhandshake; + + $block->set_value("config", <<_EOC_) +listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock ssl; + +location /t { + content_by_lua_block { + -- sync + ngx.sleep(0.2) + + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:\$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + $sslhandshake + local req = "GET /hello HTTP/1.0\\r\\nHost: test.com\\r\\nConnection: close\\r\\n\\r\\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } +} +_EOC_ + } }); run_tests(); @@ -115,53 +162,86 @@ ssl: IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz r8yiEiskqRmy7P7MY9hDmEbG -----END PRIVATE KEY----- - sni: "test.com" ---- config -listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; - -location /t { - content_by_lua_block { - -- sync - ngx.sleep(0.2) - - do - local sock = ngx.socket.tcp() - - sock:settimeout(2000) - - local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") - if not ok then - ngx.say("failed to connect: ", err) - return - end - - local sess, err = sock:sslhandshake(nil, "test.com", false) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - - local req = "GET /hello HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" - local bytes, err = sock:send(req) - if not bytes then - ngx.say("failed to send http request: ", err) - return - end + snis: + - "t.com" + - "test.com" +--- sslhandshake +local sess, err = sock:sslhandshake(nil, "test.com", false) +if not sess then + ngx.say("failed to do SSL handshake: ", err) + return +end +--- response_body +received: HTTP/1.1 200 OK +close: 1 nil +--- error_log +lua ssl server name: "test.com" - local line, err = sock:receive() - if not line then - ngx.say("failed to receive: ", err) - return - end - ngx.say("received: ", line) - local ok, err = sock:close() - ngx.say("close: ", ok, " ", err) - end -- do - -- collectgarbage() - } -} +=== TEST 2: single sni +--- apisix_yaml +ssl: + - + cert: | + -----BEGIN CERTIFICATE----- + MIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV + BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL + BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl + ci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV + BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL + BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl + ci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/qwxCR7g5S + s9+VleopkLi5pAszEkHYOBpwF/hDeRdxU0I0e1zZTdTlwwPy2vf8m3kwoq6fmNCt + tdUUXh5Wvgi/2OA8HBBzaQFQL1Av9qWwyES5cx6p0ZBwIrcXQIsl1XfNSUpQNTSS + D44TGduXUIdeshukPvMvLWLezynf2/WlgVh/haWtDG99r/Gj3uBdjl0m/xGvKvIv + NFy6EdgG9fkwcIalutjrUnGl9moGjwKYu4eXW2Zt5el0d1AHXUsqK4voe0p+U2Nz + quDmvxteXWdlsz8o5kQT6a4DUtWhpPIfNj9oZfPRs3LhBFQ74N70kVxMOCdec1lU + bnFzLIMGlz0CAwEAAaNQME4wHQYDVR0OBBYEFFHeljijrr+SPxlH5fjHRPcC7bv2 + MB8GA1UdIwQYMBaAFFHeljijrr+SPxlH5fjHRPcC7bv2MAwGA1UdEwQFMAMBAf8w + DQYJKoZIhvcNAQELBQADggEBAG6NNTK7sl9nJxeewVuogCdMtkcdnx9onGtCOeiQ + qvh5Xwn9akZtoLMVEdceU0ihO4wILlcom3OqHs9WOd6VbgW5a19Thh2toxKidHz5 + rAaBMyZsQbFb6+vFshZwoCtOLZI/eIZfUUMFqMXlEPrKru1nSddNdai2+zi5rEnM + HCot43+3XYuqkvWlOjoi9cP+C4epFYrxpykVbcrtbd7TK+wZNiK3xtDPnVzjdNWL + geAEl9xrrk0ss4nO/EreTQgS46gVU+tLC+b23m2dU7dcKZ7RDoiA9bdVc4a2IsaS + 2MvLL4NZ2nUh8hAEHiLtGMAV3C6xNbEyM07hEpDW6vk6tqk= + -----END CERTIFICATE----- + key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf + lZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV + FF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O + Exnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc + uhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg + 5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x + cyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk186lHI3z7K1 + 5nB7zt+bwVY0AUpagv3wvXoB5lrYVOsJpa9y5iAb3GqYMc/XDCKfD/KLea5hwfcn + BctEn0LjsPVKLDrLs2t2gBDWG2EU+udunwQh7XTdp2Nb6V3FdOGbGAg2LgrSwP1g + 0r4z14F70oWGYyTQ5N8UGuyryVrzQH525OYl38Yt7R6zJ/44FVi/2TvdfHM5ss39 + SXWi00Q30fzaBEf4AdHVwVCRKctwSbrIOyM53kiScFDmBGRblCWOxXbiFV+d3bjX + gf2zxs7QYZrFOzOO7kLtHGua4itEB02497v+1oKDwQKBgQDOBvCVGRe2WpItOLnj + SF8iz7Sm+jJGQz0D9FhWyGPvrN7IXGrsXavA1kKRz22dsU8xdKk0yciOB13Wb5y6 + yLsr/fPBjAhPb4h543VHFjpAQcxpsH51DE0b2oYOWMmz+rXGB5Jy8EkP7Q4njIsc + 2wLod1dps8OT8zFx1jX3Us6iUQKBgQDGtKkfsvWi3HkwjFTR+/Y0oMz7bSruE5Z8 + g0VOHPkSr4XiYgLpQxjbNjq8fwsa/jTt1B57+By4xLpZYD0BTFuf5po+igSZhH8s + QS5XnUnbM7d6Xr/da7ZkhSmUbEaMeHONSIVpYNgtRo4bB9Mh0l1HWdoevw/w5Ryt + L/OQiPhfLQKBgQCh1iG1fPh7bbnVe/HI71iL58xoPbCwMLEFIjMiOFcINirqCG6V + LR91Ytj34JCihl1G4/TmWnsH1hGIGDRtJLCiZeHL70u32kzCMkI1jOhFAWqoutMa + 7obDkmwraONIVW/kFp6bWtSJhhTQTD4adI9cPCKWDXdcCHSWj0Xk+U8HgQKBgBng + t1HYhaLzIZlP/U/nh3XtJyTrX7bnuCZ5FhKJNWrYjxAfgY+NXHRYCKg5x2F5j70V + be7pLhxmCnrPTMKZhik56AaTBOxVVBaYWoewhUjV4GRAaK5Wc8d9jB+3RizPFwVk + V3OU2DJ1SNZ+W2HBOsKrEfwFF/dgby6i2w6MuAP1AoGBAIxvxUygeT/6P0fHN22P + zAHFI4v2925wYdb7H//D8DIADyBwv18N6YH8uH7L+USZN7e4p2k8MGGyvTXeC6aX + IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz + r8yiEiskqRmy7P7MY9hDmEbG + -----END PRIVATE KEY----- + sni: "test.com" +--- sslhandshake +local sess, err = sock:sslhandshake(nil, "test.com", false) +if not sess then + ngx.say("failed to do SSL handshake: ", err) + return +end --- response_body received: HTTP/1.1 200 OK close: 1 nil