diff --git a/changelog/unreleased/kong/feat-aws-lambda-max-uri-args.yml b/changelog/unreleased/kong/feat-aws-lambda-max-uri-args.yml new file mode 100644 index 00000000000..95359150412 --- /dev/null +++ b/changelog/unreleased/kong/feat-aws-lambda-max-uri-args.yml @@ -0,0 +1,3 @@ +message: "**aws-lambda**: Add `max_uri_args` configuration to override the default limit of 100 query string parameters, and `reject_if_max_uri_args_exceeded` to return 414 when the limit is exceeded." +type: feature +scope: Plugin diff --git a/kong/plugins/aws-lambda/handler.lua b/kong/plugins/aws-lambda/handler.lua index 5e590fb90bc..dd8cc7d8665 100644 --- a/kong/plugins/aws-lambda/handler.lua +++ b/kong/plugins/aws-lambda/handler.lua @@ -67,6 +67,18 @@ local AWSLambdaHandler = { function AWSLambdaHandler:access(conf) + -- Reject early if query string parameter count exceeds the configured limit + if conf.reject_if_max_uri_args_exceeded then + local args = ngx.req.get_uri_args(conf.max_uri_args) + local count = 0 + for _ in pairs(args) do + count = count + 1 + end + if count >= conf.max_uri_args then + return kong.response.exit(414, { message = "URI Too Long" }) + end + end + -- TRACING: set KONG_WAITING_TIME start local kong_wait_time_start = get_now() diff --git a/kong/plugins/aws-lambda/request-util.lua b/kong/plugins/aws-lambda/request-util.lua index 4391d7b2116..067dd2f0cda 100644 --- a/kong/plugins/aws-lambda/request-util.lua +++ b/kong/plugins/aws-lambda/request-util.lua @@ -170,7 +170,8 @@ local function aws_serializer(ctx, config) end -- query parameters - local queryStringParameters = ngx_req_get_uri_args() + local max_args = config and config.max_uri_args or nil + local queryStringParameters = ngx_req_get_uri_args(max_args) local multiValueQueryStringParameters = {} for qname, qvalue in pairs(queryStringParameters) do if type(qvalue) == "table" then diff --git a/kong/plugins/aws-lambda/schema.lua b/kong/plugins/aws-lambda/schema.lua index 744ca4debbf..f192aa0dd77 100644 --- a/kong/plugins/aws-lambda/schema.lua +++ b/kong/plugins/aws-lambda/schema.lua @@ -117,6 +117,17 @@ return { default = "v1", one_of = { "v1", "v2" } } }, + { max_uri_args = { + description = "The maximum number of query string parameters to parse from the request URI. Nginx's default limit is 100.", + type = "integer", + default = 100, + between = { 1, 1000 }, + } }, + { reject_if_max_uri_args_exceeded = { + description = "If enabled, the plugin returns a 414 URI Too Long response when the number of query string parameters exceeds max_uri_args, instead of forwarding the request to Lambda with truncated parameters.", + type = "boolean", + default = false, + } }, { 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", diff --git a/spec/03-plugins/27-aws-lambda/02-schema_spec.lua b/spec/03-plugins/27-aws-lambda/02-schema_spec.lua index baa1d2b34b6..1fdb890e0d6 100644 --- a/spec/03-plugins/27-aws-lambda/02-schema_spec.lua +++ b/spec/03-plugins/27-aws-lambda/02-schema_spec.lua @@ -122,4 +122,76 @@ describe("Plugin: AWS Lambda (schema)", function() assert.is_nil(err) assert.truthy(ok) end) + + describe("max_uri_args", function() + it("defaults to 100", function() + local ok, err = v({ + aws_region = "us-east-1", + function_name = "my-function" + }, schema_def) + + assert.is_nil(err) + assert.truthy(ok) + assert.equal(100, ok.config.max_uri_args) + end) + + it("accepts a valid value", function() + local ok, err = v({ + aws_region = "us-east-1", + function_name = "my-function", + max_uri_args = 1000, + }, schema_def) + + assert.is_nil(err) + assert.truthy(ok) + assert.equal(1000, ok.config.max_uri_args) + end) + + it("errors with a value less than 1", function() + local ok, err = v({ + aws_region = "us-east-1", + function_name = "my-function", + max_uri_args = 0, + }, schema_def) + + assert.equal("value should be between 1 and 1000", err.config.max_uri_args) + assert.falsy(ok) + end) + + it("errors with a value greater than 1000", function() + local ok, err = v({ + aws_region = "us-east-1", + function_name = "my-function", + max_uri_args = 1001, + }, schema_def) + + assert.equal("value should be between 1 and 1000", err.config.max_uri_args) + assert.falsy(ok) + end) + end) + + describe("reject_if_max_uri_args_exceeded", function() + it("defaults to false", function() + local ok, err = v({ + aws_region = "us-east-1", + function_name = "my-function" + }, schema_def) + + assert.is_nil(err) + assert.truthy(ok) + assert.is_false(ok.config.reject_if_max_uri_args_exceeded) + end) + + it("accepts true", function() + local ok, err = v({ + aws_region = "us-east-1", + function_name = "my-function", + reject_if_max_uri_args_exceeded = true, + }, schema_def) + + assert.is_nil(err) + assert.truthy(ok) + assert.is_true(ok.config.reject_if_max_uri_args_exceeded) + end) + end) end) diff --git a/spec/03-plugins/27-aws-lambda/05-aws-serializer_spec.lua b/spec/03-plugins/27-aws-lambda/05-aws-serializer_spec.lua index b4e90ac8003..2ff6e9f976f 100644 --- a/spec/03-plugins/27-aws-lambda/05-aws-serializer_spec.lua +++ b/spec/03-plugins/27-aws-lambda/05-aws-serializer_spec.lua @@ -298,4 +298,52 @@ describe("[AWS Lambda] aws-gateway input", function() end end + it("passes max_uri_args to ngx.req.get_uri_args", function() + local captured_max_args + + mock_request = { + http_version = "1.1", + start_time = 1662436514, + headers = { + ["user-agent"] = "curl/7.54.0", + }, + query = { + foo = "bar", + }, + body = "", + var = { + request_method = "GET", + upstream_uri = "/test?foo=bar", + kong_request_id = "1234567890", + host = "abc.myhost.test", + remote_addr = "123.123.123.123" + }, + ctx = { + router_matches = { + uri = "/test" + }, + }, + } + + -- patch get_uri_args to capture the argument + local orig_get_uri_args = _G.ngx.req.get_uri_args + _G.ngx.req.get_uri_args = function(max_args) + captured_max_args = max_args + return orig_get_uri_args() + end + + reload_module() + + aws_serialize(nil, { max_uri_args = 500 }) + assert.equal(500, captured_max_args) + + -- verify default (nil config) passes nil + captured_max_args = "not called" + aws_serialize() + assert.is_nil(captured_max_args) + + -- restore + _G.ngx.req.get_uri_args = orig_get_uri_args + end) + end) 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 d9f699708ac..85d0978d032 100644 --- a/spec/03-plugins/27-aws-lambda/99-access_spec.lua +++ b/spec/03-plugins/27-aws-lambda/99-access_spec.lua @@ -204,6 +204,18 @@ for _, strategy in helpers.each_strategy() do service = null, } + local route30 = bp.routes:insert { + hosts = { "lambda30.test" }, + protocols = { "http", "https" }, + service = null, + } + + local route31 = bp.routes:insert { + hosts = { "lambda31.test" }, + protocols = { "http", "https" }, + service = null, + } + bp.plugins:insert { name = "aws-lambda", route = { id = route1.id }, @@ -603,6 +615,34 @@ for _, strategy in helpers.each_strategy() do } } + bp.plugins:insert { + name = "aws-lambda", + route = { id = route30.id }, + config = { + port = 10001, + aws_key = "mock-key", + aws_secret = "mock-secret", + aws_region = "us-east-1", + function_name = "kongLambdaTest", + max_uri_args = 3, + reject_if_max_uri_args_exceeded = true, + } + } + + bp.plugins:insert { + name = "aws-lambda", + route = { id = route31.id }, + config = { + port = 10001, + aws_key = "mock-key", + aws_secret = "mock-secret", + aws_region = "us-east-1", + function_name = "kongLambdaTest", + max_uri_args = 3, + reject_if_max_uri_args_exceeded = false, + } + } + fixtures.dns_mock:A({ name = "custom.lambda.endpoint", address = "127.0.0.1", @@ -1344,6 +1384,45 @@ for _, strategy in helpers.each_strategy() do assert.equals("https", req.vars.scheme) end) + it("returns 414 when reject_if_max_uri_args_exceeded is true and args exceed max_uri_args", function() + -- route30 has max_uri_args=3, reject_if_max_uri_args_exceeded=true + local res = assert(proxy_client:send { + method = "GET", + path = "/get?a=1&b=2&c=3&d=4", + headers = { + ["Host"] = "lambda30.test" + } + }) + assert.res_status(414, res) + local body = assert.response(res).has.jsonbody() + assert.equal("URI Too Long", body.message) + end) + + it("passes request through when reject_if_max_uri_args_exceeded is true and args are within limit", function() + -- route30 has max_uri_args=3, reject_if_max_uri_args_exceeded=true + local res = assert(proxy_client:send { + method = "GET", + path = "/get?a=1&b=2", + headers = { + ["Host"] = "lambda30.test" + } + }) + assert.res_status(200, res) + end) + + it("does not return 414 when reject_if_max_uri_args_exceeded is false even if args exceed max_uri_args", function() + -- route31 has max_uri_args=3, reject_if_max_uri_args_exceeded=false + local res = assert(proxy_client:send { + method = "GET", + path = "/get?a=1&b=2&c=3&d=4", + headers = { + ["Host"] = "lambda31.test" + } + }) + -- should NOT be 414, should pass through to Lambda + assert.res_status(200, res) + end) + it("#test2 works normally by removing transfer encoding header when proxy integration mode", function () proxy_client:set_timeout(3000) assert.eventually(function ()