diff --git a/CHANGELOG.md b/CHANGELOG.md index 62665be58948..beb17a4dfab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## [Unreleased][unreleased] +### Added + +- Ability to hide Kong-specific response headers. Two new configuration fields: + `server_tokens` and `latency_tokens` will respectively toggle whether the + `Server` and `X-Kong-*-Latency` headers should be sent to downstream clients. + [#2259](https://github.com/Mashape/kong/pull/2259) + ## [0.10.1] - 2017/03/27 ### Changed diff --git a/kong.conf.default b/kong.conf.default index aa03e529501d..f4540b48ab1d 100644 --- a/kong.conf.default +++ b/kong.conf.default @@ -120,6 +120,16 @@ # process. When this number is exceeded, the # least recently used connections are closed. +#server_tokens = on # Enables or disables emitting Kong version on + # error pages and in the "Server" or "Via" + # (in case the request was proxied) response + # header field. + +#latency_tokens = on # Enables or disables emitting Kong latency + # information in the "X-Kong-Proxy-Latency" + # and "X-Kong-Upstream-Latency" response + # header fields. + #------------------------------------------------------------------------------ # DATASTORE #------------------------------------------------------------------------------ diff --git a/kong/conf_loader.lua b/kong/conf_loader.lua index 3c98d7a81389..1c65e45f828c 100644 --- a/kong/conf_loader.lua +++ b/kong/conf_loader.lua @@ -62,6 +62,8 @@ local CONF_INFERENCES = { nginx_user = {typ = "string"}, nginx_worker_processes = {typ = "string"}, upstream_keepalive = {typ = "number"}, + server_tokens = {typ = "boolean"}, + latency_tokens = {typ = "boolean"}, database = {enum = {"postgres", "cassandra"}}, pg_port = {typ = "number"}, @@ -425,7 +427,7 @@ local function load(path, custom_conf) -- initialize the dns client, so the globally patched tcp.connect method -- will work from here onwards. assert(require("kong.tools.dns")(conf)) - + return setmetatable(conf, nil) -- remove Map mt end diff --git a/kong/core/error_handlers.lua b/kong/core/error_handlers.lua index 751ee683b169..69f6e2b79bbc 100644 --- a/kong/core/error_handlers.lua +++ b/kong/core/error_handlers.lua @@ -1,3 +1,5 @@ +local singletons = require "kong.singletons" + local find = string.find local format = string.format @@ -52,7 +54,10 @@ return function(ngx) local status = ngx.status message = BODIES["s"..status] and BODIES["s"..status] or format(BODIES.default, status) - ngx.header["Server"] = SERVER_HEADER + if singletons.configuration.server_tokens then + ngx.header["Server"] = SERVER_HEADER + end + ngx.header["Content-Type"] = content_type ngx.say(format(template, message)) -end \ No newline at end of file +end diff --git a/kong/core/handler.lua b/kong/core/handler.lua index da9e139880ca..de3e09fec097 100644 --- a/kong/core/handler.lua +++ b/kong/core/handler.lua @@ -119,7 +119,7 @@ return { end -- if set `host_header` is the original header to be preserved - var.upstream_host = host_header or + var.upstream_host = host_header or balancer_address.hostname..":"..balancer_address.port end, @@ -137,19 +137,34 @@ return { }, header_filter = { before = function() - if ngx.ctx.KONG_PROXIED then + local ctx = ngx.ctx + + if ctx.KONG_PROXIED then local now = get_now() - ngx.ctx.KONG_WAITING_TIME = now - ngx.ctx.KONG_ACCESS_ENDED_AT -- time spent waiting for a response from upstream - ngx.ctx.KONG_HEADER_FILTER_STARTED_AT = now + ctx.KONG_WAITING_TIME = now - ctx.KONG_ACCESS_ENDED_AT -- time spent waiting for a response from upstream + ctx.KONG_HEADER_FILTER_STARTED_AT = now end end, after = function() - if ngx.ctx.KONG_PROXIED then - ngx.header[constants.HEADERS.UPSTREAM_LATENCY] = ngx.ctx.KONG_WAITING_TIME - ngx.header[constants.HEADERS.PROXY_LATENCY] = ngx.ctx.KONG_PROXY_LATENCY - ngx.header["Via"] = server_header + local ctx, header = ngx.ctx, ngx.header + + if ctx.KONG_PROXIED then + if singletons.configuration.latency_tokens then + header[constants.HEADERS.UPSTREAM_LATENCY] = ctx.KONG_WAITING_TIME + header[constants.HEADERS.PROXY_LATENCY] = ctx.KONG_PROXY_LATENCY + end + + if singletons.configuration.server_tokens then + header["Via"] = server_header + end + else - ngx.header["Server"] = server_header + if singletons.configuration.server_tokens then + header["Server"] = server_header + + else + header["Server"] = nil + end end end }, diff --git a/kong/templates/kong_defaults.lua b/kong/templates/kong_defaults.lua index 534ba5259bf4..ae8c4ca848d3 100644 --- a/kong/templates/kong_defaults.lua +++ b/kong/templates/kong_defaults.lua @@ -20,6 +20,8 @@ admin_ssl = on admin_ssl_cert = NONE admin_ssl_cert_key = NONE upstream_keepalive = 60 +server_tokens = on +latency_tokens = on database = postgres pg_host = 127.0.0.1 diff --git a/spec/02-integration/05-proxy/10-server_tokens_spec.lua b/spec/02-integration/05-proxy/10-server_tokens_spec.lua new file mode 100644 index 000000000000..fc3081f6c354 --- /dev/null +++ b/spec/02-integration/05-proxy/10-server_tokens_spec.lua @@ -0,0 +1,288 @@ +local helpers = require "spec.helpers" +local constants = require "kong.constants" + + +local default_server_header = _KONG._NAME .. "/" .. _KONG._VERSION + + +local function start(config) + return function() + helpers.dao.apis:insert { + name = "api-1", + upstream_url = "http://localhost:9999/headers-inspect", + hosts = { + "headers-inspect.com", + } + } + + config = config or {} + config.nginx_conf = "spec/fixtures/custom_nginx.template" + + assert(helpers.start_kong(config)) + end +end + + +describe("Server Tokens", function() + local client + + before_each(function() + client = helpers.proxy_client() + end) + + after_each(function() + if client then + client:close() + end + end) + + describe("(with default configration values)", function() + + setup(start { + nginx_conf = "spec/fixtures/custom_nginx.template", + }) + + teardown(helpers.stop_kong) + + it("should return Kong 'Via' header but not change the 'Server' header when request was proxied", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "headers-inspect.com", + } + }) + + assert.res_status(200, res) + assert.not_equal(default_server_header, res.headers["server"]) + assert.equal(default_server_header, res.headers["via"]) + end) + + it("should return Kong 'Server' header but not the Kong 'Via' header when no API matched (no proxy)", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "404.com", + } + }) + + assert.res_status(404, res) + assert.equal(default_server_header, res.headers["server"]) + assert.is_nil(res.headers["via"]) + end) + + end) + + describe("(with server_tokens = on)", function() + + setup(start { + nginx_conf = "spec/fixtures/custom_nginx.template", + server_tokens = "on", + }) + + teardown(helpers.stop_kong) + + it("should return Kong 'Via' header but not change the 'Server' header when request was proxied", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "headers-inspect.com", + } + }) + + assert.res_status(200, res) + assert.not_equal(default_server_header, res.headers["server"]) + assert.equal(default_server_header, res.headers["via"]) + end) + + it("should return Kong 'Server' header but not the Kong 'Via' header when no API matched (no proxy)", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "404.com", + } + }) + + assert.res_status(404, res) + assert.equal(default_server_header, res.headers["server"]) + assert.is_nil(res.headers["via"]) + end) + + end) + + describe("(with server_tokens = off)", function() + + setup(start { + nginx_conf = "spec/fixtures/custom_nginx.template", + server_tokens = "off", + }) + + teardown(helpers.stop_kong) + + it("should not return Kong 'Via' header but it should forward the 'Server' header when request was proxied", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "headers-inspect.com", + } + }) + + assert.res_status(200, res) + assert.response(res).has.header "server" + assert.response(res).has_not.header "via" + assert.not_equal(default_server_header, res.headers["server"]) + end) + + it("should not return Kong 'Server' or 'Via' headers when no API matched (no proxy)", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "404.com", + } + }) + + assert.res_status(404, res) + assert.is_nil(res.headers["server"]) + assert.is_nil(res.headers["via"]) + end) + + end) +end) + + +describe("Latency Tokens", function() + local client + + before_each(function() + client = helpers.proxy_client() + end) + + after_each(function() + if client then + client:close() + end + end) + + describe("(with default configration values)", function() + + setup(start { + nginx_conf = "spec/fixtures/custom_nginx.template", + }) + + teardown(helpers.stop_kong) + + it("should be returned when request was proxied", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "headers-inspect.com", + } + }) + + assert.res_status(200, res) + assert.is_not_nil(res.headers[constants.HEADERS.UPSTREAM_LATENCY]) + assert.is_not_nil(res.headers[constants.HEADERS.PROXY_LATENCY]) + end) + + it("should not be returned when no API matched (no proxy)", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "404.com", + } + }) + + assert.res_status(404, res) + assert.is_nil(res.headers[constants.HEADERS.UPSTREAM_LATENCY]) + assert.is_nil(res.headers[constants.HEADERS.PROXY_LATENCY]) + end) + + end) + + describe("(with latency_tokens = on)", function() + + setup(start { + nginx_conf = "spec/fixtures/custom_nginx.template", + latency_tokens = "on", + }) + + teardown(helpers.stop_kong) + + it("should be returned when request was proxied", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "headers-inspect.com" + } + }) + + assert.res_status(200, res) + assert.is_not_nil(res.headers[constants.HEADERS.UPSTREAM_LATENCY]) + assert.is_not_nil(res.headers[constants.HEADERS.PROXY_LATENCY]) + end) + + it("should not be returned when no API matched (no proxy)", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "404.com", + } + }) + + assert.res_status(404, res) + assert.is_nil(res.headers[constants.HEADERS.UPSTREAM_LATENCY]) + assert.is_nil(res.headers[constants.HEADERS.PROXY_LATENCY]) + end) + + end) + + describe("(with latency_tokens = off)", function() + + setup(start { + nginx_conf = "spec/fixtures/custom_nginx.template", + latency_tokens = "off", + }) + + teardown(function() + helpers.stop_kong() + end) + + it("should not be returned when request was proxied", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "headers-inspect.com", + } + }) + + assert.res_status(200, res) + assert.is_nil(res.headers[constants.HEADERS.UPSTREAM_LATENCY]) + assert.is_nil(res.headers[constants.HEADERS.PROXY_LATENCY]) + end) + + it("should not be returned when no API matched (no proxy)", function() + local res = assert(client:send { + method = "GET", + path = "/get", + headers = { + host = "404.com", + } + }) + + assert.res_status(404, res) + assert.is_nil(res.headers[constants.HEADERS.UPSTREAM_LATENCY]) + assert.is_nil(res.headers[constants.HEADERS.PROXY_LATENCY]) + end) + + end) +end)