diff --git a/apisix/plugins/jwe-decrypt.lua b/apisix/plugins/jwe-decrypt.lua index 1ecef6795380..f669eb20d611 100644 --- a/apisix/plugins/jwe-decrypt.lua +++ b/apisix/plugins/jwe-decrypt.lua @@ -18,7 +18,6 @@ local core = require("apisix.core") local consumer_mod = require("apisix.consumer") local base64 = require("ngx.base64") local aes = require("resty.aes") -local ngx = ngx local sub_str = string.sub local cipher = aes.cipher(256, "gcm") @@ -147,24 +146,6 @@ local function jwe_decrypt_with_obj(o, consumer) end -local function jwe_encrypt(o, consumer) - local secret = get_secret(consumer.auth_conf) - local enc = base64.encode_base64url - - local aes_default = aes:new( - secret, - nil, - cipher, - {iv = o.iv}) - - local encrypted = aes_default:encrypt(o.plaintext) - - o.ciphertext = encrypted[1] - o.tag = encrypted[2] - return o.header .. ".." .. enc(o.iv) .. "." .. enc(o.ciphertext) .. "." .. enc(o.tag) -end - - local function get_consumer(key) local consumer_conf = consumer_mod.plugin(plugin_name) if not consumer_conf then @@ -220,57 +201,4 @@ function _M.rewrite(conf, ctx) core.request.set_header(ctx, conf.forward_header, plaintext) end - -local function gen_token() - local args = core.request.get_uri_args() - if not args or not args.key then - return core.response.exit(400) - end - - local key = args.key - local payload = args.payload - if payload then - payload = ngx.unescape_uri(payload) - end - - local consumer = get_consumer(key) - if not consumer then - return core.response.exit(404) - end - - local iv = args.iv - if not iv then - -- TODO: random bytes - iv = "123456789012" - end - - local obj = { - iv = iv, - plaintext = payload, - header_obj = { - kid = key, - alg = "dir", - enc = "A256GCM", - }, - } - obj.header = base64.encode_base64url(core.json.encode(obj.header_obj)) - local jwe_token = jwe_encrypt(obj, consumer) - if jwe_token then - return core.response.exit(200, jwe_token) - end - - return core.response.exit(404) -end - - -function _M.api() - return { - { - methods = { "GET" }, - uri = "/apisix/plugin/jwe/encrypt", - handler = gen_token, - } - } -end - return _M diff --git a/docs/en/latest/plugins/jwe-decrypt.md b/docs/en/latest/plugins/jwe-decrypt.md index 709cf8e8d200..02dafc7367a7 100644 --- a/docs/en/latest/plugins/jwe-decrypt.md +++ b/docs/en/latest/plugins/jwe-decrypt.md @@ -39,7 +39,7 @@ import TabItem from '@theme/TabItem'; The `jwe-decrypt` Plugin decrypts [JWE](https://datatracker.ietf.org/doc/html/rfc7516) authorization headers in requests sent to APISIX [Routes](../terminology/route.md) or [Services](../terminology/service.md). -This Plugin adds an endpoint `/apisix/plugin/jwe/encrypt` for JWE encryption. For decryption, the key should be configured in [Consumer](../terminology/consumer.md). +The decryption key should be configured in [Consumer](../terminology/consumer.md). ## Attributes @@ -73,28 +73,15 @@ admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"/ ::: -### Expose JWE Encryption Endpoint and Generate JWE Token +### Create a Consumer with the Decryption Key -The following example demonstrates how to expose the JWE encryption endpoint and generate a JWE token. +The following example demonstrates how to create a Consumer with the decryption key and generate a JWE token for it. -The `jwe-decrypt` Plugin creates an internal endpoint at `/apisix/plugin/jwe/encrypt` to encrypt JWE. Expose the endpoint with the [public-api](public-api.md) Plugin: +Create a Consumer with `jwe-decrypt` and configure the decryption key: -```shell -curl "http://127.0.0.1:9180/apisix/admin/routes/jwe-encrypt-api" -X PUT \ - -H "X-API-KEY: ${admin_key}" \ - -d '{ - "uri": "/apisix/plugin/jwe/encrypt", - "plugins": { - "public-api": {} - } - }' -``` - -Create a Consumer with `jwe-decrypt` and configure the decryption key: - ```shell curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \ -H "X-API-KEY: ${admin_key}" \ @@ -113,7 +100,7 @@ curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \ -Expose the JWE encryption endpoint and create a Consumer with `jwe-decrypt` Credential: +Create a Consumer with `jwe-decrypt` Credential: ```yaml title="adc.yaml" consumers: @@ -122,20 +109,6 @@ consumers: jwe-decrypt: key: jack-key secret: key-length-should-be-32-chars123 -services: - - name: jwe-encrypt-api-service - routes: - - name: jwe-encrypt-api-route - uris: - - /apisix/plugin/jwe/encrypt - plugins: - public-api: {} - upstream: - type: roundrobin - nodes: - - host: httpbin.org - port: 80 - weight: 1 ``` Synchronize the configuration to the gateway: @@ -148,12 +121,12 @@ adc sync -f adc.yaml -Create a Consumer with `jwe-decrypt` and expose the JWE encryption endpoint with the `public-api` Plugin: +Create a Consumer with `jwe-decrypt`: -```yaml title="jwe-encrypt-api-ic.yaml" +```yaml title="jwe-consumer-ic.yaml" apiVersion: apisix.apache.org/v1alpha1 kind: Consumer metadata: @@ -167,44 +140,12 @@ spec: config: key: jack-key secret: key-length-should-be-32-chars123 ---- -apiVersion: apisix.apache.org/v1alpha1 -kind: PluginConfig -metadata: - namespace: aic - name: jwe-encrypt-api-plugin-config -spec: - plugins: - - name: public-api - config: - _meta: - disable: false ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - namespace: aic - name: jwe-encrypt-api-route -spec: - parentRefs: - - name: apisix - rules: - - matches: - - path: - type: Exact - value: /apisix/plugin/jwe/encrypt - filters: - - type: ExtensionRef - extensionRef: - group: apisix.apache.org - kind: PluginConfig - name: jwe-encrypt-api-plugin-config ``` Apply the configuration to your cluster: ```shell -kubectl apply -f jwe-encrypt-api-ic.yaml +kubectl apply -f jwe-consumer-ic.yaml ``` @@ -219,22 +160,23 @@ kubectl apply -f jwe-encrypt-api-ic.yaml -Send a request to the encryption endpoint with Consumer key to encrypt some sample data in the payload: +To generate a JWE token for the Consumer, encrypt the payload offline with any AES-256-GCM library, using the Consumer secret as the key. The token structure is: -```shell -curl "http://127.0.0.1:9080/apisix/plugin/jwe/encrypt?key=jack-key" \ - --data-urlencode 'payload={"uid":10000,"uname":"test"}' -G +```text +base64url(header)..base64url(iv).base64url(ciphertext).base64url(tag) ``` -You should see a response similar to the following, with the JWE encrypted data in the response body: +where the header is `{"alg":"dir","enc":"A256GCM","kid":""}`. The IV must be unique and randomly generated for every token; never reuse an IV with the same key. + +For example, the following token encrypts the payload `{"uid":10000,"uname":"test"}` for the Consumer key `jack-key` with the secret configured above: ```text -eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.IUFW_q4igO_wvf63i-3VwV0MEetPL9C20tlgcQ.fveViMUi0ijJlQ19D7kDrg +eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A ``` ### Decrypt Data with JWE -The following example demonstrates how to decrypt the previously generated JWE token. +The following example demonstrates how to decrypt the JWE token generated above. Create a Route with `jwe-decrypt` to decrypt the authorization header: @@ -365,7 +307,7 @@ kubectl apply -f jwe-decrypt-ic.yaml Send a request to the Route with the JWE encrypted data in the `Authorization` header: ```shell -curl "http://127.0.0.1:9080/anything/jwe" -H 'Authorization: eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.IUFW_q4igO_wvf63i-3VwV0MEetPL9C20tlgcQ.fveViMUi0ijJlQ19D7kDrg' +curl "http://127.0.0.1:9080/anything/jwe" -H 'Authorization: eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A' ``` You should see a response similar to the following, where the `Authorization` header shows the plaintext of the payload: diff --git a/docs/zh/latest/plugins/jwe-decrypt.md b/docs/zh/latest/plugins/jwe-decrypt.md index ba5f412e09dc..eb6fcf9b9cd8 100644 --- a/docs/zh/latest/plugins/jwe-decrypt.md +++ b/docs/zh/latest/plugins/jwe-decrypt.md @@ -39,7 +39,7 @@ import TabItem from '@theme/TabItem'; `jwe-decrypt` 插件解密发送到 APISIX [路由](../terminology/route.md)或[服务](../terminology/service.md)的请求中的 [JWE](https://datatracker.ietf.org/doc/html/rfc7516) 授权请求头。 -该插件添加了一个 `/apisix/plugin/jwe/encrypt` 内部端点用于 JWE 加密。解密时,密钥应配置在[消费者](../terminology/consumer.md)中。 +解密密钥应配置在[消费者](../terminology/consumer.md)中。 ## 属性 @@ -73,28 +73,15 @@ admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"/ ::: -### 暴露 JWE 加密端点并生成 JWE 令牌 +### 创建带有解密密钥的消费者 -以下示例演示如何暴露 JWE 加密端点并生成 JWE 令牌。 +以下示例演示如何创建带有解密密钥的消费者,并为其生成 JWE 令牌。 -`jwe-decrypt` 插件在 `/apisix/plugin/jwe/encrypt` 创建一个内部端点用于 JWE 加密。使用 [public-api](public-api.md) 插件暴露该端点: +创建带有 `jwe-decrypt` 的消费者并配置解密密钥: -```shell -curl "http://127.0.0.1:9180/apisix/admin/routes/jwe-encrypt-api" -X PUT \ - -H "X-API-KEY: ${admin_key}" \ - -d '{ - "uri": "/apisix/plugin/jwe/encrypt", - "plugins": { - "public-api": {} - } - }' -``` - -创建带有 `jwe-decrypt` 的消费者并配置解密密钥: - ```shell curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \ -H "X-API-KEY: ${admin_key}" \ @@ -113,7 +100,7 @@ curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \ -暴露 JWE 加密端点并创建带有 `jwe-decrypt` 凭证的消费者: +创建带有 `jwe-decrypt` 凭证的消费者: ```yaml title="adc.yaml" consumers: @@ -122,20 +109,6 @@ consumers: jwe-decrypt: key: jack-key secret: key-length-should-be-32-chars123 -services: - - name: jwe-encrypt-api-service - routes: - - name: jwe-encrypt-api-route - uris: - - /apisix/plugin/jwe/encrypt - plugins: - public-api: {} - upstream: - type: roundrobin - nodes: - - host: httpbin.org - port: 80 - weight: 1 ``` 将配置同步到网关: @@ -148,12 +121,12 @@ adc sync -f adc.yaml -创建带有 `jwe-decrypt` 的消费者并使用 `public-api` 插件暴露 JWE 加密端点: +创建带有 `jwe-decrypt` 的消费者: -```yaml title="jwe-encrypt-api-ic.yaml" +```yaml title="jwe-consumer-ic.yaml" apiVersion: apisix.apache.org/v1alpha1 kind: Consumer metadata: @@ -167,44 +140,12 @@ spec: config: key: jack-key secret: key-length-should-be-32-chars123 ---- -apiVersion: apisix.apache.org/v1alpha1 -kind: PluginConfig -metadata: - namespace: aic - name: jwe-encrypt-api-plugin-config -spec: - plugins: - - name: public-api - config: - _meta: - disable: false ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - namespace: aic - name: jwe-encrypt-api-route -spec: - parentRefs: - - name: apisix - rules: - - matches: - - path: - type: Exact - value: /apisix/plugin/jwe/encrypt - filters: - - type: ExtensionRef - extensionRef: - group: apisix.apache.org - kind: PluginConfig - name: jwe-encrypt-api-plugin-config ``` 将配置应用到集群: ```shell -kubectl apply -f jwe-encrypt-api-ic.yaml +kubectl apply -f jwe-consumer-ic.yaml ``` @@ -219,22 +160,23 @@ kubectl apply -f jwe-encrypt-api-ic.yaml -向加密端点发送请求,使用消费者密钥加密 payload 中的示例数据: +要为消费者生成 JWE 令牌,可使用任意 AES-256-GCM 库离线加密 payload,加密密钥为消费者的 secret。令牌结构如下: -```shell -curl "http://127.0.0.1:9080/apisix/plugin/jwe/encrypt?key=jack-key" \ - --data-urlencode 'payload={"uid":10000,"uname":"test"}' -G +```text +base64url(header)..base64url(iv).base64url(ciphertext).base64url(tag) ``` -你应该看到类似以下的响应,响应体中包含 JWE 加密数据: +其中 header 为 `{"alg":"dir","enc":"A256GCM","kid":""}`。每个令牌的 IV 必须唯一且随机生成,切勿在同一密钥下复用 IV。 + +例如,以下令牌使用上面配置的 secret,为消费者密钥 `jack-key` 加密了 payload `{"uid":10000,"uname":"test"}`: ```text -eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.IUFW_q4igO_wvf63i-3VwV0MEetPL9C20tlgcQ.fveViMUi0ijJlQ19D7kDrg +eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A ``` ### 使用 JWE 解密数据 -以下示例演示如何解密上述生成的 JWE 令牌。 +以下示例演示如何解密上面生成的 JWE 令牌。 创建带有 `jwe-decrypt` 的路由以解密授权请求头: @@ -365,7 +307,7 @@ kubectl apply -f jwe-decrypt-ic.yaml 在 `Authorization` 请求头中携带 JWE 加密数据向路由发送请求: ```shell -curl "http://127.0.0.1:9080/anything/jwe" -H 'Authorization: eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.IUFW_q4igO_wvf63i-3VwV0MEetPL9C20tlgcQ.fveViMUi0ijJlQ19D7kDrg' +curl "http://127.0.0.1:9080/anything/jwe" -H 'Authorization: eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A' ``` 你应该看到类似以下的响应,其中 `Authorization` 请求头显示了 payload 的明文: diff --git a/t/plugin/jwe-decrypt.t b/t/plugin/jwe-decrypt.t index b606f6dd5234..1d72e86aafba 100644 --- a/t/plugin/jwe-decrypt.t +++ b/t/plugin/jwe-decrypt.t @@ -242,74 +242,7 @@ passed -=== TEST 9: create public API route (jwe-decrypt sign) ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/2', - ngx.HTTP_PUT, - [[{ - "plugins": { - "public-api": {} - }, - "uri": "/apisix/plugin/jwe/encrypt" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- response_body -passed ---- no_error_log -12345678901234567890123456789012 - - - -=== TEST 10: sign / verify in argument ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, err, token = t('/apisix/plugin/jwe/encrypt?key=user-key&payload=hello', - ngx.HTTP_GET - ) - - if code > 200 then - ngx.status = code - ngx.say(err) - return - end - - code, err, body = t('/hello', - ngx.HTTP_GET, - nil, - nil, - { Authorization = token } - ) - - ngx.print(body) - } - } ---- response_body -hello world ---- no_error_log -12345678901234567890123456789012 - - - -=== TEST 11: test for unsupported method ---- request -PATCH /apisix/plugin/jwe/encrypt?key=user-key ---- error_code: 404 - - - -=== TEST 12: verify, missing token +=== TEST 9: verify, missing token --- request GET /hello --- error_code: 403 @@ -318,7 +251,7 @@ GET /hello -=== TEST 13: verify: invalid JWE token +=== TEST 10: verify: invalid JWE token --- request GET /hello --- more_headers @@ -329,7 +262,7 @@ Authorization: invalid-eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6IkEyNTZHQ -=== TEST 14: verify (in header) +=== TEST 11: verify (in header) --- request GET /hello --- more_headers @@ -339,7 +272,7 @@ hello world -=== TEST 15: verify (in header without Bearer) +=== TEST 12: verify (in header without Bearer) --- request GET /hello --- more_headers @@ -349,7 +282,7 @@ hello world -=== TEST 16: verify (header with bearer) +=== TEST 13: verify (header with bearer) --- request GET /hello --- more_headers @@ -359,7 +292,7 @@ hello world -=== TEST 17: verify (invalid bearer token) +=== TEST 14: verify (invalid bearer token) --- request GET /hello --- more_headers @@ -370,7 +303,7 @@ Authorization: bearer invalid-eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6Ik -=== TEST 18: delete a exist consumer +=== TEST 15: delete a exist consumer --- config location /t { content_by_lua_block { @@ -407,8 +340,14 @@ Authorization: bearer invalid-eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6Ik ngx.HTTP_DELETE) ngx.say("code: ", code < 300, " body: ", body) - code, body = t('/apisix/plugin/jwe/encrypt?key=chen-key&payload=hello', - ngx.HTTP_GET) + -- the remaining consumer can still be verified + local chen_token = "eyJhbGciOiJkaXIiLCJraWQiOiJjaGVuLWtleSIsImVuYyI6IkEyNTZHQ00ifQ" + .. "..MTIzNDU2Nzg5MDEy.ar0vE2I.AOndbhR7J1e2oM3N2c-KYQ" + code, body = t('/hello', + ngx.HTTP_GET, + nil, + nil, + { Authorization = chen_token }) ngx.say("code: ", code < 300, " body: ", body) } } @@ -419,10 +358,11 @@ code: true body: passed code: true body: passed --- no_error_log 12345678901234567890123456789012 +12345678901234567890123456789021 -=== TEST 19: add consumer with username and plugins with base64 secret +=== TEST 16: add consumer with username and plugins with base64 secret --- config location /t { content_by_lua_block { @@ -454,7 +394,7 @@ fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc= -=== TEST 20: enable jwt decrypt plugin with base64 secret +=== TEST 17: enable jwt decrypt plugin with base64 secret --- config location /t { content_by_lua_block { @@ -490,69 +430,7 @@ fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc= -=== TEST 21: create public API route (jwe-decrypt sign) ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/2', - ngx.HTTP_PUT, - [[{ - "plugins": { - "public-api": {} - }, - "uri": "/apisix/plugin/jwe/encrypt" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- response_body -passed ---- no_error_log -fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc= - - - -=== TEST 22: sign / verify in argument ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, err, token = t('/apisix/plugin/jwe/encrypt?key=user-key&payload=hello', - ngx.HTTP_GET - ) - - if code > 200 then - ngx.status = code - ngx.say(err) - return - end - - ngx.log(ngx.WARN, "dibag: ", token) - - code, err, body = t('/hello', - ngx.HTTP_GET, - nil, - nil, - { Authorization = token } - ) - - ngx.print(body) - } - } ---- response_body -hello world ---- no_error_log -fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc= - - - -=== TEST 23: verify (in header) +=== TEST 18: verify (in header) --- request GET /hello --- more_headers @@ -564,7 +442,7 @@ fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc= -=== TEST 24: verify (in header without Bearer) +=== TEST 19: verify (in header without Bearer) --- request GET /hello --- more_headers @@ -574,7 +452,7 @@ hello world -=== TEST 25: enable jwt decrypt plugin with test upstream route +=== TEST 20: enable jwt decrypt plugin with test upstream route --- config location /t { content_by_lua_block { @@ -610,7 +488,7 @@ fo4XKdZ1xSrIZyms4q2BwPrW5lMpls9qqy5tiAk2esc= -=== TEST 26: verify in upstream header +=== TEST 21: verify in upstream header --- request GET /headers --- more_headers