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

Reply via email to