diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua index 27dbf2847a1b0..86b62c9ddff6b 100644 --- a/apisix/cli/ngx_tpl.lua +++ b/apisix/cli/ngx_tpl.lua @@ -653,6 +653,10 @@ http { } {% if ssl.enable then %} + ssl_client_hello_by_lua_block { + apisix.http_ssl_protocols_phase() + } + ssl_certificate_by_lua_block { apisix.http_ssl_phase() } diff --git a/apisix/init.lua b/apisix/init.lua index 5552934903e3a..9fb8fc4c34966 100644 --- a/apisix/init.lua +++ b/apisix/init.lua @@ -178,11 +178,29 @@ end function _M.http_ssl_phase() + local ok, err = router.router_ssl.set(ngx.ctx.matched_ssl) + if not ok then + if err then + core.log.error("failed to fetch ssl config: ", err) + end + ngx_exit(-1) + end +end + +function _M.http_ssl_protocols_phase() + local ssl_clt = require "ngx.ssl.clienthello" + local host, err = ssl_clt.get_client_hello_server_name() + + if err then + core.log.error("failed to get the SNI name: ", err) + ngx_exit(-1) + end + local ngx_ctx = ngx.ctx local api_ctx = core.tablepool.fetch("api_ctx", 0, 32) ngx_ctx.api_ctx = api_ctx - local ok, err = router.router_ssl.match_and_set(api_ctx) + local ok, err = router.router_ssl.match_and_set(api_ctx, true, host) ngx_ctx.matched_ssl = api_ctx.matched_ssl core.tablepool.release("api_ctx", api_ctx) @@ -194,8 +212,12 @@ function _M.http_ssl_phase() end ngx_exit(-1) end -end + local ssl_protocols = ngx_ctx.matched_ssl.value.ssl_protocols + if ssl_protocols then + ssl_clt.set_protocols(ssl_protocols) + end +end local function stash_ngx_ctx() local ref = ctxdump.stash_ngx_ctx() diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index f1d4d97efe5d6..c36465c5ce38e 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -790,6 +790,15 @@ _M.ssl = { enum = {1, 0}, default = 1 }, + ssl_protocols = { + description = "set ssl protocols", + type = "array", + maxItems = 3, + uniqueItems = true, + items = { + enum = {"TLSv1.1","TLSv1.2", "TLSv1.3"} + }, + }, validity_end = timestamp_def, validity_start = timestamp_def, create_time = timestamp_def, diff --git a/apisix/ssl/router/radixtree_sni.lua b/apisix/ssl/router/radixtree_sni.lua index b0e78a25fdc96..24edbe2a1a3dc 100644 --- a/apisix/ssl/router/radixtree_sni.lua +++ b/apisix/ssl/router/radixtree_sni.lua @@ -205,13 +205,35 @@ function _M.match_and_set(api_ctx, match_only, alt_sni) end end - local matched_ssl = api_ctx.matched_ssl - core.log.info("debug - matched: ", core.json.delay_encode(matched_ssl, true)) + core.log.info("debug - matched: ", core.json.delay_encode(api_ctx.matched_ssl, true)) if match_only then return true end + ok, err = _M.set(api_ctx.matched_ssl, sni) + if not ok then + return false, err + end + + return true +end + + +function _M.set(matched_ssl, sni) + if not matched_ssl then + return false, "not found any matched ssl certificate" + end + local err,ok + if not sni then + sni, err = apisix_ssl.server_name() + if type(sni) ~= "string" then + local advise = "please check if the client requests via IP or uses an outdated " .. + "protocol. If you need to report an issue, " .. + "provide a packet capture file of the TLS handshake." + return false, "failed to find SNI: " .. (err or advise) + end + end ngx_ssl.clear_certs() local new_ssl_value = secret.fetch_secrets(matched_ssl.value) or matched_ssl.value diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 88add7798c910..8637c9dc07cb1 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -1194,6 +1194,7 @@ SSL resource request address: /apisix/admin/ssls/{id} | update_time | False | Auxiliary | Epoch timestamp (in seconds) of the updated time. If missing, this field will be populated automatically. | 1602883670 | | type | False | Auxiliary | Identifies the type of certificate, default `server`. | `client` Indicates that the certificate is a client certificate, which is used when APISIX accesses the upstream; `server` Indicates that the certificate is a server-side certificate, which is used by APISIX when verifying client requests. | | status | False | Auxiliary | Enables the current SSL. Set to `1` (enabled) by default. | `1` to enable, `0` to disable | +| ssl_protocols | False | An array of ssl protocols | It is used to control the SSL/TLS protocol version used between servers and clients. Defaults to `["TLSv1.2","TLSv1.3"]`. | | Example Configuration: diff --git a/docs/en/latest/ssl-protocols.md b/docs/en/latest/ssl-protocols.md new file mode 100644 index 0000000000000..54356fe168d86 --- /dev/null +++ b/docs/en/latest/ssl-protocols.md @@ -0,0 +1,248 @@ +--- +title: SSL Protocol +--- + + + +`APISIX` supports dynamically specifying different TLS protocol versions for each host. + +## Configuration instructions + +- Static configuration +The ssl_protocols parameters in the static configuration will apply globally to apisix, but cannot be modified dynamically. + +```yaml +apisix: + ssl: + ssl_protocols: TLSv1.2 TLSv1.3 +``` + +- Dynamic resource allocation + +Dynamic resource configuration is to create and manage ssl resources through the admin API interface of apisix. The new ssl. ssl_protocols configuration item can control fine grain for the host and dynamically specify the TLS protocol version of each host. + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/1 +{ + "cert": "$cert", + "key": "$key", + "snis": ["test.com"], + "ssl_protocols": [ + "TLSv1.2", + "TLSv1.3" + ] +} +``` + +The configuration will be subject to the ssl resource, and the static configuration will be overwritten . For example, if you set ssl_protocols: TLSv1.2 TLSv1.3 in config.yaml, but set ssl.ssl_protocols: [TLSv1.3] in the resource configuration, then the final apisix will use the TLSv1.3 protocol. Therefore, when using the ssl configuration of apisix, you need to pay attention to the following points: + +- SSL resource configuration will override static configuration globally, subject to resource configuration. +- SSL resource configuration can be modified dynamically, while static configuration requires a restart of apisix to take effect. +- SSL resource configuration can be controlled according to fine grain sni. + +## Usage examples + +### Scenario, one-on-one adaptation of multiple TLS protocol versions + +In the communication between end point products and servers, we need to consider the TLS protocol compatibility issues of multiple end point products. For example, some old products, old Android phones, TVs and other end point devices, still use the lower-level TLSv1.1 protocol version, while new products use the higher-level TLS protocol version. If the new product supports TLSv1.1, it may bring some security risks. In order to ensure that the product can establish secure communication, we need to adapt between protocol versions. +As shown in the following example, app.org is the domain name used by the end point device of the old product and needs to be configured as TLSv1.1, while app2.org belongs to the new product and supports the TLSv1.2 and TLSv1.3 protocols. + +1. Specify the TLSv1.1 protocol version for app.org legacy products. + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/app +{ + "cert": "$app_cert", + "key": "$app_key", + "snis": ["app.org"], + "ssl_protocols": [ + "TLSv1.1" + ] +} +``` + +2. app2.org new product line specifies support for the TLSv1.2 and TLSv1.3 protocols. + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/app2 +{ + "cert": "$app2_cert", + "key": "$app2_key", + "snis": ["app2.org"], + "ssl_protocols": [ + "TLSv1.2", + "TLSv1.3" + ] +} +curl --tls-max 1.1 --tlsv1.1 https://app.org # tls 1.1 + +curl --tls-max 1.3 --tlsv1.2 https://app2.org # tls 1.2 +``` + +### Scenario, two or more domain names use different protocols, but are associated with the same certificate. + +Sometimes, we may encounter a scenario where multiple domain names are associated with the same certificate, but they need to use different versions of the TLS protocol to ensure security. For example, test.com domain names need to use the TlSv1.2 protocol, while test2.com domain names need to use the TLSv1.3 protocol. In this case, we cannot simply use the same SSL object for all domain names, but need to create a separate SSL object for each domain name and specify the corresponding protocol version. In this way, we can perform correct SSL handshaking and encrypted communication based on different domain names and protocol versions. An example is as follows: + +1. Create ssl object for test.com using certificate and specify TLSv1.2 protocol + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/test +{ + "cert": "$cert", + "key": "$key", + "snis": ["test.com"], + "ssl_protocols": [ + "TLSv1.2" + ] +} +``` + +2. Using the same certificate as test.com, create an SSL object for test2.com and specify the TLSv1.3 protocol. + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/test2 +{ + "cert": "$cert", + "key": "$key", + "snis": ["test2.com"], + "ssl_protocols": [ + "TLSv1.3" + ] +} +``` + +3. verify + +* Successfully to accessed test.com with TLSv1.2 protocol + +```shell +$ curl --tls-max 1.2 --tlsv1.2 https://test.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* TLSv1.2 (IN), TLS handshake, Server hello (2): +* TLSv1.2 (IN), TLS handshake, Certificate (11): +* TLSv1.2 (IN), TLS handshake, Server key exchange (12): +* TLSv1.2 (IN), TLS handshake, Server finished (14): +* TLSv1.2 (OUT), TLS handshake, Client key exchange (16): +* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): +* TLSv1.2 (OUT), TLS handshake, Finished (20): +* TLSv1.2 (IN), TLS handshake, Finished (20): +* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 +* ALPN, server accepted to use h2 +* Server certificate: +* subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com +* start date: Jul 20 15:50:08 2023 GMT +* expire date: Jul 17 15:50:08 2033 GMT +* issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com +* SSL certificate verify result: EE certificate key too weak (66), continuing anyway. +* Using HTTP2, server supports multi-use +* Connection state changed (HTTP/2 confirmed) +* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 +* Using Stream ID: 1 (easy handle 0x5608905ee2e0) +> HEAD / HTTP/2 +> Host: test.com:9443 +> user-agent: curl/7.74.0 +> accept: */* + +``` + +* Failed to accessed test.com with TLSv1.3 protocol + +```shell +$ curl --tls-max 1.3 --tlsv1.3 https://test.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +* TLSv1.3 (IN), TLS alert, protocol version (582): +* error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version +* Closing connection 0 +curl: (35) error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version + +``` + +* Successfully to accessed test2.com with TLSv1.3 protocol + +```shell +$ curl --tls-max 1.3 --tlsv1.3 https://test2.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test2.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +* TLSv1.3 (IN), TLS handshake, Server hello (2): +* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): +* TLSv1.3 (IN), TLS handshake, Certificate (11): +* TLSv1.3 (IN), TLS handshake, CERT verify (15): +* TLSv1.3 (IN), TLS handshake, Finished (20): +* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): +* TLSv1.3 (OUT), TLS handshake, Finished (20): +* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 +* ALPN, server accepted to use h2 +* Server certificate: +* subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test2.com +* start date: Jul 20 16:05:47 2023 GMT +* expire date: Jul 17 16:05:47 2033 GMT +* issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test2.com +* SSL certificate verify result: EE certificate key too weak (66), continuing anyway. +* Using HTTP2, server supports multi-use +* Connection state changed (HTTP/2 confirmed) +* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 +* Using Stream ID: 1 (easy handle 0x55569cbe42e0) +> HEAD / HTTP/2 +> Host: test2.com:9443 +> user-agent: curl/7.74.0 +> accept: */* +> +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +* old SSL session ID is stale, removing +``` + +* Failed to accessed test2.com with TLSv1.2 protocol + +```shell +$ curl --tls-max 1.2 --tlsv1.2 https://test2.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test2.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* TLSv1.2 (IN), TLS alert, protocol version (582): +* error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version +* Closing connection 0 +curl: (35) error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version +``` diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md index 18efb19c566f9..1fe6959cf94bd 100644 --- a/docs/zh/latest/admin-api.md +++ b/docs/zh/latest/admin-api.md @@ -1202,6 +1202,7 @@ SSL 资源请求地址:/apisix/admin/ssls/{id} | update_time | 否 | 辅助 | epoch 时间戳,单位为秒。如果不指定则自动创建。 | 1602883670 | | type | 否 | 辅助 | 标识证书的类型,默认值为 `server`。 | `client` 表示证书是客户端证书,APISIX 访问上游时使用;`server` 表示证书是服务端证书,APISIX 验证客户端请求时使用。 | | status | 否 | 辅助 | 当设置为 `1` 时,启用此 SSL,默认值为 `1`。 | `1` 表示启用,`0` 表示禁用 | +| ssl_protocols | 否 | tls协议字符串数组 | 用于控制服务器与客户端之间使用的 SSL/TLS 协议版本。 默认值为 `["TLSv1.2","TLSv1.3"]`. | | SSL 对象 JSON 配置示例: diff --git a/docs/zh/latest/ssl-protocols.md b/docs/zh/latest/ssl-protocols.md new file mode 100644 index 0000000000000..0be970e5c9de6 --- /dev/null +++ b/docs/zh/latest/ssl-protocols.md @@ -0,0 +1,252 @@ +--- +title: 证书 +--- + + + +`APISIX` 支持通过 TLS 扩展 SNI 实现加载特定的 SSL 证书以实现对 https 的支持。 + +## 配置说明 + +- 静态配置 + +静态配置中的 ssl_protocols 参数会作用于 apisix 全局,但是不能动态修改。 + +```yaml +apisix: + ssl: + ssl_protocols: TLSv1.2 TLSv1.3 +``` + +- 动态资源配置 + +动态资源配置是通过 apisix 的 admin API 接口来创建和管理 ssl 资源,新增的 ssl.ssl_protocols 配置项,可针对 host 进行细粒度的控制,可动态指定每一个 host 的 TLS 协议版本。 + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/1 +{ + "cert": "$cert", + "key": "$key", + "snis": ["test.com"], + "ssl_protocols": [ + "TLSv1.2", + "TLSv1.3" + ] +} +``` + +配置将以 ssl 资源为准,静态配置会被覆盖。例如,如果在 config.yaml 中设置了 ssl_protocols: TLSv1.2 TLSv1.3,但是在资源配置中设置了 ssl.ssl_protocols: [TLSv1.3],那么最终 apisix 会使用 TLSv1.3 协议。因此,在使用 apisix 的 ssl 配置时,需要注意以下几点: + +- ssl 资源配置会全局覆盖静态配置,以资源配置为准。 +- ssl 资源配置可以动态修改,而静态配置需要重启 apisix 才能生效。 +- ssl 资源配置可以根据 sni 进行细粒度的控制。 + +### 对多个 TLS 协议版本适配 + +在终端产品与服务器之间的通信中,我们需要考虑多个终端产品的 TLS 协议兼容性问题。例如,一些老旧的产品,老旧的安卓手机、电视等终端设备,仍然采用较低级别的 TLSv1.1 协议版本,而新产品则采用较高级别的 TLS 协议版本,如果让新产品支持 TLSv1.1 可能会带来一些安全隐患。为了保证产品能够建立安全的通信,我们需要在协议版本之间进行适配。 +如下例子所示,app.org 是旧产品终端设备所使用的域名,需要将其配置为 TLSv1.1,而 app2.org 属于新产品,同时支持了 TLSv1.2,TLSv1.3 协议。 + +1. 为 app.org 旧产品指定 TLSv1.1 协议版本。 + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/app +{ + "cert": "$app_cert", + "key": "$app_key", + "snis": ["app.org"], + "ssl_protocols": [ + "TLSv1.1" + ] +} +``` + +2. app2.org 新产品线指定 TLSv1.2 和 TLSv1.3 协议的支持。 + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/app2 +{ + "cert": "$app2_cert", + "key": "$app2_key", + "snis": ["app2.org"], + "ssl_protocols": [ + "TLSv1.2", + "TLSv1.3" + ] +} +``` + +3. 访问验证 + +```bash +curl --tls-max 1.1 --tlsv1.1 https://app.org # tls 1.1 + +curl --tls-max 1.3 --tlsv1.2 https://app2.org # tls 1.2 +``` + +### 多域名分别使用不同的协议,但关联同一个证书, + +有时候,我们可能会遇到这样一种场景,即多个域名关联了同一个证书,但是它们需要使用不同的 TLS 协议版本来保证安全性。例如,test.com 域名需要使用 TlSv1.2 协议,而 test2.com 域名则需要使用 TLSv1.3 协议。在这种情况下,我们不能简单地为所有的域名使用同一个 SSL 对象,而是需要为每个域名单独创建一个 SSL 对象,并指定相应的协议版本。这样,我们就可以根据不同的域名和协议版本来进行正确的 SSL 握手和加密通信。示例如下: + +1. 使用证书为 test.com 创建 ssl 对象,并指定 TLSv1.2 协议 + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/test +{ + "cert": "$cert", + "key": "$key", + "snis": ["test.com"], + "ssl_protocols": [ + "TLSv1.2" + ] +} +``` + +2. 使用与 test.com 同一证书,为 test2.com 创建 ssl 对象,并指定 TLSv1.3 协议。 + +```bash +# curl http://127.0.0.1:9180/admin/apisix/ssls/test2 +{ + "cert": "$cert", + "key": "$key", + "snis": ["test2.com"], + "ssl_protocols": [ + "TLSv1.3" + ] +} +``` + +3. 访问验证 + +* 使用 TLSv1.2 访问 test.com 成功 + +```shell +$ curl --tls-max 1.2 --tlsv1.2 https://test.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* TLSv1.2 (IN), TLS handshake, Server hello (2): +* TLSv1.2 (IN), TLS handshake, Certificate (11): +* TLSv1.2 (IN), TLS handshake, Server key exchange (12): +* TLSv1.2 (IN), TLS handshake, Server finished (14): +* TLSv1.2 (OUT), TLS handshake, Client key exchange (16): +* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): +* TLSv1.2 (OUT), TLS handshake, Finished (20): +* TLSv1.2 (IN), TLS handshake, Finished (20): +* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 +* ALPN, server accepted to use h2 +* Server certificate: +* subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com +* start date: Jul 20 15:50:08 2023 GMT +* expire date: Jul 17 15:50:08 2033 GMT +* issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com +* SSL certificate verify result: EE certificate key too weak (66), continuing anyway. +* Using HTTP2, server supports multi-use +* Connection state changed (HTTP/2 confirmed) +* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 +* Using Stream ID: 1 (easy handle 0x5608905ee2e0) +> HEAD / HTTP/2 +> Host: test.com:9443 +> user-agent: curl/7.74.0 +> accept: */* + +``` + +* 使用 TLSv1.3 协议访问 test.com 失败 + +```shell +$ curl --tls-max 1.3 --tlsv1.3 https://test.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +* TLSv1.3 (IN), TLS alert, protocol version (582): +* error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version +* Closing connection 0 +curl: (35) error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version + +``` + +* 使用 TLSv1.3 协议访问 test2.com 成功 + +```shell +$ curl --tls-max 1.3 --tlsv1.3 https://test2.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test2.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +* TLSv1.3 (IN), TLS handshake, Server hello (2): +* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): +* TLSv1.3 (IN), TLS handshake, Certificate (11): +* TLSv1.3 (IN), TLS handshake, CERT verify (15): +* TLSv1.3 (IN), TLS handshake, Finished (20): +* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): +* TLSv1.3 (OUT), TLS handshake, Finished (20): +* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 +* ALPN, server accepted to use h2 +* Server certificate: +* subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test2.com +* start date: Jul 20 16:05:47 2023 GMT +* expire date: Jul 17 16:05:47 2033 GMT +* issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test2.com +* SSL certificate verify result: EE certificate key too weak (66), continuing anyway. +* Using HTTP2, server supports multi-use +* Connection state changed (HTTP/2 confirmed) +* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 +* Using Stream ID: 1 (easy handle 0x55569cbe42e0) +> HEAD / HTTP/2 +> Host: test2.com:9443 +> user-agent: curl/7.74.0 +> accept: */* +> +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +* old SSL session ID is stale, removing +``` + +* 使用 TLSv1.2 协议访问 test2.com 失败 + +```shell +$ curl --tls-max 1.2 --tlsv1.2 https://test2.com:9443 -v -k -I +* Trying 127.0.0.1:9443... +* Connected to test2.com (127.0.0.1) port 9443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt +* CApath: /etc/ssl/certs +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* TLSv1.2 (IN), TLS alert, protocol version (582): +* error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version +* Closing connection 0 +curl: (35) error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version +``` diff --git a/t/APISIX.pm b/t/APISIX.pm index 92e58a7ba2655..a07eaccc9301a 100644 --- a/t/APISIX.pm +++ b/t/APISIX.pm @@ -712,6 +712,12 @@ _EOC_ ssl_certificate_key cert/apisix.key; lua_ssl_trusted_certificate cert/apisix.crt; + ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; + + ssl_client_hello_by_lua_block { + apisix.http_ssl_protocols_phase() + } + ssl_certificate_by_lua_block { apisix.http_ssl_phase() } diff --git a/t/admin/ssl5.t b/t/admin/ssl5.t new file mode 100644 index 0000000000000..c9bd7b162133f --- /dev/null +++ b/t/admin/ssl5.t @@ -0,0 +1,86 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +no_root_location(); + +run_tests; + +__DATA__ + +=== TEST 1: Not supported set TLSv1.0 for ssl_protocols +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + 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, sni = "test.com", ssl_protocols = {"TLSv1.0", "TLSv1.2"}} + + local code, body = t.test('/apisix/admin/ssls/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "key": "/apisix/ssls/1" + }]] + ) + + ngx.status = code + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"ssl_protocols\" validation failed: failed to validate item 1: matches none of the enum values"} + + + +=== TEST 2: The default value for the ssl_protocols is null +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + 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, sni = "test.com"} + + local code, body = t.test('/apisix/admin/ssls/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "value": { + "sni": "test.com", + "ssl_protocols": null, + }, + "key": "/apisix/ssls/1" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed diff --git a/t/node/ssl-protocols.t b/t/node/ssl-protocols.t new file mode 100644 index 0000000000000..22513b4afe766 --- /dev/null +++ b/t/node/ssl-protocols.t @@ -0,0 +1,289 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +log_level('info'); +no_root_location(); +no_shuffle(); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $yaml_config = $block->yaml_config // <<_EOC_; +deployment: + role: traditional + role_traditional: + config_provider: etcd + admin: + admin_key: null +apisix: + node_listen: 1984 + proxy_mode: http&stream + stream_proxy: + tcp: + - 9100 + enable_resolv_search_opt: false + ssl: + ssl_protocols: TLSv1.1 TLSv1.2 TLSv1.3 + ssl_ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA +_EOC_ + + $block->set_value("yaml_config", $yaml_config); +}); + +run_tests(); + +__DATA__ + + +=== TEST 1: set route +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uris": ["/hello", "/world"] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(message) + return + end + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed + + + +=== TEST 2: create ssl for test.com (unset ssl_protocols) +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + 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, sni = "test.com"} + + local code, body = t.test('/apisix/admin/ssls/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "value": { + "sni": "test.com", + "ssl_protocols": null, + }, + "key": "/apisix/ssls/1" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 3: Successfully, access test.com with TLSv1.3 +--- exec +curl -k -v --tls-max 1.3 --tlsv1.3 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.3 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 4: Successfully, access test.com with TLSv1.2 +--- exec +curl -k -v --tls-max 1.2 --tlsv1.2 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.2 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 5: Successfully, access test.com with TLSv1.1 +--- exec +curl -k -v --tls-max 1.1 --tlsv1.1 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.1 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 6: set TLSv1.2 and TLSv1.3 for test.com +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + 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, sni = "test.com", ssl_protocols = {"TLSv1.2", "TLSv1.3"}} + + local code, body = t.test('/apisix/admin/ssls/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "value": { + "sni": "test.com", + "ssl_protocols": ["TLSv1.2", "TLSv1.3"], + }, + "key": "/apisix/ssls/1" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 7: Set TLSv1.3 for the test2.com +--- config +location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("t/certs/test2.crt") + local ssl_key = t.read_file("t/certs/test2.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "test2.com", ssl_protocols = {"TLSv1.3"}} + + local code, body = t.test('/apisix/admin/ssls/2', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "value": { + "sni": "test2.com" + }, + "key": "/apisix/ssls/2" + }]] + ) + + ngx.status = code + ngx.say(body) + } +} +--- response_body +passed +--- request +GET /t + + + +=== TEST 8: Successfully, access test.com with TLSv1.3 +--- exec +curl -k -v --tls-max 1.3 --tlsv1.3 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.3 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 9: Successfully, access test.com with TLSv1.2 +--- exec +curl -k -v --tls-max 1.2 --tlsv1.2 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.2 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 10: Successfully, access test2.com with TLSv1.3 +--- exec +curl -k -v --tls-max 1.3 --tlsv1.3 --resolve "test2.com:1994:127.0.0.1" https://test2.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.3 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 11: Failed, access test2.com with TLSv1.2 +--- exec +curl -k -v --tls-max 1.2 --tlsv1.2 --resolve "test2.com:1994:127.0.0.1" https://test2.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.2 \(IN\), TLS alert/ + + + +=== TEST 12: set TLSv1.1 for test.com +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + 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, sni = "test.com", ssl_protocols = {"TLSv1.1"}} + + local code, body = t.test('/apisix/admin/ssls/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "value": { + "sni": "test.com", + "ssl_protocols": ["TLSv1.1"], + }, + "key": "/apisix/ssls/1" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 13: Successfully, access test.com with TLSv1.1 +--- exec +curl -k -v --tls-max 1.1 --tlsv1.1 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.1 \(IN\), TLS handshake, Server hello(?s).*hello world/ + + + +=== TEST 14: Failed, access test.com with TLSv1.3 +--- exec +curl -k -v --tls-max 1.3 --tlsv1.3 --resolve "test.com:1994:127.0.0.1" https://test.com:1994/hello 2>&1 | cat +--- response_body eval +qr/TLSv1\.3 \(IN\), TLS alert/ \ No newline at end of file