Tim Landscheidt has uploaded a new change for review. https://gerrit.wikimedia.org/r/266448
Change subject: Tools: Allow proxymanager to add and remove proxy forward entries ...................................................................... Tools: Allow proxymanager to add and remove proxy forward entries Currently proxy forward entries are managed by a separate daemon "proxylistener" running on the proxy server. Web services connect to this daemon with a custom protocol. This change adds this functionality to the nginx service itself in the form of a RESTful API. Web services can manage their forward at http://$proxy_server:8081/v1/proxy-forwards/$toolname. As with proxylistener, proxymanager restricts changes of the forward to requests made by the corresponding tool account as identified by an ident query (RFC 1413). Change-Id: I2d643fc902208eafaaa0d7814e586f0c326f16b5 --- A modules/dynamicproxy/README.md A modules/dynamicproxy/files/proxymanager/v1/proxy-forwards-entry.lua A modules/dynamicproxy/files/proxymanager/v1/proxy-forwards.lua M modules/dynamicproxy/manifests/init.pp M modules/dynamicproxy/templates/proxymanager.conf.erb 5 files changed, 179 insertions(+), 2 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/operations/puppet refs/changes/48/266448/1 diff --git a/modules/dynamicproxy/README.md b/modules/dynamicproxy/README.md new file mode 100644 index 0000000..9fe2634 --- /dev/null +++ b/modules/dynamicproxy/README.md @@ -0,0 +1,21 @@ +# proxymanager API + +For URL proxy forwards, for example in the Tools project, dynamicproxy +exposes a RESTful API at port 8081. + +The list of all active forwards is managed at `/v1/proxy-forwards`. +This endpoint only supports `GET` requests; its list is structured as: + + ["admin","test2"] + +Individual forwards are managed at `/v1/proxy-forwards/$prefix`. This +endpoint unconditionally supports `GET` requests that return +structures like: + + {".*":"http:\/\/toolsbeta-webgrid-lighttpd-1406.toolsbeta.eqiad.wmflabs:51856"} + +for active forwards. `PUT` and `DELETE` cause the requester to be +authenticated by ident (RFC 1413). Only if the user name of the +requester is equal to the name of the forward, prefixed with the name +of the project and a dot ("."), does the creation, update or deletion +of the forward succeed. diff --git a/modules/dynamicproxy/files/proxymanager/v1/proxy-forwards-entry.lua b/modules/dynamicproxy/files/proxymanager/v1/proxy-forwards-entry.lua new file mode 100644 index 0000000..afe92f6 --- /dev/null +++ b/modules/dynamicproxy/files/proxymanager/v1/proxy-forwards-entry.lua @@ -0,0 +1,84 @@ +-- Copyright 2016 Tim Landscheidt +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Extract tool's name from URI. +local toolname = string.sub(ngx.var.uri, string.len('/v1/proxy-forwards/') + 1) + +-- Connect to Redis database. +local redis = require('resty.redis') +local red = redis:new() +red:set_timeout(1000) +red:connect('127.0.0.1', 6379) + +-- Require json module. +local json = require('json') + +-- Access for "GET" requests is not restricted, so they are handled +-- first. +if ngx.req.get_method() == 'GET' then + -- Retrieve database entry for prefix. + local proxy_entries = red:hgetall(ngx.var.proxymanager_proxy_forward_redis_prefix .. toolname) + if #proxy_entries == 0 then + ngx.log(ngx.WARN, 'No proxy forward in database for ', toolname) + ngx.exit(ngx.HTTP_NOT_FOUND) + end + + -- Return result. + ngx.header['Content-Type'] = 'application/json' + ngx.say(json.encode(red:array_to_hash(proxy_entries))) + ngx.exit(ngx.HTTP_OK) +end + +-- Access for other methods is restricted to the referenced tool, so +-- query ident server. +local sock = ngx.socket.tcp() +sock:settimeout(5000) +local ok, err = sock:connect(ngx.var.remote_addr, 113) +if not ok then + ngx.log(ngx.ERR, 'Failed to connect to ident server on ', ngx.var.remote_addr, ': ', err) + ngx.exit(ngx.HTTP_UNAUTHORIZED) +end +sock:send(ngx.var.remote_port .. ',' .. ngx.var.server_port .. '\r\n') +local line, err, partial = sock:receive() +sock:close() +if not line then + ngx.log(ngx.ERR, 'Failed to receive response from ident server on ', ngx.var.remote_addr, ': ', err) + ngx.exit(ngx.HTTP_UNAUTHORIZED) +end +if line ~= ngx.var.remote_port .. ' , ' .. ngx.var.server_port .. ' : USERID : UNIX , UTF-8 :' .. ngx.var.proxymanager_labsproject_prefix .. toolname then + ngx.log(ngx.ERR, 'Unauthorized attempt for ', toolname, ': ', line) + ngx.exit(ngx.HTTP_UNAUTHORIZED) +end + +-- At this point the requester is authorized to manage the proxy +-- forward for the tool. +if ngx.req.get_method() == 'DELETE' then + -- Delete proxy forward entry. + red:del(ngx.var.proxymanager_proxy_forward_redis_prefix .. toolname) + ngx.exit(ngx.HTTP_OK) +elseif ngx.req.get_method() == 'PUT' then + -- Create/update proxy forward entry. + ngx.req.read_body() + local req_body = ngx.req.get_body_data() + local new_proxy_forwards = json.decode(req_body) + if not new_proxy_forwards then + ngx.exit(ngx.HTTP_BAD_REQUEST) + end + for k, v in pairs(new_proxy_forwards) do + red:hset(ngx.var.proxymanager_proxy_forward_redis_prefix .. toolname, k, v) + end + ngx.exit(ngx.HTTP_OK) +else + ngx.exit(ngx.HTTP_NOT_ALLOWED) +end diff --git a/modules/dynamicproxy/files/proxymanager/v1/proxy-forwards.lua b/modules/dynamicproxy/files/proxymanager/v1/proxy-forwards.lua new file mode 100644 index 0000000..49bb255 --- /dev/null +++ b/modules/dynamicproxy/files/proxymanager/v1/proxy-forwards.lua @@ -0,0 +1,41 @@ +-- Copyright 2016 Tim Landscheidt +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Connect to Redis database. +local redis = require('resty.redis') +local red = redis:new() +red:set_timeout(1000) +red:connect('127.0.0.1', 6379) + +-- Get list of proxy entries. +local proxy_entries = red:keys(ngx.var.proxymanager_proxy_forward_redis_prefix .. '*') + +-- Use a connection pool of 16 connections with a 32 s idle timeout. +-- This also closes the current Redis connection. +red:set_keepalive(1000 * 32, 16) + +if ngx.req.get_method() == 'GET' then + local json = require('json') + + local result = {} + for i, v in ipairs(proxy_entries) do + table.insert(result, string.sub(v, string.len(ngx.var.proxymanager_proxy_forward_redis_prefix) + 1)) + end + + ngx.header['Content-Type'] = 'application/json' + ngx.say(json.encode(result)) + ngx.exit(ngx.HTTP_OK) +else + ngx.exit(ngx.HTTP_NOT_ALLOWED) +end diff --git a/modules/dynamicproxy/manifests/init.pp b/modules/dynamicproxy/manifests/init.pp index e7e0a06..3b8c7cf 100644 --- a/modules/dynamicproxy/manifests/init.pp +++ b/modules/dynamicproxy/manifests/init.pp @@ -156,6 +156,20 @@ notify => Service['nginx'], } + file { '/etc/nginx/lua/proxymanager-v1-proxy-forwards.lua': + ensure => 'file', + source => 'puppet:///modules/dynamicproxy/proxymanager/v1/proxy-forwards.lua', + require => [File['/etc/nginx/lua'], Package['lua-json']], + notify => Service['nginx'], + } + + file { '/etc/nginx/lua/proxymanager-v1-proxy-forwards-entry.lua': + ensure => 'file', + source => 'puppet:///modules/dynamicproxy/proxymanager/v1/proxy-forwards-entry.lua', + require => [File['/etc/nginx/lua'], Package['lua-json']], + notify => Service['nginx'], + } + package { 'lua-json': ensure => installed, } @@ -163,7 +177,9 @@ nginx::site { 'proxymanager': content => template('dynamicproxy/proxymanager.conf.erb'), require => [Ferm::Service['proxymanager'], - File['/etc/nginx/lua/list-proxy-entries.lua']], + File['/etc/nginx/lua/list-proxy-entries.lua'], + File['/etc/nginx/lua/proxymanager-v1-proxy-forwards.lua'], + File['/etc/nginx/lua/proxymanager-v1-proxy-forwards-entry.lua']], } ferm::service { 'proxymanager': diff --git a/modules/dynamicproxy/templates/proxymanager.conf.erb b/modules/dynamicproxy/templates/proxymanager.conf.erb index 93330e5..86a74bd 100644 --- a/modules/dynamicproxy/templates/proxymanager.conf.erb +++ b/modules/dynamicproxy/templates/proxymanager.conf.erb @@ -3,11 +3,26 @@ listen 8081; - # Provide a list of active proxy entries. + # Configuration. + set $proxymanager_labsproject_prefix '<%= @labsproject %>.'; + set $proxymanager_proxy_forward_redis_prefix 'prefix:'; + + # Provide a list of active proxy entries (legacy endpoint used by + # labs/toollabs's www/content/list.php). location = /list { content_by_lua_file /etc/nginx/lua/list-proxy-entries.lua; } + # Provide a list of active proxy entries. + location = /v1/proxy-forwards { + content_by_lua_file /etc/nginx/lua/proxymanager-v1-proxy-forwards.lua; + } + + # Manage an active proxy entry. + location /v1/proxy-forwards/ { + content_by_lua_file /etc/nginx/lua/proxymanager-v1-proxy-forwards-entry.lua; + } + # Suppress default nginx page location / { return 404 "Page not found\n"; -- To view, visit https://gerrit.wikimedia.org/r/266448 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I2d643fc902208eafaaa0d7814e586f0c326f16b5 Gerrit-PatchSet: 1 Gerrit-Project: operations/puppet Gerrit-Branch: production Gerrit-Owner: Tim Landscheidt <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
