From f24b4af023a5fde1f49d1cde3287706e9d20f426 Mon Sep 17 00:00:00 2001 From: Michal Cichra Date: Wed, 11 Jan 2017 12:10:35 +0100 Subject: [PATCH 1/5] [busted] fix passing args to busted --- bin/busted | 12 +----------- bin/busted.lua | 8 ++++++++ 2 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 bin/busted.lua diff --git a/bin/busted b/bin/busted index ed31b5e1b..1c7537606 100755 --- a/bin/busted +++ b/bin/busted @@ -1,13 +1,3 @@ #!/usr/bin/env sh -exec resty -e "$(cat < Date: Wed, 11 Jan 2017 13:26:52 +0100 Subject: [PATCH 2/5] [module] allow requiring the module root --- apicast/src/module.lua | 18 ++++++++++++++++++ spec/module_spec.lua | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/apicast/src/module.lua b/apicast/src/module.lua index d27d8fe6d..e3476d8b6 100644 --- a/apicast/src/module.lua +++ b/apicast/src/module.lua @@ -47,6 +47,11 @@ function _M.call(self, phase, ...) end local cache = {} + +function _M.flush() + cache = {} +end + local prequire = function(file) if cache[file] then @@ -105,4 +110,17 @@ function _M.load(name, phase) return nil, 'could not load plugin' end +function _M:require() + local name = self.name + + if not name then + return nil, 'not initialized' + end + + local ok, ret = prequire(name) + + if ok and ret then return ret + else return ok, ret end +end + return _M diff --git a/spec/module_spec.lua b/spec/module_spec.lua index 5a2244206..82ee83f5d 100644 --- a/spec/module_spec.lua +++ b/spec/module_spec.lua @@ -11,6 +11,7 @@ describe('module', function() end) describe('.call', function() + after_each(function() module.flush() end) it('returns', function() package.loaded['foobar'] = { phase = function() return 'foobar' end } @@ -24,4 +25,18 @@ describe('module', function() end) end) + + describe(':require', function() + it('returns a module', function() + local foobar = { _VERSION = '1.1', _NAME = 'Foo Bar' } + package.loaded['foobar'] = foobar + + local m = module.new('foobar') + + local mod, err = m:require() + + assert.equal(mod, foobar) + assert.falsy(err) + end) + end) end) From c8923ac9b7b4f99ec246811062492fa2209df041 Mon Sep 17 00:00:00 2001 From: Michal Cichra Date: Wed, 11 Jan 2017 13:29:18 +0100 Subject: [PATCH 3/5] [user-agent] introduce user agent module RFC 7231 complainant User-Agent --- apicast/src/user_agent.lua | 75 ++++++++++++++++++++++++++++++++++++++ spec/user_agent_spec.lua | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 apicast/src/user_agent.lua create mode 100644 spec/user_agent_spec.lua diff --git a/apicast/src/user_agent.lua b/apicast/src/user_agent.lua new file mode 100644 index 000000000..eea767000 --- /dev/null +++ b/apicast/src/user_agent.lua @@ -0,0 +1,75 @@ +local ffi = require 'ffi' +local module = require 'module' + +local setmetatable = setmetatable + +local _M = { + _VERSION = '2.0' +} + +function _M.deployment() + return _M.threescale_deployment_env or 'unknown' +end + +-- User-Agent: / +-- User-Agent: Mozilla/ () () + +function _M.call() + return 'APIcast/' .. _M._VERSION .. ' (' .. _M.system_information() .. ') ' .. _M.platform() +end + +function _M.system_information() + return ffi.os .. '; ' .. ffi.arch .. '; env:' .. _M.deployment() +end + +function _M.platform() + local m = module.new() + local table, err = m:require() + local version = table and table._VERSION + local name = table and table._NAME or m.name + + if not name then + return nil, 'missing module name' + end + + if not table and err then + return nil, err + end + + if version then + return name ..'/'.. version + else + return name + end +end + +local mt = { + __call = _M.call, + __tostring = _M.call +} + +function _M.reset() + local env = { + threescale_deployment_env = os.getenv('THREESCALE_DEPLOYMENT_ENV') + } + + mt.__index = env + + _M.env = env + + mt.__call = _M.call + mt.__tostring = _M.call +end + +function _M.cache() + _M.reset() + + local user_agent = _M.call() + + mt.__call = function() return user_agent end + mt.__tostring = mt.__call +end + +setmetatable(_M, mt) + +return _M diff --git a/spec/user_agent_spec.lua b/spec/user_agent_spec.lua new file mode 100644 index 000000000..1c3115373 --- /dev/null +++ b/spec/user_agent_spec.lua @@ -0,0 +1,71 @@ +local user_agent = require 'user_agent' +local ffi = require("ffi") + +describe('3scale', function() + before_each(function() user_agent.reset() end) + + describe('.deployment', function() + it('reads from environment', function() + stub(os, 'getenv').on_call_with('THREESCALE_DEPLOYMENT_ENV').returns('foobar') + + user_agent.reset() + + assert.same('foobar', user_agent.deployment()) + end) + + it('uses internal structure', function() + user_agent.env.threescale_deployment_env = 'bar' + + assert.same('bar', user_agent.deployment()) + end) + end) + + describe('.user_agent', function() + -- User-Agent: / + -- User-Agent: Mozilla/ () () + + it('matches common format', function() + + user_agent.env.threescale_deployment_env = 'production' + + assert.match('APIcast/' .. user_agent._VERSION, user_agent.call()) + end) + + it('includes system information', function() + assert.match('(' .. user_agent.system_information() .. ')', user_agent.call()) + end) + + it('includes platform information', function() + assert.match(' ' .. user_agent.platform(), user_agent.call()) + end) + + it('works as tostring', function() + assert.equal(user_agent.call(), tostring(user_agent)) + end) + + it('works as function', function() + assert.equal(user_agent.call(), user_agent()) + end) + end) + + + describe('.system_information', function() + it('includes os information', function() + assert.match(ffi.os ..'; ' .. ffi.arch, user_agent.system_information()) + end) + + it('includes deployment information', function() + user_agent.env.threescale_deployment_env = 'foobar' + assert.match(' env:foobar', user_agent.system_information()) + end) + end) + + describe('.platform', function() + it('includes os information', function() + local apicast = require('apicast') + + assert.same('APIcast/' .. apicast._VERSION, user_agent.platform()) + end) + end) + +end) From bf221771409b0a0a0d7ead33ec8c1a3fbe41146d Mon Sep 17 00:00:00 2001 From: Michal Cichra Date: Wed, 11 Jan 2017 13:35:12 +0100 Subject: [PATCH 4/5] track deployment usage by new User-Agent header make apicast work with user agent --- CHANGELOG.md | 1 + Makefile | 2 +- apicast/conf.d/apicast.conf | 18 +++++++++++++++--- apicast/src/apicast.lua | 6 +++++- apicast/src/configuration_loader/remote_v1.lua | 3 +++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be38f4a39..18cab02c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - A CHANGELOG.md to track important changes +- User-Agent header with APIcast version and system information [PR #214](https://github.com/3scale/apicast/pull/214) ### Changed - Require openresty 1.11.2 [PR #194](https://github.com/3scale/apicast/pull/194) diff --git a/Makefile b/Makefile index a83f0a17a..a1bcc1d98 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ test-builder-image: builder-image clean ## Smoke test the builder image. Pass an @echo -e $(SEPARATOR) $(DOCKER_COMPOSE) run --rm test curl --fail -X POST http://gateway:8090/boot @echo -e $(SEPARATOR) - $(DOCKER_COMPOSE) run --rm -e THREESCALE_PORTAL_ENDPOINT=https://echo-api.3scale.net gateway /opt/app/libexec/boot | grep lua-resty-http + $(DOCKER_COMPOSE) run --rm -e THREESCALE_PORTAL_ENDPOINT=https://echo-api.3scale.net gateway /opt/app/libexec/boot | grep 'APIcast/' @echo -e $(SEPARATOR) test-runtime-image: export IMAGE_NAME = apicast-release-test diff --git a/apicast/conf.d/apicast.conf b/apicast/conf.d/apicast.conf index e1668259a..dc4fc82b6 100644 --- a/apicast/conf.d/apicast.conf +++ b/apicast/conf.d/apicast.conf @@ -1,4 +1,8 @@ -set_by_lua $deployment 'return os.getenv("THREESCALE_DEPLOYMENT_ENV");'; +set_by_lua $user_agent 'return require("user_agent")()'; +set_by_lua_block $deployment { + local user_agent = require('user_agent') + return user_agent.platform() .. '+' .. user_agent.deployment() +} # TODO: enable in the future when we support SSL # ssl_certificate_by_lua_block { require('module').call() } @@ -17,7 +21,8 @@ location = /threescale_authrep { proxy_http_version 1.1; proxy_pass $backend_endpoint$path; proxy_set_header Host "$backend_host"; - proxy_set_header X-3scale-User-Agent "nginx$deployment"; + proxy_set_header User-Agent "$user_agent"; + proxy_set_header X-3scale-User-Agent "$deployment"; proxy_set_header X-3scale-Version "$version"; proxy_set_header Connection ""; @@ -88,6 +93,9 @@ location = /_threescale/oauth_store_token { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host "$backend_host"; + proxy_set_header User-Agent "$user_agent"; + proxy_set_header X-3scale-User-Agent "$deployment"; + proxy_set_header X-3scale-Version "$version"; proxy_pass $backend_endpoint/services/$service_id/oauth_access_tokens.xml?$backend_authentication_type=$backend_authentication_value; } @@ -98,6 +106,9 @@ location = /_threescale/check_credentials { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host "$backend_host"; + proxy_set_header User-Agent "$user_agent"; + proxy_set_header X-3scale-User-Agent "$deployment"; + proxy_set_header X-3scale-Version "$version"; proxy_pass $backend_endpoint/transactions/oauth_authorize.xml?$backend_authentication_type=$backend_authentication_value&service_id=$service_id&$args; } @@ -105,7 +116,8 @@ location = /_threescale/check_credentials { location = /threescale_oauth_authrep { internal; proxy_set_header Host "$backend_host"; - proxy_set_header X-3scale-User-Agent "nginx$deployment"; + proxy_set_header User-Agent "$user_agent"; + proxy_set_header X-3scale-User-Agent "$deployment"; proxy_set_header X-3scale-Version "$version"; proxy_set_header X-3scale-OAuth2-Grant-Type "authorization_code"; diff --git a/apicast/src/apicast.lua b/apicast/src/apicast.lua index bf85ff89a..e3d203cc3 100644 --- a/apicast/src/apicast.lua +++ b/apicast/src/apicast.lua @@ -7,9 +7,11 @@ local tonumber = tonumber local math = math local getenv = os.getenv local reload_config = util.env_enabled('APICAST_RELOAD_CONFIG') +local user_agent = require('user_agent') local _M = { - _VERSION = '0.1' + _VERSION = '2.0', + _NAME = 'APIcast' } local missing_configuration = getenv('APICAST_MISSING_CONFIGURATION') or 'log' @@ -28,6 +30,8 @@ local function handle_missing_configuration(err) end function _M.init() + user_agent.cache() + math.randomseed(ngx.now()) -- First calls to math.random after a randomseed tend to be similar; discard them for _=1,3 do math.random() end diff --git a/apicast/src/configuration_loader/remote_v1.lua b/apicast/src/configuration_loader/remote_v1.lua index d411d5974..cd3edcc6c 100644 --- a/apicast/src/configuration_loader/remote_v1.lua +++ b/apicast/src/configuration_loader/remote_v1.lua @@ -8,6 +8,7 @@ local resty_url = require 'resty.url' local http = require "resty.http" local configuration = require 'configuration' local util = require 'util' +local user_agent = require 'user_agent' local _M = { version = '0.1' @@ -90,6 +91,8 @@ function _M.download(endpoint, _) headers['Authorization'] = "Basic " .. ngx.encode_base64(concat({ user or '', pass or '' }, ':')) end + headers['User-Agent'] = user_agent() + -- TODO: this does not fully implement HTTP spec, it first should send -- request without Authentication and then send it after gettting 401 From f21c89f25770f29cee27f1f32ff5e5fb63cdb3fb Mon Sep 17 00:00:00 2001 From: Michal Cichra Date: Wed, 11 Jan 2017 14:21:40 +0100 Subject: [PATCH 5/5] [module] detect recursive module loading --- apicast/src/module.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apicast/src/module.lua b/apicast/src/module.lua index e3476d8b6..d10b7999d 100644 --- a/apicast/src/module.lua +++ b/apicast/src/module.lua @@ -65,6 +65,11 @@ local prequire = function(file) ok, ret = pcall(dofile, file) end + if type(ret) == 'userdata' then + ngx.log(ngx.WARN, 'cyclic require detected: ', debug.traceback()) + return false, ret + end + if ok then cache[file] = ret else