From 24c7ef921aec8932a57b8fe7b9e2c2ee8d63f98f Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Mon, 3 Jun 2024 20:40:50 +0800 Subject: [PATCH 01/34] chore(release): forward 3.7.0 changelog to master (#13145) * Generate 3.7.0 changelog (#12965) Co-authored-by: lena-larionova <54370747+lena-larionova@users.noreply.github.com> Co-authored-by: Vinicius Mignot * docs(release): genereate 3.7.0 changelog (#13058) Co-authored-by: Andy Zhang --------- Co-authored-by: lena-larionova <54370747+lena-larionova@users.noreply.github.com> Co-authored-by: Vinicius Mignot --- changelog/3.7.0/3.7.0.md | 393 ++++++++++++++++++ changelog/3.7.0/kong-manager/.gitkeep | 0 .../3.7.0/kong-manager/expressions_routes.yml | 5 + .../plugin_forms_improvements.yml | 14 + .../3.7.0/kong-manager/ui_improvements.yml | 15 + changelog/3.7.0/kong/.gitkeep | 0 .../kong/add-ai-data-report.yml | 0 .../kong/add-messages-api-to-anthropic.yml | 0 .../{unreleased => 3.7.0}/kong/add_tzdata.yml | 0 .../kong/ai-proxy-client-params.yml | 0 .../kong/ai-proxy-preserve-mode.yml | 0 .../kong/analytics-for-anthropic.yml | 0 .../kong/bump-atc-router.yml | 0 .../kong/bump-libexpat.yml | 0 .../kong/bump-lua-kong-nginx-module.yml | 0 .../kong/bump-lua-protobuf.yml | 0 .../kong/bump-lua-resty-acme.yml | 0 .../kong/bump-lua-resty-aws.yml | 0 .../kong/bump-lua-resty-http-0.17.2.yml | 0 .../kong/bump-lua-resty-lmdb.yml | 0 .../kong/bump-lua-resty-openssl.yml | 0 .../kong/bump-lua-resty-timer-ng.yml | 0 .../kong/bump-luarocks.yml | 0 .../kong/bump-ngx-wasm-module.yml | 0 .../{unreleased => 3.7.0}/kong/bump-pcre.yml | 0 .../kong/bump-penlight.yml | 0 .../{unreleased => 3.7.0}/kong/bump-v8.yml | 0 .../kong/bump-wasmtime.yml | 0 .../{unreleased => 3.7.0}/kong/cleanup_ai.yml | 0 .../decrease-cocurrency-limit-of-timer-ng.yml | 0 .../kong/disable-TLSv1_1-in-openssl3.yml | 0 ...feat-add-workspace-label-to-prometheus.yml | 0 .../kong/feat-ai-proxy-add-streaming.yml | 0 .../kong/feat-emmy-debugger.yml | 0 .../feat-hybrid-sync-mixed-route-policy.yml | 0 ...e-ai-anthropic-regex-expression-length.yml | 0 .../kong/feat-jwt-eddsa.yml | 0 .../kong/feat-jwt-es512.yml | 0 .../kong/feat-wasm-general-shm-kv.yml | 0 .../kong/fix-acme-renewal-bug.yml | 0 .../kong/fix-aws-lambda-kong-latency.yml | 0 .../kong/fix-cjson-t-end.yml | 0 .../kong/fix-cli-db-timeout-overrides.yml | 0 .../kong/fix-ctx-host-port.yml | 0 .../fix-dbless-duplicate-target-error.yml | 0 ...lue-of-upstream-keepalive-max-requests.yml | 0 .../kong/fix-dns-resolv-timeout-zero.yml | 0 .../kong/fix-external-plugin-instance.yml | 0 .../kong/fix-file-permission-of-logrotate.yml | 0 ...-dp-certificate-with-vault-not-refresh.yml | 0 .../kong/fix-jwt-plugin-check.yml | 0 .../fix-migrations-for-redis-plugins-acme.yml | 0 ...grations-for-redis-plugins-response-rl.yml | 0 .../fix-migrations-for-redis-plugins-rl.yml | 0 ...ng-router-section-of-request-debugging.yml | 0 .../kong/fix-mlcache-renew-lock-leaks.yml | 0 .../kong/fix-router-rebuing-flag.yml | 0 ...ix-snis-tls-passthrough-in-trad-compat.yml | 0 .../kong/fix-upstream-status-unset.yml | 0 .../kong/fix-vault-init-worker.yml | 0 .../fix-vault-secret-update-without-ttl.yml | 0 .../kong/fix-vault-workspaces.yml | 0 .../fix-wasm-disable-pwm-lua-resolver.yml | 0 .../fix_api_405_vaults_validate_endpoint.yml | 0 ..._balancer_healthecker_unexpected_panic.yml | 0 .../kong/fix_privileged_agent_id_1.yml | 0 ...xpressions-supports-traditional-fields.yml | 0 .../kong/key_auth_www_authenticate.yml | 0 .../kong/log-serializer-kong-latency.yml | 0 .../kong/log-serializer-receive-latency.yml | 0 .../otel-increase-queue-max-batch-size.yml | 0 ...ling-panic-when-header-trace-id-enable.yml | 0 .../kong/plugin-schema-deprecation-record.yml | 0 .../kong/plugin_server_restart.yml | 0 .../kong/pluginsocket-proto-wrong-type.yml | 0 .../kong/propagation-module-rework.yml | 0 .../kong/revert-req-body-limitation-patch.yml | 0 ...che_invalidation_cluster_event_channel.yml | 0 .../kong/set_grpc_tls_seclevel.yml | 0 .../speed_up_internal_hooking_mechanism.yml | 0 .../kong/speed_up_router.yml | 0 .../kong/tracing-pdk-short-trace-ids.yml | 0 .../kong/update-ai-proxy-telemetry.yml | 0 .../kong/wasm-bundled-filters.yml | 0 .../unreleased/kong/force-no_sync-noip.yml | 5 - 85 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 changelog/3.7.0/3.7.0.md create mode 100644 changelog/3.7.0/kong-manager/.gitkeep create mode 100644 changelog/3.7.0/kong-manager/expressions_routes.yml create mode 100644 changelog/3.7.0/kong-manager/plugin_forms_improvements.yml create mode 100644 changelog/3.7.0/kong-manager/ui_improvements.yml create mode 100644 changelog/3.7.0/kong/.gitkeep rename changelog/{unreleased => 3.7.0}/kong/add-ai-data-report.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/add-messages-api-to-anthropic.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/add_tzdata.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/ai-proxy-client-params.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/ai-proxy-preserve-mode.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/analytics-for-anthropic.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-atc-router.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-libexpat.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-kong-nginx-module.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-protobuf.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-resty-acme.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-resty-aws.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-resty-http-0.17.2.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-resty-lmdb.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-resty-openssl.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-lua-resty-timer-ng.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-luarocks.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-ngx-wasm-module.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-pcre.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-penlight.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-v8.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/bump-wasmtime.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/cleanup_ai.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/decrease-cocurrency-limit-of-timer-ng.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/disable-TLSv1_1-in-openssl3.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-add-workspace-label-to-prometheus.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-ai-proxy-add-streaming.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-emmy-debugger.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-hybrid-sync-mixed-route-policy.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-increase-ai-anthropic-regex-expression-length.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-jwt-eddsa.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-jwt-es512.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/feat-wasm-general-shm-kv.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-acme-renewal-bug.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-aws-lambda-kong-latency.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-cjson-t-end.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-cli-db-timeout-overrides.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-ctx-host-port.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-dbless-duplicate-target-error.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-default-value-of-upstream-keepalive-max-requests.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-dns-resolv-timeout-zero.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-external-plugin-instance.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-file-permission-of-logrotate.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-hybrid-dp-certificate-with-vault-not-refresh.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-jwt-plugin-check.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-migrations-for-redis-plugins-acme.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-migrations-for-redis-plugins-response-rl.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-migrations-for-redis-plugins-rl.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-missing-router-section-of-request-debugging.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-mlcache-renew-lock-leaks.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-router-rebuing-flag.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-snis-tls-passthrough-in-trad-compat.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-upstream-status-unset.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-vault-init-worker.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-vault-secret-update-without-ttl.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-vault-workspaces.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix-wasm-disable-pwm-lua-resolver.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix_api_405_vaults_validate_endpoint.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix_balancer_healthecker_unexpected_panic.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/fix_privileged_agent_id_1.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/flavor-expressions-supports-traditional-fields.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/key_auth_www_authenticate.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/log-serializer-kong-latency.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/log-serializer-receive-latency.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/otel-increase-queue-max-batch-size.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/otel-sampling-panic-when-header-trace-id-enable.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/plugin-schema-deprecation-record.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/plugin_server_restart.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/pluginsocket-proto-wrong-type.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/propagation-module-rework.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/revert-req-body-limitation-patch.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/separate_kong_cache_invalidation_cluster_event_channel.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/set_grpc_tls_seclevel.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/speed_up_internal_hooking_mechanism.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/speed_up_router.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/tracing-pdk-short-trace-ids.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/update-ai-proxy-telemetry.yml (100%) rename changelog/{unreleased => 3.7.0}/kong/wasm-bundled-filters.yml (100%) delete mode 100644 changelog/unreleased/kong/force-no_sync-noip.yml diff --git a/changelog/3.7.0/3.7.0.md b/changelog/3.7.0/3.7.0.md new file mode 100644 index 000000000000..7d062259d324 --- /dev/null +++ b/changelog/3.7.0/3.7.0.md @@ -0,0 +1,393 @@ +## Kong + + +### Performance +#### Performance + +- Improved proxy performance by refactoring internal hooking mechanism. + [#12784](https://github.com/Kong/kong/issues/12784) + [KAG-3653](https://konghq.atlassian.net/browse/KAG-3653) + +- Sped up the router matching when the `router_flavor` is `traditional_compatible` or `expressions`. + [#12467](https://github.com/Kong/kong/issues/12467) + [KAG-3653](https://konghq.atlassian.net/browse/KAG-3653) +#### Plugin + +- **Opentelemetry**: Increased queue max batch size to 200. + [#12488](https://github.com/Kong/kong/issues/12488) + [KAG-3173](https://konghq.atlassian.net/browse/KAG-3173) + +### Breaking Changes +#### Plugin + +- **AI Proxy**: To support the new messages API of `Anthropic`, the upstream path of the `Anthropic` for `llm/v1/chat` route type has changed from `/v1/complete` to `/v1/messages`. + [#12699](https://github.com/Kong/kong/issues/12699) + [FTI-5770](https://konghq.atlassian.net/browse/FTI-5770) + + +### Dependencies +#### Core + +- Bumped atc-router from v1.6.0 to v1.6.2 + [#12231](https://github.com/Kong/kong/issues/12231) + [KAG-3403](https://konghq.atlassian.net/browse/KAG-3403) + +- Bumped libexpat to 2.6.2 + [#12910](https://github.com/Kong/kong/issues/12910) + [CVE-2023](https://konghq.atlassian.net/browse/CVE-2023) [CVE-2013](https://konghq.atlassian.net/browse/CVE-2013) [CVE-2024](https://konghq.atlassian.net/browse/CVE-2024) [KAG-4331](https://konghq.atlassian.net/browse/KAG-4331) + +- Bumped lua-kong-nginx-module from 0.8.0 to 0.11.0 + [#12752](https://github.com/Kong/kong/issues/12752) + [KAG-4050](https://konghq.atlassian.net/browse/KAG-4050) + +- Bumped lua-protobuf to 0.5.1 + [#12834](https://github.com/Kong/kong/issues/12834) + + +- Bumped lua-resty-acme to 0.13.0 + [#12909](https://github.com/Kong/kong/issues/12909) + [KAG-4330](https://konghq.atlassian.net/browse/KAG-4330) + +- Bumped lua-resty-aws from 1.3.6 to 1.4.1 + [#12846](https://github.com/Kong/kong/issues/12846) + [KAG-3424](https://konghq.atlassian.net/browse/KAG-3424) [FTI-5732](https://konghq.atlassian.net/browse/FTI-5732) + +- Bumped lua-resty-lmdb from 1.4.1 to 1.4.2 + [#12786](https://github.com/Kong/kong/issues/12786) + + +- Bumped lua-resty-openssl from 1.2.0 to 1.3.1 + [#12665](https://github.com/Kong/kong/issues/12665) + + +- Bumped lua-resty-timer-ng to 0.2.7 + [#12756](https://github.com/Kong/kong/issues/12756) + [KAG-3653](https://konghq.atlassian.net/browse/KAG-3653) + +- Bumped PCRE from the legacy libpcre 8.45 to libpcre2 10.43 + [#12366](https://github.com/Kong/kong/issues/12366) + [KAG-3571](https://konghq.atlassian.net/browse/KAG-3571) [KAG-3521](https://konghq.atlassian.net/browse/KAG-3521) [KAG-2025](https://konghq.atlassian.net/browse/KAG-2025) + +- Bumped penlight to 1.14.0 + [#12862](https://github.com/Kong/kong/issues/12862) + +#### Default + +- Added package `tzdata` to DEB Docker image for convenient timezone setting. + [#12609](https://github.com/Kong/kong/issues/12609) + [FTI-5698](https://konghq.atlassian.net/browse/FTI-5698) + +- Bumped lua-resty-http to 0.17.2. + [#12908](https://github.com/Kong/kong/issues/12908) + + +- Bumped LuaRocks from 3.9.2 to 3.11.0 + [#12662](https://github.com/Kong/kong/issues/12662) + [KAG-3883](https://konghq.atlassian.net/browse/KAG-3883) + +- Bumped `ngx_wasm_module` to `91d447ffd0e9bb08f11cc69d1aa9128ec36b4526` + [#12011](https://github.com/Kong/kong/issues/12011) + + +- Bumped `V8` version to `12.0.267.17` + [#12704](https://github.com/Kong/kong/issues/12704) + + +- Bumped `Wasmtime` version to `19.0.0` + [#12011](https://github.com/Kong/kong/issues/12011) + + +- Improved the robustness of lua-cjson when handling unexpected input. + [#12904](https://github.com/Kong/kong/issues/12904) + [KAG-4275](https://konghq.atlassian.net/browse/KAG-4275) + +### Features +#### Configuration + +- TLSv1.1 and lower versions are disabled by default in OpenSSL 3.x. + [#12420](https://github.com/Kong/kong/issues/12420) + [KAG-3259](https://konghq.atlassian.net/browse/KAG-3259) + +- Introduced `nginx_wasm_main_shm_kv` configuration parameter, which enables +Wasm filters to use the Proxy-Wasm operations `get_shared_data` and +`set_shared_data` without namespaced keys. + [#12663](https://github.com/Kong/kong/issues/12663) + + +- **Schema**: Added a deprecation field attribute to identify deprecated fields + [#12686](https://github.com/Kong/kong/issues/12686) + [KAG-3915](https://konghq.atlassian.net/browse/KAG-3915) + +- Added the `wasm_filters` configuration parameter for enabling individual filters + [#12843](https://github.com/Kong/kong/issues/12843) + [KAG-4211](https://konghq.atlassian.net/browse/KAG-4211) +#### Core + +- Added `events:ai:response_tokens`, `events:ai:prompt_tokens` and `events:ai:requests` to the anonymous report to start counting AI usage + [#12924](https://github.com/Kong/kong/issues/12924) + + +- Improved config handling when the CP runs with the router set to the `expressions` flavor: + - If mixed config is detected and a lower DP is attached to the CP, no config will be sent at all + - If the expression is invalid on the CP, no config will be sent at all + - If the expression is invalid on a lower DP, it will be sent to the DP and DP validation will catch this and communicate back to the CP (this could result in partial config application) + [#12967](https://github.com/Kong/kong/issues/12967) + [KAG-3806](https://konghq.atlassian.net/browse/KAG-3806) + +- The route entity now supports the following fields when the +`router_flavor` is `expressions`: `methods`, `hosts`, `paths`, `headers`, +`snis`, `sources`, `destinations`, and `regex_priority`. +The meaning of these fields are consistent with the traditional route entity. + [#12667](https://github.com/Kong/kong/issues/12667) + [KAG-3805](https://konghq.atlassian.net/browse/KAG-3805) [KAG-3807](https://konghq.atlassian.net/browse/KAG-3807) +#### PDK + +- Added the `latencies.receive` property to the log serializer + [#12730](https://github.com/Kong/kong/issues/12730) + [KAG-3798](https://konghq.atlassian.net/browse/KAG-3798) +#### Plugin + +- AI Proxy now reads most prompt tuning parameters from the client, +while the plugin config parameters under `model_options` are now just defaults. +This fixes support for using the respective provider's native SDK. + [#12903](https://github.com/Kong/kong/issues/12903) + [KAG-4126](https://konghq.atlassian.net/browse/KAG-4126) + +- AI Proxy now has a `preserve` option for `route_type`, where the requests and responses +are passed directly to the upstream LLM. This is to enable compatibility with any +and all models and SDKs that may be used when calling the AI services. + [#12903](https://github.com/Kong/kong/issues/12903) + [KAG-4126](https://konghq.atlassian.net/browse/KAG-4126) + +- **Prometheus**: Added workspace label to Prometheus plugin metrics. + [#12836](https://github.com/Kong/kong/issues/12836) + [FTI-5573](https://konghq.atlassian.net/browse/FTI-5573) + +- **AI Proxy**: Added support for streaming event-by-event responses back to the client on supported providers. + [#12792](https://github.com/Kong/kong/issues/12792) + [KAG-4124](https://konghq.atlassian.net/browse/KAG-4124) + +- **AI Prompt Guard**: Increased the maximum length of regex expressions to 500 for the allow and deny parameters. + [#12731](https://github.com/Kong/kong/issues/12731) + [FTI-5767](https://konghq.atlassian.net/browse/FTI-5767) + +- Addded support for EdDSA algorithms in JWT plugin + [#12726](https://github.com/Kong/kong/issues/12726) + + +- Added support for ES512, PS256, PS384, PS512 algorithms in JWT plugin + [#12638](https://github.com/Kong/kong/issues/12638) + [KAG-3821](https://konghq.atlassian.net/browse/KAG-3821) + +- **OpenTelemetry, Zipkin**: The propagation module has been reworked. The new +options allow better control over the configuration of tracing headers propagation. + [#12670](https://github.com/Kong/kong/issues/12670) + [KAG-1886](https://konghq.atlassian.net/browse/KAG-1886) [KAG-1887](https://konghq.atlassian.net/browse/KAG-1887) +#### Default + +- Added support for debugging with EmmyLuaDebugger. This feature is a +tech preview and not officially supported by Kong Inc. for now. + [#12899](https://github.com/Kong/kong/issues/12899) + [KAG-4316](https://konghq.atlassian.net/browse/KAG-4316) + +### Fixes +#### CLI Command + +- Fixed an issue where the `pg_timeout` was overridden to `60s` even if `--db-timeout` +was not explicitly passed in CLI arguments. + [#12981](https://github.com/Kong/kong/issues/12981) + [KAG-4416](https://konghq.atlassian.net/browse/KAG-4416) +#### Configuration + +- Fixed the default value in kong.conf.default documentation from 1000 to 10000 +for the `upstream_keepalive_max_requests` option. + [#12643](https://github.com/Kong/kong/issues/12643) + [KAG-3360](https://konghq.atlassian.net/browse/KAG-3360) + +- Fixed an issue where an external plugin (Go, Javascript, or Python) would fail to +apply a change to the plugin config via the Admin API. + [#12718](https://github.com/Kong/kong/issues/12718) + [KAG-3949](https://konghq.atlassian.net/browse/KAG-3949) + +- Disabled usage of the Lua DNS resolver from proxy-wasm by default. + [#12825](https://github.com/Kong/kong/issues/12825) + [KAG-4277](https://konghq.atlassian.net/browse/KAG-4277) + +- Set security level of gRPC's TLS to 0 when `ssl_cipher_suite` is set to `old`. + [#12613](https://github.com/Kong/kong/issues/12613) + [KAG-3259](https://konghq.atlassian.net/browse/KAG-3259) +#### Core + +- Fixed an issue where `POST /config?flatten_errors=1` could not return a proper response if the input included duplicate upstream targets. + [#12797](https://github.com/Kong/kong/issues/12797) + [KAG-4144](https://konghq.atlassian.net/browse/KAG-4144) + +- **DNS Client**: Ignore a non-positive values on resolv.conf for options timeout, and use a default value of 2 seconds instead. + [#12640](https://github.com/Kong/kong/issues/12640) + [FTI-5791](https://konghq.atlassian.net/browse/FTI-5791) + +- Updated the file permission of `kong.logrotate` to 644. + [#12629](https://github.com/Kong/kong/issues/12629) + [FTI-5756](https://konghq.atlassian.net/browse/FTI-5756) + +- Fixed a problem on hybrid mode DPs, where a certificate entity configured with a vault reference may not get refreshed on time. + [#12868](https://github.com/Kong/kong/issues/12868) + [FTI-5881](https://konghq.atlassian.net/browse/FTI-5881) + +- Fixed the missing router section for the output of the request-debugging. + [#12234](https://github.com/Kong/kong/issues/12234) + [KAG-3438](https://konghq.atlassian.net/browse/KAG-3438) + +- Fixed an issue in the internal caching logic where mutexes could get never unlocked. + [#12743](https://github.com/Kong/kong/issues/12743) + + +- Fixed an issue where the router didn't work correctly +when the route's configuration changed. + [#12654](https://github.com/Kong/kong/issues/12654) + [KAG-3857](https://konghq.atlassian.net/browse/KAG-3857) + +- Fixed an issue where SNI-based routing didn't work +using `tls_passthrough` and the `traditional_compatible` router flavor. + [#12681](https://github.com/Kong/kong/issues/12681) + [KAG-3922](https://konghq.atlassian.net/browse/KAG-3922) [FTI-5781](https://konghq.atlassian.net/browse/FTI-5781) + +- Fixed a bug that `X-Kong-Upstream-Status` didn't appear in the response headers even if it was set in the `headers` parameter in the `kong.conf` file when the response was hit and returned by the Proxy Cache plugin. + [#12744](https://github.com/Kong/kong/issues/12744) + [FTI-5827](https://konghq.atlassian.net/browse/FTI-5827) + +- Fixed vault initialization by postponing vault reference resolving on init_worker + [#12554](https://github.com/Kong/kong/issues/12554) + [KAG-2907](https://konghq.atlassian.net/browse/KAG-2907) + +- Fixed a bug that allowed vault secrets to refresh even when they had no TTL set. + [#12877](https://github.com/Kong/kong/issues/12877) + [FTI-5906](https://konghq.atlassian.net/browse/FTI-5906) [FTI-5916](https://konghq.atlassian.net/browse/FTI-5916) + +- **Vault**: do not use incorrect (default) workspace identifier when retrieving vault entity by prefix + [#12572](https://github.com/Kong/kong/issues/12572) + [FTI-5762](https://konghq.atlassian.net/browse/FTI-5762) + +- **Core**: Fixed unexpected table nil panic in the balancer's stop_healthchecks function + [#12865](https://github.com/Kong/kong/issues/12865) + + +- Use `-1` as the worker ID of privileged agent to avoid access issues. + [#12385](https://github.com/Kong/kong/issues/12385) + [FTI-5707](https://konghq.atlassian.net/browse/FTI-5707) + +- **Plugin Server**: Fixed an issue where Kong failed to properly restart MessagePack-based pluginservers (used in Python and Javascript plugins, for example). + [#12582](https://github.com/Kong/kong/issues/12582) + [KAG-3765](https://konghq.atlassian.net/browse/KAG-3765) + +- Reverted the hard-coded limitation of the `ngx.read_body()` API in OpenResty upstreams' new versions when downstream connections are in HTTP/2 or HTTP/3 stream modes. + [#12658](https://github.com/Kong/kong/issues/12658) + [FTI-5766](https://konghq.atlassian.net/browse/FTI-5766) [FTI-5795](https://konghq.atlassian.net/browse/FTI-5795) + +- Each Kong cache instance now utilizes its own cluster event channel. This approach isolates cache invalidation events and reducing the generation of unnecessary worker events. + [#12321](https://github.com/Kong/kong/issues/12321) + [FTI-5559](https://konghq.atlassian.net/browse/FTI-5559) + +- Updated telemetry collection for AI Plugins to allow multiple plugins data to be set for the same request. + [#12583](https://github.com/Kong/kong/issues/12583) + [KAG-3759](https://konghq.atlassian.net/browse/KAG-3759) [KAG-4124](https://konghq.atlassian.net/browse/KAG-4124) +#### PDK + +- **PDK:** Fixed `kong.request.get_forwarded_port` to always return a number, +which was caused by an incorrectly stored string value in `ngx.ctx.host_port`. + [#12806](https://github.com/Kong/kong/issues/12806) + [KAG-4158](https://konghq.atlassian.net/browse/KAG-4158) + +- The value of `latencies.kong` in the log serializer payload no longer includes +the response receive time, so it now has the same value as the +`X-Kong-Proxy-Latency` response header. Response receive time is recorded in +the new `latencies.receive` metric, so if desired, the old value can be +calculated as `latencies.kong + latencies.receive`. **Note:** this also +affects payloads from all logging plugins that use the log serializer: +`file-log`, `tcp-log`, `udp-log`,`http-log`, `syslog`, and `loggly`, e.g. +[descriptions of JSON objects for the HTTP Log Plugin's log format](https://docs.konghq.com/hub/kong-inc/http-log/log-format/#json-object-descriptions). + [#12795](https://github.com/Kong/kong/issues/12795) + [KAG-3798](https://konghq.atlassian.net/browse/KAG-3798) + +- **Tracing**: enhanced robustness of trace ID parsing + [#12848](https://github.com/Kong/kong/issues/12848) + [KAG-4218](https://konghq.atlassian.net/browse/KAG-4218) +#### Plugin + +- **AI-proxy-plugin**: Fixed the bug that the `route_type` `/llm/v1/chat` didn't include the analytics in the responses. + [#12781](https://github.com/Kong/kong/issues/12781) + [FTI-5769](https://konghq.atlassian.net/browse/FTI-5769) + +- **ACME**: Fixed an issue where the certificate was not successfully renewed during ACME renewal. + [#12773](https://github.com/Kong/kong/issues/12773) + [KAG-4008](https://konghq.atlassian.net/browse/KAG-4008) + +- **AWS-Lambda**: Fixed an issue where the latency attributed to AWS Lambda API requests was counted as part of the latency in Kong. + [#12835](https://github.com/Kong/kong/issues/12835) + [FTI-5261](https://konghq.atlassian.net/browse/FTI-5261) + +- **Jwt**: Fixed an issue where the plugin would fail when using invalid public keys for ES384 and ES512 algorithms. + [#12724](https://github.com/Kong/kong/issues/12724) + + +- Added WWW-Authenticate headers to all 401 responses in the Key Auth plugin. + [#11794](https://github.com/Kong/kong/issues/11794) + [KAG-321](https://konghq.atlassian.net/browse/KAG-321) + +- **Opentelemetry**: Fixed an OTEL sampling mode Lua panic bug, which happened when the `http_response_header_for_traceid` option was enabled. + [#12544](https://github.com/Kong/kong/issues/12544) + [FTI-5742](https://konghq.atlassian.net/browse/FTI-5742) + +- Improve error handling in AI plugins. + [#12991](https://github.com/Kong/kong/issues/12991) + [KAG-4311](https://konghq.atlassian.net/browse/KAG-4311) + +- **ACME**: Fixed migration of redis configuration. + [#12989](https://github.com/Kong/kong/issues/12989) + [KAG-4419](https://konghq.atlassian.net/browse/KAG-4419) + +- **Response-RateLimiting**: Fixed migration of redis configuration. + [#12989](https://github.com/Kong/kong/issues/12989) + [KAG-4419](https://konghq.atlassian.net/browse/KAG-4419) + +- **Rate-Limiting**: Fixed migration of redis configuration. + [#12989](https://github.com/Kong/kong/issues/12989) + [KAG-4419](https://konghq.atlassian.net/browse/KAG-4419) +#### Admin API + +- **Admin API**: fixed an issue where calling the endpoint `POST /schemas/vaults/validate` was conflicting with the endpoint `/schemas/vaults/:name` which only has GET implemented, hence resulting in a 405. + [#12607](https://github.com/Kong/kong/issues/12607) + [KAG-3699](https://konghq.atlassian.net/browse/KAG-3699) +#### Default + +- Fixed a bug where, if the the ulimit setting (open files) was low, Kong would fail to start as the `lua-resty-timer-ng` exhausted the available `worker_connections`. Decreased the concurrency range of the `lua-resty-timer-ng` library from `[512, 2048]` to `[256, 1024]` to fix this bug. + [#12606](https://github.com/Kong/kong/issues/12606) + [KAG-3779](https://konghq.atlassian.net/browse/KAG-3779) [FTI-5780](https://konghq.atlassian.net/browse/FTI-5780) + +- Fix an issue where external plugins using the protobuf-based protocol would fail to call the `kong.Service.SetUpstream` method with an error `bad argument #2 to 'encode' (table expected, got boolean)`. + [#12727](https://github.com/Kong/kong/issues/12727) + +## Kong-Manager + + + + + + +### Features +#### Default + +- Kong Manager now supports creating and editing Expressions routes with an interactive in-browser editor with syntax highlighting and autocompletion features for Kong's Expressions language. + [#217](https://github.com/Kong/kong-manager/issues/217) + + +- Kong Manager now groups the parameters to provide a better user experience while configuring plugins. Meanwhile, several issues with the plugin form page were fixed. + [#195](https://github.com/Kong/kong-manager/issues/195) [#199](https://github.com/Kong/kong-manager/issues/199) [#201](https://github.com/Kong/kong-manager/issues/201) [#202](https://github.com/Kong/kong-manager/issues/202) [#207](https://github.com/Kong/kong-manager/issues/207) [#208](https://github.com/Kong/kong-manager/issues/208) [#209](https://github.com/Kong/kong-manager/issues/209) [#213](https://github.com/Kong/kong-manager/issues/213) [#216](https://github.com/Kong/kong-manager/issues/216) + + +### Fixes +#### Default + +- Improved the user experience in Kong Manager by fixing various UI-related issues. + [#185](https://github.com/Kong/kong-manager/issues/185) [#188](https://github.com/Kong/kong-manager/issues/188) [#190](https://github.com/Kong/kong-manager/issues/190) [#195](https://github.com/Kong/kong-manager/issues/195) [#199](https://github.com/Kong/kong-manager/issues/199) [#201](https://github.com/Kong/kong-manager/issues/201) [#202](https://github.com/Kong/kong-manager/issues/202) [#207](https://github.com/Kong/kong-manager/issues/207) [#208](https://github.com/Kong/kong-manager/issues/208) [#209](https://github.com/Kong/kong-manager/issues/209) [#213](https://github.com/Kong/kong-manager/issues/213) [#216](https://github.com/Kong/kong-manager/issues/216) + diff --git a/changelog/3.7.0/kong-manager/.gitkeep b/changelog/3.7.0/kong-manager/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/changelog/3.7.0/kong-manager/expressions_routes.yml b/changelog/3.7.0/kong-manager/expressions_routes.yml new file mode 100644 index 000000000000..e4784dd9d2ce --- /dev/null +++ b/changelog/3.7.0/kong-manager/expressions_routes.yml @@ -0,0 +1,5 @@ +message: >- + Kong Manager now supports creating and editing Expressions routes with an interactive in-browser + editor with syntax highlighting and autocompletion features for Kong's Expressions language. +type: feature +githubs: [217] diff --git a/changelog/3.7.0/kong-manager/plugin_forms_improvements.yml b/changelog/3.7.0/kong-manager/plugin_forms_improvements.yml new file mode 100644 index 000000000000..3cb617a855e1 --- /dev/null +++ b/changelog/3.7.0/kong-manager/plugin_forms_improvements.yml @@ -0,0 +1,14 @@ +message: >- + Kong Manager now groups the parameters to provide a better user experience while configuring plugins. + Meanwhile, several issues with the plugin form page were fixed. +type: feature +githubs: + - 195 + - 199 + - 201 + - 202 + - 207 + - 208 + - 209 + - 213 + - 216 diff --git a/changelog/3.7.0/kong-manager/ui_improvements.yml b/changelog/3.7.0/kong-manager/ui_improvements.yml new file mode 100644 index 000000000000..1c493f2d6cff --- /dev/null +++ b/changelog/3.7.0/kong-manager/ui_improvements.yml @@ -0,0 +1,15 @@ +message: Improved the user experience in Kong Manager by fixing various UI-related issues. +type: bugfix +githubs: + - 185 + - 188 + - 190 + - 195 + - 199 + - 201 + - 202 + - 207 + - 208 + - 209 + - 213 + - 216 diff --git a/changelog/3.7.0/kong/.gitkeep b/changelog/3.7.0/kong/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/changelog/unreleased/kong/add-ai-data-report.yml b/changelog/3.7.0/kong/add-ai-data-report.yml similarity index 100% rename from changelog/unreleased/kong/add-ai-data-report.yml rename to changelog/3.7.0/kong/add-ai-data-report.yml diff --git a/changelog/unreleased/kong/add-messages-api-to-anthropic.yml b/changelog/3.7.0/kong/add-messages-api-to-anthropic.yml similarity index 100% rename from changelog/unreleased/kong/add-messages-api-to-anthropic.yml rename to changelog/3.7.0/kong/add-messages-api-to-anthropic.yml diff --git a/changelog/unreleased/kong/add_tzdata.yml b/changelog/3.7.0/kong/add_tzdata.yml similarity index 100% rename from changelog/unreleased/kong/add_tzdata.yml rename to changelog/3.7.0/kong/add_tzdata.yml diff --git a/changelog/unreleased/kong/ai-proxy-client-params.yml b/changelog/3.7.0/kong/ai-proxy-client-params.yml similarity index 100% rename from changelog/unreleased/kong/ai-proxy-client-params.yml rename to changelog/3.7.0/kong/ai-proxy-client-params.yml diff --git a/changelog/unreleased/kong/ai-proxy-preserve-mode.yml b/changelog/3.7.0/kong/ai-proxy-preserve-mode.yml similarity index 100% rename from changelog/unreleased/kong/ai-proxy-preserve-mode.yml rename to changelog/3.7.0/kong/ai-proxy-preserve-mode.yml diff --git a/changelog/unreleased/kong/analytics-for-anthropic.yml b/changelog/3.7.0/kong/analytics-for-anthropic.yml similarity index 100% rename from changelog/unreleased/kong/analytics-for-anthropic.yml rename to changelog/3.7.0/kong/analytics-for-anthropic.yml diff --git a/changelog/unreleased/kong/bump-atc-router.yml b/changelog/3.7.0/kong/bump-atc-router.yml similarity index 100% rename from changelog/unreleased/kong/bump-atc-router.yml rename to changelog/3.7.0/kong/bump-atc-router.yml diff --git a/changelog/unreleased/kong/bump-libexpat.yml b/changelog/3.7.0/kong/bump-libexpat.yml similarity index 100% rename from changelog/unreleased/kong/bump-libexpat.yml rename to changelog/3.7.0/kong/bump-libexpat.yml diff --git a/changelog/unreleased/kong/bump-lua-kong-nginx-module.yml b/changelog/3.7.0/kong/bump-lua-kong-nginx-module.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-kong-nginx-module.yml rename to changelog/3.7.0/kong/bump-lua-kong-nginx-module.yml diff --git a/changelog/unreleased/kong/bump-lua-protobuf.yml b/changelog/3.7.0/kong/bump-lua-protobuf.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-protobuf.yml rename to changelog/3.7.0/kong/bump-lua-protobuf.yml diff --git a/changelog/unreleased/kong/bump-lua-resty-acme.yml b/changelog/3.7.0/kong/bump-lua-resty-acme.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-resty-acme.yml rename to changelog/3.7.0/kong/bump-lua-resty-acme.yml diff --git a/changelog/unreleased/kong/bump-lua-resty-aws.yml b/changelog/3.7.0/kong/bump-lua-resty-aws.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-resty-aws.yml rename to changelog/3.7.0/kong/bump-lua-resty-aws.yml diff --git a/changelog/unreleased/kong/bump-lua-resty-http-0.17.2.yml b/changelog/3.7.0/kong/bump-lua-resty-http-0.17.2.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-resty-http-0.17.2.yml rename to changelog/3.7.0/kong/bump-lua-resty-http-0.17.2.yml diff --git a/changelog/unreleased/kong/bump-lua-resty-lmdb.yml b/changelog/3.7.0/kong/bump-lua-resty-lmdb.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-resty-lmdb.yml rename to changelog/3.7.0/kong/bump-lua-resty-lmdb.yml diff --git a/changelog/unreleased/kong/bump-lua-resty-openssl.yml b/changelog/3.7.0/kong/bump-lua-resty-openssl.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-resty-openssl.yml rename to changelog/3.7.0/kong/bump-lua-resty-openssl.yml diff --git a/changelog/unreleased/kong/bump-lua-resty-timer-ng.yml b/changelog/3.7.0/kong/bump-lua-resty-timer-ng.yml similarity index 100% rename from changelog/unreleased/kong/bump-lua-resty-timer-ng.yml rename to changelog/3.7.0/kong/bump-lua-resty-timer-ng.yml diff --git a/changelog/unreleased/kong/bump-luarocks.yml b/changelog/3.7.0/kong/bump-luarocks.yml similarity index 100% rename from changelog/unreleased/kong/bump-luarocks.yml rename to changelog/3.7.0/kong/bump-luarocks.yml diff --git a/changelog/unreleased/kong/bump-ngx-wasm-module.yml b/changelog/3.7.0/kong/bump-ngx-wasm-module.yml similarity index 100% rename from changelog/unreleased/kong/bump-ngx-wasm-module.yml rename to changelog/3.7.0/kong/bump-ngx-wasm-module.yml diff --git a/changelog/unreleased/kong/bump-pcre.yml b/changelog/3.7.0/kong/bump-pcre.yml similarity index 100% rename from changelog/unreleased/kong/bump-pcre.yml rename to changelog/3.7.0/kong/bump-pcre.yml diff --git a/changelog/unreleased/kong/bump-penlight.yml b/changelog/3.7.0/kong/bump-penlight.yml similarity index 100% rename from changelog/unreleased/kong/bump-penlight.yml rename to changelog/3.7.0/kong/bump-penlight.yml diff --git a/changelog/unreleased/kong/bump-v8.yml b/changelog/3.7.0/kong/bump-v8.yml similarity index 100% rename from changelog/unreleased/kong/bump-v8.yml rename to changelog/3.7.0/kong/bump-v8.yml diff --git a/changelog/unreleased/kong/bump-wasmtime.yml b/changelog/3.7.0/kong/bump-wasmtime.yml similarity index 100% rename from changelog/unreleased/kong/bump-wasmtime.yml rename to changelog/3.7.0/kong/bump-wasmtime.yml diff --git a/changelog/unreleased/kong/cleanup_ai.yml b/changelog/3.7.0/kong/cleanup_ai.yml similarity index 100% rename from changelog/unreleased/kong/cleanup_ai.yml rename to changelog/3.7.0/kong/cleanup_ai.yml diff --git a/changelog/unreleased/kong/decrease-cocurrency-limit-of-timer-ng.yml b/changelog/3.7.0/kong/decrease-cocurrency-limit-of-timer-ng.yml similarity index 100% rename from changelog/unreleased/kong/decrease-cocurrency-limit-of-timer-ng.yml rename to changelog/3.7.0/kong/decrease-cocurrency-limit-of-timer-ng.yml diff --git a/changelog/unreleased/kong/disable-TLSv1_1-in-openssl3.yml b/changelog/3.7.0/kong/disable-TLSv1_1-in-openssl3.yml similarity index 100% rename from changelog/unreleased/kong/disable-TLSv1_1-in-openssl3.yml rename to changelog/3.7.0/kong/disable-TLSv1_1-in-openssl3.yml diff --git a/changelog/unreleased/kong/feat-add-workspace-label-to-prometheus.yml b/changelog/3.7.0/kong/feat-add-workspace-label-to-prometheus.yml similarity index 100% rename from changelog/unreleased/kong/feat-add-workspace-label-to-prometheus.yml rename to changelog/3.7.0/kong/feat-add-workspace-label-to-prometheus.yml diff --git a/changelog/unreleased/kong/feat-ai-proxy-add-streaming.yml b/changelog/3.7.0/kong/feat-ai-proxy-add-streaming.yml similarity index 100% rename from changelog/unreleased/kong/feat-ai-proxy-add-streaming.yml rename to changelog/3.7.0/kong/feat-ai-proxy-add-streaming.yml diff --git a/changelog/unreleased/kong/feat-emmy-debugger.yml b/changelog/3.7.0/kong/feat-emmy-debugger.yml similarity index 100% rename from changelog/unreleased/kong/feat-emmy-debugger.yml rename to changelog/3.7.0/kong/feat-emmy-debugger.yml diff --git a/changelog/unreleased/kong/feat-hybrid-sync-mixed-route-policy.yml b/changelog/3.7.0/kong/feat-hybrid-sync-mixed-route-policy.yml similarity index 100% rename from changelog/unreleased/kong/feat-hybrid-sync-mixed-route-policy.yml rename to changelog/3.7.0/kong/feat-hybrid-sync-mixed-route-policy.yml diff --git a/changelog/unreleased/kong/feat-increase-ai-anthropic-regex-expression-length.yml b/changelog/3.7.0/kong/feat-increase-ai-anthropic-regex-expression-length.yml similarity index 100% rename from changelog/unreleased/kong/feat-increase-ai-anthropic-regex-expression-length.yml rename to changelog/3.7.0/kong/feat-increase-ai-anthropic-regex-expression-length.yml diff --git a/changelog/unreleased/kong/feat-jwt-eddsa.yml b/changelog/3.7.0/kong/feat-jwt-eddsa.yml similarity index 100% rename from changelog/unreleased/kong/feat-jwt-eddsa.yml rename to changelog/3.7.0/kong/feat-jwt-eddsa.yml diff --git a/changelog/unreleased/kong/feat-jwt-es512.yml b/changelog/3.7.0/kong/feat-jwt-es512.yml similarity index 100% rename from changelog/unreleased/kong/feat-jwt-es512.yml rename to changelog/3.7.0/kong/feat-jwt-es512.yml diff --git a/changelog/unreleased/kong/feat-wasm-general-shm-kv.yml b/changelog/3.7.0/kong/feat-wasm-general-shm-kv.yml similarity index 100% rename from changelog/unreleased/kong/feat-wasm-general-shm-kv.yml rename to changelog/3.7.0/kong/feat-wasm-general-shm-kv.yml diff --git a/changelog/unreleased/kong/fix-acme-renewal-bug.yml b/changelog/3.7.0/kong/fix-acme-renewal-bug.yml similarity index 100% rename from changelog/unreleased/kong/fix-acme-renewal-bug.yml rename to changelog/3.7.0/kong/fix-acme-renewal-bug.yml diff --git a/changelog/unreleased/kong/fix-aws-lambda-kong-latency.yml b/changelog/3.7.0/kong/fix-aws-lambda-kong-latency.yml similarity index 100% rename from changelog/unreleased/kong/fix-aws-lambda-kong-latency.yml rename to changelog/3.7.0/kong/fix-aws-lambda-kong-latency.yml diff --git a/changelog/unreleased/kong/fix-cjson-t-end.yml b/changelog/3.7.0/kong/fix-cjson-t-end.yml similarity index 100% rename from changelog/unreleased/kong/fix-cjson-t-end.yml rename to changelog/3.7.0/kong/fix-cjson-t-end.yml diff --git a/changelog/unreleased/kong/fix-cli-db-timeout-overrides.yml b/changelog/3.7.0/kong/fix-cli-db-timeout-overrides.yml similarity index 100% rename from changelog/unreleased/kong/fix-cli-db-timeout-overrides.yml rename to changelog/3.7.0/kong/fix-cli-db-timeout-overrides.yml diff --git a/changelog/unreleased/kong/fix-ctx-host-port.yml b/changelog/3.7.0/kong/fix-ctx-host-port.yml similarity index 100% rename from changelog/unreleased/kong/fix-ctx-host-port.yml rename to changelog/3.7.0/kong/fix-ctx-host-port.yml diff --git a/changelog/unreleased/kong/fix-dbless-duplicate-target-error.yml b/changelog/3.7.0/kong/fix-dbless-duplicate-target-error.yml similarity index 100% rename from changelog/unreleased/kong/fix-dbless-duplicate-target-error.yml rename to changelog/3.7.0/kong/fix-dbless-duplicate-target-error.yml diff --git a/changelog/unreleased/kong/fix-default-value-of-upstream-keepalive-max-requests.yml b/changelog/3.7.0/kong/fix-default-value-of-upstream-keepalive-max-requests.yml similarity index 100% rename from changelog/unreleased/kong/fix-default-value-of-upstream-keepalive-max-requests.yml rename to changelog/3.7.0/kong/fix-default-value-of-upstream-keepalive-max-requests.yml diff --git a/changelog/unreleased/kong/fix-dns-resolv-timeout-zero.yml b/changelog/3.7.0/kong/fix-dns-resolv-timeout-zero.yml similarity index 100% rename from changelog/unreleased/kong/fix-dns-resolv-timeout-zero.yml rename to changelog/3.7.0/kong/fix-dns-resolv-timeout-zero.yml diff --git a/changelog/unreleased/kong/fix-external-plugin-instance.yml b/changelog/3.7.0/kong/fix-external-plugin-instance.yml similarity index 100% rename from changelog/unreleased/kong/fix-external-plugin-instance.yml rename to changelog/3.7.0/kong/fix-external-plugin-instance.yml diff --git a/changelog/unreleased/kong/fix-file-permission-of-logrotate.yml b/changelog/3.7.0/kong/fix-file-permission-of-logrotate.yml similarity index 100% rename from changelog/unreleased/kong/fix-file-permission-of-logrotate.yml rename to changelog/3.7.0/kong/fix-file-permission-of-logrotate.yml diff --git a/changelog/unreleased/kong/fix-hybrid-dp-certificate-with-vault-not-refresh.yml b/changelog/3.7.0/kong/fix-hybrid-dp-certificate-with-vault-not-refresh.yml similarity index 100% rename from changelog/unreleased/kong/fix-hybrid-dp-certificate-with-vault-not-refresh.yml rename to changelog/3.7.0/kong/fix-hybrid-dp-certificate-with-vault-not-refresh.yml diff --git a/changelog/unreleased/kong/fix-jwt-plugin-check.yml b/changelog/3.7.0/kong/fix-jwt-plugin-check.yml similarity index 100% rename from changelog/unreleased/kong/fix-jwt-plugin-check.yml rename to changelog/3.7.0/kong/fix-jwt-plugin-check.yml diff --git a/changelog/unreleased/kong/fix-migrations-for-redis-plugins-acme.yml b/changelog/3.7.0/kong/fix-migrations-for-redis-plugins-acme.yml similarity index 100% rename from changelog/unreleased/kong/fix-migrations-for-redis-plugins-acme.yml rename to changelog/3.7.0/kong/fix-migrations-for-redis-plugins-acme.yml diff --git a/changelog/unreleased/kong/fix-migrations-for-redis-plugins-response-rl.yml b/changelog/3.7.0/kong/fix-migrations-for-redis-plugins-response-rl.yml similarity index 100% rename from changelog/unreleased/kong/fix-migrations-for-redis-plugins-response-rl.yml rename to changelog/3.7.0/kong/fix-migrations-for-redis-plugins-response-rl.yml diff --git a/changelog/unreleased/kong/fix-migrations-for-redis-plugins-rl.yml b/changelog/3.7.0/kong/fix-migrations-for-redis-plugins-rl.yml similarity index 100% rename from changelog/unreleased/kong/fix-migrations-for-redis-plugins-rl.yml rename to changelog/3.7.0/kong/fix-migrations-for-redis-plugins-rl.yml diff --git a/changelog/unreleased/kong/fix-missing-router-section-of-request-debugging.yml b/changelog/3.7.0/kong/fix-missing-router-section-of-request-debugging.yml similarity index 100% rename from changelog/unreleased/kong/fix-missing-router-section-of-request-debugging.yml rename to changelog/3.7.0/kong/fix-missing-router-section-of-request-debugging.yml diff --git a/changelog/unreleased/kong/fix-mlcache-renew-lock-leaks.yml b/changelog/3.7.0/kong/fix-mlcache-renew-lock-leaks.yml similarity index 100% rename from changelog/unreleased/kong/fix-mlcache-renew-lock-leaks.yml rename to changelog/3.7.0/kong/fix-mlcache-renew-lock-leaks.yml diff --git a/changelog/unreleased/kong/fix-router-rebuing-flag.yml b/changelog/3.7.0/kong/fix-router-rebuing-flag.yml similarity index 100% rename from changelog/unreleased/kong/fix-router-rebuing-flag.yml rename to changelog/3.7.0/kong/fix-router-rebuing-flag.yml diff --git a/changelog/unreleased/kong/fix-snis-tls-passthrough-in-trad-compat.yml b/changelog/3.7.0/kong/fix-snis-tls-passthrough-in-trad-compat.yml similarity index 100% rename from changelog/unreleased/kong/fix-snis-tls-passthrough-in-trad-compat.yml rename to changelog/3.7.0/kong/fix-snis-tls-passthrough-in-trad-compat.yml diff --git a/changelog/unreleased/kong/fix-upstream-status-unset.yml b/changelog/3.7.0/kong/fix-upstream-status-unset.yml similarity index 100% rename from changelog/unreleased/kong/fix-upstream-status-unset.yml rename to changelog/3.7.0/kong/fix-upstream-status-unset.yml diff --git a/changelog/unreleased/kong/fix-vault-init-worker.yml b/changelog/3.7.0/kong/fix-vault-init-worker.yml similarity index 100% rename from changelog/unreleased/kong/fix-vault-init-worker.yml rename to changelog/3.7.0/kong/fix-vault-init-worker.yml diff --git a/changelog/unreleased/kong/fix-vault-secret-update-without-ttl.yml b/changelog/3.7.0/kong/fix-vault-secret-update-without-ttl.yml similarity index 100% rename from changelog/unreleased/kong/fix-vault-secret-update-without-ttl.yml rename to changelog/3.7.0/kong/fix-vault-secret-update-without-ttl.yml diff --git a/changelog/unreleased/kong/fix-vault-workspaces.yml b/changelog/3.7.0/kong/fix-vault-workspaces.yml similarity index 100% rename from changelog/unreleased/kong/fix-vault-workspaces.yml rename to changelog/3.7.0/kong/fix-vault-workspaces.yml diff --git a/changelog/unreleased/kong/fix-wasm-disable-pwm-lua-resolver.yml b/changelog/3.7.0/kong/fix-wasm-disable-pwm-lua-resolver.yml similarity index 100% rename from changelog/unreleased/kong/fix-wasm-disable-pwm-lua-resolver.yml rename to changelog/3.7.0/kong/fix-wasm-disable-pwm-lua-resolver.yml diff --git a/changelog/unreleased/kong/fix_api_405_vaults_validate_endpoint.yml b/changelog/3.7.0/kong/fix_api_405_vaults_validate_endpoint.yml similarity index 100% rename from changelog/unreleased/kong/fix_api_405_vaults_validate_endpoint.yml rename to changelog/3.7.0/kong/fix_api_405_vaults_validate_endpoint.yml diff --git a/changelog/unreleased/kong/fix_balancer_healthecker_unexpected_panic.yml b/changelog/3.7.0/kong/fix_balancer_healthecker_unexpected_panic.yml similarity index 100% rename from changelog/unreleased/kong/fix_balancer_healthecker_unexpected_panic.yml rename to changelog/3.7.0/kong/fix_balancer_healthecker_unexpected_panic.yml diff --git a/changelog/unreleased/kong/fix_privileged_agent_id_1.yml b/changelog/3.7.0/kong/fix_privileged_agent_id_1.yml similarity index 100% rename from changelog/unreleased/kong/fix_privileged_agent_id_1.yml rename to changelog/3.7.0/kong/fix_privileged_agent_id_1.yml diff --git a/changelog/unreleased/kong/flavor-expressions-supports-traditional-fields.yml b/changelog/3.7.0/kong/flavor-expressions-supports-traditional-fields.yml similarity index 100% rename from changelog/unreleased/kong/flavor-expressions-supports-traditional-fields.yml rename to changelog/3.7.0/kong/flavor-expressions-supports-traditional-fields.yml diff --git a/changelog/unreleased/kong/key_auth_www_authenticate.yml b/changelog/3.7.0/kong/key_auth_www_authenticate.yml similarity index 100% rename from changelog/unreleased/kong/key_auth_www_authenticate.yml rename to changelog/3.7.0/kong/key_auth_www_authenticate.yml diff --git a/changelog/unreleased/kong/log-serializer-kong-latency.yml b/changelog/3.7.0/kong/log-serializer-kong-latency.yml similarity index 100% rename from changelog/unreleased/kong/log-serializer-kong-latency.yml rename to changelog/3.7.0/kong/log-serializer-kong-latency.yml diff --git a/changelog/unreleased/kong/log-serializer-receive-latency.yml b/changelog/3.7.0/kong/log-serializer-receive-latency.yml similarity index 100% rename from changelog/unreleased/kong/log-serializer-receive-latency.yml rename to changelog/3.7.0/kong/log-serializer-receive-latency.yml diff --git a/changelog/unreleased/kong/otel-increase-queue-max-batch-size.yml b/changelog/3.7.0/kong/otel-increase-queue-max-batch-size.yml similarity index 100% rename from changelog/unreleased/kong/otel-increase-queue-max-batch-size.yml rename to changelog/3.7.0/kong/otel-increase-queue-max-batch-size.yml diff --git a/changelog/unreleased/kong/otel-sampling-panic-when-header-trace-id-enable.yml b/changelog/3.7.0/kong/otel-sampling-panic-when-header-trace-id-enable.yml similarity index 100% rename from changelog/unreleased/kong/otel-sampling-panic-when-header-trace-id-enable.yml rename to changelog/3.7.0/kong/otel-sampling-panic-when-header-trace-id-enable.yml diff --git a/changelog/unreleased/kong/plugin-schema-deprecation-record.yml b/changelog/3.7.0/kong/plugin-schema-deprecation-record.yml similarity index 100% rename from changelog/unreleased/kong/plugin-schema-deprecation-record.yml rename to changelog/3.7.0/kong/plugin-schema-deprecation-record.yml diff --git a/changelog/unreleased/kong/plugin_server_restart.yml b/changelog/3.7.0/kong/plugin_server_restart.yml similarity index 100% rename from changelog/unreleased/kong/plugin_server_restart.yml rename to changelog/3.7.0/kong/plugin_server_restart.yml diff --git a/changelog/unreleased/kong/pluginsocket-proto-wrong-type.yml b/changelog/3.7.0/kong/pluginsocket-proto-wrong-type.yml similarity index 100% rename from changelog/unreleased/kong/pluginsocket-proto-wrong-type.yml rename to changelog/3.7.0/kong/pluginsocket-proto-wrong-type.yml diff --git a/changelog/unreleased/kong/propagation-module-rework.yml b/changelog/3.7.0/kong/propagation-module-rework.yml similarity index 100% rename from changelog/unreleased/kong/propagation-module-rework.yml rename to changelog/3.7.0/kong/propagation-module-rework.yml diff --git a/changelog/unreleased/kong/revert-req-body-limitation-patch.yml b/changelog/3.7.0/kong/revert-req-body-limitation-patch.yml similarity index 100% rename from changelog/unreleased/kong/revert-req-body-limitation-patch.yml rename to changelog/3.7.0/kong/revert-req-body-limitation-patch.yml diff --git a/changelog/unreleased/kong/separate_kong_cache_invalidation_cluster_event_channel.yml b/changelog/3.7.0/kong/separate_kong_cache_invalidation_cluster_event_channel.yml similarity index 100% rename from changelog/unreleased/kong/separate_kong_cache_invalidation_cluster_event_channel.yml rename to changelog/3.7.0/kong/separate_kong_cache_invalidation_cluster_event_channel.yml diff --git a/changelog/unreleased/kong/set_grpc_tls_seclevel.yml b/changelog/3.7.0/kong/set_grpc_tls_seclevel.yml similarity index 100% rename from changelog/unreleased/kong/set_grpc_tls_seclevel.yml rename to changelog/3.7.0/kong/set_grpc_tls_seclevel.yml diff --git a/changelog/unreleased/kong/speed_up_internal_hooking_mechanism.yml b/changelog/3.7.0/kong/speed_up_internal_hooking_mechanism.yml similarity index 100% rename from changelog/unreleased/kong/speed_up_internal_hooking_mechanism.yml rename to changelog/3.7.0/kong/speed_up_internal_hooking_mechanism.yml diff --git a/changelog/unreleased/kong/speed_up_router.yml b/changelog/3.7.0/kong/speed_up_router.yml similarity index 100% rename from changelog/unreleased/kong/speed_up_router.yml rename to changelog/3.7.0/kong/speed_up_router.yml diff --git a/changelog/unreleased/kong/tracing-pdk-short-trace-ids.yml b/changelog/3.7.0/kong/tracing-pdk-short-trace-ids.yml similarity index 100% rename from changelog/unreleased/kong/tracing-pdk-short-trace-ids.yml rename to changelog/3.7.0/kong/tracing-pdk-short-trace-ids.yml diff --git a/changelog/unreleased/kong/update-ai-proxy-telemetry.yml b/changelog/3.7.0/kong/update-ai-proxy-telemetry.yml similarity index 100% rename from changelog/unreleased/kong/update-ai-proxy-telemetry.yml rename to changelog/3.7.0/kong/update-ai-proxy-telemetry.yml diff --git a/changelog/unreleased/kong/wasm-bundled-filters.yml b/changelog/3.7.0/kong/wasm-bundled-filters.yml similarity index 100% rename from changelog/unreleased/kong/wasm-bundled-filters.yml rename to changelog/3.7.0/kong/wasm-bundled-filters.yml diff --git a/changelog/unreleased/kong/force-no_sync-noip.yml b/changelog/unreleased/kong/force-no_sync-noip.yml deleted file mode 100644 index a4e9bac413cf..000000000000 --- a/changelog/unreleased/kong/force-no_sync-noip.yml +++ /dev/null @@ -1,5 +0,0 @@ -message: | - Added a new function to bypass the Kong's DNS client synchronization option - when resolving hostnames. -type: feature -scope: Core From 4052fbbfee77be52bcd5c876719a84bcbc8ba337 Mon Sep 17 00:00:00 2001 From: Samuele Date: Mon, 3 Jun 2024 16:04:42 +0200 Subject: [PATCH 02/34] feat(response-transformer): added support for json_body rename (#13131) * feat(response-transformer): added support for json_body rename detail here: https://github.com/Kong/kong/discussions/12824. * test(response-transformer): fix test * fix(response-transformer): compatibility with older data planes * fix(response-transformer): version 3.7.0 to 3.8.0 * fix tests --------- Co-authored-by: zhongweikang Co-authored-by: zhongweikang --- .../feat-response-transformer-json-rename.yml | 4 + kong/clustering/compat/removed_fields.lua | 9 ++- .../response-transformer/body_transformer.lua | 9 +++ .../header_transformer.lua | 1 + kong/plugins/response-transformer/schema.lua | 1 + .../01-db/01-schema/07-plugins_spec.lua | 1 + .../09-hybrid_mode/09-config-compat_spec.lua | 42 +++++++++++ .../01-header_transformer_spec.lua | 17 +++-- .../02-body_transformer_spec.lua | 74 ++++++++++++++++++- .../15-response-transformer/03-api_spec.lua | 5 ++ 10 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 changelog/unreleased/kong/feat-response-transformer-json-rename.yml diff --git a/changelog/unreleased/kong/feat-response-transformer-json-rename.yml b/changelog/unreleased/kong/feat-response-transformer-json-rename.yml new file mode 100644 index 000000000000..42d23ded398f --- /dev/null +++ b/changelog/unreleased/kong/feat-response-transformer-json-rename.yml @@ -0,0 +1,4 @@ +message: | + Added support for json_body rename in response-transformer plugin +type: feature +scope: Plugin diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 213c0e0e71e2..77368d2d18f2 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -140,6 +140,13 @@ return { }, key_auth = { "realm" - } + }, + }, + + -- Any dataplane older than 3.8.0 + [3008000000] = { + response_transformer = { + "rename.json", + }, }, } diff --git a/kong/plugins/response-transformer/body_transformer.lua b/kong/plugins/response-transformer/body_transformer.lua index 03d10eaa310c..b9f229e8fff7 100644 --- a/kong/plugins/response-transformer/body_transformer.lua +++ b/kong/plugins/response-transformer/body_transformer.lua @@ -123,6 +123,15 @@ function _M.transform_json_body(conf, buffered_data) json_body[name] = nil end + -- rename key to body + for _, old_name, new_name in iter(conf.rename.json) do + if json_body[old_name] ~= nil and new_name then + local value = json_body[old_name] + json_body[new_name] = value + json_body[old_name] = nil + end + end + -- replace key:value to body local replace_json_types = conf.replace.json_types for i, name, value in iter(conf.replace.json) do diff --git a/kong/plugins/response-transformer/header_transformer.lua b/kong/plugins/response-transformer/header_transformer.lua index b548dfd98a95..4ed1863ba6cd 100644 --- a/kong/plugins/response-transformer/header_transformer.lua +++ b/kong/plugins/response-transformer/header_transformer.lua @@ -64,6 +64,7 @@ end local function is_body_transform_set(conf) return not isempty(conf.add.json ) or + not isempty(conf.rename.json ) or not isempty(conf.remove.json ) or not isempty(conf.replace.json) or not isempty(conf.append.json ) diff --git a/kong/plugins/response-transformer/schema.lua b/kong/plugins/response-transformer/schema.lua index fecbf62b5d56..a119e18bf294 100644 --- a/kong/plugins/response-transformer/schema.lua +++ b/kong/plugins/response-transformer/schema.lua @@ -73,6 +73,7 @@ local colon_headers_array = { local colon_rename_strings_array_record = { type = "record", fields = { + { json = colon_string_array }, { headers = colon_headers_array } }, } diff --git a/spec/01-unit/01-db/01-schema/07-plugins_spec.lua b/spec/01-unit/01-db/01-schema/07-plugins_spec.lua index 4de064d3861d..f9a38d3eaa72 100644 --- a/spec/01-unit/01-db/01-schema/07-plugins_spec.lua +++ b/spec/01-unit/01-db/01-schema/07-plugins_spec.lua @@ -145,6 +145,7 @@ describe("plugins", function() }, rename = { headers = {}, + json = {} }, replace = { headers = {}, diff --git a/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua b/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua index 606f383fe552..18465456a086 100644 --- a/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua +++ b/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua @@ -660,6 +660,48 @@ describe("CP/DP config compat transformations #" .. strategy, function() admin.plugins:remove({ id = key_auth.id }) end) end) + + describe("compatibility test for response-transformer plugin", function() + it("removes `config.rename.json` before sending them to older(less than 3.8.0.0) DP nodes", function() + local rt = admin.plugins:insert { + name = "response-transformer", + enabled = true, + config = { + rename = { + -- [[ new fields 3.8.0 + json = {"old:new"} + -- ]] + } + } + } + + assert.not_nil(rt.config.rename.json) + local expected_rt = cycle_aware_deep_copy(rt) + expected_rt.config.rename.json = nil + do_assert(uuid(), "3.7.0", expected_rt) + + -- cleanup + admin.plugins:remove({ id = rt.id }) + end) + + it("does not remove `config.rename.json` from DP nodes that are already compatible", function() + local rt = admin.plugins:insert { + name = "response-transformer", + enabled = true, + config = { + rename = { + -- [[ new fields 3.8.0 + json = {"old:new"} + -- ]] + } + } + } + do_assert(uuid(), "3.8.0", rt) + + -- cleanup + admin.plugins:remove({ id = rt.id }) + end) + end) end) end) diff --git a/spec/03-plugins/15-response-transformer/01-header_transformer_spec.lua b/spec/03-plugins/15-response-transformer/01-header_transformer_spec.lua index 4aa1595f5f75..7b384fbdb3d5 100644 --- a/spec/03-plugins/15-response-transformer/01-header_transformer_spec.lua +++ b/spec/03-plugins/15-response-transformer/01-header_transformer_spec.lua @@ -108,10 +108,11 @@ describe("Plugin: response-transformer", function() describe("rename", function() local conf = { remove = { - json = {}, + json = {}, headers = {} }, - rename = { + rename = { + json = {}, headers = {"h1:h2", "h3:h4"} }, replace = { @@ -295,7 +296,8 @@ describe("Plugin: response-transformer", function() json = {"p1"}, headers = {"h1", "h2"} }, - rename = { + rename = { + json = {}, headers = {} }, replace = { @@ -339,7 +341,8 @@ describe("Plugin: response-transformer", function() json = {}, headers = {} }, - rename = { + rename = { + json = {}, headers = {} }, replace = { @@ -383,7 +386,8 @@ describe("Plugin: response-transformer", function() json = {}, headers = {} }, - rename = { + rename = { + json = {}, headers = {} }, replace = { @@ -427,7 +431,8 @@ describe("Plugin: response-transformer", function() json = {}, headers = {} }, - rename = { + rename = { + json = {}, headers = {} }, replace = { diff --git a/spec/03-plugins/15-response-transformer/02-body_transformer_spec.lua b/spec/03-plugins/15-response-transformer/02-body_transformer_spec.lua index a39c2eaaa618..8795b8b8afa4 100644 --- a/spec/03-plugins/15-response-transformer/02-body_transformer_spec.lua +++ b/spec/03-plugins/15-response-transformer/02-body_transformer_spec.lua @@ -9,6 +9,9 @@ describe("Plugin: response-transformer", function() remove = { json = {}, }, + rename = { + json = {} + }, replace = { json = {} }, @@ -58,6 +61,9 @@ describe("Plugin: response-transformer", function() remove = { json = {} }, + rename = { + json = {} + }, replace = { json = {} }, @@ -113,6 +119,9 @@ describe("Plugin: response-transformer", function() remove = { json = {"p1", "p2"} }, + rename = { + json = {} + }, replace = { json = {} }, @@ -137,11 +146,59 @@ describe("Plugin: response-transformer", function() end) end) + describe("rename", function() + local conf = { + remove = { + json = {} + }, + rename = { + json = {"p1:k1", "p2:k2", "p3:k3", "p4:k4", "p5:k5"}, + }, + replace = { + json = {} + }, + add = { + json = {} + }, + append = { + json = {} + } + } + it("parameter", function() + local json = [[{"p1" : "v1", "p2" : "v2"}]] + local body = body_transformer.transform_json_body(conf, json) + local body_json = cjson.decode(body) + assert.same({k1 = "v1", k2 = "v2"}, body_json) + end) + it("preserves empty arrays", function() + local json = [[{"p1" : "v1", "p2" : "v2", "p3": []}]] + local body = body_transformer.transform_json_body(conf, json) + local body_json = cjson.decode(body) + assert.same({k1 = "v1", k2 = "v2", k3 = {}}, body_json) + assert.equals('[]', cjson.encode(body_json.k3)) + end) + it("number", function() + local json = [[{"p3" : -1}]] + local body = body_transformer.transform_json_body(conf, json) + local body_json = cjson.decode(body) + assert.same({k3 = -1}, body_json) + end) + it("boolean", function() + local json = [[{"p4" : false, "p5" : true}]] + local body = body_transformer.transform_json_body(conf, json) + local body_json = cjson.decode(body) + assert.same({k4 = false, k5 = true}, body_json) + end) + end) + describe("replace", function() local conf = { remove = { json = {} }, + rename = { + json = {} + }, replace = { json = {"p1:v2", "p2:\"v2\"", "p3:-1", "p4:false", "p5:true"}, json_types = {"string", "string", "number", "boolean", "boolean"} @@ -192,11 +249,14 @@ describe("Plugin: response-transformer", function() end) end) - describe("remove, replace, add, append", function() + describe("remove, rename, replace, add, append", function() local conf = { remove = { json = {"p1"} }, + rename = { + json = {"p4:p2"} + }, replace = { json = {"p2:v2"} }, @@ -208,13 +268,13 @@ describe("Plugin: response-transformer", function() }, } it("combination", function() - local json = [[{"p1" : "v1", "p2" : "v1"}]] + local json = [[{"p1" : "v1", "p4" : "v1"}]] local body = body_transformer.transform_json_body(conf, json) local body_json = cjson.decode(body) assert.same({p2 = "v2", p3 = {"v1", "v2"}}, body_json) end) it("preserves empty array", function() - local json = [[{"p1" : "v1", "p2" : "v1", "a" : []}]] + local json = [[{"p1" : "v1", "p4" : "v1", "a" : []}]] local body = body_transformer.transform_json_body(conf, json) local body_json = cjson.decode(body) assert.same({p2 = "v2", p3 = {"v1", "v2"}, a = {}}, body_json) @@ -259,6 +319,10 @@ describe("Plugin: response-transformer", function() headers = {"h1", "h2", "h3"}, json = {} }, + rename = { + headers = {}, + json = {}, + }, add = { headers = {}, json = {}, @@ -349,6 +413,10 @@ describe("Plugin: response-transformer", function() headers = {}, json = { "foo" } }, + rename = { + headers = {}, + json = {}, + }, add = { headers = {}, json = {}, diff --git a/spec/03-plugins/15-response-transformer/03-api_spec.lua b/spec/03-plugins/15-response-transformer/03-api_spec.lua index 05038995b46e..36e0a29aa9c3 100644 --- a/spec/03-plugins/15-response-transformer/03-api_spec.lua +++ b/spec/03-plugins/15-response-transformer/03-api_spec.lua @@ -56,6 +56,7 @@ for _, strategy in helpers.each_strategy() do end) it("rename succeeds with colons", function() local rename_header = "x-request-id:x-custom-request-id" + local rename_json = "old_key:new_key" local res = assert(admin_client:send { method = "POST", path = "/plugins", @@ -64,6 +65,7 @@ for _, strategy in helpers.each_strategy() do config = { rename = { headers = { rename_header }, + json = { rename_json }, }, }, }, @@ -74,6 +76,7 @@ for _, strategy in helpers.each_strategy() do assert.response(res).has.status(201) local body = assert.response(res).has.jsonbody() assert.equals(rename_header, body.config.rename.headers[1]) + assert.equals(rename_json, body.config.rename.json[1]) admin_client:send { method = "DELETE", @@ -201,6 +204,7 @@ for _, strategy in helpers.each_strategy() do }, rename = { headers = cjson.null, + json = cjson.null, }, replace = { headers = cjson.null, @@ -232,6 +236,7 @@ for _, strategy in helpers.each_strategy() do }, rename = { headers = "required field missing", + json = "required field missing", }, replace = { headers = "required field missing", From c1d082a3ce32c62854cec99043234917c268be1e Mon Sep 17 00:00:00 2001 From: Harry Tran Date: Mon, 3 Jun 2024 13:23:09 -0400 Subject: [PATCH 03/34] ci: label `schema-change-noteworthy` for AI plugins (#13133) --- .github/labeler.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 4d80a6fc92f2..f0a57e8ae9c1 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -260,7 +260,10 @@ plugins/standard-webhooks: schema-change-noteworthy: - changed-files: - - any-glob-to-any-file: ['kong/db/schema/**/*.lua', 'kong/**/schema.lua', 'kong/plugins/**/daos.lua', 'plugins-ee/**/daos.lua', 'plugins-ee/**/schema.lua', 'kong/db/dao/*.lua', 'kong/enterprise_edition/redis/init.lua'] + - any-glob-to-any-file: [ + 'kong/db/schema/**/*.lua', 'kong/**/schema.lua', 'kong/plugins/**/daos.lua', 'plugins-ee/**/daos.lua', 'plugins-ee/**/schema.lua', 'kong/db/dao/*.lua', 'kong/enterprise_edition/redis/init.lua', + 'kong/llm/init.lua', + ] build/bazel: - changed-files: From 639171196294066157fe6e3e00e39a53b91519fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20H=C3=BCbner?= Date: Mon, 3 Jun 2024 19:34:43 +0200 Subject: [PATCH 04/34] fix(doc): fix typo (#13039) From 6c474d537cb928d3b816d8a7dd4bf93331ba57b2 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Mon, 3 Jun 2024 19:50:44 +0200 Subject: [PATCH 05/34] chore(docs): document KONG_TEST_USER_CARGO_DISABLED build setting (#12849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(docs): add undocumented setting * Update DEVELOPER.md --------- Co-authored-by: Hans Hübner --- DEVELOPER.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DEVELOPER.md b/DEVELOPER.md index b847a0f00739..7a1af2872e6e 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -202,6 +202,8 @@ Install the development dependencies ([busted](https://lunarmodules.github.io/bu make dev ``` +If Rust/Cargo doesn't work, try setting `export KONG_TEST_USER_CARGO_DISABLED=1` first. + Kong relies on three test suites using the [busted](https://lunarmodules.github.io/busted/) testing library: * Unit tests From 3066f5032ea2bb30afa39a2ee91c93a0a85d0ce2 Mon Sep 17 00:00:00 2001 From: Jiayi Ding Date: Tue, 4 Jun 2024 10:50:08 +0800 Subject: [PATCH 06/34] fix(http-log): add port information to the host header (#13116) Fix https://github.com/Kong/kong/issues/13067 --- .../kong/fix-http-log-host-header.yml | 4 +++ kong/plugins/http-log/handler.lua | 1 - spec/03-plugins/03-http-log/01-log_spec.lua | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/kong/fix-http-log-host-header.yml diff --git a/changelog/unreleased/kong/fix-http-log-host-header.yml b/changelog/unreleased/kong/fix-http-log-host-header.yml new file mode 100644 index 000000000000..76e0cf986e34 --- /dev/null +++ b/changelog/unreleased/kong/fix-http-log-host-header.yml @@ -0,0 +1,4 @@ +message: "**HTTP-Log**: Fix an issue where the plugin doesn't include port information in the HTTP host header when sending requests to the log server." +type: bugfix +scope: Plugin + diff --git a/kong/plugins/http-log/handler.lua b/kong/plugins/http-log/handler.lua index 8bd382f926c0..fd1d0cd48eeb 100644 --- a/kong/plugins/http-log/handler.lua +++ b/kong/plugins/http-log/handler.lua @@ -114,7 +114,6 @@ local function send_entries(conf, entries) httpc:set_timeout(timeout) local headers = { - ["Host"] = host, ["Content-Type"] = content_type, ["Content-Length"] = content_length, ["Authorization"] = userinfo and "Basic " .. encode_base64(userinfo) or nil diff --git a/spec/03-plugins/03-http-log/01-log_spec.lua b/spec/03-plugins/03-http-log/01-log_spec.lua index 55591eb85dde..fb96cb03d38e 100644 --- a/spec/03-plugins/03-http-log/01-log_spec.lua +++ b/spec/03-plugins/03-http-log/01-log_spec.lua @@ -209,6 +209,22 @@ for _, strategy in helpers.each_strategy() do } } + local route5 = bp.routes:insert { + hosts = { "http_host_header.test" }, + service = service1 + } + + bp.plugins:insert { + route = { id = route5.id }, + name = "http-log", + config = { + http_endpoint = "http://" .. helpers.mock_upstream_host + .. ":" + .. helpers.mock_upstream_port + .. "/post_log/http_host_header" + } + } + local route6 = bp.routes:insert { hosts = { "https_logging_faulty.test" }, service = service2 @@ -535,6 +551,24 @@ for _, strategy in helpers.each_strategy() do assert.same(vault_env_value, entries[1].log_req_headers.key2) end) + it("http client implicitly adds Host header", function() + reset_log("http_host_header") + local res = proxy_client:get("/status/200", { + headers = { + ["Host"] = "http_host_header.test" + } + }) + assert.res_status(200, res) + + local entries = get_log("http_host_header", 1) + local host_header + if helpers.mock_upstream_port == 80 then + host_header = helpers.mock_upstream_host + else + host_header = helpers.mock_upstream_host .. ":" .. helpers.mock_upstream_port + end + assert.same(entries[1].log_req_headers['host'] or "", host_header) + end) it("puts changed configuration into effect immediately", function() local admin_client = assert(helpers.admin_client()) From f46a9572b359a0d02fed1defe997d400436ba822 Mon Sep 17 00:00:00 2001 From: hulk Date: Tue, 4 Jun 2024 11:37:50 +0800 Subject: [PATCH 07/34] fix(prometheus): improve logging when having the inconsistent labels count (#13020) Currently, the Prometheus plugin will log the following error if we have encountered an inconsistent label count while debugging: ``` [error]... inconsistent labels count, expected 6, got 5 ``` It's hard to identify which metric is going wrong, and it will be helpful if we can bring the metric name as well: ``` [error]... metric 'bandwidth_bytes' has the inconsistent labels count, expected 6, got 5 ``` Co-authored-by: Qi <44437200+ADD-SP@users.noreply.github.com> --- .../unreleased/kong/improve-prometheus-error-logging.yml | 4 ++++ kong/plugins/prometheus/prometheus.lua | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/kong/improve-prometheus-error-logging.yml diff --git a/changelog/unreleased/kong/improve-prometheus-error-logging.yml b/changelog/unreleased/kong/improve-prometheus-error-logging.yml new file mode 100644 index 000000000000..eec0b28d5630 --- /dev/null +++ b/changelog/unreleased/kong/improve-prometheus-error-logging.yml @@ -0,0 +1,4 @@ +message: | + **Prometheus**: Improved error logging when having inconsistent labels count. +type: bugfix +scope: Plugin diff --git a/kong/plugins/prometheus/prometheus.lua b/kong/plugins/prometheus/prometheus.lua index 796a76c8813e..d7c0ddfe4db3 100644 --- a/kong/plugins/prometheus/prometheus.lua +++ b/kong/plugins/prometheus/prometheus.lua @@ -377,8 +377,8 @@ local function lookup_or_create(self, label_values) local cnt = label_values and #label_values or 0 -- specially, if first element is nil, # will treat it as "non-empty" if cnt ~= self.label_count or (self.label_count > 0 and label_values[1] == nil) then - return nil, string.format("inconsistent labels count, expected %d, got %d", - self.label_count, cnt) + return nil, string.format("metric '%s' has inconsistent labels count, expected %d, got %d", + self.name, self.label_count, cnt) end local t = self.lookup if label_values then From dfbe0c9867a22a614a90b886b25b2942145f289f Mon Sep 17 00:00:00 2001 From: Robin Xiang Date: Tue, 4 Jun 2024 15:36:33 +0800 Subject: [PATCH 08/34] chore(changelog): move changelog files to correct location (#13152) --- changelog/unreleased/{ => kong}/host_header.yml | 0 changelog/unreleased/{ => kong}/migration_of_ai_proxy_plugin.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename changelog/unreleased/{ => kong}/host_header.yml (100%) rename changelog/unreleased/{ => kong}/migration_of_ai_proxy_plugin.yml (100%) diff --git a/changelog/unreleased/host_header.yml b/changelog/unreleased/kong/host_header.yml similarity index 100% rename from changelog/unreleased/host_header.yml rename to changelog/unreleased/kong/host_header.yml diff --git a/changelog/unreleased/migration_of_ai_proxy_plugin.yml b/changelog/unreleased/kong/migration_of_ai_proxy_plugin.yml similarity index 100% rename from changelog/unreleased/migration_of_ai_proxy_plugin.yml rename to changelog/unreleased/kong/migration_of_ai_proxy_plugin.yml From d44223e7ea4447c1dde4007c7f266705f5797b30 Mon Sep 17 00:00:00 2001 From: jeremyjpj0916 <31913027+jeremyjpj0916@users.noreply.github.com> Date: Tue, 4 Jun 2024 03:46:01 -0400 Subject: [PATCH 09/34] style(template) fix comment typo (#13154) ngx.location.capture was what Kong meant to put here. --- kong/templates/nginx_kong.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong/templates/nginx_kong.lua b/kong/templates/nginx_kong.lua index 5053c26764ce..db83ba957827 100644 --- a/kong/templates/nginx_kong.lua +++ b/kong/templates/nginx_kong.lua @@ -337,7 +337,7 @@ server { set $kong_proxy_mode 'http'; rewrite_by_lua_block { - -- ngx.localtion.capture will create a new nginx request, + -- ngx.location.capture will create a new nginx request, -- so the upstream ssl-related info attached to the `r` gets lost. -- we need to re-set them here to the new nginx request. local ctx = ngx.ctx From 16fe25eba68b4ddfc83b74fec5c9cbcd13b0a34b Mon Sep 17 00:00:00 2001 From: Chrono Date: Tue, 4 Jun 2024 16:16:19 +0800 Subject: [PATCH 10/34] refactor(router/atc): unify expression validation function in schema (#12878) Simplify the code, moving some logic into `transform.lua`. KAG-4138 --- kong/db/schema/entities/routes.lua | 6 +- kong/router/compat.lua | 5 -- kong/router/expressions.lua | 98 ++++++++++++------------------ kong/router/transform.lua | 36 +++++++++++ 4 files changed, 77 insertions(+), 68 deletions(-) diff --git a/kong/db/schema/entities/routes.lua b/kong/db/schema/entities/routes.lua index aa48e92594cc..47063e169faa 100644 --- a/kong/db/schema/entities/routes.lua +++ b/kong/db/schema/entities/routes.lua @@ -50,12 +50,10 @@ if kong_router_flavor == "traditional_compatible" or kong_router_flavor == "expr local router = require("resty.router.router") local transform = require("kong.router.transform") local get_schema = require("kong.router.atc").schema - local get_expression = kong_router_flavor == "traditional_compatible" and - require("kong.router.compat").get_expression or - require("kong.router.expressions").transform_expression local is_null = transform.is_null local is_empty_field = transform.is_empty_field + local amending_expression = transform.amending_expression local HTTP_PATH_SEGMENTS_PREFIX = "http.path.segments." local HTTP_PATH_SEGMENTS_SUFFIX_REG = [[^(0|[1-9]\d*)(_([1-9]\d*))?$]] @@ -102,7 +100,7 @@ if kong_router_flavor == "traditional_compatible" or kong_router_flavor == "expr end local schema = get_schema(entity.protocols) - local exp = get_expression(entity) + local exp = amending_expression(entity) local fields, err = router.validate(schema, exp) if not fields then diff --git a/kong/router/compat.lua b/kong/router/compat.lua index a3f3f21e1d00..d67789151fef 100644 --- a/kong/router/compat.lua +++ b/kong/router/compat.lua @@ -27,13 +27,8 @@ function _M.new(routes_and_services, cache, cache_neg, old_router) end --- for schema validation and unit-testing -_M.get_expression = get_expression - - -- for unit-testing purposes only _M._set_ngx = atc._set_ngx -_M._get_priority = get_priority return _M diff --git a/kong/router/expressions.lua b/kong/router/expressions.lua index 7d11022344e9..ec7f0b00f1d2 100644 --- a/kong/router/expressions.lua +++ b/kong/router/expressions.lua @@ -1,63 +1,63 @@ local _M = {} -local re_gsub = ngx.re.gsub - - local atc = require("kong.router.atc") local transform = require("kong.router.transform") -local get_expression = transform.get_expression local get_priority = transform.get_priority -local gen_for_field = transform.gen_for_field -local OP_EQUAL = transform.OP_EQUAL -local LOGICAL_AND = transform.LOGICAL_AND - - -local NET_PORT_REG = [[(net\.port)(\s*)([=> net.dst.port -local function transform_expression(route) - local exp = get_expression(route) - - if not exp then - return nil - end - - if not exp:find("net.port", 1, true) then - return exp + local function protocol_val_transform(_, p) + return PROTOCOLS_OVERRIDE[p] or p end - -- there is "net.port" in expression - - local new_exp = re_gsub(exp, NET_PORT_REG, NET_PORT_REPLACE, "jo") + get_expression = function(route) + local exp = amending_expression(route) + if not exp then + return nil + end + + local protocols = route.protocols + + -- give the chance for http redirection (301/302/307/308/426) + -- and allow tcp works with tls + if protocols and #protocols == 1 and + (protocols[1] == "https" or + protocols[1] == "tls" or + protocols[1] == "tls_passthrough") + then + return exp + end + + local gen = gen_for_field("net.protocol", OP_EQUAL, protocols, + protocol_val_transform) + if gen then + exp = exp .. LOGICAL_AND .. gen + end - if exp ~= new_exp then - ngx.log(ngx.WARN, "The field 'net.port' of expression is deprecated " .. - "and will be removed in the upcoming major release, " .. - "please use 'net.dst.port' instead.") + return exp end - - return new_exp end -_M.transform_expression = transform_expression local function get_exp_and_priority(route) - local exp = transform_expression(route) + local exp = get_expression(route) if not exp then ngx.log(ngx.ERR, "expecting an expression route while it's not (probably a traditional route). ", "Likely it's a misconfiguration. Please check the 'router_flavor' config in kong.conf") @@ -66,26 +66,6 @@ local function get_exp_and_priority(route) local priority = get_priority(route) - local protocols = route.protocols - - -- give the chance for http redirection (301/302/307/308/426) - -- and allow tcp works with tls - if protocols and #protocols == 1 and - (protocols[1] == "https" or - protocols[1] == "tls" or - protocols[1] == "tls_passthrough") - then - return exp, priority - end - - local gen = gen_for_field("net.protocol", OP_EQUAL, protocols, - function(_, p) - return PROTOCOLS_OVERRIDE[p] or p - end) - if gen then - exp = exp .. LOGICAL_AND .. gen - end - return exp, priority end diff --git a/kong/router/transform.lua b/kong/router/transform.lua index 68e0958cf766..25c1c3d626dc 100644 --- a/kong/router/transform.lua +++ b/kong/router/transform.lua @@ -757,6 +757,40 @@ local function split_routes_and_services_by_path(routes_and_services) end +local amending_expression +do + local re_gsub = ngx.re.gsub + + local NET_PORT_REG = [[(net\.port)(\s*)([=> net.dst.port + amending_expression = function(route) + local exp = get_expression(route) + + if not exp then + return nil + end + + if not exp:find("net.port", 1, true) then + return exp + end + + -- there is "net.port" in expression + + local new_exp = re_gsub(exp, NET_PORT_REG, NET_PORT_REPLACE, "jo") + + if exp ~= new_exp then + ngx.log(ngx.WARN, "The field 'net.port' of expression is deprecated " .. + "and will be removed in the upcoming major release, " .. + "please use 'net.dst.port' instead.") + end + + return new_exp + end +end + + return { OP_EQUAL = OP_EQUAL, @@ -774,4 +808,6 @@ return { get_priority = get_priority, split_routes_and_services_by_path = split_routes_and_services_by_path, + + amending_expression = amending_expression, } From 065a70c4491e89db18de563545d2b4c91c7e767f Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Tue, 4 Jun 2024 11:20:35 +0300 Subject: [PATCH 11/34] chore(deps): bump `bazelisk` from `1.19.0` to `1.20.0` (#13129) ### Summary #### New Features - The Go version now supports BAZELISK_NOJDK #### Bug Fixes & Improvements - It's now easier to use Bazelisk programmatically - Bazelisk will retry more connection errors - A display bug in the download progress bar has been fixed KAG-4615 KAG-4624 Signed-off-by: Aapo Talvensaari --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f2ec119d7278..2e030a4de3ac 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ endif ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) KONG_SOURCE_LOCATION ?= $(ROOT_DIR) GRPCURL_VERSION ?= 1.8.5 -BAZLISK_VERSION ?= 1.19.0 +BAZLISK_VERSION ?= 1.20.0 H2CLIENT_VERSION ?= 0.4.4 BAZEL := $(shell command -v bazel 2> /dev/null) VENV = /dev/null # backward compatibility when no venv is built @@ -209,4 +209,3 @@ install-legacy: @luarocks make OPENSSL_DIR=$(OPENSSL_DIR) CRYPTO_DIR=$(OPENSSL_DIR) YAML_DIR=$(YAML_DIR) dev-legacy: remove install-legacy dependencies - From 6c4978ce55d3bce87b0ea9aa3a24399083bb8b86 Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Tue, 4 Jun 2024 11:50:33 +0300 Subject: [PATCH 12/34] chore(deps): bump luarocks from 3.11.0 to 3.11.1 (#13132) ### Summary #### Fixes - normalize namespace names to lowercase when performing dependency resolution, to match CLI behavior - `luarocks build`: ensure `--force` works - `luarocks init`: check if we can create .gitignore - Unix: honor umask correctly - Fix error when failing to open cached files - Fix behavior of luarocks.lock file when dealing with dependencies Signed-off-by: Aapo Talvensaari --- .requirements | 4 ++-- changelog/unreleased/kong/bump-luarocks.yml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/kong/bump-luarocks.yml diff --git a/.requirements b/.requirements index 56cecf064320..346d65fd07df 100644 --- a/.requirements +++ b/.requirements @@ -2,8 +2,8 @@ KONG_PACKAGE_NAME=kong OPENRESTY=1.25.3.1 OPENRESTY_SHA256=32ec1a253a5a13250355a075fe65b7d63ec45c560bbe213350f0992a57cd79df -LUAROCKS=3.11.0 -LUAROCKS_SHA256=25f56b3c7272fb35b869049371d649a1bbe668a56d24df0a66e3712e35dd44a6 +LUAROCKS=3.11.1 +LUAROCKS_SHA256=c3fb3d960dffb2b2fe9de7e3cb004dc4d0b34bb3d342578af84f84325c669102 OPENSSL=3.2.1 OPENSSL_SHA256=83c7329fe52c850677d75e5d0b0ca245309b97e8ecbcfdc1dfdc4ab9fac35b39 PCRE=10.43 diff --git a/changelog/unreleased/kong/bump-luarocks.yml b/changelog/unreleased/kong/bump-luarocks.yml new file mode 100644 index 000000000000..92dcf7b5495d --- /dev/null +++ b/changelog/unreleased/kong/bump-luarocks.yml @@ -0,0 +1,2 @@ +message: "Bumped LuaRocks from 3.11.0 to 3.11.1" +type: dependency From 3df996a3ac14f38ba9e9d354aea37f1a08f530b9 Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Tue, 4 Jun 2024 12:52:29 +0300 Subject: [PATCH 13/34] chore(deps): bump `luacheck` from `1.1.2` to `1.2.0` (#13125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary #### Features - Add builtin standards support for minetest — @BuckarooBanzay and @appgurueu #### Performance - Memoize results to addresses speed refression from new feature in v0.26 — @tomlau10 KAG-4615 KAG-4621 Signed-off-by: Aapo Talvensaari --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2e030a4de3ac..2cfe608cdbbc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ OS := $(shell uname | awk '{print tolower($$0)}') MACHINE := $(shell uname -m) -DEV_ROCKS = "busted 2.2.0" "busted-hjtest 0.0.5" "luacheck 1.1.2" "lua-llthreads2 0.1.6" "ldoc 1.5.0" "luacov 0.15.0" +DEV_ROCKS = "busted 2.2.0" "busted-hjtest 0.0.5" "luacheck 1.2.0" "lua-llthreads2 0.1.6" "ldoc 1.5.0" "luacov 0.15.0" WIN_SCRIPTS = "bin/busted" "bin/kong" "bin/kong-health" BUSTED_ARGS ?= -v TEST_CMD ?= bin/busted $(BUSTED_ARGS) From 76f2612aaea6df0bc83fa8b485b7420279c177f9 Mon Sep 17 00:00:00 2001 From: Keery Nie Date: Tue, 4 Jun 2024 19:08:35 +0800 Subject: [PATCH 14/34] feat(aws-lambda): add new configuration field `empty_arrays_mode` to control empty array decoding behavior (#13084) Co-authored-by: Yusheng Li Co-authored-by: Zachary Hu <6426329+outsinre@users.noreply.github.com> --- .../feat-aws-lambda-decode-empty-array.yml | 4 ++ kong-3.8.0-0.rockspec | 2 +- kong/clustering/compat/removed_fields.lua | 3 + kong/plugins/aws-lambda/handler.lua | 13 ++++ kong/plugins/aws-lambda/request-util.lua | 27 ++++++++ kong/plugins/aws-lambda/schema.lua | 7 +++ .../27-aws-lambda/99-access_spec.lua | 62 +++++++++++++++++++ spec/fixtures/aws-lambda.lua | 5 ++ 8 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/kong/feat-aws-lambda-decode-empty-array.yml diff --git a/changelog/unreleased/kong/feat-aws-lambda-decode-empty-array.yml b/changelog/unreleased/kong/feat-aws-lambda-decode-empty-array.yml new file mode 100644 index 000000000000..731d9f2bef01 --- /dev/null +++ b/changelog/unreleased/kong/feat-aws-lambda-decode-empty-array.yml @@ -0,0 +1,4 @@ +message: | + **AWS-Lambda**: A new configuration field `empty_arrays_mode` is now added to control whether Kong should send `[]` empty arrays (returned by Lambda function) as `[]` empty arrays or `{}` empty objects in JSON responses.` +type: feature +scope: Plugin diff --git a/kong-3.8.0-0.rockspec b/kong-3.8.0-0.rockspec index cb3eb3e0669f..5813d1fb86b0 100644 --- a/kong-3.8.0-0.rockspec +++ b/kong-3.8.0-0.rockspec @@ -33,7 +33,7 @@ dependencies = { "lua-protobuf == 0.5.1", "lua-resty-healthcheck == 3.0.2", "lua-messagepack == 0.5.4", - "lua-resty-aws == 1.4.1", + "lua-resty-aws == 1.5.0", "lua-resty-openssl == 1.4.0", "lua-resty-counter == 0.2.1", "lua-resty-ipmatcher == 0.6.1", diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 77368d2d18f2..359d9b763797 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -148,5 +148,8 @@ return { response_transformer = { "rename.json", }, + aws_lambda = { + "empty_arrays_mode", + } }, } diff --git a/kong/plugins/aws-lambda/handler.lua b/kong/plugins/aws-lambda/handler.lua index 68f4fe398996..430f1f4f2713 100644 --- a/kong/plugins/aws-lambda/handler.lua +++ b/kong/plugins/aws-lambda/handler.lua @@ -18,6 +18,7 @@ local VIA_HEADER_VALUE = meta._NAME .. "/" .. meta._VERSION local request_util = require "kong.plugins.aws-lambda.request-util" local build_request_payload = request_util.build_request_payload local extract_proxy_response = request_util.extract_proxy_response +local remove_array_mt_for_empty_table = request_util.remove_array_mt_for_empty_table local aws = require("resty.aws") local AWS_GLOBAL_CONFIG @@ -240,6 +241,18 @@ function AWSLambdaHandler:access(conf) headers[VIA_HEADER] = VIA_HEADER_VALUE end + -- TODO: remove this in the next major release + -- function to remove array_mt metatables from empty tables + -- This is just a backward compatibility code to keep a + -- long-lived behavior that Kong responsed JSON objects + -- instead of JSON arrays for empty arrays. + if conf.empty_arrays_mode == "legacy" then + local ct = headers["Content-Type"] + if ct and ct:lower():match("application/.*json") then + content = remove_array_mt_for_empty_table(content) + end + end + return kong.response.exit(status, content, headers) end diff --git a/kong/plugins/aws-lambda/request-util.lua b/kong/plugins/aws-lambda/request-util.lua index 3dd5b551c9b6..1a152b6778b8 100644 --- a/kong/plugins/aws-lambda/request-util.lua +++ b/kong/plugins/aws-lambda/request-util.lua @@ -9,6 +9,7 @@ local get_request_id = require("kong.tracing.request_id").get local EMPTY = {} +local isempty = require "table.isempty" local split = pl_stringx.split local ngx_req_get_headers = ngx.req.get_headers local ngx_req_get_uri_args = ngx.req.get_uri_args @@ -325,10 +326,36 @@ local function build_request_payload(conf) end +-- TODO: remove this in the next major release +-- function to remove array_mt metatables from empty tables +-- This is just a backward compatibility code to keep a +-- long-lived behavior that Kong responsed JSON objects +-- instead of JSON arrays for empty arrays. +local function remove_array_mt_for_empty_table(tbl) + if type(tbl) ~= "table" then + return tbl + end + + -- Check if the current table(array) is empty and has a array_mt metatable, and remove it + if isempty(tbl) and getmetatable(tbl) == cjson.array_mt then + setmetatable(tbl, nil) + end + + for _, value in pairs(tbl) do + if type(value) == "table" then + remove_array_mt_for_empty_table(value) + end + end + + return tbl +end + + return { aws_serializer = aws_serializer, validate_http_status_code = validate_http_status_code, validate_custom_response = validate_custom_response, build_request_payload = build_request_payload, extract_proxy_response = extract_proxy_response, + remove_array_mt_for_empty_table = remove_array_mt_for_empty_table, } diff --git a/kong/plugins/aws-lambda/schema.lua b/kong/plugins/aws-lambda/schema.lua index 21cacba59555..767262d66045 100644 --- a/kong/plugins/aws-lambda/schema.lua +++ b/kong/plugins/aws-lambda/schema.lua @@ -116,6 +116,13 @@ return { default = "v1", one_of = { "v1", "v2" } } }, + { empty_arrays_mode = { -- TODO: this config field is added for backward compatibility and will be removed in next major version + description = "An optional value that defines whether Kong should send empty arrays (returned by Lambda function) as `[]` arrays or `{}` objects in JSON responses. The value `legacy` means Kong will send empty arrays as `{}` objects in response", + type = "string", + required = true, + default = "legacy", + one_of = { "legacy", "correct" } + } }, } }, } }, diff --git a/spec/03-plugins/27-aws-lambda/99-access_spec.lua b/spec/03-plugins/27-aws-lambda/99-access_spec.lua index ee7543d695d3..7f29aa904043 100644 --- a/spec/03-plugins/27-aws-lambda/99-access_spec.lua +++ b/spec/03-plugins/27-aws-lambda/99-access_spec.lua @@ -176,6 +176,18 @@ for _, strategy in helpers.each_strategy() do service = null, } + local route26 = bp.routes:insert { + hosts = { "lambda26.test" }, + protocols = { "http", "https" }, + service = null, + } + + local route27 = bp.routes:insert { + hosts = { "lambda27.test" }, + protocols = { "http", "https" }, + service = null, + } + bp.plugins:insert { name = "aws-lambda", route = { id = route1.id }, @@ -522,6 +534,32 @@ for _, strategy in helpers.each_strategy() do } } + bp.plugins:insert { + name = "aws-lambda", + route = { id = route26.id }, + config = { + port = 10001, + aws_key = "mock-key", + aws_secret = "mock-secret", + aws_region = "us-east-1", + function_name = "functionWithEmptyArray", + empty_arrays_mode = "legacy", + } + } + + bp.plugins:insert { + name = "aws-lambda", + route = { id = route27.id }, + config = { + port = 10001, + aws_key = "mock-key", + aws_secret = "mock-secret", + aws_region = "us-east-1", + function_name = "functionWithEmptyArray", + empty_arrays_mode = "correct", + } + } + fixtures.dns_mock:A({ name = "custom.lambda.endpoint", address = "127.0.0.1", @@ -923,6 +961,30 @@ for _, strategy in helpers.each_strategy() do end, 10) end) + it("invokes a Lambda function with empty array", function() + local res = assert(proxy_client:send { + method = "GET", + path = "/get", + headers = { + ["Host"] = "lambda26.test" + } + }) + + local body = assert.res_status(200, res) + assert.matches("\"testbody\":{}", body) + + local res = assert(proxy_client:send { + method = "GET", + path = "/get", + headers = { + ["Host"] = "lambda27.test" + } + }) + + local body = assert.res_status(200, res) + assert.matches("\"testbody\":%[%]", body) + end) + describe("config.is_proxy_integration = true", function() diff --git a/spec/fixtures/aws-lambda.lua b/spec/fixtures/aws-lambda.lua index 612dbf8aacca..3ab5b0ac0fa6 100644 --- a/spec/fixtures/aws-lambda.lua +++ b/spec/fixtures/aws-lambda.lua @@ -65,6 +65,11 @@ local fixtures = { ngx.sleep(2) ngx.say("{\"statusCodge\": 200, \"body\": \"dGVzdA=\", \"isBase64Encoded\": false}") + elseif string.match(ngx.var.uri, "functionWithEmptyArray") then + ngx.header["Content-Type"] = "application/json" + local str = "{\"statusCode\": 200, \"testbody\": [], \"isBase64Encoded\": false}" + ngx.say(str) + elseif type(res) == 'string' then ngx.header["Content-Length"] = #res + 1 ngx.say(res) From d08b79fa94df05c2da86c644da1a6a6548f2b202 Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Thu, 30 May 2024 19:38:03 +0300 Subject: [PATCH 15/34] chore(deps): bump github-cli from 2.30.0 to 2.50.0 ### Summary See: https://github.com/cli/cli/releases Signed-off-by: Aapo Talvensaari --- build/repositories.bzl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/repositories.bzl b/build/repositories.bzl index c86dfec2f37d..9b808b4cdde2 100644 --- a/build/repositories.bzl +++ b/build/repositories.bzl @@ -35,16 +35,16 @@ def github_cli_repositories(): """Defines the github cli repositories""" gh_matrix = [ - ["linux", "amd64", "tar.gz", "5aee45bd42a27f5be309373c326e45cbcc7f04591b1798581a3094af767225b7"], - ["linux", "arm64", "tar.gz", "3ef741bcc1ae8bb975adb79a78e26ab7f18a246197f193aaa8cb5c3bdc373a3f"], - ["macOS", "amd64", "zip", "6b91c446586935de0e9df82da58309b2d1b83061cfcd4cc173124270f1277ca7"], - ["macOS", "arm64", "zip", "32a71652367f3cf664894456e4c4f655faa95964d71cc3a449fbf64bdce1fff1"], + ["linux", "amd64", "tar.gz", "7f9795b3ce99351a1bfc6ea3b09b7363cb1eccca19978a046bcb477839efab82"], + ["linux", "arm64", "tar.gz", "115e1a18695fcc2e060711207f0c297f1cca8b76dd1d9cd0cf071f69ccac7422"], + ["macOS", "amd64", "zip", "d18acd3874c9b914e0631c308f8e2609bd45456272bacfa70221c46c76c635f6"], + ["macOS", "arm64", "zip", "85fced36325e212410d0eea97970251852b317d49d6d72fd6156e522f2896bc5"], ] for name, arch, type, sha in gh_matrix: http_archive( name = "gh_%s_%s" % (name, arch), - url = "https://github.com/cli/cli/releases/download/v2.30.0/gh_2.30.0_%s_%s.%s" % (name, arch, type), - strip_prefix = "gh_2.30.0_%s_%s" % (name, arch), + url = "https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_%s_%s.%s" % (name, arch, type), + strip_prefix = "gh_2.50.0_%s_%s" % (name, arch), sha256 = sha, build_file_content = _SRCS_BUILD_FILE_CONTENT, ) From b088118d01a546ee27808b3e03bf00bac6d46c92 Mon Sep 17 00:00:00 2001 From: Niklaus Schen <8458369+Water-Melon@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:19:34 +0800 Subject: [PATCH 16/34] chore(ci): generate the Docker tag latest-ubuntu only for the latest commit on the default branch (#13096) KAG-4374 --- .github/workflows/release.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 709d6559d2c0..ccfc66b436b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -597,6 +597,12 @@ jobs: username: ${{ secrets.GHA_DOCKERHUB_PUSH_USER }} password: ${{ secrets.GHA_KONG_ORG_DOCKERHUB_PUSH_TOKEN }} + - uses: actions/checkout@v3 + + - name: Get latest commit SHA on master + run: | + echo "latest_sha=$(git ls-remote origin -h refs/heads/${{ github.event.inputs.default_branch }} | cut -f1)" >> $GITHUB_ENV + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -604,7 +610,7 @@ jobs: images: ${{ needs.metadata.outputs.docker-repository }} sep-tags: " " tags: | - type=raw,value=latest,enable=${{ matrix.label == 'ubuntu' }} + type=raw,value=latest,enable=${{ matrix.label == 'ubuntu' && github.ref_name == github.event.inputs.default_branch && env.latest_sha == github.event.pull_request.head.sha }} type=match,enable=${{ github.event_name == 'workflow_dispatch' }},pattern=\d.\d,value=${{ github.event.inputs.version }} type=match,enable=${{ github.event_name == 'workflow_dispatch' && matrix.label == 'ubuntu' }},pattern=\d.\d,value=${{ github.event.inputs.version }},suffix= type=raw,enable=${{ github.event_name == 'workflow_dispatch' }},${{ github.event.inputs.version }} From fb5d828d8c34c13f493a2fc70a02456402636ff7 Mon Sep 17 00:00:00 2001 From: Niklaus Schen <8458369+Water-Melon@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:53:12 +0800 Subject: [PATCH 17/34] refactor(tools/http): update references from `kong.tools.utils` to `kong.tools.http` (#13156) KAG-3147 --- kong/api/api_helpers.lua | 4 +- kong/db/schema/typedefs.lua | 6 +- kong/error_handlers.lua | 6 +- kong/pdk/response.lua | 6 +- kong/plugins/response-transformer/schema.lua | 2 +- kong/tracing/propagation/schema.lua | 4 +- spec/01-unit/05-utils_spec.lua | 118 +++++++++---------- 7 files changed, 73 insertions(+), 73 deletions(-) diff --git a/kong/api/api_helpers.lua b/kong/api/api_helpers.lua index ef130a4d39c2..69e9822a8ede 100644 --- a/kong/api/api_helpers.lua +++ b/kong/api/api_helpers.lua @@ -1,4 +1,3 @@ -local utils = require "kong.tools.utils" local kong_table = require "kong.tools.table" local cjson = require "cjson" local pl_pretty = require "pl.pretty" @@ -7,6 +6,7 @@ local app_helpers = require "lapis.application" local arguments = require "kong.api.arguments" local Errors = require "kong.db.errors" local hooks = require "kong.hooks" +local decode_args = require("kong.tools.http").decode_args local ngx = ngx @@ -300,7 +300,7 @@ local function parse_params(fn) return kong.response.exit(400, { message = "Cannot parse JSON body" }) elseif find(content_type, "application/x-www-form-urlencode", 1, true) then - self.params = utils.decode_args(self.params) + self.params = decode_args(self.params) end end end diff --git a/kong/db/schema/typedefs.lua b/kong/db/schema/typedefs.lua index 99d64fe95e5c..4ab319267010 100644 --- a/kong/db/schema/typedefs.lua +++ b/kong/db/schema/typedefs.lua @@ -1,6 +1,5 @@ --- A library of ready-to-use type synonyms to use in schema definitions. -- @module kong.db.schema.typedefs -local utils = require "kong.tools.utils" local queue_schema = require "kong.tools.queue_schema" local propagation_schema = require "kong.tracing.propagation.schema" local openssl_pkey = require "resty.openssl.pkey" @@ -10,6 +9,7 @@ local socket_url = require "socket.url" local constants = require "kong.constants" local tools_ip = require "kong.tools.ip" local validate_utf8 = require("kong.tools.string").validate_utf8 +local tools_http = require "kong.tools.http" local DAO_MAX_TTL = constants.DATABASE.DAO_MAX_TTL @@ -337,14 +337,14 @@ typedefs.url = Schema.define { typedefs.cookie_name = Schema.define { type = "string", - custom_validator = utils.validate_cookie_name, + custom_validator = tools_http.validate_cookie_name, description = "A string representing an HTTP token defined by RFC 2616." } -- should we also allow all http token for this? typedefs.header_name = Schema.define { type = "string", - custom_validator = utils.validate_header_name, + custom_validator = tools_http.validate_header_name, description = "A string representing an HTTP header name." } diff --git a/kong/error_handlers.lua b/kong/error_handlers.lua index 8fd83cf55aaf..91db16a825ca 100644 --- a/kong/error_handlers.lua +++ b/kong/error_handlers.lua @@ -1,8 +1,8 @@ local kong = kong local find = string.find local fmt = string.format -local utils = require "kong.tools.utils" local request_id = require "kong.tracing.request_id" +local tools_http = require "kong.tools.http" local CONTENT_TYPE = "Content-Type" @@ -71,9 +71,9 @@ return function(ctx) message = { message = message } else - local mime_type = utils.get_response_type(accept_header) + local mime_type = tools_http.get_response_type(accept_header) local rid = request_id.get() or "" - message = fmt(utils.get_error_template(mime_type), message, rid) + message = fmt(tools_http.get_error_template(mime_type), message, rid) headers = { [CONTENT_TYPE] = mime_type } end diff --git a/kong/pdk/response.lua b/kong/pdk/response.lua index 37a0c67d11f4..44035bf54aff 100644 --- a/kong/pdk/response.lua +++ b/kong/pdk/response.lua @@ -16,9 +16,9 @@ local buffer = require "string.buffer" local cjson = require "cjson.safe" local checks = require "kong.pdk.private.checks" local phase_checker = require "kong.pdk.private.phases" -local utils = require "kong.tools.utils" local request_id = require "kong.tracing.request_id" local constants = require "kong.constants" +local tools_http = require "kong.tools.http" local ngx = ngx @@ -1146,7 +1146,7 @@ local function new(self, major_version) content_type = CONTENT_TYPE_GRPC else local accept_header = ngx.req.get_headers()[ACCEPT_NAME] - content_type = utils.get_response_type(accept_header) + content_type = tools_http.get_response_type(accept_header) end end @@ -1156,7 +1156,7 @@ local function new(self, major_version) if content_type ~= CONTENT_TYPE_GRPC then local actual_message = message or get_http_error_message(status) local rid = request_id.get() or "" - body = fmt(utils.get_error_template(content_type), actual_message, rid) + body = fmt(tools_http.get_error_template(content_type), actual_message, rid) end local ctx = ngx.ctx diff --git a/kong/plugins/response-transformer/schema.lua b/kong/plugins/response-transformer/schema.lua index a119e18bf294..28bd6fc967b2 100644 --- a/kong/plugins/response-transformer/schema.lua +++ b/kong/plugins/response-transformer/schema.lua @@ -1,5 +1,5 @@ local typedefs = require "kong.db.schema.typedefs" -local validate_header_name = require("kong.tools.utils").validate_header_name +local validate_header_name = require("kong.tools.http").validate_header_name local function validate_headers(pair, validate_value) diff --git a/kong/tracing/propagation/schema.lua b/kong/tracing/propagation/schema.lua index 0ca294e9b61f..3911b061bd96 100644 --- a/kong/tracing/propagation/schema.lua +++ b/kong/tracing/propagation/schema.lua @@ -1,6 +1,6 @@ local Schema = require "kong.db.schema" -local utils = require "kong.tools.utils" local formats = require "kong.tracing.propagation.utils".FORMATS +local validate_header_name = require("kong.tools.http").validate_header_name local extractors = {} @@ -35,7 +35,7 @@ return Schema.define { type = "array", elements = { type = "string", - custom_validator = utils.validate_header_name, + custom_validator = validate_header_name, } } }, diff --git a/spec/01-unit/05-utils_spec.lua b/spec/01-unit/05-utils_spec.lua index 930b9454b00e..14e23b3d85b7 100644 --- a/spec/01-unit/05-utils_spec.lua +++ b/spec/01-unit/05-utils_spec.lua @@ -1,7 +1,7 @@ -local utils = require "kong.tools.utils" local kong_table = require "kong.tools.table" local pl_path = require "pl.path" local tools_ip = require "kong.tools.ip" +local tools_http = require "kong.tools.http" describe("Utils", function() @@ -105,19 +105,19 @@ describe("Utils", function() it("should validate an HTTPS scheme", function() ngx.var.scheme = "hTTps" -- mixed casing to ensure case insensitiveness - assert.is.truthy(utils.check_https(true, false)) + assert.is.truthy(tools_http.check_https(true, false)) end) it("should invalidate non-HTTPS schemes", function() ngx.var.scheme = "hTTp" - assert.is.falsy(utils.check_https(true, false)) + assert.is.falsy(tools_http.check_https(true, false)) ngx.var.scheme = "something completely different" - assert.is.falsy(utils.check_https(true, false)) + assert.is.falsy(tools_http.check_https(true, false)) end) it("should invalidate non-HTTPS schemes with proto header allowed", function() ngx.var.scheme = "hTTp" - assert.is.falsy(utils.check_https(true, true)) + assert.is.falsy(tools_http.check_https(true, true)) end) end) @@ -130,42 +130,42 @@ describe("Utils", function() it("should validate any scheme with X-Forwarded_Proto as HTTPS", function() headers["x-forwarded-proto"] = "hTTPs" -- check mixed casing for case insensitiveness ngx.var.scheme = "hTTps" - assert.is.truthy(utils.check_https(true, true)) + assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" - assert.is.truthy(utils.check_https(true, true)) + assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "something completely different" - assert.is.truthy(utils.check_https(true, true)) + assert.is.truthy(tools_http.check_https(true, true)) end) it("should validate only https scheme with X-Forwarded_Proto as non-HTTPS", function() headers["x-forwarded-proto"] = "hTTP" ngx.var.scheme = "hTTps" - assert.is.truthy(utils.check_https(true, true)) + assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" - assert.is.falsy(utils.check_https(true, true)) + assert.is.falsy(tools_http.check_https(true, true)) ngx.var.scheme = "something completely different" - assert.is.falsy(utils.check_https(true, true)) + assert.is.falsy(tools_http.check_https(true, true)) end) it("should return an error with multiple X-Forwarded_Proto headers", function() headers["x-forwarded-proto"] = { "hTTP", "https" } ngx.var.scheme = "hTTps" - assert.is.truthy(utils.check_https(true, true)) + assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" assert.are.same({ nil, "Only one X-Forwarded-Proto header allowed" }, - { utils.check_https(true, true) }) + { tools_http.check_https(true, true) }) end) it("should not use X-Forwarded-Proto when the client is untrusted", function() headers["x-forwarded-proto"] = "https" ngx.var.scheme = "http" - assert.is_false(utils.check_https(false, false)) - assert.is_false(utils.check_https(false, true)) + assert.is_false(tools_http.check_https(false, false)) + assert.is_false(tools_http.check_https(false, true)) headers["x-forwarded-proto"] = "https" ngx.var.scheme = "https" - assert.is_true(utils.check_https(false, false)) - assert.is_true(utils.check_https(false, true)) + assert.is_true(tools_http.check_https(false, false)) + assert.is_true(tools_http.check_https(false, true)) end) it("should use X-Forwarded-Proto when the client is trusted", function() @@ -173,14 +173,14 @@ describe("Utils", function() ngx.var.scheme = "http" -- trusted client but do not allow terminated - assert.is_false(utils.check_https(true, false)) + assert.is_false(tools_http.check_https(true, false)) - assert.is_true(utils.check_https(true, true)) + assert.is_true(tools_http.check_https(true, true)) headers["x-forwarded-proto"] = "https" ngx.var.scheme = "https" - assert.is_true(utils.check_https(true, false)) - assert.is_true(utils.check_https(true, true)) + assert.is_true(tools_http.check_https(true, false)) + assert.is_true(tools_http.check_https(true, true)) end) end) end) @@ -220,54 +220,54 @@ describe("Utils", function() describe("encode_args()", function() it("should encode a Lua table to a querystring", function() - local str = utils.encode_args { + local str = tools_http.encode_args { foo = "bar", hello = "world" } assert.equal("foo=bar&hello=world", str) end) it("should encode multi-value query args", function() - local str = utils.encode_args { + local str = tools_http.encode_args { foo = {"bar", "zoo"}, hello = "world" } assert.equal("foo%5b1%5d=bar&foo%5b2%5d=zoo&hello=world", str) end) it("should percent-encode given values", function() - local str = utils.encode_args { + local str = tools_http.encode_args { encode = {"abc|def", ",$@|`"} } assert.equal("encode%5b1%5d=abc%7cdef&encode%5b2%5d=%2c%24%40%7c%60", str) end) it("should percent-encode given query args keys", function() - local str = utils.encode_args { + local str = tools_http.encode_args { ["hello world"] = "foo" } assert.equal("hello%20world=foo", str) end) it("should support Lua numbers", function() - local str = utils.encode_args { + local str = tools_http.encode_args { a = 1, b = 2 } assert.equal("a=1&b=2", str) end) it("should support a boolean argument", function() - local str = utils.encode_args { + local str = tools_http.encode_args { a = true, b = 1 } assert.equal("a=true&b=1", str) end) it("should ignore nil and false values", function() - local str = utils.encode_args { + local str = tools_http.encode_args { a = nil, b = false } assert.equal("b=false", str) end) it("should encode complex query args", function() - local encode = utils.encode_args + local encode = tools_http.encode_args assert.equal("falsy=false", encode({ falsy = false })) assert.equal("multiple%20values=true", @@ -286,13 +286,13 @@ describe("Utils", function() encode({ hybrid = { 1, 2, n = 3 } })) end) it("should not interpret the `%` character followed by 2 characters in the [0-9a-f] group as an hexadecimal value", function() - local str = utils.encode_args { + local str = tools_http.encode_args { foo = "%bar%" } assert.equal("foo=%25bar%25", str) end) it("does not percent-encode if given a `raw` option", function() - local encode = utils.encode_args + local encode = tools_http.encode_args -- this is useful for kong.tools.http_client assert.equal("hello world=foo, bar", encode({ ["hello world"] = "foo, bar" }, true)) @@ -308,7 +308,7 @@ describe("Utils", function() encode({ hybrid = { 1, 2, n = 3 } }, true)) end) it("does not include index numbers in arrays if given the `no_array_indexes` flag", function() - local encode = utils.encode_args + local encode = tools_http.encode_args assert.equal("falsy=false", encode({ falsy = false }, nil, true)) assert.equal("multiple%20values=true", @@ -327,7 +327,7 @@ describe("Utils", function() encode({ hybrid = { 1, 2, n = 3 } }, nil, true)) end) it("does not percent-encode and does not add index numbers if both `raw` and `no_array_indexes` are active", function() - local encode = utils.encode_args + local encode = tools_http.encode_args -- this is useful for kong.tools.http_client assert.equal("hello world=foo, bar", encode({ ["hello world"] = "foo, bar" }, true, true)) @@ -343,7 +343,7 @@ describe("Utils", function() encode({ hybrid = { 1, 2, n = 3 } }, true, true)) end) it("transforms ngx.null into empty string", function() - local str = utils.encode_args({ x = ngx.null, y = "foo" }) + local str = tools_http.encode_args({ x = ngx.null, y = "foo" }) assert.equal("x=&y=foo", str) end) -- while this method's purpose is to mimic 100% the behavior of ngx.encode_args, @@ -351,26 +351,26 @@ describe("Utils", function() -- Hence, a `raw` parameter allows encoding for bodies. describe("raw", function() it("should not percent-encode values", function() - local str = utils.encode_args({ + local str = tools_http.encode_args({ foo = "hello world" }, true) assert.equal("foo=hello world", str) end) it("should not percent-encode keys", function() - local str = utils.encode_args({ + local str = tools_http.encode_args({ ["hello world"] = "foo" }, true) assert.equal("hello world=foo", str) end) it("should plainly include true and false values", function() - local str = utils.encode_args({ + local str = tools_http.encode_args({ a = true, b = false }, true) assert.equal("a=true&b=false", str) end) it("should prevent double percent-encoding", function() - local str = utils.encode_args({ + local str = tools_http.encode_args({ foo = "hello%20world" }, true) assert.equal("foo=hello%20world", str) @@ -657,10 +657,10 @@ describe("Utils", function() local c = string.char(i) if string.find(header_chars, c, nil, true) then - assert(utils.validate_header_name(c) == c, + assert(tools_http.validate_header_name(c) == c, "ascii character '" .. c .. "' (" .. i .. ") should have been allowed") else - assert(utils.validate_header_name(c) == nil, + assert(tools_http.validate_header_name(c) == nil, "ascii character " .. i .. " should not have been allowed") end end @@ -672,10 +672,10 @@ describe("Utils", function() local c = string.char(i) if string.find(cookie_chars, c, nil, true) then - assert(utils.validate_cookie_name(c) == c, + assert(tools_http.validate_cookie_name(c) == c, "ascii character '" .. c .. "' (" .. i .. ") should have been allowed") else - assert(utils.validate_cookie_name(c) == nil, + assert(tools_http.validate_cookie_name(c) == nil, "ascii character " .. i .. " should not have been allowed") end end @@ -797,26 +797,26 @@ describe("Utils", function() describe("get_mime_type()", function() it("with valid mime types", function() - assert.equal("application/json; charset=utf-8", utils.get_mime_type("application/json")) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("application/json; charset=utf-8")) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("application/*")) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("application/*; charset=utf-8")) - assert.equal("text/html; charset=utf-8", utils.get_mime_type("text/html")) - assert.equal("text/plain; charset=utf-8", utils.get_mime_type("text/plain")) - assert.equal("text/plain; charset=utf-8", utils.get_mime_type("text/*")) - assert.equal("text/plain; charset=utf-8", utils.get_mime_type("text/*; charset=utf-8")) - assert.equal("application/xml; charset=utf-8", utils.get_mime_type("application/xml")) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("*/*; charset=utf-8")) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("*/*")) - assert.equal("", utils.get_mime_type("application/grpc")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/json")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/json; charset=utf-8")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/*")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/*; charset=utf-8")) + assert.equal("text/html; charset=utf-8", tools_http.get_mime_type("text/html")) + assert.equal("text/plain; charset=utf-8", tools_http.get_mime_type("text/plain")) + assert.equal("text/plain; charset=utf-8", tools_http.get_mime_type("text/*")) + assert.equal("text/plain; charset=utf-8", tools_http.get_mime_type("text/*; charset=utf-8")) + assert.equal("application/xml; charset=utf-8", tools_http.get_mime_type("application/xml")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("*/*; charset=utf-8")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("*/*")) + assert.equal("", tools_http.get_mime_type("application/grpc")) end) it("with unsupported or invalid mime types", function() - assert.equal("application/json; charset=utf-8", utils.get_mime_type("audio/*", true)) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("text/css")) - assert.equal("application/json; charset=utf-8", utils.get_mime_type("default")) - assert.is_nil(utils.get_mime_type("video/json", false)) - assert.is_nil(utils.get_mime_type("text/javascript", false)) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("audio/*", true)) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("text/css")) + assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("default")) + assert.is_nil(tools_http.get_mime_type("video/json", false)) + assert.is_nil(tools_http.get_mime_type("text/javascript", false)) end) end) From c39b654f78893e43e943562629a3be9b065eabe6 Mon Sep 17 00:00:00 2001 From: Chrono Date: Wed, 5 Jun 2024 13:21:11 +0800 Subject: [PATCH 18/34] refactor(tools): update references of `tools.utils` to correct module (#13162) This is a follow-up of [#13156](https://github.com/Kong/kong/pull/13156), clean some references of `tools.utils`. KAG-3136 --- kong/plugins/file-log/handler.lua | 1 - kong/tools/utils.lua | 2 +- spec/helpers.lua | 6 +++--- spec/helpers/http_mock/nginx_instance.lua | 13 ++++++++++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/kong/plugins/file-log/handler.lua b/kong/plugins/file-log/handler.lua index 2b27622293c7..c7d4fe9a1954 100644 --- a/kong/plugins/file-log/handler.lua +++ b/kong/plugins/file-log/handler.lua @@ -1,5 +1,4 @@ -- Copyright (C) Kong Inc. -require "kong.tools.utils" -- ffi.cdefs local kong_meta = require "kong.meta" diff --git a/kong/tools/utils.lua b/kong/tools/utils.lua index b7cff5add1e5..4b958d90fe79 100644 --- a/kong/tools/utils.lua +++ b/kong/tools/utils.lua @@ -24,9 +24,9 @@ do "kong.tools.rand", "kong.tools.time", "kong.tools.string", - -- ]] keep it here for compatibility "kong.tools.ip", "kong.tools.http", + -- ]] keep it here for compatibility } for _, str in ipairs(modules) do diff --git a/spec/helpers.lua b/spec/helpers.lua index 57b9fea00da3..5cedb14fee35 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -695,7 +695,7 @@ end -- @param opts table with options. See [lua-resty-http](https://github.com/pintsized/lua-resty-http) function resty_http_proxy_mt:send(opts, is_reopen) local cjson = require "cjson" - local utils = require "kong.tools.utils" + local encode_args = require("kong.tools.http").encode_args opts = opts or {} @@ -709,7 +709,7 @@ function resty_http_proxy_mt:send(opts, is_reopen) opts.body = cjson.encode(opts.body) elseif string.find(content_type, "www-form-urlencoded", nil, true) and t_body_table then - opts.body = utils.encode_args(opts.body, true, opts.no_array_indexes) + opts.body = encode_args(opts.body, true, opts.no_array_indexes) elseif string.find(content_type, "multipart/form-data", nil, true) and t_body_table then local form = opts.body @@ -738,7 +738,7 @@ function resty_http_proxy_mt:send(opts, is_reopen) -- build querystring (assumes none is currently in 'opts.path') if type(opts.query) == "table" then - local qs = utils.encode_args(opts.query) + local qs = encode_args(opts.query) opts.path = opts.path .. "?" .. qs opts.query = nil end diff --git a/spec/helpers/http_mock/nginx_instance.lua b/spec/helpers/http_mock/nginx_instance.lua index f27917bd5ba2..50b926ef9944 100644 --- a/spec/helpers/http_mock/nginx_instance.lua +++ b/spec/helpers/http_mock/nginx_instance.lua @@ -14,9 +14,16 @@ local error = error local assert = assert local ngx = ngx local io = io --- It can't be changed to kong.tools.table because the old version --- does not have kong/tools/table.lua, so the upgrade test will fail. -local shallow_copy = require("kong.tools.utils").shallow_copy + +local shallow_copy +do + local clone = require "table.clone" + + shallow_copy = function(orig) + assert(type(orig) == "table") + return clone(orig) + end +end local template = assert(pl_template.compile(template_str)) local render_env = {ipairs = ipairs, pairs = pairs, error = error, } From 33765abea234442ac29db4fd13474c38c2ddc3f0 Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Wed, 5 Jun 2024 13:29:29 +0800 Subject: [PATCH 19/34] fix(pdk): fix log serialize upstream_status entry nil bug in subrequest case (#12953) FTI-5844 --- ...fix-log-upstream-status-nil-subrequest.yml | 4 ++ kong/pdk/log.lua | 5 +- spec/03-plugins/04-file-log/01-log_spec.lua | 52 +++++++++++++++++++ t/01-pdk/02-log/00-phase_checks.t | 7 ++- 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 changelog/unreleased/kong/fix-log-upstream-status-nil-subrequest.yml diff --git a/changelog/unreleased/kong/fix-log-upstream-status-nil-subrequest.yml b/changelog/unreleased/kong/fix-log-upstream-status-nil-subrequest.yml new file mode 100644 index 000000000000..2ed6449459cf --- /dev/null +++ b/changelog/unreleased/kong/fix-log-upstream-status-nil-subrequest.yml @@ -0,0 +1,4 @@ +message: | + **PDK**: Fixed a bug that log serializer will log `upstream_status` as nil in the requests that contains subrequest +type: bugfix +scope: PDK diff --git a/kong/pdk/log.lua b/kong/pdk/log.lua index a55f8373bf99..a4197a449555 100644 --- a/kong/pdk/log.lua +++ b/kong/pdk/log.lua @@ -814,10 +814,7 @@ do end end - -- The value of upstream_status is a string, and status codes may be - -- seperated by comma or grouped by colon, according to - -- the nginx doc: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream_status - local upstream_status = var.upstream_status or "" + local upstream_status = var.upstream_status or ctx.buffered_status or "" local response_source = okong.response.get_source(ongx.ctx) local response_source_name = TYPE_NAMES[response_source] diff --git a/spec/03-plugins/04-file-log/01-log_spec.lua b/spec/03-plugins/04-file-log/01-log_spec.lua index bc4aa3eb385b..1230dfe9ac5a 100644 --- a/spec/03-plugins/04-file-log/01-log_spec.lua +++ b/spec/03-plugins/04-file-log/01-log_spec.lua @@ -247,6 +247,36 @@ for _, strategy in helpers.each_strategy() do }, } + local route10 = bp.routes:insert { + hosts = { "file_logging10.test" }, + response_buffering = true, + } + + bp.plugins:insert({ + name = "pre-function", + route = { id = route10.id }, + config = { + access = { + [[ + kong.service.request.enable_buffering() + ]], + }, + } + }) + + bp.plugins:insert { + route = { id = route10.id }, + name = "file-log", + config = { + path = FILE_LOG_PATH, + reopen = true, + custom_fields_by_lua = { + new_field = "return 123", + route = "return nil", -- unset route field + }, + }, + } + assert(helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", @@ -337,6 +367,28 @@ for _, strategy in helpers.each_strategy() do assert.is_number(log_message.response.size) assert.same(nil, log_message.route) end) + it("correct upstream status when we use response phase", function() + local uuid = random_string() + + -- Making the request + local res = assert(proxy_client:send({ + method = "GET", + path = "/status/200", + headers = { + ["file-log-uuid"] = uuid, + ["Host"] = "file_logging10.test" + } + })) + assert.res_status(200, res) + + local log_message = wait_for_json_log_entry() + assert.same("127.0.0.1", log_message.client_ip) + assert.same(uuid, log_message.request.headers["file-log-uuid"]) + assert.is_number(log_message.request.size) + assert.is_number(log_message.response.size) + assert.same(nil, log_message.route) + assert.same(200, log_message.upstream_status) + end) end) it("logs to file #grpc", function() diff --git a/t/01-pdk/02-log/00-phase_checks.t b/t/01-pdk/02-log/00-phase_checks.t index ecea2458341d..cef91c1755c6 100644 --- a/t/01-pdk/02-log/00-phase_checks.t +++ b/t/01-pdk/02-log/00-phase_checks.t @@ -66,7 +66,12 @@ qq{ }, response = { get_source = function() return "service" end, - }, + }, + service = { + response = { + get_status = function() return 200 end, + }, + }, } } }, From f46ac8e7fa87d4dd8ac2293a309183b35acc4667 Mon Sep 17 00:00:00 2001 From: windmgc Date: Mon, 3 Jun 2024 15:42:23 +0800 Subject: [PATCH 20/34] fix(cmd): fix kong cli not printing some debug level error log --- bin/kong | 12 ++++++++++-- changelog/unreleased/kong/fix-cmd-error-log.yml | 4 ++++ spec/02-integration/02-cmd/16-verbose_spec.lua | 11 +++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/kong/fix-cmd-error-log.yml create mode 100644 spec/02-integration/02-cmd/16-verbose_spec.lua diff --git a/bin/kong b/bin/kong index 0ed5a347e615..7bbda5894e95 100755 --- a/bin/kong +++ b/bin/kong @@ -141,9 +141,17 @@ package.path = (os.getenv("KONG_LUA_PATH_OVERRIDE") or "") .. "./?.lua;./?/init. require("kong.cmd.init")("%s", %s) ]], cmd_name, args_str) +local resty_ngx_log_level +if arg.vv then + resty_ngx_log_level = "debug" +elseif arg.v then + resty_ngx_log_level = "info" +end + local resty_cmd = string.format( - "resty --main-conf \"%s\" --http-conf \"%s\" --stream-conf \"%s\" -e '%s'", - main_conf, http_conf, stream_conf, inline_code) + "resty %s --main-conf \"%s\" --http-conf \"%s\" --stream-conf \"%s\" -e '%s'", + resty_ngx_log_level and ("--errlog-level " .. resty_ngx_log_level) or "", main_conf, + http_conf, stream_conf, inline_code) local _, code = pl_utils.execute(resty_cmd) os.exit(code) diff --git a/changelog/unreleased/kong/fix-cmd-error-log.yml b/changelog/unreleased/kong/fix-cmd-error-log.yml new file mode 100644 index 000000000000..0c74fedac3f4 --- /dev/null +++ b/changelog/unreleased/kong/fix-cmd-error-log.yml @@ -0,0 +1,4 @@ +message: | + Fixed an issue where some debug level error logs were not being displayed by the CLI. +type: bugfix +scope: CLI Command diff --git a/spec/02-integration/02-cmd/16-verbose_spec.lua b/spec/02-integration/02-cmd/16-verbose_spec.lua new file mode 100644 index 000000000000..b0993c8dfa44 --- /dev/null +++ b/spec/02-integration/02-cmd/16-verbose_spec.lua @@ -0,0 +1,11 @@ +local helpers = require "spec.helpers" +local meta = require "kong.meta" + +describe("kong cli verbose output", function() + it("--vv outputs debug level log", function() + local _, stderr, stdout = assert(helpers.kong_exec("version --vv")) + -- globalpatches debug log will be printed by upper level resty command that runs kong.cmd + assert.matches("installing the globalpatches", stderr) + assert.matches("Kong: " .. meta._VERSION, stdout) + end) +end) From 4adb6772a13e036f770ef6110c665e663db8cade Mon Sep 17 00:00:00 2001 From: Niklaus Schen <8458369+Water-Melon@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:25:53 +0800 Subject: [PATCH 21/34] tests(helpers): specify a larger buffer to avoid read stalling issues caused by insufficient buffer size (#13163) --- spec/helpers.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/helpers.lua b/spec/helpers.lua index 5cedb14fee35..71eab581cc76 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -3312,7 +3312,8 @@ luassert:register("assertion", "partial_match", partial_match, -- (ok, code, stdout, stderr); if `returns` is false, -- returns either (false, stderr) or (true, stderr, stdout). function exec(cmd, returns) - local ok, stdout, stderr, _, code = shell.run(cmd, nil, 0) + --100MB for retrieving stdout & stderr + local ok, stdout, stderr, _, code = shell.run(cmd, nil, 0, 1024*1024*100) if returns then return ok, code, stdout, stderr end From 22c96a27569d726126512c84a242eb7d25ea1f9c Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 5 Jun 2024 09:30:14 -0700 Subject: [PATCH 22/34] feat(wasm): add support for wasmtime cache (#12930) * feat(wasm): add support for wasmtime cache This adds support for Wasmtime's module caching. See also: * https://github.com/Kong/ngx_wasm_module/pull/540 * https://github.com/Kong/ngx_wasm_module/blob/b19d405403ca6765c548e571010aea3af1accaea/docs/DIRECTIVES.md?plain=1#L136-L149 * https://docs.wasmtime.dev/cli-cache.html * tests(wasm): add start/restart test for wasmtime cache --- .../unreleased/kong/wasm-module-cache.yml | 3 + kong-3.8.0-0.rockspec | 1 + kong/cmd/utils/prefix_handler.lua | 30 +++- kong/conf_loader/init.lua | 6 + kong/templates/nginx.lua | 6 +- kong/templates/wasmtime_cache_config.lua | 10 ++ spec/01-unit/03-conf_loader_spec.lua | 13 ++ spec/01-unit/04-prefix_handler_spec.lua | 8 + .../20-wasm/10-wasmtime_spec.lua | 169 ++++++++++++++++++ spec/fixtures/custom_nginx.template | 6 +- 10 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/kong/wasm-module-cache.yml create mode 100644 kong/templates/wasmtime_cache_config.lua create mode 100644 spec/02-integration/20-wasm/10-wasmtime_spec.lua diff --git a/changelog/unreleased/kong/wasm-module-cache.yml b/changelog/unreleased/kong/wasm-module-cache.yml new file mode 100644 index 000000000000..1b9bd0c8119b --- /dev/null +++ b/changelog/unreleased/kong/wasm-module-cache.yml @@ -0,0 +1,3 @@ +message: Configure Wasmtime module cache when Wasm is enabled +type: feature +scope: Configuration diff --git a/kong-3.8.0-0.rockspec b/kong-3.8.0-0.rockspec index 5813d1fb86b0..50f18028eca1 100644 --- a/kong-3.8.0-0.rockspec +++ b/kong-3.8.0-0.rockspec @@ -109,6 +109,7 @@ build = { ["kong.templates.nginx_kong_inject"] = "kong/templates/nginx_kong_inject.lua", ["kong.templates.nginx_kong_stream_inject"] = "kong/templates/nginx_kong_stream_inject.lua", ["kong.templates.kong_yml"] = "kong/templates/kong_yml.lua", + ["kong.templates.wasmtime_cache_config"] = "kong/templates/wasmtime_cache_config.lua", ["kong.resty.dns.client"] = "kong/resty/dns/client.lua", ["kong.resty.dns.utils"] = "kong/resty/dns/utils.lua", diff --git a/kong/cmd/utils/prefix_handler.lua b/kong/cmd/utils/prefix_handler.lua index 189c3a03981c..14ca40f81a1f 100644 --- a/kong/cmd/utils/prefix_handler.lua +++ b/kong/cmd/utils/prefix_handler.lua @@ -5,6 +5,7 @@ local kong_nginx_stream_template = require "kong.templates.nginx_kong_stream" local nginx_main_inject_template = require "kong.templates.nginx_inject" local nginx_http_inject_template = require "kong.templates.nginx_kong_inject" local nginx_stream_inject_template = require "kong.templates.nginx_kong_stream_inject" +local wasmtime_cache_template = require "kong.templates.wasmtime_cache_config" local system_constants = require "lua_system_constants" local process_secrets = require "kong.cmd.utils.process_secrets" local openssl_bignum = require "resty.openssl.bn" @@ -41,6 +42,7 @@ local math = math local join = pl_path.join local io = io local os = os +local fmt = string.format local function pre_create_private_file(file) @@ -235,6 +237,10 @@ local function get_ulimit() end end +local function quote(s) + return fmt("%q", s) +end + local function compile_conf(kong_config, conf_template, template_env_inject) -- computed config properties for templating local compile_env = { @@ -244,7 +250,8 @@ local function compile_conf(kong_config, conf_template, template_env_inject) tostring = tostring, os = { getenv = os.getenv, - } + }, + quote = quote, } local kong_proxy_access_log = kong_config.proxy_access_log @@ -419,6 +426,10 @@ local function compile_nginx_conf(kong_config, template) return compile_conf(kong_config, template) end +local function compile_wasmtime_cache_conf(kong_config) + return compile_conf(kong_config, wasmtime_cache_template) +end + local function prepare_prefixed_interface_dir(usr_path, interface_dir, kong_config) local usr_interface_path = usr_path .. "/" .. interface_dir local interface_path = kong_config.prefix .. "/" .. interface_dir @@ -673,6 +684,23 @@ local function prepare_prefix(kong_config, nginx_custom_template_path, skip_writ return true end + if kong_config.wasm then + if kong_config.wasmtime_cache_directory then + local ok, err = makepath(kong_config.wasmtime_cache_directory) + if not ok then + return nil, err + end + end + + if kong_config.wasmtime_cache_config_file then + local wasmtime_conf, err = compile_wasmtime_cache_conf(kong_config) + if not wasmtime_conf then + return nil, err + end + pl_file.write(kong_config.wasmtime_cache_config_file, wasmtime_conf) + end + end + -- compile Nginx configurations local nginx_template if nginx_custom_template_path then diff --git a/kong/conf_loader/init.lua b/kong/conf_loader/init.lua index 57e793137eb1..6f395b015be6 100644 --- a/kong/conf_loader/init.lua +++ b/kong/conf_loader/init.lua @@ -647,6 +647,12 @@ local function load(path, custom_conf, opts) -- TODO: as a temporary compatibility fix, we are forcing it to 'off'. add_wasm_directive("nginx_http_proxy_wasm_lua_resolver", "off") + -- configure wasmtime module cache + if conf.role == "traditional" or conf.role == "data_plane" then + conf.wasmtime_cache_directory = pl_path.join(conf.prefix, ".wasmtime_cache") + conf.wasmtime_cache_config_file = pl_path.join(conf.prefix, ".wasmtime_config.toml") + end + -- wasm vm properties are inherited from previously set directives if conf.lua_ssl_trusted_certificate and #conf.lua_ssl_trusted_certificate >= 1 then add_wasm_directive("tls_trusted_certificate", conf.lua_ssl_trusted_certificate[1], wasm_main_prefix) diff --git a/kong/templates/nginx.lua b/kong/templates/nginx.lua index 9d29e1ea5a77..108d268f56be 100644 --- a/kong/templates/nginx.lua +++ b/kong/templates/nginx.lua @@ -38,8 +38,12 @@ wasm { > end > end -> if #nginx_wasm_wasmtime_directives > 0 then +> if #nginx_wasm_wasmtime_directives > 0 or wasmtime_cache_config_file then wasmtime { +> if wasmtime_cache_config_file then + cache_config $(quote(wasmtime_cache_config_file)); +> end + > for _, el in ipairs(nginx_wasm_wasmtime_directives) do flag $(el.name) $(el.value); > end diff --git a/kong/templates/wasmtime_cache_config.lua b/kong/templates/wasmtime_cache_config.lua new file mode 100644 index 000000000000..fdc169917356 --- /dev/null +++ b/kong/templates/wasmtime_cache_config.lua @@ -0,0 +1,10 @@ +return [[ +# ************************* +# * DO NOT EDIT THIS FILE * +# ************************* +# This configuration file is auto-generated. +# Any modifications made here will be lost. +[cache] +enabled = true +directory = $(quote(wasmtime_cache_directory)) +]] diff --git a/spec/01-unit/03-conf_loader_spec.lua b/spec/01-unit/03-conf_loader_spec.lua index 10e0403d254e..47c96492e443 100644 --- a/spec/01-unit/03-conf_loader_spec.lua +++ b/spec/01-unit/03-conf_loader_spec.lua @@ -2105,6 +2105,19 @@ describe("Configuration loader", function() assert.is_true(found, "expected the user filter to be enabled") end) + it("populates wasmtime_cache_* properties", function() + local conf, err = conf_loader(nil, { + wasm = "on", + wasm_filters = "bundled,user", + wasm_filters_path = temp_dir, + }) + assert.is_nil(err) + + assert.is_string(conf.wasmtime_cache_directory, + "wasmtime_cache_directory was not set") + assert.is_string(conf.wasmtime_cache_config_file, + "wasmtime_cache_config_file was not set") + end) end) describe("errors", function() diff --git a/spec/01-unit/04-prefix_handler_spec.lua b/spec/01-unit/04-prefix_handler_spec.lua index 521d2223a421..40dca3dd4749 100644 --- a/spec/01-unit/04-prefix_handler_spec.lua +++ b/spec/01-unit/04-prefix_handler_spec.lua @@ -1024,6 +1024,14 @@ describe("NGINX conf compiler", function() }, debug) ) end) + it("injects wasmtime cache_config", function() + assert.matches( + "wasm {.+wasmtime {.+cache_config .+%.wasmtime_config%.toml.*;", + ngx_cfg({ + wasm = true, + }, debug) + ) + end) describe("injects inherited directives", function() it("only if one isn't explicitly set", function() assert.matches( diff --git a/spec/02-integration/20-wasm/10-wasmtime_spec.lua b/spec/02-integration/20-wasm/10-wasmtime_spec.lua new file mode 100644 index 000000000000..7a1ba07c185c --- /dev/null +++ b/spec/02-integration/20-wasm/10-wasmtime_spec.lua @@ -0,0 +1,169 @@ +local helpers = require "spec.helpers" +local fmt = string.format + +for _, role in ipairs({"traditional", "control_plane", "data_plane"}) do + +describe("#wasm wasmtime (role: " .. role .. ")", function() + describe("kong prepare", function() + local conf + local prefix = "./wasm" + + lazy_setup(function() + helpers.clean_prefix(prefix) + assert(helpers.kong_exec("prepare", { + database = role == "data_plane" and "off" or "postgres", + nginx_conf = "spec/fixtures/custom_nginx.template", + wasm = true, + prefix = prefix, + role = role, + cluster_cert = "spec/fixtures/kong_clustering.crt", + cluster_cert_key = "spec/fixtures/kong_clustering.key", + })) + + conf = assert(helpers.get_running_conf(prefix)) + end) + + lazy_teardown(function() + helpers.clean_prefix(prefix) + end) + + if role == "control_plane" then + it("does not populate wasmtime config values", function() + assert.is_nil(conf.wasmtime_cache_directory, + "wasmtime_cache_directory should not be set") + assert.is_nil(conf.wasmtime_cache_config_file, + "wasmtime_cache_config_file should not be set") + end) + + else + it("populates wasmtime config values", function() + assert.is_string(conf.wasmtime_cache_directory, + "wasmtime_cache_directory was not set") + assert.is_string(conf.wasmtime_cache_config_file, + "wasmtime_cache_config_file was not set") + end) + + it("creates the cache directory", function() + assert(helpers.path.isdir(conf.wasmtime_cache_directory), + fmt("expected cache directory (%s) to exist", + conf.wasmtime_cache_directory)) + end) + + it("creates the cache config file", function() + assert(helpers.path.isfile(conf.wasmtime_cache_config_file), + fmt("expected cache config file (%s) to exist", + conf.wasmtime_cache_config_file)) + + local cache_config = assert(helpers.file.read(conf.wasmtime_cache_config_file)) + assert.matches(conf.wasmtime_cache_directory, cache_config, nil, true, + "expected cache config file to reference the cache directory") + end) + end + end) -- kong prepare + + describe("kong stop/start/restart", function() + local conf + local prefix = "./wasm" + local log = prefix .. "/logs/error.log" + local status_port + local client + local cp_prefix = "./wasm-cp" + + lazy_setup(function() + if role == "traditional" then + helpers.get_db_utils("postgres") + end + + helpers.clean_prefix(prefix) + status_port = helpers.get_available_port() + + assert(helpers.kong_exec("prepare", { + database = role == "data_plane" and "off" or "postgres", + nginx_conf = "spec/fixtures/custom_nginx.template", + wasm = true, + prefix = prefix, + role = role, + --wasm_filters_path = helpers.test_conf.wasm_filters_path, + wasm_filters = "tests,response_transformer", + cluster_cert = "spec/fixtures/kong_clustering.crt", + cluster_cert_key = "spec/fixtures/kong_clustering.key", + + status_listen = "127.0.0.1:" .. status_port, + nginx_main_worker_processes = 2, + })) + + conf = assert(helpers.get_running_conf(prefix)) + + -- we need to briefly spin up a control plane, or else we will get + -- error.log entries when our data plane tries to connect + if role == "data_plane" then + helpers.get_db_utils("postgres") + + assert(helpers.start_kong({ + database = "postgres", + nginx_conf = "spec/fixtures/custom_nginx.template", + wasm = true, + prefix = cp_prefix, + role = "control_plane", + wasm_filters = "tests,response_transformer", + cluster_cert = "spec/fixtures/kong_clustering.crt", + cluster_cert_key = "spec/fixtures/kong_clustering.key", + status_listen = "off", + nginx_main_worker_processes = 2, + })) + end + end) + + lazy_teardown(function() + if client then + client:close() + end + + helpers.stop_kong(prefix) + + if role == "data_plane" then + helpers.stop_kong(cp_prefix) + end + end) + + it("does not introduce any errors", function() + local function assert_no_errors() + assert.logfile(log).has.no.line("[error]", true, 0) + assert.logfile(log).has.no.line("[alert]", true, 0) + assert.logfile(log).has.no.line("[emerg]", true, 0) + assert.logfile(log).has.no.line("[crit]", true, 0) + end + + local function assert_kong_status(context) + if not client then + client = helpers.proxy_client(1000, status_port) + client.reopen = true + end + + assert.eventually(function() + local res, err = client:send({ path = "/status", method = "GET" }) + if res and res.status == 200 then + return true + end + + return nil, err or "non-200 status" + end) + .is_truthy("failed waiting for kong status " .. context) + end + + assert(helpers.start_kong(conf, nil, true)) + assert_no_errors() + + assert_kong_status("after fresh startup") + assert_no_errors() + + assert(helpers.restart_kong(conf)) + assert_no_errors() + + assert_kong_status("after restart") + assert_no_errors() + end) + end) -- kong stop/start/restart + +end) -- wasmtime +end -- each role diff --git a/spec/fixtures/custom_nginx.template b/spec/fixtures/custom_nginx.template index d2df0e5f7735..98e4b4bebefd 100644 --- a/spec/fixtures/custom_nginx.template +++ b/spec/fixtures/custom_nginx.template @@ -48,8 +48,12 @@ wasm { > end > end -> if #nginx_wasm_wasmtime_directives > 0 then +> if #nginx_wasm_wasmtime_directives > 0 or wasmtime_cache_config_file then wasmtime { +> if wasmtime_cache_config_file then + cache_config $(quote(wasmtime_cache_config_file)); +> end + > for _, el in ipairs(nginx_wasm_wasmtime_directives) do flag $(el.name) $(el.value); > end From c4a25e80aa8299bc649df70540306ba3f7c597bc Mon Sep 17 00:00:00 2001 From: Chrono Date: Thu, 6 Jun 2024 14:17:50 +0800 Subject: [PATCH 23/34] style(conf_loader): add "jo" option for `ngx.re.sub` (#13169) --- kong/conf_loader/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong/conf_loader/init.lua b/kong/conf_loader/init.lua index 6f395b015be6..be077ee747dc 100644 --- a/kong/conf_loader/init.lua +++ b/kong/conf_loader/init.lua @@ -141,7 +141,7 @@ local function load_config(thing) -- remove trailing comment, if any -- and remove escape chars from octothorpes if value then - value = ngx.re.sub(value, [[\s*(? Date: Thu, 6 Jun 2024 15:39:32 +0800 Subject: [PATCH 24/34] tests(cmd): match kong version in cmd verbose test should use plain match (#13174) --- spec/02-integration/02-cmd/16-verbose_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/02-integration/02-cmd/16-verbose_spec.lua b/spec/02-integration/02-cmd/16-verbose_spec.lua index b0993c8dfa44..6257e7b85b69 100644 --- a/spec/02-integration/02-cmd/16-verbose_spec.lua +++ b/spec/02-integration/02-cmd/16-verbose_spec.lua @@ -6,6 +6,6 @@ describe("kong cli verbose output", function() local _, stderr, stdout = assert(helpers.kong_exec("version --vv")) -- globalpatches debug log will be printed by upper level resty command that runs kong.cmd assert.matches("installing the globalpatches", stderr) - assert.matches("Kong: " .. meta._VERSION, stdout) + assert.matches("Kong: " .. meta._VERSION, stdout, nil, true) end) end) From 406acd3a801ee78f5ba8d8cb6c1f179ccae8ada9 Mon Sep 17 00:00:00 2001 From: Qi Date: Thu, 30 May 2024 08:48:40 +0000 Subject: [PATCH 25/34] refactor(pdk): avoid create EMPTY table for each call of `kong.log.serialize()` --- kong/pdk/log.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kong/pdk/log.lua b/kong/pdk/log.lua index a4197a449555..9b71122f7705 100644 --- a/kong/pdk/log.lua +++ b/kong/pdk/log.lua @@ -41,6 +41,7 @@ local byte = string.byte local request_id_get = require "kong.tracing.request_id".get +local EMPTY_TAB = require("pl.tablex").readonly({}) local _PREFIX = "[kong] " local _DEFAULT_FORMAT = "%file_src:%line_src %message" local _DEFAULT_NAMESPACED_FORMAT = "%file_src:%line_src [%namespace] %message" @@ -794,7 +795,7 @@ do function serialize(options) check_phase(PHASES_LOG) - options = options or {} + options = options or EMPTY_TAB local ongx = options.ngx or ngx local okong = options.kong or kong @@ -850,7 +851,7 @@ do request = tonumber(var.request_time) * 1000, receive = ctx.KONG_RECEIVE_TIME or 0, }, - tries = (ctx.balancer_data or {}).tries, + tries = (ctx.balancer_data or EMPTY_TAB).tries, authenticated_entity = build_authenticated_entity(ctx), route = cycle_aware_deep_copy(ctx.route), service = cycle_aware_deep_copy(ctx.service), @@ -870,7 +871,7 @@ do function serialize(options) check_phase(PHASES_LOG) - options = options or {} + options = options or EMPTY_TAB local ongx = options.ngx or ngx local okong = options.kong or kong @@ -895,7 +896,7 @@ do kong = ctx.KONG_PROXY_LATENCY or ctx.KONG_RESPONSE_LATENCY or 0, session = var.session_time * 1000, }, - tries = (ctx.balancer_data or {}).tries, + tries = (ctx.balancer_data or EMPTY_TAB).tries, authenticated_entity = build_authenticated_entity(ctx), route = cycle_aware_deep_copy(ctx.route), service = cycle_aware_deep_copy(ctx.service), From 34240ac871cae59fe0cd07393a4fcf2a0ec5e36e Mon Sep 17 00:00:00 2001 From: Qi Date: Thu, 6 Jun 2024 14:08:48 +0800 Subject: [PATCH 26/34] refactor(pdk): localize some functions --- kong/pdk/log.lua | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/kong/pdk/log.lua b/kong/pdk/log.lua index 9b71122f7705..212055010e66 100644 --- a/kong/pdk/log.lua +++ b/kong/pdk/log.lua @@ -10,15 +10,17 @@ -- @module kong.log -local buffer = require "string.buffer" -local errlog = require "ngx.errlog" -local ngx_re = require "ngx.re" -local inspect = require "inspect" -local ngx_ssl = require "ngx.ssl" -local phase_checker = require "kong.pdk.private.phases" +local buffer = require("string.buffer") +local errlog = require("ngx.errlog") +local ngx_re = require("ngx.re") +local inspect = require("inspect") +local phase_checker = require("kong.pdk.private.phases") +local constants = require("kong.constants") + +local request_id_get = require("kong.tracing.request_id").get local cycle_aware_deep_copy = require("kong.tools.table").cycle_aware_deep_copy -local constants = require "kong.constants" -local workspace = require "kong.workspaces" +local get_tls1_version_str = require("ngx.ssl").get_tls1_version_str +local get_workspace_name = require("kong.workspaces").get_workspace_name local sub = string.sub local type = type @@ -38,7 +40,6 @@ local kong = kong local check_phase = phase_checker.check local split = require("kong.tools.string").split local byte = string.byte -local request_id_get = require "kong.tracing.request_id".get local EMPTY_TAB = require("pl.tablex").readonly({}) @@ -716,7 +717,7 @@ do local function build_tls_info(var, override) local tls_info - local tls_info_ver = ngx_ssl.get_tls1_version_str() + local tls_info_ver = get_tls1_version_str() if tls_info_ver then tls_info = { version = tls_info_ver, @@ -861,7 +862,7 @@ do source = response_source_name, workspace = ctx.workspace, - workspace_name = workspace.get_workspace_name(), + workspace_name = get_workspace_name(), } return edit_result(ctx, root) @@ -905,7 +906,7 @@ do started_at = okong.request.get_start_time(), workspace = ctx.workspace, - workspace_name = workspace.get_workspace_name(), + workspace_name = get_workspace_name(), } return edit_result(ctx, root) From f77db1f3bc3a60775a2a88c5bd2faac442f980fa Mon Sep 17 00:00:00 2001 From: Qi Date: Thu, 6 Jun 2024 14:14:47 +0800 Subject: [PATCH 27/34] refactor(pdk): cache a varable for reducing table-lookup --- kong/pdk/log.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kong/pdk/log.lua b/kong/pdk/log.lua index 212055010e66..e6f0fe3242fb 100644 --- a/kong/pdk/log.lua +++ b/kong/pdk/log.lua @@ -799,6 +799,7 @@ do options = options or EMPTY_TAB local ongx = options.ngx or ngx local okong = options.kong or kong + local okong_request = okong.request local ctx = ongx.ctx local var = ongx.var @@ -833,9 +834,9 @@ do id = request_id_get() or "", uri = request_uri, url = url, - querystring = okong.request.get_query(), -- parameters, as a table - method = okong.request.get_method(), -- http method - headers = okong.request.get_headers(), + querystring = okong_request.get_query(), -- parameters, as a table + method = okong_request.get_method(), -- http method + headers = okong_request.get_headers(), size = to_decimal(var.request_length), tls = build_tls_info(var, ctx.CLIENT_VERIFY_OVERRIDE), }, @@ -858,7 +859,7 @@ do service = cycle_aware_deep_copy(ctx.service), consumer = cycle_aware_deep_copy(ctx.authenticated_consumer), client_ip = var.remote_addr, - started_at = okong.request.get_start_time(), + started_at = okong_request.get_start_time(), source = response_source_name, workspace = ctx.workspace, From 5ef398bea6a1eaead7ecc8e29b7bc50758c198dd Mon Sep 17 00:00:00 2001 From: Qi Date: Thu, 6 Jun 2024 14:25:52 +0800 Subject: [PATCH 28/34] feat(pdk): add an option to skip fetching req/resp headers THIS IS AN INTERNAL ONLY FLAG TO SKIP FETCHING HEADERS, AND THIS FLAG MIGHT BE REMOVED IN THE FUTURE WITHOUT ANY NOTICE AND DEPRECATION. --- kong/pdk/log.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kong/pdk/log.lua b/kong/pdk/log.lua index e6f0fe3242fb..2c3dabc1e9fe 100644 --- a/kong/pdk/log.lua +++ b/kong/pdk/log.lua @@ -817,6 +817,15 @@ do end end + local request_headers, response_headers = nil, nil + -- THIS IS AN INTERNAL ONLY FLAG TO SKIP FETCHING HEADERS, + -- AND THIS FLAG MIGHT BE REMOVED IN THE FUTURE + -- WITHOUT ANY NOTICE AND DEPRECATION. + if not options.__skip_fetch_headers__ then + request_headers = okong_request.get_headers() + response_headers = ongx.resp.get_headers() + end + local upstream_status = var.upstream_status or ctx.buffered_status or "" local response_source = okong.response.get_source(ongx.ctx) @@ -836,7 +845,7 @@ do url = url, querystring = okong_request.get_query(), -- parameters, as a table method = okong_request.get_method(), -- http method - headers = okong_request.get_headers(), + headers = request_headers, size = to_decimal(var.request_length), tls = build_tls_info(var, ctx.CLIENT_VERIFY_OVERRIDE), }, @@ -844,7 +853,7 @@ do upstream_status = upstream_status, response = { status = ongx.status, - headers = ongx.resp.get_headers(), + headers = response_headers, size = to_decimal(var.bytes_sent), }, latencies = { From e5efc733b4ef8a9232a2336ae443f105efa9556c Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Thu, 6 Jun 2024 14:19:01 +0300 Subject: [PATCH 29/34] chore(deps): bump nfpm from 2.31.0 to 2.37.1 (#13118) ### Summary See: https://github.com/goreleaser/nfpm/releases Signed-off-by: Aapo Talvensaari --- build/nfpm/repositories.bzl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/nfpm/repositories.bzl b/build/nfpm/repositories.bzl index b865a6e89bd6..c92cc9de0df9 100644 --- a/build/nfpm/repositories.bzl +++ b/build/nfpm/repositories.bzl @@ -36,15 +36,15 @@ nfpm_release_select = repository_rule( def nfpm_repositories(): npfm_matrix = [ - ["linux", "x86_64", "6dd3b07d4d6ee373baea5b5fca179ebf78dec38c9a55392bae34040e596e4de7"], - ["linux", "arm64", "e6487dca9d9e9b1781fe7fa0a3d844e70cf12d92f3b5fc0c4ff771aa776b05ca"], - ["Darwin", "x86_64", "19954ef8e6bfa0607efccd0a97452b6d571830665bd76a2f9957413f93f9d8cd"], - ["Darwin", "arm64", "9fd82cda017cdfd49b010199a2eed966d0a645734d9a6bf932c4ef82c8c12c96"], + ["linux", "x86_64", "3e1fe85c9a224a221c64cf72fc19e7cd6a0a51a5c4f4b336e3b8eccd417116a3"], + ["linux", "arm64", "df8f272195b7ddb09af9575673a9b8111f9eb7529cdd0a3fac4d44b52513a1e1"], + ["Darwin", "x86_64", "0213fa5d5af6f209d953c963103f9b6aec8a0e89d4bf0ab3d531f5f8b20b8eeb"], + ["Darwin", "arm64", "5162ce5a59fe8d3b511583cb604c34d08bd2bcced87d9159c7005fc35287b9cd"], ] for name, arch, sha in npfm_matrix: http_archive( name = "nfpm_%s_%s" % (name, arch), - url = "https://github.com/goreleaser/nfpm/releases/download/v2.31.0/nfpm_2.31.0_%s_%s.tar.gz" % (name, arch), + url = "https://github.com/goreleaser/nfpm/releases/download/v2.37.1/nfpm_2.37.1_%s_%s.tar.gz" % (name, arch), sha256 = sha, build_file = "//build/nfpm:BUILD.bazel", ) From 18a189baff77ed23a32ec7a01d45d116aa6d6fce Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Thu, 6 Jun 2024 17:37:10 +0200 Subject: [PATCH 30/34] fix(ai-proxy): prevent shared state (#13028) The object initializer (new) would set properties on class instead of on the instance. Also cleans up some code. AG-44 --- .../kong/fix-ai-proxy-shared-state.yml | 3 + kong-3.8.0-0.rockspec | 1 + kong/llm/init.lua | 516 ++++++------------ kong/llm/schemas/init.lua | 226 ++++++++ kong/plugins/ai-proxy/handler.lua | 176 +++--- .../ai-request-transformer/handler.lua | 10 +- .../ai-response-transformer/handler.lua | 16 +- .../01-transformer_spec.lua | 20 +- .../01-transformer_spec.lua | 12 +- .../02-integration_spec.lua | 14 +- 10 files changed, 528 insertions(+), 466 deletions(-) create mode 100644 changelog/unreleased/kong/fix-ai-proxy-shared-state.yml create mode 100644 kong/llm/schemas/init.lua diff --git a/changelog/unreleased/kong/fix-ai-proxy-shared-state.yml b/changelog/unreleased/kong/fix-ai-proxy-shared-state.yml new file mode 100644 index 000000000000..bb967a946566 --- /dev/null +++ b/changelog/unreleased/kong/fix-ai-proxy-shared-state.yml @@ -0,0 +1,3 @@ +message: "**AI-Proxy**: Resolved a bug where the object constructor would set data on the class instead of the instance" +type: bugfix +scope: Plugin diff --git a/kong-3.8.0-0.rockspec b/kong-3.8.0-0.rockspec index 50f18028eca1..52a46d65d6bd 100644 --- a/kong-3.8.0-0.rockspec +++ b/kong-3.8.0-0.rockspec @@ -593,6 +593,7 @@ build = { ["kong.plugins.ai-response-transformer.schema"] = "kong/plugins/ai-response-transformer/schema.lua", ["kong.llm"] = "kong/llm/init.lua", + ["kong.llm.schemas"] = "kong/llm/schemas/init.lua", ["kong.llm.drivers.shared"] = "kong/llm/drivers/shared.lua", ["kong.llm.drivers.openai"] = "kong/llm/drivers/openai.lua", ["kong.llm.drivers.azure"] = "kong/llm/drivers/azure.lua", diff --git a/kong/llm/init.lua b/kong/llm/init.lua index af3833ff44f1..aaf3af08a790 100644 --- a/kong/llm/init.lua +++ b/kong/llm/init.lua @@ -1,393 +1,225 @@ --- imports -local typedefs = require("kong.db.schema.typedefs") -local fmt = string.format -local cjson = require("cjson.safe") -local re_match = ngx.re.match local ai_shared = require("kong.llm.drivers.shared") --- - -local _M = {} - -local auth_schema = { - type = "record", - required = false, - fields = { - { header_name = { - type = "string", - description = "If AI model requires authentication via Authorization or API key header, specify its name here.", - required = false, - referenceable = true }}, - { header_value = { - type = "string", - description = "Specify the full auth header value for 'header_name', for example 'Bearer key' or just 'key'.", - required = false, - encrypted = true, -- [[ ee declaration ]] - referenceable = true }}, - { param_name = { - type = "string", - description = "If AI model requires authentication via query parameter, specify its name here.", - required = false, - referenceable = true }}, - { param_value = { - type = "string", - description = "Specify the full parameter value for 'param_name'.", - required = false, - encrypted = true, -- [[ ee declaration ]] - referenceable = true }}, - { param_location = { - type = "string", - description = "Specify whether the 'param_name' and 'param_value' options go in a query string, or the POST form/JSON body.", - required = false, - one_of = { "query", "body" } }}, - } -} +local re_match = ngx.re.match +local cjson = require("cjson.safe") +local fmt = string.format +local EMPTY = {} -local model_options_schema = { - description = "Key/value settings for the model", - type = "record", - required = false, - fields = { - { max_tokens = { - type = "integer", - description = "Defines the max_tokens, if using chat or completion models.", - required = false, - default = 256 }}, - { temperature = { - type = "number", - description = "Defines the matching temperature, if using chat or completion models.", - required = false, - between = { 0.0, 5.0 }}}, - { top_p = { - type = "number", - description = "Defines the top-p probability mass, if supported.", - required = false, - between = { 0, 1 }}}, - { top_k = { - type = "integer", - description = "Defines the top-k most likely tokens, if supported.", - required = false, - between = { 0, 500 }}}, - { anthropic_version = { - type = "string", - description = "Defines the schema/API version, if using Anthropic provider.", - required = false }}, - { azure_instance = { - type = "string", - description = "Instance name for Azure OpenAI hosted models.", - required = false }}, - { azure_api_version = { - type = "string", - description = "'api-version' for Azure OpenAI instances.", - required = false, - default = "2023-05-15" }}, - { azure_deployment_id = { - type = "string", - description = "Deployment ID for Azure OpenAI instances.", - required = false }}, - { llama2_format = { - type = "string", - description = "If using llama2 provider, select the upstream message format.", - required = false, - one_of = { "raw", "openai", "ollama" }}}, - { mistral_format = { - type = "string", - description = "If using mistral provider, select the upstream message format.", - required = false, - one_of = { "openai", "ollama" }}}, - { upstream_url = typedefs.url { - description = "Manually specify or override the full URL to the AI operation endpoints, " - .. "when calling (self-)hosted models, or for running via a private endpoint.", - required = false }}, - { upstream_path = { - description = "Manually specify or override the AI operation path, " - .. "used when e.g. using the 'preserve' route_type.", - type = "string", - required = false }}, - } -} -local model_schema = { - type = "record", - required = true, - fields = { - { provider = { - type = "string", description = "AI provider request format - Kong translates " - .. "requests to and from the specified backend compatible formats.", - required = true, - one_of = { "openai", "azure", "anthropic", "cohere", "mistral", "llama2" }}}, - { name = { - type = "string", - description = "Model name to execute.", - required = false }}, - { options = model_options_schema }, - } +-- The module table +local _M = { + config_schema = require "kong.llm.schemas", } -local logging_schema = { - type = "record", - required = true, - fields = { - { log_statistics = { - type = "boolean", - description = "If enabled and supported by the driver, " - .. "will add model usage and token metrics into the Kong log plugin(s) output.", - required = true, - default = false }}, - { log_payloads = { - type = "boolean", - description = "If enabled, will log the request and response body into the Kong log plugin(s) output.", - required = true, default = false }}, - } -} -local UNSUPPORTED_LOG_STATISTICS = { - ["llm/v1/completions"] = { ["anthropic"] = true }, -} -_M.config_schema = { - type = "record", - fields = { - { route_type = { - type = "string", - description = "The model's operation implementation, for this provider. " .. - "Set to `preserve` to pass through without transformation.", - required = true, - one_of = { "llm/v1/chat", "llm/v1/completions", "preserve" } }}, - { auth = auth_schema }, - { model = model_schema }, - { logging = logging_schema }, - }, - entity_checks = { - -- these three checks run in a chain, to ensure that all auth params for each respective "set" are specified - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "openai", "azure", "anthropic", "cohere" } }, - then_at_least_one_of = { "auth.header_name", "auth.param_name" }, - then_err = "must set one of %s, and its respective options, when provider is not self-hosted" }}, - - { mutually_required = { "auth.header_name", "auth.header_value" }, }, - { mutually_required = { "auth.param_name", "auth.param_value", "auth.param_location" }, }, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "llama2" } }, - then_at_least_one_of = { "model.options.llama2_format" }, - then_err = "must set %s for llama2 provider" }}, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "mistral" } }, - then_at_least_one_of = { "model.options.mistral_format" }, - then_err = "must set %s for mistral provider" }}, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "anthropic" } }, - then_at_least_one_of = { "model.options.anthropic_version" }, - then_err = "must set %s for anthropic provider" }}, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "azure" } }, - then_at_least_one_of = { "model.options.azure_instance" }, - then_err = "must set %s for azure provider" }}, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "azure" } }, - then_at_least_one_of = { "model.options.azure_api_version" }, - then_err = "must set %s for azure provider" }}, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "azure" } }, - then_at_least_one_of = { "model.options.azure_deployment_id" }, - then_err = "must set %s for azure provider" }}, - - { conditional_at_least_one_of = { if_field = "model.provider", - if_match = { one_of = { "mistral", "llama2" } }, - then_at_least_one_of = { "model.options.upstream_url" }, - then_err = "must set %s for self-hosted providers/models" }}, - - { - custom_entity_check = { - field_sources = { "route_type", "model", "logging" }, - fn = function(entity) - if entity.logging.log_statistics and UNSUPPORTED_LOG_STATISTICS[entity.route_type] - and UNSUPPORTED_LOG_STATISTICS[entity.route_type][entity.model.provider] then - return nil, fmt("%s does not support statistics when route_type is %s", - entity.model.provider, entity.route_type) - - else - return true - end - end, - } +do + -- formats_compatible is a map of formats that are compatible with each other. + local formats_compatible = { + ["llm/v1/chat"] = { + ["llm/v1/chat"] = true, }, - }, -} + ["llm/v1/completions"] = { + ["llm/v1/completions"] = true, + }, + } -local formats_compatible = { - ["llm/v1/chat"] = { - ["llm/v1/chat"] = true, - }, - ["llm/v1/completions"] = { - ["llm/v1/completions"] = true, - }, -} -local function identify_request(request) - -- primitive request format determination - local formats = {} - if request.messages - and type(request.messages) == "table" - and #request.messages > 0 - then - table.insert(formats, "llm/v1/chat") - end - - if request.prompt - and type(request.prompt) == "string" - then - table.insert(formats, "llm/v1/completions") - end + -- identify_request determines the format of the request. + -- It returns the format, or nil and an error message. + -- @tparam table request The request to identify + -- @treturn[1] string The format of the request + -- @treturn[2] nil + -- @treturn[2] string An error message if unidentified, or matching multiple formats + local function identify_request(request) + -- primitive request format determination + local formats = {} - if #formats > 1 then - return nil, "request matches multiple LLM request formats" - elseif not formats_compatible[formats[1]] then - return nil, "request format not recognised" - else - return formats[1] - end -end + if type(request.messages) == "table" and #request.messages > 0 then + table.insert(formats, "llm/v1/chat") + end -function _M.is_compatible(request, route_type) - if route_type == "preserve" then - return true - end + if type(request.prompt) == "string" then + table.insert(formats, "llm/v1/completions") + end - local format, err = identify_request(request) - if err then - return nil, err + if formats[2] then + return nil, "request matches multiple LLM request formats" + elseif not formats_compatible[formats[1] or false] then + return nil, "request format not recognised" + else + return formats[1] + end end - if formats_compatible[format][route_type] then - return true - end - return false, fmt("[%s] message format is not compatible with [%s] route type", format, route_type) -end -function _M:ai_introspect_body(request, system_prompt, http_opts, response_regex_match) - local err, _ + --- Check if a request is compatible with a route type. + -- @tparam table request The request to check + -- @tparam string route_type The route type to check against, eg. "llm/v1/chat" + -- @treturn[1] boolean True if compatible + -- @treturn[2] boolean False if not compatible + -- @treturn[2] string Error message if not compatible + -- @treturn[3] nil + -- @treturn[3] string Error message if request format is not recognised + function _M.is_compatible(request, route_type) + if route_type == "preserve" then + return true + end - -- set up the request - local ai_request = { - messages = { - [1] = { - role = "system", - content = system_prompt, - }, - [2] = { - role = "user", - content = request, - } - }, - stream = false, - } + local format, err = identify_request(request) + if err then + return nil, err + end + + if formats_compatible[format][route_type] then + return true + end - -- convert it to the specified driver format - ai_request, _, err = self.driver.to_format(ai_request, self.conf.model, "llm/v1/chat") - if err then - return nil, err + return false, fmt("[%s] message format is not compatible with [%s] route type", format, route_type) end +end - -- run the shared logging/analytics/auth function - ai_shared.pre_request(self.conf, ai_request) - -- send it to the ai service - local ai_response, _, err = self.driver.subrequest(ai_request, self.conf, http_opts, false) - if err then - return nil, "failed to introspect request with AI service: " .. err - end +do + ------------------------------------------------------------------------------ + -- LLM class implementation + ------------------------------------------------------------------------------ + local LLM = {} + LLM.__index = LLM - -- parse and convert the response - local ai_response, _, err = self.driver.from_format(ai_response, self.conf.model, self.conf.route_type) - if err then - return nil, "failed to convert AI response to Kong format: " .. err - end - -- run the shared logging/analytics function - ai_shared.post_request(self.conf, ai_response) - local ai_response, err = cjson.decode(ai_response) - if err then - return nil, "failed to convert AI response to JSON: " .. err - end + function LLM:ai_introspect_body(request, system_prompt, http_opts, response_regex_match) + local err, _ - local new_request_body = ai_response.choices - and #ai_response.choices > 0 - and ai_response.choices[1] - and ai_response.choices[1].message - and ai_response.choices[1].message.content - if not new_request_body then - return nil, "no 'choices' in upstream AI service response" - end + -- set up the request + local ai_request = { + messages = { + [1] = { + role = "system", + content = system_prompt, + }, + [2] = { + role = "user", + content = request, + } + }, + stream = false, + } - -- if specified, extract the first regex match from the AI response - -- this is useful for AI models that pad with assistant text, even when - -- we ask them NOT to. - if response_regex_match then - local matches, err = re_match(new_request_body, response_regex_match, "ijom") + -- convert it to the specified driver format + ai_request, _, err = self.driver.to_format(ai_request, self.conf.model, "llm/v1/chat") if err then - return nil, "failed regex matching ai response: " .. err + return nil, err end - if matches then - new_request_body = matches[0] -- this array DOES start at 0, for some reason + -- run the shared logging/analytics/auth function + ai_shared.pre_request(self.conf, ai_request) - else - return nil, "AI response did not match specified regular expression" + -- send it to the ai service + local ai_response, _, err = self.driver.subrequest(ai_request, self.conf, http_opts, false) + if err then + return nil, "failed to introspect request with AI service: " .. err + end + -- parse and convert the response + local ai_response, _, err = self.driver.from_format(ai_response, self.conf.model, self.conf.route_type) + if err then + return nil, "failed to convert AI response to Kong format: " .. err end - end - return new_request_body -end + -- run the shared logging/analytics function + ai_shared.post_request(self.conf, ai_response) -function _M:parse_json_instructions(in_body) - local err - if type(in_body) == "string" then - in_body, err = cjson.decode(in_body) + local ai_response, err = cjson.decode(ai_response) if err then - return nil, nil, nil, err + return nil, "failed to convert AI response to JSON: " .. err end - end - if type(in_body) ~= "table" then - return nil, nil, nil, "input not table or string" + local new_request_body = ((ai_response.choices or EMPTY)[1].message or EMPTY).content + if not new_request_body then + return nil, "no 'choices' in upstream AI service response" + end + + -- if specified, extract the first regex match from the AI response + -- this is useful for AI models that pad with assistant text, even when + -- we ask them NOT to. + if response_regex_match then + local matches, err = re_match(new_request_body, response_regex_match, "ijom") + if err then + return nil, "failed regex matching ai response: " .. err + end + + if matches then + new_request_body = matches[0] -- this array DOES start at 0, for some reason + + else + return nil, "AI response did not match specified regular expression" + + end + end + + return new_request_body end - return - in_body.headers, - in_body.body or in_body, - in_body.status or 200 -end -function _M:new(conf, http_opts) - local o = {} - setmetatable(o, self) - self.__index = self - self.conf = conf or {} - self.http_opts = http_opts or {} + -- Parse the response instructions. + -- @tparam string|table in_body The response to parse, if a string, it will be parsed as JSON. + -- @treturn[1] table The headers, field `in_body.headers` + -- @treturn[1] string The body, field `in_body.body` (or if absent `in_body` itself as a table) + -- @treturn[1] number The status, field `in_body.status` (or 200 if absent) + -- @treturn[2] nil + -- @treturn[2] string An error message if parsing failed or input wasn't a table + function LLM:parse_json_instructions(in_body) + local err + if type(in_body) == "string" then + in_body, err = cjson.decode(in_body) + if err then + return nil, nil, nil, err + end + end + + if type(in_body) ~= "table" then + return nil, nil, nil, "input not table or string" + end + + return + in_body.headers, + in_body.body or in_body, + in_body.status or 200 + end - local driver = fmt("kong.llm.drivers.%s", conf - and conf.model - and conf.model.provider - or "NONE_SET") - self.driver = require(driver) - if not self.driver then - return nil, fmt("could not instantiate %s package", driver) + --- Instantiate a new LLM driver instance. + -- @tparam table conf Configuration table + -- @tparam table http_opts HTTP options table + -- @treturn[1] table A new LLM driver instance + -- @treturn[2] nil + -- @treturn[2] string An error message if instantiation failed + function _M.new_driver(conf, http_opts) + local self = { + conf = conf or {}, + http_opts = http_opts or {}, + } + setmetatable(self, LLM) + + local provider = (self.conf.model or {}).provider or "NONE_SET" + local driver_module = "kong.llm.drivers." .. provider + local ok + ok, self.driver = pcall(require, driver_module) + if not ok then + local err = "could not instantiate " .. driver_module .. " package" + kong.log.err(err) + return nil, err + end + + return self end - return o end + return _M diff --git a/kong/llm/schemas/init.lua b/kong/llm/schemas/init.lua new file mode 100644 index 000000000000..37b5aaf34761 --- /dev/null +++ b/kong/llm/schemas/init.lua @@ -0,0 +1,226 @@ +local typedefs = require("kong.db.schema.typedefs") +local fmt = string.format + + + +local auth_schema = { + type = "record", + required = false, + fields = { + { header_name = { + type = "string", + description = "If AI model requires authentication via Authorization or API key header, specify its name here.", + required = false, + referenceable = true }}, + { header_value = { + type = "string", + description = "Specify the full auth header value for 'header_name', for example 'Bearer key' or just 'key'.", + required = false, + encrypted = true, -- [[ ee declaration ]] + referenceable = true }}, + { param_name = { + type = "string", + description = "If AI model requires authentication via query parameter, specify its name here.", + required = false, + referenceable = true }}, + { param_value = { + type = "string", + description = "Specify the full parameter value for 'param_name'.", + required = false, + encrypted = true, -- [[ ee declaration ]] + referenceable = true }}, + { param_location = { + type = "string", + description = "Specify whether the 'param_name' and 'param_value' options go in a query string, or the POST form/JSON body.", + required = false, + one_of = { "query", "body" } }}, + } +} + + + +local model_options_schema = { + description = "Key/value settings for the model", + type = "record", + required = false, + fields = { + { max_tokens = { + type = "integer", + description = "Defines the max_tokens, if using chat or completion models.", + required = false, + default = 256 }}, + { temperature = { + type = "number", + description = "Defines the matching temperature, if using chat or completion models.", + required = false, + between = { 0.0, 5.0 }}}, + { top_p = { + type = "number", + description = "Defines the top-p probability mass, if supported.", + required = false, + between = { 0, 1 }}}, + { top_k = { + type = "integer", + description = "Defines the top-k most likely tokens, if supported.", + required = false, + between = { 0, 500 }}}, + { anthropic_version = { + type = "string", + description = "Defines the schema/API version, if using Anthropic provider.", + required = false }}, + { azure_instance = { + type = "string", + description = "Instance name for Azure OpenAI hosted models.", + required = false }}, + { azure_api_version = { + type = "string", + description = "'api-version' for Azure OpenAI instances.", + required = false, + default = "2023-05-15" }}, + { azure_deployment_id = { + type = "string", + description = "Deployment ID for Azure OpenAI instances.", + required = false }}, + { llama2_format = { + type = "string", + description = "If using llama2 provider, select the upstream message format.", + required = false, + one_of = { "raw", "openai", "ollama" }}}, + { mistral_format = { + type = "string", + description = "If using mistral provider, select the upstream message format.", + required = false, + one_of = { "openai", "ollama" }}}, + { upstream_url = typedefs.url { + description = "Manually specify or override the full URL to the AI operation endpoints, " + .. "when calling (self-)hosted models, or for running via a private endpoint.", + required = false }}, + { upstream_path = { + description = "Manually specify or override the AI operation path, " + .. "used when e.g. using the 'preserve' route_type.", + type = "string", + required = false }}, + } +} + + + +local model_schema = { + type = "record", + required = true, + fields = { + { provider = { + type = "string", description = "AI provider request format - Kong translates " + .. "requests to and from the specified backend compatible formats.", + required = true, + one_of = { "openai", "azure", "anthropic", "cohere", "mistral", "llama2" }}}, + { name = { + type = "string", + description = "Model name to execute.", + required = false }}, + { options = model_options_schema }, + } +} + + + +local logging_schema = { + type = "record", + required = true, + fields = { + { log_statistics = { + type = "boolean", + description = "If enabled and supported by the driver, " + .. "will add model usage and token metrics into the Kong log plugin(s) output.", + required = true, + default = false }}, + { log_payloads = { + type = "boolean", + description = "If enabled, will log the request and response body into the Kong log plugin(s) output.", + required = true, default = false }}, + } +} + + + +local UNSUPPORTED_LOG_STATISTICS = { + ["llm/v1/completions"] = { ["anthropic"] = true }, +} + + + +return { + type = "record", + fields = { + { route_type = { + type = "string", + description = "The model's operation implementation, for this provider. " .. + "Set to `preserve` to pass through without transformation.", + required = true, + one_of = { "llm/v1/chat", "llm/v1/completions", "preserve" } }}, + { auth = auth_schema }, + { model = model_schema }, + { logging = logging_schema }, + }, + entity_checks = { + -- these three checks run in a chain, to ensure that all auth params for each respective "set" are specified + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "openai", "azure", "anthropic", "cohere" } }, + then_at_least_one_of = { "auth.header_name", "auth.param_name" }, + then_err = "must set one of %s, and its respective options, when provider is not self-hosted" }}, + + { mutually_required = { "auth.header_name", "auth.header_value" }, }, + { mutually_required = { "auth.param_name", "auth.param_value", "auth.param_location" }, }, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "llama2" } }, + then_at_least_one_of = { "model.options.llama2_format" }, + then_err = "must set %s for llama2 provider" }}, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "mistral" } }, + then_at_least_one_of = { "model.options.mistral_format" }, + then_err = "must set %s for mistral provider" }}, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "anthropic" } }, + then_at_least_one_of = { "model.options.anthropic_version" }, + then_err = "must set %s for anthropic provider" }}, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "azure" } }, + then_at_least_one_of = { "model.options.azure_instance" }, + then_err = "must set %s for azure provider" }}, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "azure" } }, + then_at_least_one_of = { "model.options.azure_api_version" }, + then_err = "must set %s for azure provider" }}, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "azure" } }, + then_at_least_one_of = { "model.options.azure_deployment_id" }, + then_err = "must set %s for azure provider" }}, + + { conditional_at_least_one_of = { if_field = "model.provider", + if_match = { one_of = { "mistral", "llama2" } }, + then_at_least_one_of = { "model.options.upstream_url" }, + then_err = "must set %s for self-hosted providers/models" }}, + + { + custom_entity_check = { + field_sources = { "route_type", "model", "logging" }, + fn = function(entity) + if entity.logging.log_statistics and UNSUPPORTED_LOG_STATISTICS[entity.route_type] + and UNSUPPORTED_LOG_STATISTICS[entity.route_type][entity.model.provider] then + return nil, fmt("%s does not support statistics when route_type is %s", + entity.model.provider, entity.route_type) + + else + return true + end + end, + } + }, + }, +} diff --git a/kong/plugins/ai-proxy/handler.lua b/kong/plugins/ai-proxy/handler.lua index e403ebb73c18..c6fcc9a3eda6 100644 --- a/kong/plugins/ai-proxy/handler.lua +++ b/kong/plugins/ai-proxy/handler.lua @@ -1,59 +1,44 @@ -local _M = {} - --- imports local ai_shared = require("kong.llm.drivers.shared") local llm = require("kong.llm") local cjson = require("cjson.safe") local kong_utils = require("kong.tools.gzip") local kong_meta = require("kong.meta") local buffer = require "string.buffer" -local strip = require("kong.tools.string").strip --- +local strip = require("kong.tools.utils").strip -_M.PRIORITY = 770 -_M.VERSION = kong_meta.version +local EMPTY = {} --- reuse this table for error message response -local ERROR_MSG = { error = { message = "" } } +local _M = { + PRIORITY = 770, + VERSION = kong_meta.version +} -local function bad_request(msg) - kong.log.warn(msg) - ERROR_MSG.error.message = msg - return kong.response.exit(400, ERROR_MSG) +--- Return a 400 response with a JSON body. This function is used to +-- return errors to the client while also logging the error. +local function bad_request(msg) + kong.log.info(msg) + return kong.response.exit(400, { error = { message = msg } }) end -local function internal_server_error(msg) - kong.log.err(msg) - ERROR_MSG.error.message = msg - - return kong.response.exit(500, ERROR_MSG) -end +-- get the token text from an event frame local function get_token_text(event_t) - -- chat - return - event_t and - event_t.choices and - #event_t.choices > 0 and - event_t.choices[1].delta and - event_t.choices[1].delta.content - - or - - -- completions - event_t and - event_t.choices and - #event_t.choices > 0 and - event_t.choices[1].text - - or "" + -- get: event_t.choices[1] + local first_choice = ((event_t or EMPTY).choices or EMPTY)[1] or EMPTY + -- return: + -- - event_t.choices[1].delta.content + -- - event_t.choices[1].text + -- - "" + return (first_choice.delta or EMPTY).content or first_choice.text or "" end + + local function handle_streaming_frame(conf) -- make a re-usable framebuffer local framebuffer = buffer.new() @@ -61,11 +46,11 @@ local function handle_streaming_frame(conf) local ai_driver = require("kong.llm.drivers." .. conf.model.provider) + local kong_ctx_plugin = kong.ctx.plugin -- create a buffer to store each response token/frame, on first pass - if conf.logging - and conf.logging.log_payloads - and (not kong.ctx.plugin.ai_stream_log_buffer) then - kong.ctx.plugin.ai_stream_log_buffer = buffer.new() + if (conf.logging or EMPTY).log_payloads and + (not kong_ctx_plugin.ai_stream_log_buffer) then + kong_ctx_plugin.ai_stream_log_buffer = buffer.new() end -- now handle each chunk/frame @@ -101,7 +86,7 @@ local function handle_streaming_frame(conf) token_t = get_token_text(event_t) end - kong.ctx.plugin.ai_stream_log_buffer:put(token_t) + kong_ctx_plugin.ai_stream_log_buffer:put(token_t) end end @@ -122,8 +107,8 @@ local function handle_streaming_frame(conf) -- but this is all we can do until OpenAI fixes this... -- -- essentially, every 4 characters is a token, with minimum of 1*4 per event - kong.ctx.plugin.ai_stream_completion_tokens = - (kong.ctx.plugin.ai_stream_completion_tokens or 0) + math.ceil(#strip(token_t) / 4) + kong_ctx_plugin.ai_stream_completion_tokens = + (kong_ctx_plugin.ai_stream_completion_tokens or 0) + math.ceil(#strip(token_t) / 4) end end end @@ -135,14 +120,14 @@ local function handle_streaming_frame(conf) end if conf.logging and conf.logging.log_statistics and metadata then - kong.ctx.plugin.ai_stream_completion_tokens = - (kong.ctx.plugin.ai_stream_completion_tokens or 0) + + kong_ctx_plugin.ai_stream_completion_tokens = + (kong_ctx_plugin.ai_stream_completion_tokens or 0) + (metadata.completion_tokens or 0) - or kong.ctx.plugin.ai_stream_completion_tokens - kong.ctx.plugin.ai_stream_prompt_tokens = - (kong.ctx.plugin.ai_stream_prompt_tokens or 0) + + or kong_ctx_plugin.ai_stream_completion_tokens + kong_ctx_plugin.ai_stream_prompt_tokens = + (kong_ctx_plugin.ai_stream_prompt_tokens or 0) + (metadata.prompt_tokens or 0) - or kong.ctx.plugin.ai_stream_prompt_tokens + or kong_ctx_plugin.ai_stream_prompt_tokens end end end @@ -156,23 +141,26 @@ local function handle_streaming_frame(conf) if finished then local fake_response_t = { - response = kong.ctx.plugin.ai_stream_log_buffer and kong.ctx.plugin.ai_stream_log_buffer:get(), + response = kong_ctx_plugin.ai_stream_log_buffer and kong_ctx_plugin.ai_stream_log_buffer:get(), usage = { - prompt_tokens = kong.ctx.plugin.ai_stream_prompt_tokens or 0, - completion_tokens = kong.ctx.plugin.ai_stream_completion_tokens or 0, - total_tokens = (kong.ctx.plugin.ai_stream_prompt_tokens or 0) - + (kong.ctx.plugin.ai_stream_completion_tokens or 0), + prompt_tokens = kong_ctx_plugin.ai_stream_prompt_tokens or 0, + completion_tokens = kong_ctx_plugin.ai_stream_completion_tokens or 0, + total_tokens = (kong_ctx_plugin.ai_stream_prompt_tokens or 0) + + (kong_ctx_plugin.ai_stream_completion_tokens or 0), } } ngx.arg[1] = nil ai_shared.post_request(conf, fake_response_t) - kong.ctx.plugin.ai_stream_log_buffer = nil + kong_ctx_plugin.ai_stream_log_buffer = nil end end function _M:header_filter(conf) - if kong.ctx.shared.skip_response_transformer then + local kong_ctx_plugin = kong.ctx.plugin + local kong_ctx_shared = kong.ctx.shared + + if kong_ctx_shared.skip_response_transformer then return end @@ -187,7 +175,7 @@ function _M:header_filter(conf) end -- we use openai's streaming mode (SSE) - if kong.ctx.shared.ai_proxy_streaming_mode then + if kong_ctx_shared.ai_proxy_streaming_mode then -- we are going to send plaintext event-stream frames for ALL models kong.response.set_header("Content-Type", "text/event-stream") return @@ -204,28 +192,26 @@ function _M:header_filter(conf) -- if this is a 'streaming' request, we can't know the final -- result of the response body, so we just proceed to body_filter -- to translate each SSE event frame - if not kong.ctx.shared.ai_proxy_streaming_mode then + if not kong_ctx_shared.ai_proxy_streaming_mode then local is_gzip = kong.response.get_header("Content-Encoding") == "gzip" if is_gzip then response_body = kong_utils.inflate_gzip(response_body) end if route_type == "preserve" then - kong.ctx.plugin.parsed_response = response_body + kong_ctx_plugin.parsed_response = response_body else local new_response_string, err = ai_driver.from_format(response_body, conf.model, route_type) if err then - kong.ctx.plugin.ai_parser_error = true - + kong_ctx_plugin.ai_parser_error = true + ngx.status = 500 - ERROR_MSG.error.message = err - - kong.ctx.plugin.parsed_response = cjson.encode(ERROR_MSG) - + kong_ctx_plugin.parsed_response = cjson.encode({ error = { message = err } }) + elseif new_response_string then -- preserve the same response content type; assume the from_format function -- has returned the body in the appropriate response output format - kong.ctx.plugin.parsed_response = new_response_string + kong_ctx_plugin.parsed_response = new_response_string end end end @@ -235,17 +221,20 @@ end function _M:body_filter(conf) + local kong_ctx_plugin = kong.ctx.plugin + local kong_ctx_shared = kong.ctx.shared + -- if body_filter is called twice, then return - if kong.ctx.plugin.body_called and not kong.ctx.shared.ai_proxy_streaming_mode then + if kong_ctx_plugin.body_called and not kong_ctx_shared.ai_proxy_streaming_mode then return end local route_type = conf.route_type - if kong.ctx.shared.skip_response_transformer and (route_type ~= "preserve") then + if kong_ctx_shared.skip_response_transformer and (route_type ~= "preserve") then local response_body - if kong.ctx.shared.parsed_response then - response_body = kong.ctx.shared.parsed_response + if kong_ctx_shared.parsed_response then + response_body = kong_ctx_shared.parsed_response elseif kong.response.get_status() == 200 then response_body = kong.service.response.get_raw_body() if not response_body then @@ -261,7 +250,7 @@ function _M:body_filter(conf) local ai_driver = require("kong.llm.drivers." .. conf.model.provider) local new_response_string, err = ai_driver.from_format(response_body, conf.model, route_type) - + if err then kong.log.warn("issue when transforming the response body for analytics in the body filter phase, ", err) elseif new_response_string then @@ -269,20 +258,20 @@ function _M:body_filter(conf) end end - if not kong.ctx.shared.skip_response_transformer then - if (kong.response.get_status() ~= 200) and (not kong.ctx.plugin.ai_parser_error) then + if not kong_ctx_shared.skip_response_transformer then + if (kong.response.get_status() ~= 200) and (not kong_ctx_plugin.ai_parser_error) then return end if route_type ~= "preserve" then - if kong.ctx.shared.ai_proxy_streaming_mode then + if kong_ctx_shared.ai_proxy_streaming_mode then handle_streaming_frame(conf) else -- all errors MUST be checked and returned in header_filter -- we should receive a replacement response body from the same thread - local original_request = kong.ctx.plugin.parsed_response + local original_request = kong_ctx_plugin.parsed_response local deflated_request = original_request - + if deflated_request then local is_gzip = kong.response.get_header("Content-Encoding") == "gzip" if is_gzip then @@ -298,26 +287,29 @@ function _M:body_filter(conf) kong.log.warn("analytics phase failed for request, ", err) end end - end + end end - kong.ctx.plugin.body_called = true + kong_ctx_plugin.body_called = true end function _M:access(conf) + local kong_ctx_plugin = kong.ctx.plugin + local kong_ctx_shared = kong.ctx.shared + -- store the route_type in ctx for use in response parsing local route_type = conf.route_type - kong.ctx.plugin.operation = route_type + kong_ctx_plugin.operation = route_type local request_table local multipart = false -- we may have received a replacement / decorated request body from another AI plugin - if kong.ctx.shared.replacement_request then + if kong_ctx_shared.replacement_request then kong.log.debug("replacement request body received from another AI plugin") - request_table = kong.ctx.shared.replacement_request + request_table = kong_ctx_shared.replacement_request else -- first, calculate the coordinates of the request @@ -353,13 +345,13 @@ function _M:access(conf) end -- stash for analytics later - kong.ctx.plugin.llm_model_requested = conf_m.model.name + kong_ctx_plugin.llm_model_requested = conf_m.model.name -- check the incoming format is the same as the configured LLM format if not multipart then local compatible, err = llm.is_compatible(request_table, route_type) if not compatible then - kong.ctx.shared.skip_response_transformer = true + kong_ctx_shared.skip_response_transformer = true return bad_request(err) end end @@ -367,7 +359,7 @@ function _M:access(conf) -- check the incoming format is the same as the configured LLM format local compatible, err = llm.is_compatible(request_table, route_type) if not compatible then - kong.ctx.shared.skip_response_transformer = true + kong_ctx_shared.skip_response_transformer = true return bad_request(err) end @@ -384,17 +376,18 @@ function _M:access(conf) end -- store token cost estimate, on first pass - if not kong.ctx.plugin.ai_stream_prompt_tokens then + if not kong_ctx_plugin.ai_stream_prompt_tokens then local prompt_tokens, err = ai_shared.calculate_cost(request_table or {}, {}, 1.8) if err then - return internal_server_error("unable to estimate request token cost: " .. err) + kong.log.err("unable to estimate request token cost: ", err) + return kong.response.exit(500) end - kong.ctx.plugin.ai_stream_prompt_tokens = prompt_tokens + kong_ctx_plugin.ai_stream_prompt_tokens = prompt_tokens end -- specific actions need to skip later for this to work - kong.ctx.shared.ai_proxy_streaming_mode = true + kong_ctx_shared.ai_proxy_streaming_mode = true else kong.service.request.enable_buffering() @@ -414,7 +407,7 @@ function _M:access(conf) -- transform the body to Kong-format for this provider/model parsed_request_body, content_type, err = ai_driver.to_format(request_table, conf_m.model, route_type) if err then - kong.ctx.shared.skip_response_transformer = true + kong_ctx_shared.skip_response_transformer = true return bad_request(err) end end @@ -432,8 +425,9 @@ function _M:access(conf) -- now re-configure the request for this operation type local ok, err = ai_driver.configure_request(conf_m) if not ok then - kong.ctx.shared.skip_response_transformer = true - return internal_server_error(err) + kong_ctx_shared.skip_response_transformer = true + kong.log.err("failed to configure request for AI service: ", err) + return kong.response.exit(500) end -- lights out, and away we go diff --git a/kong/plugins/ai-request-transformer/handler.lua b/kong/plugins/ai-request-transformer/handler.lua index 9517be366325..0eb5cd89d8f2 100644 --- a/kong/plugins/ai-request-transformer/handler.lua +++ b/kong/plugins/ai-request-transformer/handler.lua @@ -31,7 +31,7 @@ local function create_http_opts(conf) http_opts.proxy_opts = http_opts.proxy_opts or {} http_opts.proxy_opts.https_proxy = fmt("http://%s:%d", conf.https_proxy_host, conf.https_proxy_port) end - + http_opts.http_timeout = conf.http_timeout http_opts.https_verify = conf.https_verify @@ -46,15 +46,15 @@ function _M:access(conf) local http_opts = create_http_opts(conf) conf.llm.__plugin_id = conf.__plugin_id conf.llm.__key__ = conf.__key__ - local ai_driver, err = llm:new(conf.llm, http_opts) - + local ai_driver, err = llm.new_driver(conf.llm, http_opts) + if not ai_driver then return internal_server_error(err) end -- if asked, introspect the request before proxying kong.log.debug("introspecting request with LLM") - local new_request_body, err = llm:ai_introspect_body( + local new_request_body, err = ai_driver:ai_introspect_body( kong.request.get_raw_body(), conf.prompt, http_opts, @@ -64,7 +64,7 @@ function _M:access(conf) if err then return bad_request(err) end - + -- set the body for later plugins kong.service.request.set_raw_body(new_request_body) diff --git a/kong/plugins/ai-response-transformer/handler.lua b/kong/plugins/ai-response-transformer/handler.lua index 7014d8938526..94a82a5ff2dc 100644 --- a/kong/plugins/ai-response-transformer/handler.lua +++ b/kong/plugins/ai-response-transformer/handler.lua @@ -21,6 +21,8 @@ local function internal_server_error(msg) return kong.response.exit(500, { error = { message = msg } }) end + + local function subrequest(httpc, request_body, http_opts) httpc:set_timeouts(http_opts.http_timeout or 60000) @@ -72,6 +74,8 @@ local function subrequest(httpc, request_body, http_opts) return res end + + local function create_http_opts(conf) local http_opts = {} @@ -84,13 +88,15 @@ local function create_http_opts(conf) http_opts.proxy_opts = http_opts.proxy_opts or {} http_opts.proxy_opts.https_proxy = fmt("http://%s:%d", conf.https_proxy_host, conf.https_proxy_port) end - + http_opts.http_timeout = conf.http_timeout http_opts.https_verify = conf.https_verify return http_opts end + + function _M:access(conf) kong.service.request.enable_buffering() kong.ctx.shared.skip_response_transformer = true @@ -99,8 +105,8 @@ function _M:access(conf) local http_opts = create_http_opts(conf) conf.llm.__plugin_id = conf.__plugin_id conf.llm.__key__ = conf.__key__ - local ai_driver, err = llm:new(conf.llm, http_opts) - + local ai_driver, err = llm.new_driver(conf.llm, http_opts) + if not ai_driver then return internal_server_error(err) end @@ -123,7 +129,7 @@ function _M:access(conf) -- if asked, introspect the request before proxying kong.log.debug("introspecting response with LLM") - local new_response_body, err = llm:ai_introspect_body( + local new_response_body, err = ai_driver:ai_introspect_body( res_body, conf.prompt, http_opts, @@ -142,7 +148,7 @@ function _M:access(conf) local headers, body, status if conf.parse_llm_response_json_instructions then - headers, body, status, err = llm:parse_json_instructions(new_response_body) + headers, body, status, err = ai_driver:parse_json_instructions(new_response_body) if err then return internal_server_error("failed to parse JSON response instructions from AI backend: " .. err) end diff --git a/spec/03-plugins/39-ai-request-transformer/01-transformer_spec.lua b/spec/03-plugins/39-ai-request-transformer/01-transformer_spec.lua index 51d84a439924..cc64fc489f67 100644 --- a/spec/03-plugins/39-ai-request-transformer/01-transformer_spec.lua +++ b/spec/03-plugins/39-ai-request-transformer/01-transformer_spec.lua @@ -1,4 +1,4 @@ -local llm_class = require("kong.llm") +local llm = require("kong.llm") local helpers = require "spec.helpers" local cjson = require "cjson" local http_mock = require "spec.helpers.http_mock" @@ -224,10 +224,10 @@ describe(PLUGIN_NAME .. ": (unit)", function() for name, format_options in pairs(FORMATS) do describe(name .. " transformer tests, exact json response", function() it("transforms request based on LLM instructions", function() - local llm = llm_class:new(format_options, {}) - assert.truthy(llm) + local llmdriver = llm.new_driver(format_options, {}) + assert.truthy(llmdriver) - local result, err = llm:ai_introspect_body( + local result, err = llmdriver:ai_introspect_body( REQUEST_BODY, -- request body SYSTEM_PROMPT, -- conf.prompt {}, -- http opts @@ -246,10 +246,10 @@ describe(PLUGIN_NAME .. ": (unit)", function() describe("openai transformer tests, pattern matchers", function() it("transforms request based on LLM instructions, with json extraction pattern", function() - local llm = llm_class:new(OPENAI_NOT_JSON, {}) - assert.truthy(llm) + local llmdriver = llm.new_driver(OPENAI_NOT_JSON, {}) + assert.truthy(llmdriver) - local result, err = llm:ai_introspect_body( + local result, err = llmdriver:ai_introspect_body( REQUEST_BODY, -- request body SYSTEM_PROMPT, -- conf.prompt {}, -- http opts @@ -265,10 +265,10 @@ describe(PLUGIN_NAME .. ": (unit)", function() end) it("transforms request based on LLM instructions, but fails to match pattern", function() - local llm = llm_class:new(OPENAI_NOT_JSON, {}) - assert.truthy(llm) + local llmdriver = llm.new_driver(OPENAI_NOT_JSON, {}) + assert.truthy(llmdriver) - local result, err = llm:ai_introspect_body( + local result, err = llmdriver:ai_introspect_body( REQUEST_BODY, -- request body SYSTEM_PROMPT, -- conf.prompt {}, -- http opts diff --git a/spec/03-plugins/40-ai-response-transformer/01-transformer_spec.lua b/spec/03-plugins/40-ai-response-transformer/01-transformer_spec.lua index d436ad536440..7f4e544ecc36 100644 --- a/spec/03-plugins/40-ai-response-transformer/01-transformer_spec.lua +++ b/spec/03-plugins/40-ai-response-transformer/01-transformer_spec.lua @@ -1,4 +1,4 @@ -local llm_class = require("kong.llm") +local llm = require("kong.llm") local helpers = require "spec.helpers" local cjson = require "cjson" local http_mock = require "spec.helpers.http_mock" @@ -90,10 +90,10 @@ describe(PLUGIN_NAME .. ": (unit)", function() describe("openai transformer tests, specific response", function() it("transforms request based on LLM instructions, with response transformation instructions format", function() - local llm = llm_class:new(OPENAI_INSTRUCTIONAL_RESPONSE, {}) - assert.truthy(llm) + local llmdriver = llm.new_driver(OPENAI_INSTRUCTIONAL_RESPONSE, {}) + assert.truthy(llmdriver) - local result, err = llm:ai_introspect_body( + local result, err = llmdriver:ai_introspect_body( REQUEST_BODY, -- request body SYSTEM_PROMPT, -- conf.prompt {}, -- http opts @@ -107,14 +107,14 @@ describe(PLUGIN_NAME .. ": (unit)", function() assert.same(EXPECTED_RESULT, table_result) -- parse in response string format - local headers, body, status, err = llm:parse_json_instructions(result) + local headers, body, status, err = llmdriver:parse_json_instructions(result) assert.is_nil(err) assert.same({ ["content-type"] = "application/xml" }, headers) assert.same(209, status) assert.same(EXPECTED_RESULT.body, body) -- parse in response table format - headers, body, status, err = llm:parse_json_instructions(table_result) + headers, body, status, err = llmdriver:parse_json_instructions(table_result) assert.is_nil(err) assert.same({ ["content-type"] = "application/xml" }, headers) assert.same(209, status) diff --git a/spec/03-plugins/40-ai-response-transformer/02-integration_spec.lua b/spec/03-plugins/40-ai-response-transformer/02-integration_spec.lua index 800100c9a67c..13be816735a1 100644 --- a/spec/03-plugins/40-ai-response-transformer/02-integration_spec.lua +++ b/spec/03-plugins/40-ai-response-transformer/02-integration_spec.lua @@ -210,7 +210,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then server { server_name llm; listen ]]..MOCK_PORT..[[; - + default_type 'application/json'; location ~/instructions { @@ -237,7 +237,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then location = "/badrequest" { content_by_lua_block { local pl_file = require "pl.file" - + ngx.status = 400 ngx.print(pl_file.read("spec/fixtures/ai-proxy/openai/llm-v1-chat/responses/bad_request.json")) } @@ -246,7 +246,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then location = "/internalservererror" { content_by_lua_block { local pl_file = require "pl.file" - + ngx.status = 500 ngx.header["content-type"] = "text/html" ngx.print(pl_file.read("spec/fixtures/ai-proxy/openai/llm-v1-chat/responses/internal_server_error.html")) @@ -357,7 +357,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then declarative_config = strategy == "off" and helpers.make_yaml_file() or nil, }, nil, nil, fixtures)) end) - + lazy_teardown(function() helpers.stop_kong() end) @@ -379,7 +379,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then }, body = REQUEST_BODY, }) - + local body = assert.res_status(209 , r) assert.same(EXPECTED_RESULT.body, body) assert.same(r.headers["content-type"], "application/xml") @@ -393,7 +393,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then }, body = REQUEST_BODY, }) - + local body = assert.res_status(200 , r) local body_table, err = cjson.decode(body) assert.is_nil(err) @@ -431,7 +431,7 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then }, body = REQUEST_BODY, }) - + local body = assert.res_status(500 , r) local body_table, err = cjson.decode(body) assert.is_nil(err) From fe49d758c62940b91478e4df1d85cf92c7e20306 Mon Sep 17 00:00:00 2001 From: Niklaus Schen <8458369+Water-Melon@users.noreply.github.com> Date: Fri, 7 Jun 2024 11:28:42 +0800 Subject: [PATCH 31/34] chore(ci): use github.sha in push event to get latest commit SHA from default branch (#13170) KAG-4374 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ccfc66b436b8..c73fb2d3eafb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -610,7 +610,7 @@ jobs: images: ${{ needs.metadata.outputs.docker-repository }} sep-tags: " " tags: | - type=raw,value=latest,enable=${{ matrix.label == 'ubuntu' && github.ref_name == github.event.inputs.default_branch && env.latest_sha == github.event.pull_request.head.sha }} + type=raw,value=latest,enable=${{ matrix.label == 'ubuntu' && github.ref_name == github.event.inputs.default_branch && env.latest_sha == needs.metadata.outputs.commit-sha }} type=match,enable=${{ github.event_name == 'workflow_dispatch' }},pattern=\d.\d,value=${{ github.event.inputs.version }} type=match,enable=${{ github.event_name == 'workflow_dispatch' && matrix.label == 'ubuntu' }},pattern=\d.\d,value=${{ github.event.inputs.version }},suffix= type=raw,enable=${{ github.event_name == 'workflow_dispatch' }},${{ github.event.inputs.version }} From 3b066dc4cb66feca63d231ec89feb66c6cb1b7f1 Mon Sep 17 00:00:00 2001 From: Chrono Date: Fri, 7 Jun 2024 14:43:18 +0800 Subject: [PATCH 32/34] refactor(tools/http): simplify `check_https()` with `ngx.var` (#13167) We used to call `ngx.req.get_headers()` to get all possible header values, but since nginx 1.23.0 or OpenResty 1.25.3.1 `ngx.var` can get a combined value for all header values with identical name (joined by comma), so I think that we could simplify these code. There is one concern: If header `X-Forwarded-Proto` has a comma separated value like `http, https`, This PR will think it is a multiple header request and report an error `Only one X-Forwarded-Proto header allowed`. But I think that it is an unlikely case in the real world. KAG-4673 --- kong/tools/http.lua | 9 +++++--- spec/01-unit/05-utils_spec.lua | 25 ++++++++++------------ t/01-pdk/05-client/08-get_protocol.t | 31 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/kong/tools/http.lua b/kong/tools/http.lua index 34ca72ccdc2b..613661d68d2f 100644 --- a/kong/tools/http.lua +++ b/kong/tools/http.lua @@ -210,16 +210,19 @@ _M.check_https = function(trusted_ip, allow_terminated) -- otherwise, we fall back to relying on the client scheme -- (which was either validated earlier, or we fall through this block) if trusted_ip then - local scheme = ngx.req.get_headers()["x-forwarded-proto"] + local scheme = ngx.var.http_x_forwarded_proto + if not scheme then + return false + end -- we could use the first entry (lower security), or check the contents of -- each of them (slow). So for now defensive, and error -- out on multiple entries for the x-forwarded-proto header. - if type(scheme) == "table" then + if scheme:find(",", 1, true) then return nil, "Only one X-Forwarded-Proto header allowed" end - return tostring(scheme):lower() == "https" + return scheme:lower() == "https" end return false diff --git a/spec/01-unit/05-utils_spec.lua b/spec/01-unit/05-utils_spec.lua index 14e23b3d85b7..32bea716132e 100644 --- a/spec/01-unit/05-utils_spec.lua +++ b/spec/01-unit/05-utils_spec.lua @@ -80,17 +80,14 @@ describe("Utils", function() describe("https_check", function() local old_ngx - local headers = {} lazy_setup(function() old_ngx = ngx _G.ngx = { var = { - scheme = nil + scheme = nil, + http_x_forwarded_proto = nil, }, - req = { - get_headers = function() return headers end - } } end) @@ -100,7 +97,7 @@ describe("Utils", function() describe("without X-Forwarded-Proto header", function() lazy_setup(function() - headers["x-forwarded-proto"] = nil + ngx.var.http_x_forwarded_proto = nil end) it("should validate an HTTPS scheme", function() @@ -124,11 +121,11 @@ describe("Utils", function() describe("with X-Forwarded-Proto header", function() lazy_teardown(function() - headers["x-forwarded-proto"] = nil + ngx.var.http_x_forwarded_proto = nil end) it("should validate any scheme with X-Forwarded_Proto as HTTPS", function() - headers["x-forwarded-proto"] = "hTTPs" -- check mixed casing for case insensitiveness + ngx.var.http_x_forwarded_proto = "hTTPs" -- check mixed casing for case insensitiveness ngx.var.scheme = "hTTps" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" @@ -138,7 +135,7 @@ describe("Utils", function() end) it("should validate only https scheme with X-Forwarded_Proto as non-HTTPS", function() - headers["x-forwarded-proto"] = "hTTP" + ngx.var.http_x_forwarded_proto = "hTTP" ngx.var.scheme = "hTTps" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" @@ -148,7 +145,7 @@ describe("Utils", function() end) it("should return an error with multiple X-Forwarded_Proto headers", function() - headers["x-forwarded-proto"] = { "hTTP", "https" } + ngx.var.http_x_forwarded_proto = "hTTP, https" ngx.var.scheme = "hTTps" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" @@ -157,19 +154,19 @@ describe("Utils", function() end) it("should not use X-Forwarded-Proto when the client is untrusted", function() - headers["x-forwarded-proto"] = "https" + ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "http" assert.is_false(tools_http.check_https(false, false)) assert.is_false(tools_http.check_https(false, true)) - headers["x-forwarded-proto"] = "https" + ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "https" assert.is_true(tools_http.check_https(false, false)) assert.is_true(tools_http.check_https(false, true)) end) it("should use X-Forwarded-Proto when the client is trusted", function() - headers["x-forwarded-proto"] = "https" + ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "http" -- trusted client but do not allow terminated @@ -177,7 +174,7 @@ describe("Utils", function() assert.is_true(tools_http.check_https(true, true)) - headers["x-forwarded-proto"] = "https" + ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "https" assert.is_true(tools_http.check_https(true, false)) assert.is_true(tools_http.check_https(true, true)) diff --git a/t/01-pdk/05-client/08-get_protocol.t b/t/01-pdk/05-client/08-get_protocol.t index b9c04899aa9b..be68dd2e0279 100644 --- a/t/01-pdk/05-client/08-get_protocol.t +++ b/t/01-pdk/05-client/08-get_protocol.t @@ -301,3 +301,34 @@ qq{ protocol=tls --- no_error_log [error] + + + +=== TEST 11: client.get_protocol() fails when kong receives an http request with multiple x-forwarded-proto headers + +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + access_by_lua_block { + ngx.ctx.route = { + protocols = { "http", "https" } + } + + local PDK = require "kong.pdk" + local pdk = PDK.new() + pdk.ip.is_trusted = function() return true end -- mock + + local ok, err = pdk.client.get_protocol(true) + assert(ok == nil) + ngx.say("err=", err) + } + } +--- request +GET /t +--- more_headers +X-Forwarded-Proto: https +X-Forwarded-Proto: http +--- response_body +err=Only one X-Forwarded-Proto header allowed +--- no_error_log +[error] From c09a09963745c922ae8ce58e71b5dd97d403d368 Mon Sep 17 00:00:00 2001 From: Chrono Date: Fri, 7 Jun 2024 14:44:32 +0800 Subject: [PATCH 33/34] refactor(conf_loader): separate system functions from conf loader (#13080) This refactor will make the code of conf loader easier to maintain. KAG-4638 --- kong-3.8.0-0.rockspec | 1 + kong/conf_loader/init.lua | 19 +++++++------------ kong/conf_loader/sys.lua | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 kong/conf_loader/sys.lua diff --git a/kong-3.8.0-0.rockspec b/kong-3.8.0-0.rockspec index 52a46d65d6bd..bec67ea7cce8 100644 --- a/kong-3.8.0-0.rockspec +++ b/kong-3.8.0-0.rockspec @@ -72,6 +72,7 @@ build = { ["kong.conf_loader"] = "kong/conf_loader/init.lua", ["kong.conf_loader.constants"] = "kong/conf_loader/constants.lua", ["kong.conf_loader.parse"] = "kong/conf_loader/parse.lua", + ["kong.conf_loader.sys"] = "kong/conf_loader/sys.lua", ["kong.conf_loader.listeners"] = "kong/conf_loader/listeners.lua", ["kong.clustering"] = "kong/clustering/init.lua", diff --git a/kong/conf_loader/init.lua b/kong/conf_loader/init.lua index be077ee747dc..40f574e43f66 100644 --- a/kong/conf_loader/init.lua +++ b/kong/conf_loader/init.lua @@ -8,6 +8,7 @@ local pl_stringx = require "pl.stringx" local socket_url = require "socket.url" local conf_constants = require "kong.conf_loader.constants" local listeners = require "kong.conf_loader.listeners" +local conf_sys = require "kong.conf_loader.sys" local conf_parse = require "kong.conf_loader.parse" local nginx_signals = require "kong.cmd.utils.nginx_signals" local pl_pretty = require "pl.pretty" @@ -18,7 +19,6 @@ local tablex = require "pl.tablex" local cycle_aware_deep_copy = require("kong.tools.table").cycle_aware_deep_copy local log = require "kong.cmd.utils.log" local env = require "kong.cmd.utils.env" -local ffi = require "ffi" local fmt = string.format @@ -36,21 +36,16 @@ local unpack = unpack local ipairs = ipairs local insert = table.insert local remove = table.remove -local getenv = os.getenv local exists = pl_path.exists local abspath = pl_path.abspath local tostring = tostring local setmetatable = setmetatable -local C = ffi.C - - -ffi.cdef([[ - struct group *getgrnam(const char *name); - struct passwd *getpwnam(const char *name); - int unsetenv(const char *name); -]]) +local getgrnam = conf_sys.getgrnam +local getpwnam = conf_sys.getpwnam +local getenv = conf_sys.getenv +local unsetenv = conf_sys.unsetenv local get_phase = conf_parse.get_phase @@ -403,7 +398,7 @@ local function load(path, custom_conf, opts) if get_phase() == "init" then local secrets = getenv("KONG_PROCESS_SECRETS") if secrets then - C.unsetenv("KONG_PROCESS_SECRETS") + unsetenv("KONG_PROCESS_SECRETS") else local path = pl_path.join(abspath(ngx.config.prefix()), unpack(conf_constants.PREFIX_PATHS.kong_process_secrets)) @@ -538,7 +533,7 @@ local function load(path, custom_conf, opts) end end - if C.getpwnam("kong") == nil or C.getgrnam("kong") == nil then + if getpwnam("kong") == nil or getgrnam("kong") == nil then if default_nginx_main_user == true and default_nginx_user == true then conf.nginx_user = nil conf.nginx_main_user = nil diff --git a/kong/conf_loader/sys.lua b/kong/conf_loader/sys.lua new file mode 100644 index 000000000000..3eb02773e3a5 --- /dev/null +++ b/kong/conf_loader/sys.lua @@ -0,0 +1,19 @@ +local ffi = require "ffi" +local C = ffi.C + + +ffi.cdef([[ + struct group *getgrnam(const char *name); + struct passwd *getpwnam(const char *name); + int unsetenv(const char *name); +]]) + + +return { + getgrnam = C.getgrnam, + getpwnam = C.getpwnam, + + getenv = os.getenv, + unsetenv = C.unsetenv, +} + From d1994f7d9121f2ca22ccbffcaf146cbc4ff6b14b Mon Sep 17 00:00:00 2001 From: Qi <44437200+ADD-SP@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:52:12 +0800 Subject: [PATCH 34/34] feat(queue): implement functions `Quque.is_full` & `Queue.can_enqueue` (#13164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(queue): implement functions `Quque.is_full` & `Queue.will_full` Generating entries is expensive in some cases, so we'd better have a way to observe the state of the Queue. Co-authored-by: Hans Hübner Co-authored-by: Chrono --- kong/tools/queue.lua | 226 ++++++++++++++++++++++++++++----- spec/01-unit/27-queue_spec.lua | 76 +++++++++++ 2 files changed, 269 insertions(+), 33 deletions(-) diff --git a/kong/tools/queue.lua b/kong/tools/queue.lua index d632cca8112e..725db7ddbf14 100644 --- a/kong/tools/queue.lua +++ b/kong/tools/queue.lua @@ -95,16 +95,106 @@ local Queue_mt = { } -local function make_queue_key(name) +local function _make_queue_key(name) return (workspaces.get_workspace_id() or "") .. "." .. name end +local function _remaining_capacity(self) + local remaining_entries = self.max_entries - self:count() + local max_bytes = self.max_bytes + + -- we enqueue entries one by one, + -- so it is impossible to have a negative value + assert(remaining_entries >= 0, "queue should not be over capacity") + + if not max_bytes then + return remaining_entries + end + + local remaining_bytes = max_bytes - self.bytes_queued + + -- we check remaining_bytes before enqueueing an entry, + -- so it is impossible to have a negative value + assert(remaining_bytes >= 0, "queue should not be over capacity") + + return remaining_entries, remaining_bytes +end + + +local function _is_reaching_max_entries(self) + -- `()` is used to get the first return value only + return (_remaining_capacity(self)) == 0 +end + + +local function _will_exceed_max_entries(self) + -- `()` is used to get the first return value only + return (_remaining_capacity(self)) - 1 < 0 +end + + +local function _is_entry_too_large(self, entry) + local max_bytes = self.max_bytes + + if not max_bytes then + return false + end + + if type(entry) ~= "string" then + -- handle non-string entry, including `nil` + return false + end + + return #entry > max_bytes +end + + +local function _is_reaching_max_bytes(self) + if not self.max_bytes then + return false + end + + local _, remaining_bytes = _remaining_capacity(self) + return remaining_bytes == 0 +end + + +local function _will_exceed_max_bytes(self, entry) + if not self.max_bytes then + return false + end + + if type(entry) ~= "string" then + -- handle non-string entry, including `nil` + return false + end + + local _, remaining_bytes = _remaining_capacity(self) + return #entry > remaining_bytes +end + + +local function _is_full(self) + return _is_reaching_max_entries(self) or _is_reaching_max_bytes(self) +end + + +local function _can_enqueue(self, entry) + return not ( + _is_full(self) or + _is_entry_too_large(self, entry) or + _will_exceed_max_entries(self) or + _will_exceed_max_bytes(self, entry) + ) +end + + local queues = {} function Queue.exists(name) - return queues[make_queue_key(name)] and true or false + return queues[_make_queue_key(name)] and true or false end ------------------------------------------------------------------------------- @@ -115,7 +205,7 @@ end local function get_or_create_queue(queue_conf, handler, handler_conf) local name = assert(queue_conf.name) - local key = make_queue_key(name) + local key = _make_queue_key(name) local queue = queues[key] if queue then @@ -193,6 +283,28 @@ function Queue:count() end +function Queue.is_full(queue_conf) + local queue = queues[_make_queue_key(queue_conf.name)] + if not queue then + -- treat non-existing queues as not full as they will be created on demand + return false + end + + return _is_full(queue) +end + + +function Queue.can_enqueue(queue_conf, entry) + local queue = queues[_make_queue_key(queue_conf.name)] + if not queue then + -- treat non-existing queues as not full as they will be created on demand + return false + end + + return _can_enqueue(queue, entry) +end + + -- Delete the frontmost entry from the queue and adjust the current utilization variables. function Queue:delete_frontmost_entry() if self.max_bytes then @@ -260,9 +372,9 @@ function Queue:process_once() for _ = 1, entry_count do self:delete_frontmost_entry() end - if self.queue_full then + if self.already_dropped_entries then self:log_info('queue resumed processing') - self.queue_full = false + self.already_dropped_entries = false end local start_time = now() @@ -393,38 +505,54 @@ local function enqueue(self, entry) self.warned = nil end - if self:count() == self.max_entries then - if not self.queue_full then - self.queue_full = true - self:log_err("queue full, dropping old entries until processing is successful again") - end + if _is_reaching_max_entries(self) then + self:log_err("queue full, dropping old entries until processing is successful again") self:drop_oldest_entry() + self.already_dropped_entries = true end + if _is_entry_too_large(self, entry) then + local err_msg = string.format( + "string to be queued is longer (%d bytes) than the queue's max_bytes (%d bytes)", + #entry, + self.max_bytes + ) + self:log_err(err_msg) + + return nil, err_msg + end + + if _will_exceed_max_bytes(self, entry) then + local dropped = 0 + + repeat + self:drop_oldest_entry() + dropped = dropped + 1 + self.already_dropped_entries = true + until not _will_exceed_max_bytes(self, entry) + + self:log_err("byte capacity exceeded, %d queue entries were dropped", dropped) + end + + -- safety guard + -- The queue should not be full if we are running into this situation. + -- Since the dropping logic is complicated, + -- further maintenancers might introduce bugs, + -- so I added this assertion to detect this kind of bug early. + -- It's better to crash early than leak memory + -- as analyze memory leak is hard. + assert( + -- assert that enough space is available on the queue now + _can_enqueue(self, entry), + "queue should not be full after dropping entries" + ) + if self.max_bytes then if type(entry) ~= "string" then self:log_err("queuing non-string entry to a queue that has queue.max_bytes set, capacity monitoring will not be correct") - else - if #entry > self.max_bytes then - local message = string.format( - "string to be queued is longer (%d bytes) than the queue's max_bytes (%d bytes)", - #entry, self.max_bytes) - self:log_err(message) - return nil, message - end - - local dropped = 0 - while self:count() > 0 and (self.bytes_queued + #entry) > self.max_bytes do - self:drop_oldest_entry() - dropped = dropped + 1 - end - if dropped > 0 then - self.queue_full = true - self:log_err("byte capacity exceeded, %d queue entries were dropped", dropped) - end - - self.bytes_queued = self.bytes_queued + #entry end + + self.bytes_queued = self.bytes_queued + #entry end self.entries[self.back] = entry @@ -442,11 +570,41 @@ function Queue.enqueue(queue_conf, handler, handler_conf, value) assert(type(handler) == "function", "arg #2 (handler) must be a function") assert(handler_conf == nil or type(handler_conf) == "table", - "arg #3 (handler_conf) must be a table") - + "arg #3 (handler_conf) must be a table or nil") assert(type(queue_conf.name) == "string", "arg #1 (queue_conf) must include a name") + assert( + type(queue_conf.max_batch_size) == "number", + "arg #1 (queue_conf) max_batch_size must be a number" + ) + assert( + type(queue_conf.max_coalescing_delay) == "number", + "arg #1 (queue_conf) max_coalescing_delay must be a number" + ) + assert( + type(queue_conf.max_entries) == "number", + "arg #1 (queue_conf) max_entries must be a number" + ) + assert( + type(queue_conf.max_retry_time) == "number", + "arg #1 (queue_conf) max_retry_time must be a number" + ) + assert( + type(queue_conf.initial_retry_delay) == "number", + "arg #1 (queue_conf) initial_retry_delay must be a number" + ) + assert( + type(queue_conf.max_retry_delay) == "number", + "arg #1 (queue_conf) max_retry_delay must be a number" + ) + + local max_bytes_type = type(queue_conf.max_bytes) + assert( + max_bytes_type == "nil" or max_bytes_type == "number", + "arg #1 (queue_conf) max_bytes must be a number or nil" + ) + local queue = get_or_create_queue(queue_conf, handler, handler_conf) return enqueue(queue, value) end @@ -454,12 +612,14 @@ end -- For testing, the _exists() function is provided to allow a test to wait for the -- queue to have been completely processed. function Queue._exists(name) - local queue = queues[make_queue_key(name)] + local queue = queues[_make_queue_key(name)] return queue and queue:count() > 0 end +-- [[ For testing purposes only Queue._CAPACITY_WARNING_THRESHOLD = CAPACITY_WARNING_THRESHOLD +-- ]] return Queue diff --git a/spec/01-unit/27-queue_spec.lua b/spec/01-unit/27-queue_spec.lua index 8960d5076198..ec166c295a4e 100644 --- a/spec/01-unit/27-queue_spec.lua +++ b/spec/01-unit/27-queue_spec.lua @@ -788,4 +788,80 @@ describe("plugin queue", function() assert.match_re(log_messages, 'WARN \\[\\] queue continue-processing: handler could not process entries: .*: hard error') assert.match_re(log_messages, 'ERR \\[\\] queue continue-processing: could not send entries, giving up after \\d retries. 1 queue entries were lost') end) + + it("sanity check for function Queue.is_full() & Queue.can_enqueue()", function() + local queue_conf = { + name = "queue-full-checking-too-many-entries", + max_batch_size = 99999, -- avoiding automatically flushing, + max_entries = 2, + max_bytes = nil, -- avoiding bytes limit + max_coalescing_delay = 99999, -- avoiding automatically flushing, + max_retry_time = 60, + initial_retry_delay = 1, + max_retry_delay = 60, + } + + local function enqueue(queue_conf, entry) + Queue.enqueue( + queue_conf, + function() + return true + end, + nil, + entry + ) + end + + assert.is_false(Queue.is_full(queue_conf)) + assert.is_false(Queue.can_enqueue(queue_conf, "One")) + enqueue(queue_conf, "One") + assert.is_false(Queue.is_full(queue_conf)) + + assert.is_true(Queue.can_enqueue(queue_conf, "Two")) + enqueue(queue_conf, "Two") + assert.is_true(Queue.is_full(queue_conf)) + + assert.is_false(Queue.can_enqueue(queue_conf, "Three")) + + + queue_conf = { + name = "queue-full-checking-too-many-bytes", + max_batch_size = 99999, -- avoiding automatically flushing, + max_entries = 99999, -- big enough to avoid entries limit + max_bytes = 2, + max_coalescing_delay = 99999, -- avoiding automatically flushing, + max_retry_time = 60, + initial_retry_delay = 1, + max_retry_delay = 60, + } + + assert.is_false(Queue.is_full(queue_conf)) + assert.is_false(Queue.can_enqueue(queue_conf, "1")) + enqueue(queue_conf, "1") + assert.is_false(Queue.is_full(queue_conf)) + + assert.is_true(Queue.can_enqueue(queue_conf, "2")) + enqueue(queue_conf, "2") + assert.is_true(Queue.is_full(queue_conf)) + + assert.is_false(Queue.can_enqueue(queue_conf, "3")) + + queue_conf = { + name = "queue-full-checking-too-large-entry", + max_batch_size = 99999, -- avoiding automatically flushing, + max_entries = 99999, -- big enough to avoid entries limit + max_bytes = 3, + max_coalescing_delay = 99999, -- avoiding automatically flushing, + max_retry_time = 60, + initial_retry_delay = 1, + max_retry_delay = 60, + } + + enqueue(queue_conf, "1") + + assert.is_false(Queue.is_full(queue_conf)) + assert.is_true(Queue.can_enqueue(queue_conf, "1")) + assert.is_true(Queue.can_enqueue(queue_conf, "11")) + assert.is_false(Queue.can_enqueue(queue_conf, "111")) + end) end)