diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c17f8d0f81ad..04d3dc16c2e9 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -149,17 +149,19 @@ jobs: - 15002:9000 - 15003:9001 - redis: - image: redis - ports: - - 6379:6379 - options: --entrypoint redis-server - zipkin: image: openzipkin/zipkin:2.19 ports: - 9411:9411 + redis: + image: redis:6.2.6-alpine + ports: + - 6379:6379 + - 6380:6380 + options: >- + --name kong_redis + steps: - name: Set environment variables run: | @@ -185,6 +187,13 @@ jobs: echo "127.0.0.1 grpcs_1.test" | sudo tee -a /etc/hosts echo "127.0.0.1 grpcs_2.test" | sudo tee -a /etc/hosts + - name: Enable SSL for Redis + run: | + docker cp ${{ github.workspace }} kong_redis:/workspace + docker cp ${{ github.workspace }}/spec/fixtures/redis/docker-entrypoint.sh kong_redis:/usr/local/bin/docker-entrypoint.sh + docker restart kong_redis + docker logs kong_redis + - name: Tests run: | eval `luarocks path` @@ -272,17 +281,19 @@ jobs: - 15002:9000 - 15003:9001 - redis: - image: redis - ports: - - 6379:6379 - options: --entrypoint redis-server - zipkin: image: openzipkin/zipkin:2.19 ports: - 9411:9411 + redis: + image: redis:6.2.6-alpine + ports: + - 6379:6379 + - 6380:6380 + options: >- + --name kong_redis + steps: - name: Set environment variables run: | @@ -307,6 +318,13 @@ jobs: run: | echo "127.0.0.1 grpcs_1.test" | sudo tee -a /etc/hosts echo "127.0.0.1 grpcs_2.test" | sudo tee -a /etc/hosts + + - name: Enable SSL for Redis + run: | + docker cp ${{ github.workspace }} kong_redis:/workspace + docker cp ${{ github.workspace }}/spec/fixtures/redis/docker-entrypoint.sh kong_redis:/usr/local/bin/docker-entrypoint.sh + docker restart kong_redis + docker logs kong_redis - name: Tests run: | diff --git a/spec/03-plugins/23-rate-limiting/04-access_spec.lua b/spec/03-plugins/23-rate-limiting/04-access_spec.lua index 6b48a1a1b104..ff75ebb887d7 100644 --- a/spec/03-plugins/23-rate-limiting/04-access_spec.lua +++ b/spec/03-plugins/23-rate-limiting/04-access_spec.lua @@ -3,7 +3,9 @@ local cjson = require "cjson" local REDIS_HOST = helpers.redis_host -local REDIS_PORT = 6379 +local REDIS_PORT = helpers.redis_port +local REDIS_SSL_PORT = helpers.redis_ssl_port +local REDIS_SSL_SNI = helpers.redis_ssl_sni local REDIS_PASSWORD = "" local REDIS_DATABASE = 1 @@ -78,460 +80,917 @@ local function flush_redis() end -for _, strategy in helpers.each_strategy() do - for _, policy in ipairs({ "local", "cluster", "redis" }) do - describe(fmt("Plugin: rate-limiting (access) with policy: %s [#%s]", policy, strategy), function() - local bp - local db - - lazy_setup(function() - helpers.kill_all() - flush_redis() - - bp, db = helpers.get_db_utils(strategy) - - local consumer1 = bp.consumers:insert { - custom_id = "provider_123", - } - - bp.keyauth_credentials:insert { - key = "apikey122", - consumer = { id = consumer1.id }, - } - - local consumer2 = bp.consumers:insert { - custom_id = "provider_124", - } - - bp.keyauth_credentials:insert { - key = "apikey123", - consumer = { id = consumer2.id }, - } - - bp.keyauth_credentials:insert { - key = "apikey333", - consumer = { id = consumer2.id }, - } - - local route1 = bp.routes:insert { - hosts = { "test1.com" }, - } - - bp.rate_limiting_plugins:insert({ - route = { id = route1.id }, - config = { - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - local route_grpc_1 = assert(bp.routes:insert { - protocols = { "grpc" }, - paths = { "/hello.HelloService/" }, - service = assert(bp.services:insert { - name = "grpc", - url = "grpc://localhost:15002", - }), - }) - - bp.rate_limiting_plugins:insert({ - route = { id = route_grpc_1.id }, - config = { - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - local route2 = bp.routes:insert { - hosts = { "test2.com" }, - } - - bp.rate_limiting_plugins:insert({ - route = { id = route2.id }, - config = { - minute = 3, - hour = 5, - fault_tolerant = false, - policy = policy, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - local route3 = bp.routes:insert { - hosts = { "test3.com" }, - } - - bp.plugins:insert { - name = "key-auth", - route = { id = route3.id }, - } - - bp.rate_limiting_plugins:insert({ - route = { id = route3.id }, - config = { - minute = 6, - limit_by = "credential", - fault_tolerant = false, - policy = policy, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - bp.rate_limiting_plugins:insert({ - route = { id = route3.id }, - consumer = { id = consumer1.id }, - config = { - minute = 8, - fault_tolerant = false, - policy = policy, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE - } - }) - - local route4 = bp.routes:insert { - hosts = { "test4.com" }, - } - - bp.plugins:insert { - name = "key-auth", - route = { id = route4.id }, - } - - bp.rate_limiting_plugins:insert({ - route = { id = route4.id }, - consumer = { id = consumer1.id }, - config = { - minute = 6, - fault_tolerant = true, - policy = policy, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - }, - }) - - local route5 = bp.routes:insert { - hosts = { "test5.com" }, - } - - bp.rate_limiting_plugins:insert({ - route = { id = route5.id }, - config = { - policy = policy, - minute = 6, - hide_client_headers = true, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - }, - }) - - local service = bp.services:insert() - bp.routes:insert { - hosts = { "test-service1.com" }, - service = service, - } - bp.routes:insert { - hosts = { "test-service2.com" }, - service = service, - } - - bp.rate_limiting_plugins:insert({ - service = { id = service.id }, - config = { - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - local service = bp.services:insert() - bp.routes:insert { - hosts = { "test-path.com" }, - service = service, - } - - bp.rate_limiting_plugins:insert({ - service = { id = service.id }, - config = { - limit_by = "path", - path = "/status/200", - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - lazy_teardown(function() - helpers.stop_kong() - assert(db:truncate()) - end) - - describe("Without authentication (IP address)", function() - it_with_retry("blocks if exceeding limit", function() - for i = 1, 6 do - local res = GET("/status/200", { - headers = { Host = "test1.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset >= 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200", { - headers = { Host = "test1.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - - it_with_retry("blocks if exceeding limit, only if done via same path", function() - for i = 1, 3 do - local res = GET("/status/200", { - headers = { Host = "test-path.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Try a different path on the same host. This should reset the timers - for i = 1, 3 do - local res = GET("/status/201", { - headers = { Host = "test-path.com" }, - }, 201) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Continue doing requests on the path which "blocks" - for i = 4, 6 do - local res = GET("/status/200", { - headers = { Host = "test-path.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200", { - headers = { Host = "test-path.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - - it_with_retry("counts against the same service register from different routes", function() - for i = 1, 3 do - local res = GET("/status/200", { - headers = { Host = "test-service1.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end +local redis_confs = { + no_ssl = { + redis_port = REDIS_PORT, + }, + ssl_verify = { + redis_ssl = true, + redis_ssl_verify = true, + redis_server_name = REDIS_SSL_SNI, + redis_port = REDIS_SSL_PORT, + }, + ssl_no_verify = { + redis_ssl = true, + redis_ssl_verify = false, + redis_server_name = "really.really.really.does.not.exist.host.test", + redis_port = REDIS_SSL_PORT, + }, +} - for i = 4, 6 do - local res = GET("/status/200", { - headers = { Host = "test-service2.com" }, - }, 200) - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200", { - headers = { Host = "test-service1.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - - it_with_retry("handles multiple limits #flaky", function() - local limits = { - minute = 3, - hour = 5 - } - - for i = 1, 3 do - local res = GET("/status/200", { - headers = { Host = "test2.com" }, - }, 200) - - assert.are.same(limits.minute, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(limits.minute - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(limits.hour, tonumber(res.headers["x-ratelimit-limit-hour"])) - assert.are.same(limits.hour - i, tonumber(res.headers["x-ratelimit-remaining-hour"])) - assert.are.same(limits.minute, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(limits.minute - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - local res, body = GET("/status/200", { - path = "/status/200", - headers = { Host = "test2.com" }, - }, 429) - - assert.are.same(limits.minute, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - assert.equal(2, tonumber(res.headers["x-ratelimit-remaining-hour"])) - assert.equal(0, tonumber(res.headers["x-ratelimit-remaining-minute"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) +for _, strategy in helpers.each_strategy() do + for _, policy in ipairs({ "local", "cluster", "redis" }) do + for redis_conf_name, redis_conf in pairs(redis_confs) do + if redis_conf_name ~= "no_ssl" and policy ~= "redis" then + goto continue + end - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - end) - describe("Without authentication (IP address)", function() - it_with_retry("blocks if exceeding limit #grpc", function() - for i = 1, 6 do - local ok, res = helpers.proxy_client_grpc(){ - service = "hello.HelloService.SayHello", - opts = { - ["-v"] = true, + describe(fmt("Plugin: rate-limiting (access) with policy: #%s #%s [#%s]", redis_conf_name, policy, strategy), function() + local bp + local db + + lazy_setup(function() + helpers.kill_all() + flush_redis() + + bp, db = helpers.get_db_utils(strategy) + + local consumer1 = bp.consumers:insert { + custom_id = "provider_123", + } + + bp.keyauth_credentials:insert { + key = "apikey122", + consumer = { id = consumer1.id }, + } + + local consumer2 = bp.consumers:insert { + custom_id = "provider_124", + } + + bp.keyauth_credentials:insert { + key = "apikey123", + consumer = { id = consumer2.id }, + } + + bp.keyauth_credentials:insert { + key = "apikey333", + consumer = { id = consumer2.id }, + } + + local route1 = bp.routes:insert { + hosts = { "test1.com" }, + } + + bp.rate_limiting_plugins:insert({ + route = { id = route1.id }, + config = { + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + local route_grpc_1 = assert(bp.routes:insert { + protocols = { "grpc" }, + paths = { "/hello.HelloService/" }, + service = assert(bp.services:insert { + name = "grpc", + url = "grpc://localhost:15002", + }), + }) + + bp.rate_limiting_plugins:insert({ + route = { id = route_grpc_1.id }, + config = { + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + local route2 = bp.routes:insert { + hosts = { "test2.com" }, + } + + bp.rate_limiting_plugins:insert({ + route = { id = route2.id }, + config = { + minute = 3, + hour = 5, + fault_tolerant = false, + policy = policy, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + local route3 = bp.routes:insert { + hosts = { "test3.com" }, + } + + bp.plugins:insert { + name = "key-auth", + route = { id = route3.id }, + } + + bp.rate_limiting_plugins:insert({ + route = { id = route3.id }, + config = { + minute = 6, + limit_by = "credential", + fault_tolerant = false, + policy = policy, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + bp.rate_limiting_plugins:insert({ + route = { id = route3.id }, + consumer = { id = consumer1.id }, + config = { + minute = 8, + fault_tolerant = false, + policy = policy, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE + } + }) + + local route4 = bp.routes:insert { + hosts = { "test4.com" }, + } + + bp.plugins:insert { + name = "key-auth", + route = { id = route4.id }, + } + + bp.rate_limiting_plugins:insert({ + route = { id = route4.id }, + consumer = { id = consumer1.id }, + config = { + minute = 6, + fault_tolerant = true, + policy = policy, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, }, + }) + + local route5 = bp.routes:insert { + hosts = { "test5.com" }, } - assert.truthy(ok) - - assert.matches("x%-ratelimit%-limit%-minute: 6", res) - assert.matches("x%-ratelimit%-remaining%-minute: " .. (6 - i), res) - assert.matches("ratelimit%-limit: 6", res) - assert.matches("ratelimit%-remaining: " .. (6 - i), res) - - local reset = tonumber(string.match(res, "ratelimit%-reset: (%d+)")) - assert.equal(true, reset <= 60 and reset >= 0) - + + bp.rate_limiting_plugins:insert({ + route = { id = route5.id }, + config = { + policy = policy, + minute = 6, + hide_client_headers = true, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + }, + }) + + local service = bp.services:insert() + bp.routes:insert { + hosts = { "test-service1.com" }, + service = service, + } + bp.routes:insert { + hosts = { "test-service2.com" }, + service = service, + } + + bp.rate_limiting_plugins:insert({ + service = { id = service.id }, + config = { + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + local service = bp.services:insert() + bp.routes:insert { + hosts = { "test-path.com" }, + service = service, + } + + bp.rate_limiting_plugins:insert({ + service = { id = service.id }, + config = { + limit_by = "path", + path = "/status/200", + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) + end) + + lazy_teardown(function() + helpers.stop_kong() + assert(db:truncate()) + end) + + describe("Without authentication (IP address)", function() + it_with_retry("blocks if exceeding limit", function() + for i = 1, 6 do + local res = GET("/status/200", { + headers = { Host = "test1.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset >= 0) + end + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200", { + headers = { Host = "test1.com" }, + }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end) + + it_with_retry("blocks if exceeding limit, only if done via same path", function() + for i = 1, 3 do + local res = GET("/status/200", { + headers = { Host = "test-path.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Try a different path on the same host. This should reset the timers + for i = 1, 3 do + local res = GET("/status/201", { + headers = { Host = "test-path.com" }, + }, 201) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Continue doing requests on the path which "blocks" + for i = 4, 6 do + local res = GET("/status/200", { + headers = { Host = "test-path.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200", { + headers = { Host = "test-path.com" }, + }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end) + + it_with_retry("counts against the same service register from different routes", function() + for i = 1, 3 do + local res = GET("/status/200", { + headers = { Host = "test-service1.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + for i = 4, 6 do + local res = GET("/status/200", { + headers = { Host = "test-service2.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200", { + headers = { Host = "test-service1.com" }, + }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end) + + it_with_retry("handles multiple limits #flaky", function() + local limits = { + minute = 3, + hour = 5 + } + + for i = 1, 3 do + local res = GET("/status/200", { + headers = { Host = "test2.com" }, + }, 200) + + assert.are.same(limits.minute, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(limits.minute - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(limits.hour, tonumber(res.headers["x-ratelimit-limit-hour"])) + assert.are.same(limits.hour - i, tonumber(res.headers["x-ratelimit-remaining-hour"])) + assert.are.same(limits.minute, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(limits.minute - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + local res, body = GET("/status/200", { + path = "/status/200", + headers = { Host = "test2.com" }, + }, 429) + + assert.are.same(limits.minute, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + assert.equal(2, tonumber(res.headers["x-ratelimit-remaining-hour"])) + assert.equal(0, tonumber(res.headers["x-ratelimit-remaining-minute"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end) + end) + describe("Without authentication (IP address)", function() + it_with_retry("blocks if exceeding limit #grpc", function() + for i = 1, 6 do + local ok, res = helpers.proxy_client_grpc(){ + service = "hello.HelloService.SayHello", + opts = { + ["-v"] = true, + }, + } + assert.truthy(ok) + + assert.matches("x%-ratelimit%-limit%-minute: 6", res) + assert.matches("x%-ratelimit%-remaining%-minute: " .. (6 - i), res) + assert.matches("ratelimit%-limit: 6", res) + assert.matches("ratelimit%-remaining: " .. (6 - i), res) + + local reset = tonumber(string.match(res, "ratelimit%-reset: (%d+)")) + assert.equal(true, reset <= 60 and reset >= 0) + + end + + -- Additonal request, while limit is 6/minute + local ok, res = helpers.proxy_client_grpc(){ + service = "hello.HelloService.SayHello", + opts = { + ["-v"] = true, + }, + } + assert.falsy(ok) + assert.matches("Code: ResourceExhausted", res) + + assert.matches("ratelimit%-limit: 6", res) + assert.matches("ratelimit%-remaining: 0", res) + + local retry = tonumber(string.match(res, "retry%-after: (%d+)")) + assert.equal(true, retry <= 60 and retry > 0) + + + local reset = tonumber(string.match(res, "ratelimit%-reset: (%d+)")) + assert.equal(true, reset <= 60 and reset > 0) + end) + end) + describe("With authentication", function() + describe("API-specific plugin", function() + it_with_retry("blocks if exceeding limit", function() + for i = 1, 6 do + local res = GET("/status/200?apikey=apikey123", { + headers = { Host = "test3.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Third query, while limit is 2/minute + local res, body = GET("/status/200?apikey=apikey123", { + headers = { Host = "test3.com" }, + }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + + -- Using a different key of the same consumer works + GET("/status/200?apikey=apikey333", { + headers = { Host = "test3.com" }, + }, 200) + end) + end) + describe("#flaky Plugin customized for specific consumer and route", function() + it_with_retry("blocks if exceeding limit", function() + for i = 1, 8 do + local res = GET("/status/200?apikey=apikey122", { + headers = { Host = "test3.com" }, + }, 200) + + assert.are.same(8, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(8 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(8, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(8 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + local res, body = GET("/status/200?apikey=apikey122", { + headers = { Host = "test3.com" }, + }, 429) + + assert.are.same(8, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end) + + it_with_retry("blocks if the only rate-limiting plugin existing is per consumer and not per API", function() + for i = 1, 6 do + local res = GET("/status/200?apikey=apikey122", { + headers = { Host = "test4.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + local res, body = GET("/status/200?apikey=apikey122", { + headers = { Host = "test4.com" }, + }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end) + end) + end) + + describe("Config with hide_client_headers", function() + it_with_retry("does not send rate-limit headers when hide_client_headers==true", function() + local res = GET("/status/200", { + headers = { Host = "test5.com" }, + }, 200) + + assert.is_nil(res.headers["x-ratelimit-limit-minute"]) + assert.is_nil(res.headers["x-ratelimit-remaining-minute"]) + assert.is_nil(res.headers["ratelimit-limit"]) + assert.is_nil(res.headers["ratelimit-remaining"]) + assert.is_nil(res.headers["ratelimit-reset"]) + assert.is_nil(res.headers["retry-after"]) + end) + end) + + if policy == "cluster" then + describe("#flaky Fault tolerancy", function() + + before_each(function() + helpers.kill_all() + + assert(db:truncate()) + + local route1 = bp.routes:insert { + hosts = { "failtest1.com" }, + } + + bp.rate_limiting_plugins:insert { + route = { id = route1.id }, + config = { minute = 6, fault_tolerant = false } + } + + local route2 = bp.routes:insert { + hosts = { "failtest2.com" }, + } + + bp.rate_limiting_plugins:insert { + name = "rate-limiting", + route = { id = route2.id }, + config = { minute = 6, fault_tolerant = true }, + } + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) + end) + + lazy_teardown(function() + helpers.kill_all() + assert(db:truncate()) + end) + + it_with_retry("does not work if an error occurs", function() + local res = GET("/status/200", { + headers = { Host = "failtest1.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + -- Simulate an error on the database + assert(db.connector:query("DROP TABLE ratelimiting_metrics")) + + -- Make another request + local _, body = GET("/status/200", { + headers = { Host = "failtest1.com" }, + }, 500) + + local json = cjson.decode(body) + assert.same({ message = "An unexpected error occurred" }, json) + + db:reset() + bp, db = helpers.get_db_utils(strategy) + end) + + it_with_retry("keeps working if an error occurs", function() + local res = GET("/status/200", { + headers = { Host = "failtest2.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + -- Simulate an error on the database + assert(db.connector:query("DROP TABLE ratelimiting_metrics")) + + -- Make another request + local res = GET("/status/200", { + headers = { Host = "failtest2.com" }, + }, 200) + + assert.falsy(res.headers["x-ratelimit-limit-minute"]) + assert.falsy(res.headers["x-ratelimit-remaining-minute"]) + assert.falsy(res.headers["ratelimit-limit"]) + assert.falsy(res.headers["ratelimit-remaining"]) + assert.falsy(res.headers["ratelimit-reset"]) + + db:reset() + bp, db = helpers.get_db_utils(strategy) + end) + end) + + elseif policy == "redis" then + describe("#flaky Fault tolerancy", function() + + before_each(function() + helpers.kill_all() + + assert(db:truncate()) + + local service1 = bp.services:insert() + + local route1 = bp.routes:insert { + hosts = { "failtest3.com" }, + protocols = { "http", "https" }, + service = service1 + } + + bp.rate_limiting_plugins:insert { + route = { id = route1.id }, + config = { minute = 6, policy = policy, redis_host = "5.5.5.5", fault_tolerant = false }, + } + + local service2 = bp.services:insert() + + local route2 = bp.routes:insert { + hosts = { "failtest4.com" }, + protocols = { "http", "https" }, + service = service2 + } + + bp.rate_limiting_plugins:insert { + name = "rate-limiting", + route = { id = route2.id }, + config = { minute = 6, policy = policy, redis_host = "5.5.5.5", fault_tolerant = true }, + } + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) + end) + + lazy_teardown(function() + helpers.kill_all() + assert(db:truncate()) + end) + + it_with_retry("does not work if an error occurs", function() + -- Make another request + local _, body = GET("/status/200", { + headers = { Host = "failtest3.com" }, + }, 500) + + local json = cjson.decode(body) + assert.same({ message = "An unexpected error occurred" }, json) + end) + + it_with_retry("keeps working if an error occurs", function() + local res = GET("/status/200", { + headers = { Host = "failtest4.com" }, + }, 200) + + assert.falsy(res.headers["x-ratelimit-limit-minute"]) + assert.falsy(res.headers["x-ratelimit-remaining-minute"]) + assert.falsy(res.headers["ratelimit-limit"]) + assert.falsy(res.headers["ratelimit-remaining"]) + assert.falsy(res.headers["ratelimit-reset"]) + end) + end) end - - -- Additonal request, while limit is 6/minute - local ok, res = helpers.proxy_client_grpc(){ - service = "hello.HelloService.SayHello", - opts = { - ["-v"] = true, - }, - } - assert.falsy(ok) - assert.matches("Code: ResourceExhausted", res) - - assert.matches("ratelimit%-limit: 6", res) - assert.matches("ratelimit%-remaining: 0", res) - - local retry = tonumber(string.match(res, "retry%-after: (%d+)")) - assert.equal(true, retry <= 60 and retry > 0) - - - local reset = tonumber(string.match(res, "ratelimit%-reset: (%d+)")) - assert.equal(true, reset <= 60 and reset > 0) + + describe("Expirations", function() + local route + + lazy_setup(function() + helpers.stop_kong() + + local bp = helpers.get_db_utils(strategy) + + route = bp.routes:insert { + hosts = { "expire1.com" }, + } + + bp.rate_limiting_plugins:insert { + route = { id = route.id }, + config = { + minute = 6, + policy = policy, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + fault_tolerant = false, + redis_database = REDIS_DATABASE, + }, + } + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) + end) + + it_with_retry("#flaky expires a counter", function() + local t = 61 - (ngx.now() % 60) + + local res = GET("/status/200", { + headers = { Host = "expire1.com" }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + ngx.sleep(t) -- Wait for minute to expire + + local res = GET("/status/200", { + headers = { Host = "expire1.com" } + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + end) + end) end) - end) - describe("With authentication", function() - describe("API-specific plugin", function() - it_with_retry("blocks if exceeding limit", function() + + describe(fmt("Plugin: rate-limiting (access - global for single consumer) with policy: #%s #%s [#%s]", redis_conf_name, policy, strategy), function() + local bp + local db + + lazy_setup(function() + helpers.kill_all() + flush_redis() + bp, db = helpers.get_db_utils(strategy) + + local consumer = bp.consumers:insert { + custom_id = "provider_125", + } + + bp.key_auth_plugins:insert() + + bp.keyauth_credentials:insert { + key = "apikey125", + consumer = { id = consumer.id }, + } + + -- just consumer, no no route or service + bp.rate_limiting_plugins:insert({ + consumer = { id = consumer.id }, + config = { + limit_by = "credential", + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + for i = 1, 6 do + bp.routes:insert({ hosts = { fmt("test%d.com", i) } }) + end + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) + end) + + lazy_teardown(function() + helpers.kill_all() + assert(db:truncate()) + end) + + it_with_retry("blocks when the consumer exceeds their quota, no matter what service/route used", function() for i = 1, 6 do - local res = GET("/status/200?apikey=apikey123", { - headers = { Host = "test3.com" }, + local res = GET("/status/200?apikey=apikey125", { + headers = { Host = fmt("test%d.com", i) }, }, 200) - + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) @@ -539,68 +998,79 @@ for _, strategy in helpers.each_strategy() do local reset = tonumber(res.headers["ratelimit-reset"]) assert.equal(true, reset <= 60 and reset > 0) end - - -- Third query, while limit is 2/minute - local res, body = GET("/status/200?apikey=apikey123", { - headers = { Host = "test3.com" }, + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200?apikey=apikey125", { + headers = { Host = "test1.com" }, }, 429) - + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - + local retry = tonumber(res.headers["retry-after"]) assert.equal(true, retry <= 60 and retry > 0) - + local reset = tonumber(res.headers["ratelimit-reset"]) assert.equal(true, reset <= 60 and reset > 0) - + local json = cjson.decode(body) assert.same({ message = "API rate limit exceeded" }, json) - - -- Using a different key of the same consumer works - GET("/status/200?apikey=apikey333", { - headers = { Host = "test3.com" }, - }, 200) end) end) - describe("#flaky Plugin customized for specific consumer and route", function() - it_with_retry("blocks if exceeding limit", function() - for i = 1, 8 do - local res = GET("/status/200?apikey=apikey122", { - headers = { Host = "test3.com" }, - }, 200) - - assert.are.same(8, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(8 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(8, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(8 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) + + describe(fmt("Plugin: rate-limiting (access - global for service) with policy: #%s #%s [#%s]", redis_conf_name, policy, strategy), function() + local bp + local db + + lazy_setup(function() + helpers.kill_all() + flush_redis() + bp, db = helpers.get_db_utils(strategy) + + -- global plugin (not attached to route, service or consumer) + bp.rate_limiting_plugins:insert({ + config = { + limit_by = "service", + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + local service = bp.services:insert() + + for i = 1, 6 do + bp.routes:insert({ + hosts = { fmt("test%d.com", i) }, + service = service, + }) end - - local res, body = GET("/status/200?apikey=apikey122", { - headers = { Host = "test3.com" }, - }, 429) - - assert.are.same(8, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) end) - - it_with_retry("blocks if the only rate-limiting plugin existing is per consumer and not per API", function() + + lazy_teardown(function() + helpers.kill_all() + assert(db:truncate()) + end) + + it_with_retry("blocks if exceeding limit", function() for i = 1, 6 do - local res = GET("/status/200?apikey=apikey122", { - headers = { Host = "test4.com" }, + local res = GET("/status/200", { + headers = { Host = fmt("test%d.com", i) }, }, 200) - + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) @@ -608,660 +1078,273 @@ for _, strategy in helpers.each_strategy() do local reset = tonumber(res.headers["ratelimit-reset"]) assert.equal(true, reset <= 60 and reset > 0) end - - local res, body = GET("/status/200?apikey=apikey122", { - headers = { Host = "test4.com" }, + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200", { + headers = { Host = "test1.com" }, }, 429) - + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - + local retry = tonumber(res.headers["retry-after"]) assert.equal(true, retry <= 60 and retry > 0) - + local reset = tonumber(res.headers["ratelimit-reset"]) assert.equal(true, reset <= 60 and reset > 0) - + local json = cjson.decode(body) assert.same({ message = "API rate limit exceeded" }, json) end) end) - end) - - describe("Config with hide_client_headers", function() - it_with_retry("does not send rate-limit headers when hide_client_headers==true", function() - local res = GET("/status/200", { - headers = { Host = "test5.com" }, - }, 200) - - assert.is_nil(res.headers["x-ratelimit-limit-minute"]) - assert.is_nil(res.headers["x-ratelimit-remaining-minute"]) - assert.is_nil(res.headers["ratelimit-limit"]) - assert.is_nil(res.headers["ratelimit-remaining"]) - assert.is_nil(res.headers["ratelimit-reset"]) - assert.is_nil(res.headers["retry-after"]) - end) - end) - - if policy == "cluster" then - describe("#flaky Fault tolerancy", function() - - before_each(function() + + describe(fmt("Plugin: rate-limiting (access - per service) with policy: #%s #%s [#%s]", redis_conf_name, policy, strategy), function() + local bp + local db + + lazy_setup(function() helpers.kill_all() - - assert(db:truncate()) - - local route1 = bp.routes:insert { - hosts = { "failtest1.com" }, - } - - bp.rate_limiting_plugins:insert { - route = { id = route1.id }, - config = { minute = 6, fault_tolerant = false } - } - - local route2 = bp.routes:insert { - hosts = { "failtest2.com" }, + flush_redis() + bp, db = helpers.get_db_utils(strategy) + + -- global plugin (not attached to route, service or consumer) + bp.rate_limiting_plugins:insert({ + config = { + limit_by = "service", + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + local service1 = bp.services:insert() + bp.routes:insert { + hosts = { "test1.com" }, + service = service1, } - - bp.rate_limiting_plugins:insert { - name = "rate-limiting", - route = { id = route2.id }, - config = { minute = 6, fault_tolerant = true }, + + local service2 = bp.services:insert() + bp.routes:insert { + hosts = { "test2.com" }, + service = service2, } - + assert(helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", })) end) - + lazy_teardown(function() helpers.kill_all() assert(db:truncate()) end) - - it_with_retry("does not work if an error occurs", function() - local res = GET("/status/200", { - headers = { Host = "failtest1.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - -- Simulate an error on the database - assert(db.connector:query("DROP TABLE ratelimiting_metrics")) - - -- Make another request - local _, body = GET("/status/200", { - headers = { Host = "failtest1.com" }, - }, 500) - - local json = cjson.decode(body) - assert.same({ message = "An unexpected error occurred" }, json) - - db:reset() + + it_with_retry("blocks if exceeding limit", function() + for i = 1, 6 do + local res = GET("/status/200", { headers = { Host = "test1.com" } }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + for i = 1, 6 do + local res = GET("/status/200", { headers = { Host = "test2.com" } }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Additonal request, while limit is 6/minute + for _, host in ipairs{ "test1.com", "test2.com" } do + local res, body = GET("/status/200", { headers = { Host = host } }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) + end + end) + end) + + describe(fmt("Plugin: rate-limiting (access - global) with policy: #%s #%s [#%s]", redis_conf_name, policy, strategy), function() + local bp + local db + + lazy_setup(function() + helpers.kill_all() + flush_redis() bp, db = helpers.get_db_utils(strategy) + + -- global plugin (not attached to route, service or consumer) + bp.rate_limiting_plugins:insert({ + config = { + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + for i = 1, 6 do + bp.routes:insert({ hosts = { fmt("test%d.com", i) } }) + end + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + })) end) - - it_with_retry("keeps working if an error occurs", function() - local res = GET("/status/200", { - headers = { Host = "failtest2.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) + + lazy_teardown(function() + helpers.kill_all() + assert(db:truncate()) + end) + + it_with_retry("blocks if exceeding limit", function() + for i = 1, 6 do + local res = GET("/status/200", { + headers = { Host = fmt("test%d.com", i) }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200", { + headers = { Host = "test1.com" }, + }, 429) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + local reset = tonumber(res.headers["ratelimit-reset"]) assert.equal(true, reset <= 60 and reset > 0) - - -- Simulate an error on the database - assert(db.connector:query("DROP TABLE ratelimiting_metrics")) - - -- Make another request - local res = GET("/status/200", { - headers = { Host = "failtest2.com" }, - }, 200) - - assert.falsy(res.headers["x-ratelimit-limit-minute"]) - assert.falsy(res.headers["x-ratelimit-remaining-minute"]) - assert.falsy(res.headers["ratelimit-limit"]) - assert.falsy(res.headers["ratelimit-remaining"]) - assert.falsy(res.headers["ratelimit-reset"]) - - db:reset() - bp, db = helpers.get_db_utils(strategy) + + local json = cjson.decode(body) + assert.same({ message = "API rate limit exceeded" }, json) end) end) - - elseif policy == "redis" then - describe("#flaky Fault tolerancy", function() - - before_each(function() + + describe(fmt("Plugin: rate-limiting (access - global) with policy: #%s #%s [#%s] by path", redis_conf_name, policy, strategy), function() + local bp + local db + + lazy_setup(function() helpers.kill_all() - - assert(db:truncate()) - - local service1 = bp.services:insert() - - local route1 = bp.routes:insert { - hosts = { "failtest3.com" }, - protocols = { "http", "https" }, - service = service1 - } - - bp.rate_limiting_plugins:insert { - route = { id = route1.id }, - config = { minute = 6, policy = policy, redis_host = "5.5.5.5", fault_tolerant = false }, - } - - local service2 = bp.services:insert() - - local route2 = bp.routes:insert { - hosts = { "failtest4.com" }, - protocols = { "http", "https" }, - service = service2 - } - - bp.rate_limiting_plugins:insert { - name = "rate-limiting", - route = { id = route2.id }, - config = { minute = 6, policy = policy, redis_host = "5.5.5.5", fault_tolerant = true }, - } - + flush_redis() + bp, db = helpers.get_db_utils(strategy) + + -- global plugin (not attached to route, service or consumer) + bp.rate_limiting_plugins:insert({ + config = { + policy = policy, + minute = 6, + fault_tolerant = false, + redis_host = REDIS_HOST, + redis_port = redis_conf.redis_port, + redis_ssl = redis_conf.redis_ssl, + redis_ssl_verify = redis_conf.redis_ssl_verify, + redis_server_name = redis_conf.redis_server_name, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DATABASE, + } + }) + + -- hosts with services + for i = 1, 3 do + bp.routes:insert({ service = bp.services:insert(), hosts = { fmt("test%d.com", i) } }) + end + + -- serviceless routes + for i = 4, 6 do + bp.routes:insert({ hosts = { fmt("test%d.com", i) } }) + end + assert(helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", })) end) - + lazy_teardown(function() helpers.kill_all() assert(db:truncate()) end) - - it_with_retry("does not work if an error occurs", function() - -- Make another request - local _, body = GET("/status/200", { - headers = { Host = "failtest3.com" }, - }, 500) - + + it_with_retry("maintains the counters for a path through different services and routes", function() + for i = 1, 6 do + local res = GET("/status/200", { + headers = { Host = fmt("test%d.com", i) }, + }, 200) + + assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) + assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + end + + -- Additonal request, while limit is 6/minute + local res, body = GET("/status/200", { + headers = { Host = "test1.com" }, + }, 429) + + assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) + assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) + + local retry = tonumber(res.headers["retry-after"]) + assert.equal(true, retry <= 60 and retry > 0) + + local reset = tonumber(res.headers["ratelimit-reset"]) + assert.equal(true, reset <= 60 and reset > 0) + local json = cjson.decode(body) - assert.same({ message = "An unexpected error occurred" }, json) - end) - - it_with_retry("keeps working if an error occurs", function() - local res = GET("/status/200", { - headers = { Host = "failtest4.com" }, - }, 200) - - assert.falsy(res.headers["x-ratelimit-limit-minute"]) - assert.falsy(res.headers["x-ratelimit-remaining-minute"]) - assert.falsy(res.headers["ratelimit-limit"]) - assert.falsy(res.headers["ratelimit-remaining"]) - assert.falsy(res.headers["ratelimit-reset"]) + assert.same({ message = "API rate limit exceeded" }, json) end) end) - end - - describe("Expirations", function() - local route - - lazy_setup(function() - helpers.stop_kong() - - local bp = helpers.get_db_utils(strategy) - - route = bp.routes:insert { - hosts = { "expire1.com" }, - } - - bp.rate_limiting_plugins:insert { - route = { id = route.id }, - config = { - minute = 6, - policy = policy, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - fault_tolerant = false, - redis_database = REDIS_DATABASE, - }, - } - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - it_with_retry("#flaky expires a counter", function() - local t = 61 - (ngx.now() % 60) - - local res = GET("/status/200", { - headers = { Host = "expire1.com" }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - ngx.sleep(t) -- Wait for minute to expire - - local res = GET("/status/200", { - headers = { Host = "expire1.com" } - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(5, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(5, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - end) - end) - end) - - describe(fmt("Plugin: rate-limiting (access - global for single consumer) with policy: %s [#%s]", policy, strategy), function() - local bp - local db - - lazy_setup(function() - helpers.kill_all() - flush_redis() - bp, db = helpers.get_db_utils(strategy) - - local consumer = bp.consumers:insert { - custom_id = "provider_125", - } - - bp.key_auth_plugins:insert() - - bp.keyauth_credentials:insert { - key = "apikey125", - consumer = { id = consumer.id }, - } - - -- just consumer, no no route or service - bp.rate_limiting_plugins:insert({ - consumer = { id = consumer.id }, - config = { - limit_by = "credential", - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - for i = 1, 6 do - bp.routes:insert({ hosts = { fmt("test%d.com", i) } }) - end - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - lazy_teardown(function() - helpers.kill_all() - assert(db:truncate()) - end) - - it_with_retry("blocks when the consumer exceeds their quota, no matter what service/route used", function() - for i = 1, 6 do - local res = GET("/status/200?apikey=apikey125", { - headers = { Host = fmt("test%d.com", i) }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200?apikey=apikey125", { - headers = { Host = "test1.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - end) - - describe(fmt("Plugin: rate-limiting (access - global for service) with policy: %s [#%s]", policy, strategy), function() - local bp - local db - - lazy_setup(function() - helpers.kill_all() - flush_redis() - bp, db = helpers.get_db_utils(strategy) - - -- global plugin (not attached to route, service or consumer) - bp.rate_limiting_plugins:insert({ - config = { - limit_by = "service", - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - local service = bp.services:insert() - - for i = 1, 6 do - bp.routes:insert({ - hosts = { fmt("test%d.com", i) }, - service = service, - }) - end - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - lazy_teardown(function() - helpers.kill_all() - assert(db:truncate()) - end) - - it_with_retry("blocks if exceeding limit", function() - for i = 1, 6 do - local res = GET("/status/200", { - headers = { Host = fmt("test%d.com", i) }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200", { - headers = { Host = "test1.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - end) - - describe(fmt("Plugin: rate-limiting (access - per service) with policy: %s [#%s]", policy, strategy), function() - local bp - local db - - lazy_setup(function() - helpers.kill_all() - flush_redis() - bp, db = helpers.get_db_utils(strategy) - -- global plugin (not attached to route, service or consumer) - bp.rate_limiting_plugins:insert({ - config = { - limit_by = "service", - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - local service1 = bp.services:insert() - bp.routes:insert { - hosts = { "test1.com" }, - service = service1, - } - - local service2 = bp.services:insert() - bp.routes:insert { - hosts = { "test2.com" }, - service = service2, - } - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - lazy_teardown(function() - helpers.kill_all() - assert(db:truncate()) - end) - - it_with_retry("blocks if exceeding limit", function() - for i = 1, 6 do - local res = GET("/status/200", { headers = { Host = "test1.com" } }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - for i = 1, 6 do - local res = GET("/status/200", { headers = { Host = "test2.com" } }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - for _, host in ipairs{ "test1.com", "test2.com" } do - local res, body = GET("/status/200", { headers = { Host = host } }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end - end) - end) - - describe(fmt("Plugin: rate-limiting (access - global) with policy: %s [#%s]", policy, strategy), function() - local bp - local db - - lazy_setup(function() - helpers.kill_all() - flush_redis() - bp, db = helpers.get_db_utils(strategy) - - -- global plugin (not attached to route, service or consumer) - bp.rate_limiting_plugins:insert({ - config = { - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - for i = 1, 6 do - bp.routes:insert({ hosts = { fmt("test%d.com", i) } }) - end - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - lazy_teardown(function() - helpers.kill_all() - assert(db:truncate()) - end) - - it_with_retry("blocks if exceeding limit", function() - for i = 1, 6 do - local res = GET("/status/200", { - headers = { Host = fmt("test%d.com", i) }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200", { - headers = { Host = "test1.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - end) - - describe(fmt("Plugin: rate-limiting (access - global) with policy: %s [#%s] by path", policy, strategy), function() - local bp - local db - - lazy_setup(function() - helpers.kill_all() - flush_redis() - bp, db = helpers.get_db_utils(strategy) - - -- global plugin (not attached to route, service or consumer) - bp.rate_limiting_plugins:insert({ - config = { - policy = policy, - minute = 6, - fault_tolerant = false, - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DATABASE, - } - }) - - -- hosts with services - for i = 1, 3 do - bp.routes:insert({ service = bp.services:insert(), hosts = { fmt("test%d.com", i) } }) - end - - -- serviceless routes - for i = 4, 6 do - bp.routes:insert({ hosts = { fmt("test%d.com", i) } }) - end - - assert(helpers.start_kong({ - database = strategy, - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - end) - - lazy_teardown(function() - helpers.kill_all() - assert(db:truncate()) - end) - - it_with_retry("maintains the counters for a path through different services and routes", function() - for i = 1, 6 do - local res = GET("/status/200", { - headers = { Host = fmt("test%d.com", i) }, - }, 200) - - assert.are.same(6, tonumber(res.headers["x-ratelimit-limit-minute"])) - assert.are.same(6 - i, tonumber(res.headers["x-ratelimit-remaining-minute"])) - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(6 - i, tonumber(res.headers["ratelimit-remaining"])) - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - end - - -- Additonal request, while limit is 6/minute - local res, body = GET("/status/200", { - headers = { Host = "test1.com" }, - }, 429) - - assert.are.same(6, tonumber(res.headers["ratelimit-limit"])) - assert.are.same(0, tonumber(res.headers["ratelimit-remaining"])) - - local retry = tonumber(res.headers["retry-after"]) - assert.equal(true, retry <= 60 and retry > 0) - - local reset = tonumber(res.headers["ratelimit-reset"]) - assert.equal(true, reset <= 60 and reset > 0) - - local json = cjson.decode(body) - assert.same({ message = "API rate limit exceeded" }, json) - end) - end) + ::continue:: + end end end diff --git a/spec/03-plugins/23-rate-limiting/05-integration_spec.lua b/spec/03-plugins/23-rate-limiting/05-integration_spec.lua index c2b479685550..0ac73c78f392 100644 --- a/spec/03-plugins/23-rate-limiting/05-integration_spec.lua +++ b/spec/03-plugins/23-rate-limiting/05-integration_spec.lua @@ -3,12 +3,14 @@ local redis = require "resty.redis" local version = require "version" -local REDIS_HOST = helpers.redis_host -local REDIS_PORT = 6379 -local REDIS_DB_1 = 1 -local REDIS_DB_2 = 2 -local REDIS_DB_3 = 3 -local REDIS_DB_4 = 4 +local REDIS_HOST = helpers.redis_host +local REDIS_PORT = helpers.redis_port +local REDIS_SSL_PORT = helpers.redis_ssl_port +local REDIS_SSL_SNI = helpers.redis_ssl_sni +local REDIS_DB_1 = 1 +local REDIS_DB_2 = 2 +local REDIS_DB_3 = 3 +local REDIS_DB_4 = 4 local REDIS_USER_VALID = "ratelimit-user" local REDIS_USER_INVALID = "some-user" @@ -67,240 +69,280 @@ describe("Plugin: rate-limiting (integration)", function() helpers.stop_kong() end) - describe("config.policy = redis", function() - -- Regression test for the following issue: - -- https://github.com/Kong/kong/issues/3292 - - lazy_setup(function() - flush_redis(red, REDIS_DB_1) - flush_redis(red, REDIS_DB_2) - flush_redis(red, REDIS_DB_3) - if red_version >= version("6.0.0") then - add_redis_user(red) - end - - local route1 = assert(bp.routes:insert { - hosts = { "redistest1.com" }, - }) - assert(bp.plugins:insert { - name = "rate-limiting", - route = { id = route1.id }, - config = { - minute = 1, - policy = "redis", - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_database = REDIS_DB_1, - fault_tolerant = false, - }, - }) - - local route2 = assert(bp.routes:insert { - hosts = { "redistest2.com" }, - }) - assert(bp.plugins:insert { - name = "rate-limiting", - route = { id = route2.id }, - config = { - minute = 1, - policy = "redis", - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_database = REDIS_DB_2, - fault_tolerant = false, - } - }) - - if red_version >= version("6.0.0") then - local route3 = assert(bp.routes:insert { - hosts = { "redistest3.com" }, + local strategies = { + no_ssl = { + redis_port = REDIS_PORT, + }, + ssl_verify = { + redis_ssl = true, + redis_ssl_verify = true, + redis_server_name = REDIS_SSL_SNI, + lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt", + redis_port = REDIS_SSL_PORT, + }, + ssl_no_verify = { + redis_ssl = true, + redis_ssl_verify = false, + redis_server_name = "really.really.really.does.not.exist.host.test", + redis_port = REDIS_SSL_PORT, + }, + } + + for strategy, config in pairs(strategies) do + describe("config.policy = redis #" .. strategy, function() + -- Regression test for the following issue: + -- https://github.com/Kong/kong/issues/3292 + + lazy_setup(function() + flush_redis(red, REDIS_DB_1) + flush_redis(red, REDIS_DB_2) + flush_redis(red, REDIS_DB_3) + if red_version >= version("6.0.0") then + add_redis_user(red) + end + + local route1 = assert(bp.routes:insert { + hosts = { "redistest1.com" }, }) assert(bp.plugins:insert { name = "rate-limiting", - route = { id = route3.id }, + route = { id = route1.id }, config = { - minute = 2, -- Handle multiple tests - policy = "redis", - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_username = REDIS_USER_VALID, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DB_3, -- ensure to not get a pooled authenticated connection by using a different db - fault_tolerant = false, - } + minute = 1, + policy = "redis", + redis_host = REDIS_HOST, + redis_port = config.redis_port, + redis_database = REDIS_DB_1, + redis_ssl = config.redis_ssl, + redis_ssl_verify = config.redis_ssl_verify, + redis_server_name = config.redis_server_name, + fault_tolerant = false, + redis_timeout = 10000, + }, }) - - local route4 = assert(bp.routes:insert { - hosts = { "redistest4.com" }, + + local route2 = assert(bp.routes:insert { + hosts = { "redistest2.com" }, }) assert(bp.plugins:insert { name = "rate-limiting", - route = { id = route4.id }, + route = { id = route2.id }, config = { - minute = 1, - policy = "redis", - redis_host = REDIS_HOST, - redis_port = REDIS_PORT, - redis_username = REDIS_USER_INVALID, - redis_password = REDIS_PASSWORD, - redis_database = REDIS_DB_4, -- ensure to not get a pooled authenticated connection by using a different db - fault_tolerant = false, - } + minute = 1, + policy = "redis", + redis_host = REDIS_HOST, + redis_port = config.redis_port, + redis_database = REDIS_DB_2, + redis_ssl = config.redis_ssl, + redis_ssl_verify = config.redis_ssl_verify, + redis_server_name = config.redis_server_name, + fault_tolerant = false, + redis_timeout = 10000, + }, }) - end - - - assert(helpers.start_kong({ - nginx_conf = "spec/fixtures/custom_nginx.template", - })) - client = helpers.proxy_client() - end) - - lazy_teardown(function() - if red_version >= version("6.0.0") then - remove_redis_user(red) - end - end) - - it("connection pool respects database setting", function() - assert(red:select(REDIS_DB_1)) - local size_1 = assert(red:dbsize()) - - assert(red:select(REDIS_DB_2)) - local size_2 = assert(red:dbsize()) - - assert.equal(0, tonumber(size_1)) - assert.equal(0, tonumber(size_2)) - if red_version >= version("6.0.0") then - assert(red:select(REDIS_DB_3)) - local size_3 = assert(red:dbsize()) - assert.equal(0, tonumber(size_3)) - end - - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "redistest1.com" - } - }) - assert.res_status(200, res) - - -- Wait for async timer to increment the limit - - ngx.sleep(SLEEP_TIME) - - assert(red:select(REDIS_DB_1)) - size_1 = assert(red:dbsize()) - - assert(red:select(REDIS_DB_2)) - size_2 = assert(red:dbsize()) - - -- TEST: DB 1 should now have one hit, DB 2 and 3 none - - assert.equal(1, tonumber(size_1)) - assert.equal(0, tonumber(size_2)) - if red_version >= version("6.0.0") then - assert(red:select(REDIS_DB_3)) - local size_3 = assert(red:dbsize()) - assert.equal(0, tonumber(size_3)) - end - - -- rate-limiting plugin will reuses the redis connection - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "redistest2.com" - } - }) - assert.res_status(200, res) - - -- Wait for async timer to increment the limit - - ngx.sleep(SLEEP_TIME) - - assert(red:select(REDIS_DB_1)) - size_1 = assert(red:dbsize()) - - assert(red:select(REDIS_DB_2)) - size_2 = assert(red:dbsize()) - - -- TEST: DB 1 and 2 should now have one hit, DB 3 none - - assert.equal(1, tonumber(size_1)) - assert.equal(1, tonumber(size_2)) - if red_version >= version("6.0.0") then - assert(red:select(REDIS_DB_3)) - local size_3 = assert(red:dbsize()) - assert.equal(0, tonumber(size_3)) - end - - if red_version >= version("6.0.0") then - -- rate-limiting plugin will reuses the redis connection + + if red_version >= version("6.0.0") then + local route3 = assert(bp.routes:insert { + hosts = { "redistest3.com" }, + }) + assert(bp.plugins:insert { + name = "rate-limiting", + route = { id = route3.id }, + config = { + minute = 2, -- Handle multiple tests + policy = "redis", + redis_host = REDIS_HOST, + redis_port = config.redis_port, + redis_username = REDIS_USER_VALID, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DB_3, -- ensure to not get a pooled authenticated connection by using a different db + redis_ssl = config.redis_ssl, + redis_ssl_verify = config.redis_ssl_verify, + redis_server_name = config.redis_server_name, + fault_tolerant = false, + redis_timeout = 10000, + }, + }) + + local route4 = assert(bp.routes:insert { + hosts = { "redistest4.com" }, + }) + assert(bp.plugins:insert { + name = "rate-limiting", + route = { id = route4.id }, + config = { + minute = 1, + policy = "redis", + redis_host = REDIS_HOST, + redis_port = config.redis_port, + redis_username = REDIS_USER_INVALID, + redis_password = REDIS_PASSWORD, + redis_database = REDIS_DB_4, -- ensure to not get a pooled authenticated connection by using a different db + redis_ssl = config.redis_ssl, + redis_ssl_verify = config.redis_ssl_verify, + redis_server_name = config.redis_server_name, + fault_tolerant = false, + redis_timeout = 10000, + }, + }) + end + + + assert(helpers.start_kong({ + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = config.lua_ssl_trusted_certificate, + })) + client = helpers.proxy_client() + end) + + lazy_teardown(function() + helpers.stop_kong() + if red_version >= version("6.0.0") then + remove_redis_user(red) + end + end) + + it("connection pool respects database setting", function() + assert(red:select(REDIS_DB_1)) + local size_1 = assert(red:dbsize()) + + assert(red:select(REDIS_DB_2)) + local size_2 = assert(red:dbsize()) + + assert.equal(0, tonumber(size_1)) + assert.equal(0, tonumber(size_2)) + if red_version >= version("6.0.0") then + assert(red:select(REDIS_DB_3)) + local size_3 = assert(red:dbsize()) + assert.equal(0, tonumber(size_3)) + end + local res = assert(client:send { method = "GET", path = "/status/200", headers = { - ["Host"] = "redistest3.com" + ["Host"] = "redistest1.com" } }) assert.res_status(200, res) - + -- Wait for async timer to increment the limit - + ngx.sleep(SLEEP_TIME) - + assert(red:select(REDIS_DB_1)) size_1 = assert(red:dbsize()) - + assert(red:select(REDIS_DB_2)) size_2 = assert(red:dbsize()) - - assert(red:select(REDIS_DB_3)) - local size_3 = assert(red:dbsize()) - - -- TEST: All DBs should now have one hit, because the - -- plugin correctly chose to select the database it is - -- configured to hit - - assert.is_true(tonumber(size_1) > 0) - assert.is_true(tonumber(size_2) > 0) - assert.is_true(tonumber(size_3) > 0) - end - end) - - it("authenticates and executes with a valid redis user having proper ACLs", function() - if red_version >= version("6.0.0") then + + -- TEST: DB 1 should now have one hit, DB 2 and 3 none + + assert.equal(1, tonumber(size_1)) + assert.equal(0, tonumber(size_2)) + if red_version >= version("6.0.0") then + assert(red:select(REDIS_DB_3)) + local size_3 = assert(red:dbsize()) + assert.equal(0, tonumber(size_3)) + end + + -- rate-limiting plugin will reuses the redis connection local res = assert(client:send { method = "GET", path = "/status/200", headers = { - ["Host"] = "redistest3.com" + ["Host"] = "redistest2.com" } }) assert.res_status(200, res) - else - ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " .. - "'authenticates and executes with a valid redis user having proper ACLs' will be skipped") - end - end) - - it("fails to rate-limit for a redis user with missing ACLs", function() - if red_version >= version("6.0.0") then - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "redistest4.com" - } - }) - assert.res_status(500, res) - else - ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " .. - "'fails to rate-limit for a redis user with missing ACLs' will be skipped") - end + + -- Wait for async timer to increment the limit + + ngx.sleep(SLEEP_TIME) + + assert(red:select(REDIS_DB_1)) + size_1 = assert(red:dbsize()) + + assert(red:select(REDIS_DB_2)) + size_2 = assert(red:dbsize()) + + -- TEST: DB 1 and 2 should now have one hit, DB 3 none + + assert.equal(1, tonumber(size_1)) + assert.equal(1, tonumber(size_2)) + if red_version >= version("6.0.0") then + assert(red:select(REDIS_DB_3)) + local size_3 = assert(red:dbsize()) + assert.equal(0, tonumber(size_3)) + end + + if red_version >= version("6.0.0") then + -- rate-limiting plugin will reuses the redis connection + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "redistest3.com" + } + }) + assert.res_status(200, res) + + -- Wait for async timer to increment the limit + + ngx.sleep(SLEEP_TIME) + + assert(red:select(REDIS_DB_1)) + size_1 = assert(red:dbsize()) + + assert(red:select(REDIS_DB_2)) + size_2 = assert(red:dbsize()) + + assert(red:select(REDIS_DB_3)) + local size_3 = assert(red:dbsize()) + + -- TEST: All DBs should now have one hit, because the + -- plugin correctly chose to select the database it is + -- configured to hit + + assert.is_true(tonumber(size_1) > 0) + assert.is_true(tonumber(size_2) > 0) + assert.is_true(tonumber(size_3) > 0) + end + end) + + it("authenticates and executes with a valid redis user having proper ACLs", function() + if red_version >= version("6.0.0") then + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "redistest3.com" + } + }) + assert.res_status(200, res) + else + ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " .. + "'authenticates and executes with a valid redis user having proper ACLs' will be skipped") + end + end) + + it("fails to rate-limit for a redis user with missing ACLs", function() + if red_version >= version("6.0.0") then + local res = assert(client:send { + method = "GET", + path = "/status/200", + headers = { + ["Host"] = "redistest4.com" + } + }) + assert.res_status(500, res) + else + ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " .. + "'fails to rate-limit for a redis user with missing ACLs' will be skipped") + end + end) + end) + end - end) end) diff --git a/spec/03-plugins/24-response-rate-limiting/04-access_spec.lua b/spec/03-plugins/24-response-rate-limiting/04-access_spec.lua index e1e8af62ee4b..c770ab0fd3cb 100644 --- a/spec/03-plugins/24-response-rate-limiting/04-access_spec.lua +++ b/spec/03-plugins/24-response-rate-limiting/04-access_spec.lua @@ -2,10 +2,10 @@ local cjson = require "cjson" local helpers = require "spec.helpers" -local REDIS_HOST = helpers.redis_host -local REDIS_PORT = 6379 -local REDIS_PASSWORD = "" -local REDIS_DATABASE = 1 +local REDIS_HOST = helpers.redis_host +local REDIS_PORT = helpers.redis_port +local REDIS_PASSWORD = "" +local REDIS_DATABASE = 1 local SLEEP_TIME = 0.01 diff --git a/spec/03-plugins/24-response-rate-limiting/05-integration_spec.lua b/spec/03-plugins/24-response-rate-limiting/05-integration_spec.lua index 15ccdccea60e..b5fcb2f3541a 100644 --- a/spec/03-plugins/24-response-rate-limiting/05-integration_spec.lua +++ b/spec/03-plugins/24-response-rate-limiting/05-integration_spec.lua @@ -4,8 +4,9 @@ local version = require "version" local tostring = tostring -local REDIS_HOST = helpers.redis_host -local REDIS_PORT = 6379 +local REDIS_HOST = helpers.redis_host +local REDIS_PORT = helpers.redis_port + local REDIS_DB_1 = 1 local REDIS_DB_2 = 2 local REDIS_DB_3 = 3 diff --git a/spec/fixtures/redis/ca.crt b/spec/fixtures/redis/ca.crt new file mode 100644 index 000000000000..34c236860d6a --- /dev/null +++ b/spec/fixtures/redis/ca.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE2jCCAsICCQCQ1FVlDnjGwTANBgkqhkiG9w0BAQUFADAuMQ0wCwYDVQQKDARL +b25nMR0wGwYDVQQDDBRLb25nIFRlc3RpbmcgUm9vdCBDQTAgFw0yMjAzMzEwMTAz +MzVaGA8zMDIxMDgwMTAxMDMzNVowLjENMAsGA1UECgwES29uZzEdMBsGA1UEAwwU +S29uZyBUZXN0aW5nIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCcRzNQRNQW4KMxve8hR5Cd1/Wf8yb+Fjouz46WDb3YL4zaTnR7M2lDr3aM +fPwU1YdBtAucTgNttfCUOSGWHx7Zt0aF0b7VbwRIxbRbJl4mvOB3Bk2RhqycaiDN +S7mQ5XQEJ6Ru2hc9j5vqIFycyMEGxftcnIjpgKrS3FPdPSEScBgO3eKzKgFPcK1+ +gl6RbVZ1L5U5Ccf6uaYvYOVwJ6UmTjeFF1XVHQlTzfgvJihGtJksddSX5pH4usAD +voD7akLvU2qKxIUvUlMuzURM+JTYZ5pPdlLLFzSxniAnG83VuDEfYdNv2gXqOkv5 +HuUL5JGN2M1FePccUpNxhGbVHM/3cgyuggVd1Pm23p3j7+ca3/2YG9yKjbcK47n+ +Uak257WYMH6+C9WsldBFC6wIlnFu+UIQAXDg+oNCqw7KBoB6cDakuyZWuOXl56BI +687xxaXOLUlSGbH2DQ1mViQCqZrBqXi6OWKbuiUTSkfkv5j29VBlnvzhS1pZ5zGv +mTdUAmcodPDlapGjRa6wIc5HuxWaN5jCdmbVy8QmJr6uX6POADx2hFUsPzL/xndW +64PlnuWZwGJ9fsfeCXgcpE2nNT7cQVUWYjbfRMOhW7w6XBKZ+O4iq0QRjKhvA2L7 +DMlZnIyev3gux7B5Qp9qAqrtR2fJO4pQlSFPruKP9cAJHQABgwIDAQABMA0GCSqG +SIb3DQEBBQUAA4ICAQBBh7YBofrMFCi9kMjXYH5Vm+qSK/UK6rbzQVwr/EZPVQg1 +ulsAJP1lAd5R0JjKenNpWVK0IZ1ZSxPK2xFSSK2R5eoLhpMxLm1Jb9C+UbyDO+3e +ydRG1KbmEgeKkdc9BvuRp51q48PHWT3bumPeNnJ8vZBQiX5KvUc0tCkfQGaQ+Hrw +LEW+2LF4D4ITj5tNIvoIcRLh13trWxIVA/gCCCSzGZ/7lhjkTSRZhbyAjm0yQVNq +MGdkmH8Ueuw1YfKIu0gVB1r2e+baY9XHcW8H2fCAUz9Way/3USHsnpujA7+dnU17 +8xGsNe4ZflH7uYBJVbBNsUa7qGpSVjOQek19KduPYjEunRrgJU7rvNZ093E77BVF +CirCyGjOmfiXDm/ObXlKFmmdhZ7t4lZ84tcLche+oZ+11KR3HfrXYdQi0qXxEdgA +8NojUoLg0IZQuYISdks3RlEfHk3gh2Lx2dMPKkuaKsVUgA/V1XLymt5+hVtbUatv +PVvV66IHA7a6gTHYuxfWGEcgMYLn+Jb87cRwQY2+5V0ajAudrnU6zZR7WeiuaErd +qaQcFV83ahAdF2XEr25Xl0lq+RugQrpirkyumsyb8nO17M1h0zar9MwfHMMpnmRD +uQjfmyIPixjscK5sDYd1TAM1x3Wy9owh+C+AdYrM85NTwxxnrGyWOHF5bmsIbA== +-----END CERTIFICATE----- diff --git a/spec/fixtures/redis/ca.key b/spec/fixtures/redis/ca.key new file mode 100644 index 000000000000..48c7915c2273 --- /dev/null +++ b/spec/fixtures/redis/ca.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/redis/docker-entrypoint.sh b/spec/fixtures/redis/docker-entrypoint.sh new file mode 100755 index 000000000000..3a1fe69576a7 --- /dev/null +++ b/spec/fixtures/redis/docker-entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +if [ -d /workspace ] ; then + redis-server \ + --tls-port 6380 \ + --tls-cert-file /workspace/spec/fixtures/redis/server.crt \ + --tls-key-file /workspace/spec/fixtures/redis/server.key \ + --tls-cluster no \ + --tls-replication no \ + --tls-auth-clients no +fi + +tail -f /dev/null \ No newline at end of file diff --git a/spec/fixtures/redis/server.crt b/spec/fixtures/redis/server.crt new file mode 100644 index 000000000000..9c162b578368 --- /dev/null +++ b/spec/fixtures/redis/server.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE3DCCAsQCCQD7NvBERA+24DANBgkqhkiG9w0BAQUFADAuMQ0wCwYDVQQKDARL +b25nMR0wGwYDVQQDDBRLb25nIFRlc3RpbmcgUm9vdCBDQTAgFw0yMjAzMzEwMTA0 +MzVaGA8zMDIxMDgwMTAxMDQzNVowMDENMAsGA1UECgwES29uZzEfMB0GA1UEAwwW +dGVzdC1yZWRpcy5leGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAMApZL2iWCqtkf/hAUWNdPjyUBp0RQH1tSsf9TmUrTcsobWWC/fKdeT+ +NdITlN/Gvuw6aDUi+Iz9sGfeV2remOVYlyvOAxjVq0g1Lj8L/SLisXzqeunnQtSk +M1zirsJPzmO5sMgQ4DiXYbm40gqEK7G3GxpQVzUUdzHiP3jHQuSSZWMKyoOkI6qs +GJQ9XNot2pC9PZADLi31FDRDzTIVW3LCn32eJy6ZKBENMMWhK7TrXTnNU6HfytWR +Y4tQHCJv+cVCMneZzQJBm2eOxoL824HGa30sNIzcknR1IORuuT6hMAbivX5PeCSw +QOipfaNY8JddfoCqLYmrfQWCharXUqizMAniOdMwEDmlOEUxcbDGlcBsHosVpLXa +raqVHfFIn9CQM9Sk/pIYC5b7Whe2XdWxuvz0ozT9Z9wiEeLmVulaEubok0yNQ+1J +ohv3yWPUeoRggmymFXjlDgLFdD9qLmhrCl/AY+t+PjkI+wKbLSQdJwaoPz0JMPoc +FB+f/MBKOFZY3AdwXfqTfeSSlBJEPVEUWDxG1Pg0l+D7A+TWjN3TTB80UIA1AqfX +zSBwna5kXjucTtBJ+2ZX86+WdppHHoPgvU8GN/mFbj8QVkuDl7sMRlDty8VGID67 +6HGtj4eHPsUSYngZkrVVLXxZnVHZguKQtCqwupElLLCFwv6LglRLAgMBAAEwDQYJ +KoZIhvcNAQEFBQADggIBAIdvbrVudOkfnqfJNlK8mlhVzMEVBTPS9lCS05UFYrys +x8HoGy/9NWP4+njMLK6g0HiSLFTdfqb1BfSyw/9RBbtFVa6uMI3jPMGGfX7n6mDY +dQmYpRK97d+5BYNG+Yl1guDSgMwmBDL3DELguayBZbgbdiyydjrvi3Hq729OPURg +U8rfIOI5gC7LqsMWZxZFPSBRQETe6mVP5/vfObC2zIzXz3MBslKm4iXx3ye7Uebn +invCYqzuxyeKDslLHbp8ZGDo3H6lKFOvOdxKLwNl4yGrYYD7/84nat+BbfBe16tQ +gN2rEfhsBVBD+ic22o9QP0HpK29B2ETayyKjbPY7RLgQQDnfm/HZzyU0fr1BAy+V +3gi2fk4MgsY6+JVIDXdAuVLfQ3+a/5s2XbnjayI8Lm6/3dRcp5OyYgEQbwld66NV +Q+SH1DloOQ3Ql8DRqTe345nifUmL6mBJ4OQwy/H3dhk5eZW6LTY8u1BhoeuQBT4z +tjBPXU+WuH85bLw2fJ/no7Qe5zbsPQpIlAo9i9n2+6RK2K63Cd2GfeKO2PRwHAeh +PB3MlfUyIU2Zv7Mh4Eds1f1mMrI/BnSsnNJTeqyhOVbtcBdsQmfnLtqKEiSCIzJa +1wtwInu67sO4LGsI70hDJkVBIpuJBsuPEexeE2q/kRKyZtZ17+9nj3nzlEVKY5+F +-----END CERTIFICATE----- diff --git a/spec/fixtures/redis/server.key b/spec/fixtures/redis/server.key new file mode 100644 index 000000000000..0d926ecb6438 --- /dev/null +++ b/spec/fixtures/redis/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/helpers.lua b/spec/helpers.lua index 605910d5a54f..fd16ab1d42c4 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -20,6 +20,10 @@ local MOCK_UPSTREAM_SSL_PORT = 15556 local MOCK_UPSTREAM_STREAM_PORT = 15557 local MOCK_UPSTREAM_STREAM_SSL_PORT = 15558 local MOCK_GRPC_UPSTREAM_PROTO_PATH = "./spec/fixtures/grpc/hello.proto" +local REDIS_HOST = os.getenv("KONG_SPEC_TEST_REDIS_HOST") or "localhost" +local REDIS_PORT = tonumber(os.getenv("KONG_SPEC_TEST_REDIS_PORT") or 6379) +local REDIS_SSL_PORT = tonumber(os.getenv("KONG_SPEC_TEST_REDIS_SSL_PORT") or 6380) +local REDIS_SSL_SNI = os.getenv("KONG_SPEC_TEST_REDIS_SSL_SNI") or "test-redis.example.com" local BLACKHOLE_HOST = "10.255.255.255" local KONG_VERSION = require("kong.meta")._VERSION local PLUGINS_LIST @@ -2835,7 +2839,10 @@ end mock_upstream_stream_ssl_port = MOCK_UPSTREAM_STREAM_SSL_PORT, mock_grpc_upstream_proto_path = MOCK_GRPC_UPSTREAM_PROTO_PATH, - redis_host = os.getenv("KONG_SPEC_REDIS_HOST") or "127.0.0.1", + redis_host = REDIS_HOST, + redis_port = REDIS_PORT, + redis_ssl_port = REDIS_SSL_PORT, + redis_ssl_sni = REDIS_SSL_SNI, blackhole_host = BLACKHOLE_HOST,