Compare commits
No commits in common. "mistress" and "config-validation" have entirely different histories.
mistress
...
config-val
9 changed files with 53 additions and 128 deletions
53
README.md
53
README.md
|
@ -19,37 +19,8 @@ It is an effective replacement to your NGINX installation, but you can have
|
||||||
them coexisting (say, NGINX on port 80, OpenResty being reverse proxied by
|
them coexisting (say, NGINX on port 80, OpenResty being reverse proxied by
|
||||||
NGINX on port 8069, though I wouldn't recommend it in production environments).
|
NGINX on port 8069, though I wouldn't recommend it in production environments).
|
||||||
|
|
||||||
### how does it work
|
|
||||||
|
|
||||||
aproxy has two "hooks" into openresty:
|
|
||||||
- initialization of the lua vm
|
|
||||||
- callback for every incoming request
|
|
||||||
|
|
||||||
initialization will run validation of your configuration file and check
|
|
||||||
if it is valid. if it is not you will see logs emitted about what failed
|
|
||||||
|
|
||||||
when a request comes in, the scripts declared in the aproxy config file will
|
|
||||||
be executed sequentially (TODO: ensure order on conf file is chain order).
|
|
||||||
|
|
||||||
each script has two types of callbacks: init and request
|
|
||||||
|
|
||||||
init callbacks are called when initializing the request, so that the script
|
|
||||||
may do some conversion or transform the config data into a more performant
|
|
||||||
in-memory structure.
|
|
||||||
|
|
||||||
request callbacks are called on each request, as directed by the main script.
|
|
||||||
scripts define which paths they want to attach to (via PCRE regex), and so
|
|
||||||
they can do their own filtering.
|
|
||||||
|
|
||||||
look at `scripts/webfinger_allowlist.lua` for an example of how this looks
|
|
||||||
in a simple form.
|
|
||||||
|
|
||||||
### actually installing aproxy
|
### actually installing aproxy
|
||||||
|
|
||||||
- get openresty installed
|
|
||||||
- keep in mind that the specifics of configuring openresty for a double reverse proxy setup aren't included here.
|
|
||||||
- instructions here are for aproxy's setup in an existing openresty installation
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir /opt
|
mkdir /opt
|
||||||
git clone https://gitdab.com/luna/aproxy
|
git clone https://gitdab.com/luna/aproxy
|
||||||
|
@ -57,9 +28,6 @@ git clone https://gitdab.com/luna/aproxy
|
||||||
cd aproxy
|
cd aproxy
|
||||||
mkdir /etc/aproxy
|
mkdir /etc/aproxy
|
||||||
cp ./conf.lua /etc/aproxy/conf.lua
|
cp ./conf.lua /etc/aproxy/conf.lua
|
||||||
|
|
||||||
# keep in mind the default configuration will lead to your users not being discovered at all,
|
|
||||||
# it is provided as an example for you to modify.
|
|
||||||
$EDITOR /etc/aproxy/conf.lua
|
$EDITOR /etc/aproxy/conf.lua
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -83,26 +51,23 @@ http {
|
||||||
|
|
||||||
You need to do the following:
|
You need to do the following:
|
||||||
- Configure OpenResty package path so that it can call aproxy.
|
- Configure OpenResty package path so that it can call aproxy.
|
||||||
- insert aproxy hooks for initialization and for callbacks on every request
|
- Insert aproxy as a callback on `location / {` block
|
||||||
|
|
||||||
It'll look something like this if you use a single `location /` block:
|
It'll look something like this:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
|
http {
|
||||||
|
lua_package_path '/opt/?.lua;/opt/aproxy/?.lua;;';
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name example.com;
|
||||||
|
location / {
|
||||||
# set this to 'on' after you have tested that it actually works.
|
# set this to 'on' after you have tested that it actually works.
|
||||||
# once you do that, performance will be increased
|
# once you do that, performance will be increased
|
||||||
# while the friction to quickly debug aproxy will also be increased
|
# while the friction to quickly debug aproxy will also be increased
|
||||||
lua_code_cache off;
|
lua_code_cache off;
|
||||||
lua_package_path '/opt/?.lua;/opt/aproxy/?.lua;;';
|
|
||||||
init_by_lua_block {
|
|
||||||
require("aproxy.main").init()
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
server {
|
|
||||||
server_name example.com;
|
|
||||||
location / {
|
|
||||||
access_by_lua_block {
|
access_by_lua_block {
|
||||||
require("aproxy.main").access()
|
require("aproxy.main")()
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
28
config.lua
28
config.lua
|
@ -2,12 +2,22 @@ local env_config_path = os.getenv('APROXY_CONFIG_PATH')
|
||||||
local DEFAULT_CONFIG_PATH = ".;/etc/aproxy"
|
local DEFAULT_CONFIG_PATH = ".;/etc/aproxy"
|
||||||
local config_path = env_config_path or DEFAULT_CONFIG_PATH
|
local config_path = env_config_path or DEFAULT_CONFIG_PATH
|
||||||
|
|
||||||
|
function mysplit (inputstr, sep)
|
||||||
|
if sep == nil then
|
||||||
|
sep = "%s"
|
||||||
|
end
|
||||||
|
local t={}
|
||||||
|
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||||
|
table.insert(t, str)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
local function findConfigFile()
|
local function findConfigFile()
|
||||||
for _, config_directory in ipairs(string.split(config_path, ";")) do
|
for _, config_directory in ipairs(mysplit(config_path, ";")) do
|
||||||
local possible_config_path = config_directory .. "/" .. "conf.lua"
|
local possible_config_path = config_directory .. "/" .. "conf.lua"
|
||||||
local fd, res = io.open(possible_config_path, "rb")
|
local fd, res = io.open(possible_config_path, "rb")
|
||||||
if fd then
|
if fd then
|
||||||
log('config found at ' .. possible_config_path)
|
|
||||||
local data = fd:read("*a")
|
local data = fd:read("*a")
|
||||||
fd:close()
|
fd:close()
|
||||||
return data
|
return data
|
||||||
|
@ -27,7 +37,7 @@ end
|
||||||
|
|
||||||
local function fakeTableSchema(value_schema)
|
local function fakeTableSchema(value_schema)
|
||||||
return setmetatable({ __list = true }, {
|
return setmetatable({ __list = true }, {
|
||||||
__index = function ()
|
__index = function (self, key)
|
||||||
return value_schema
|
return value_schema
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
@ -35,8 +45,8 @@ end
|
||||||
|
|
||||||
local SPECIAL_KEYS = {__list = true}
|
local SPECIAL_KEYS = {__list = true}
|
||||||
|
|
||||||
local function validateSchema(schema, input, errors_in)
|
local function validateSchema(schema, input, errors)
|
||||||
local errors = errors_in or {}
|
local errors = errors or {}
|
||||||
if schema.__list then
|
if schema.__list then
|
||||||
-- generate full schema for lists that are same size as input
|
-- generate full schema for lists that are same size as input
|
||||||
for k, _ in pairs(input) do schema[k] = schema.__schema_value end
|
for k, _ in pairs(input) do schema[k] = schema.__schema_value end
|
||||||
|
@ -95,8 +105,12 @@ local function validateConfigFile(config_object)
|
||||||
return all_schema_errors
|
return all_schema_errors
|
||||||
end
|
end
|
||||||
|
|
||||||
local function loadConfigFile(options_in)
|
local function writeSchemaErrors(errors, out)
|
||||||
local options = options_in or {}
|
out('sex')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function loadConfigFile(options)
|
||||||
|
local options = options or {}
|
||||||
|
|
||||||
local config_file_data = assert(findConfigFile(), 'no config file found, config path: ' .. config_path)
|
local config_file_data = assert(findConfigFile(), 'no config file found, config path: ' .. config_path)
|
||||||
local config_file_function = assert(loadstring(config_file_data))
|
local config_file_function = assert(loadstring(config_file_data))
|
||||||
|
|
12
ctx.lua
12
ctx.lua
|
@ -1,5 +1,3 @@
|
||||||
local util = require("util")
|
|
||||||
|
|
||||||
function log(msg)
|
function log(msg)
|
||||||
ngx.log(ngx.STDERR, tostring(msg))
|
ngx.log(ngx.STDERR, tostring(msg))
|
||||||
end
|
end
|
||||||
|
@ -15,14 +13,13 @@ function ctx:loadFromConfig(conf)
|
||||||
ctx:loadChain()
|
ctx:loadChain()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function ctx:loadChain()
|
function ctx:loadChain()
|
||||||
self.compiled_chain = {}
|
self.compiled_chain = {}
|
||||||
for module_name, module_config in pairs(self._wanted_scripts) do
|
for module_name, module_config in pairs(self._wanted_scripts) do
|
||||||
local module = require('scripts.' .. module_name)
|
local module = require('scripts.' .. module_name)
|
||||||
local module_config_readonly = table.readonly(module_config)
|
local module_state = module.init(module_config)
|
||||||
local module_state = module.init(module_config_readonly)
|
-- TODO is it possible to make module_config readonly?
|
||||||
table.insert(self.compiled_chain, {module, module_config_readonly, module_state})
|
table.insert(self.compiled_chain, {module, module_config, module_state})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,7 +32,7 @@ function ctx:onRequest()
|
||||||
local module, module_config, state = unpack(filter)
|
local module, module_config, state = unpack(filter)
|
||||||
|
|
||||||
for callback_regex, callback_function in pairs(module.callbacks) do
|
for callback_regex, callback_function in pairs(module.callbacks) do
|
||||||
local match = ngx.re.match(request_uri, callback_regex)
|
local match, error = ngx.re.match(request_uri, callback_regex)
|
||||||
if match then
|
if match then
|
||||||
table.insert(callbacks_to_call, {module, callback_function, module_config, state})
|
table.insert(callbacks_to_call, {module, callback_function, module_config, state})
|
||||||
end
|
end
|
||||||
|
@ -50,7 +47,6 @@ function ctx:onRequest()
|
||||||
ngx.status = status_code
|
ngx.status = status_code
|
||||||
ngx.say(body or "request denied")
|
ngx.say(body or "request denied")
|
||||||
ngx.exit(status_code)
|
ngx.exit(status_code)
|
||||||
return
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
8
main.lua
8
main.lua
|
@ -1,3 +1,11 @@
|
||||||
|
|
||||||
|
-- function loadConfig()
|
||||||
|
-- -- TODO load config_path
|
||||||
|
-- return require("./config.lua")
|
||||||
|
-- end
|
||||||
|
--
|
||||||
|
-- local config = loadConfig()
|
||||||
|
|
||||||
local ctx = require('ctx')
|
local ctx = require('ctx')
|
||||||
local config = require('config')
|
local config = require('config')
|
||||||
require('util')
|
require('util')
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
lua_code_cache off;
|
|
||||||
init_by_lua_block {
|
init_by_lua_block {
|
||||||
require("aproxy.main").init()
|
require("aproxy.main").init()
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
lua_code_cache off;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
local function searchInit(cfg)
|
|
||||||
return {} -- no ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
local function searchCallback(cfg, _ctx)
|
|
||||||
local h, err = ngx.req.get_headers()
|
|
||||||
|
|
||||||
if err == "truncated" then
|
|
||||||
return 400, 'too many headers'
|
|
||||||
end
|
|
||||||
|
|
||||||
local authheader = h["authorization"]
|
|
||||||
|
|
||||||
if authheader == nil then
|
|
||||||
return 401, "requires authentication"
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
name='PleromaRestrictUnauthenticatedSearch',
|
|
||||||
author='luna@l4.pm',
|
|
||||||
title='restrict unauth search',
|
|
||||||
description=[[
|
|
||||||
Search can be a DoS vector. restrict it without Authorization header.
|
|
||||||
Useful for small instances.
|
|
||||||
]],
|
|
||||||
version=1,
|
|
||||||
init=searchInit,
|
|
||||||
callbacks = {
|
|
||||||
['/api/v2/search'] = searchCallback
|
|
||||||
},
|
|
||||||
config={},
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
local function webfingerInit(cfg)
|
function webfingerInit(cfg)
|
||||||
local accounts_set = {}
|
local accounts_set = {}
|
||||||
for _, account in ipairs(cfg.accounts) do
|
for _, account in ipairs(cfg.accounts) do
|
||||||
accounts_set["acct:" .. account] = true
|
accounts_set["acct:" .. account] = true
|
||||||
|
@ -6,7 +6,7 @@ local function webfingerInit(cfg)
|
||||||
return accounts_set
|
return accounts_set
|
||||||
end
|
end
|
||||||
|
|
||||||
local function webfingerCallback(cfg, accounts_set)
|
function webfingerCallback(cfg, accounts_set)
|
||||||
local args, err = ngx.req.get_uri_args()
|
local args, err = ngx.req.get_uri_args()
|
||||||
if err == "truncated" then
|
if err == "truncated" then
|
||||||
return 400, 'uri args too long'
|
return 400, 'uri args too long'
|
||||||
|
|
6
test.lua
6
test.lua
|
@ -64,7 +64,7 @@ function setupTest(module_require_path, input_config)
|
||||||
local count = table.pprint(schema_errors)
|
local count = table.pprint(schema_errors)
|
||||||
lu.assertIs(count, 0)
|
lu.assertIs(count, 0)
|
||||||
|
|
||||||
local state = module.init(input_config)
|
state = module.init(input_config)
|
||||||
ctx.compiled_chain = {
|
ctx.compiled_chain = {
|
||||||
{module, input_config, state}
|
{module, input_config, state}
|
||||||
}
|
}
|
||||||
|
@ -74,9 +74,9 @@ end
|
||||||
|
|
||||||
function onRequest()
|
function onRequest()
|
||||||
ctx:setWantedScripts()
|
ctx:setWantedScripts()
|
||||||
local context = require('ctx')
|
local ctx = require('ctx')
|
||||||
do
|
do
|
||||||
context:onRequest()
|
ctx:onRequest()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
33
util.lua
33
util.lua
|
@ -4,11 +4,11 @@ function table.len(t)
|
||||||
return count
|
return count
|
||||||
end
|
end
|
||||||
|
|
||||||
function table.pprint(t, options_in, ident_in, total_count_in)
|
function table.pprint(t, options, ident, total_count)
|
||||||
local ident = ident_in or 0
|
local ident = ident or 0
|
||||||
local total_count = total_count_in or 0
|
local total_count = total_count or 0
|
||||||
|
|
||||||
local options = options_in or {}
|
local options = options or {}
|
||||||
local print_function = options.call or print
|
local print_function = options.call or print
|
||||||
if type(t) == 'table' then
|
if type(t) == 'table' then
|
||||||
local count = 0
|
local count = 0
|
||||||
|
@ -18,7 +18,7 @@ function table.pprint(t, options_in, ident_in, total_count_in)
|
||||||
total_count = table.pprint(v, options, ident + 1, total_count)
|
total_count = table.pprint(v, options, ident + 1, total_count)
|
||||||
end
|
end
|
||||||
if count == 0 then
|
if count == 0 then
|
||||||
print_function('{}')
|
--print('<empty table>')
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
print_function(string.rep('\t', ident) .. tostring(t))
|
print_function(string.rep('\t', ident) .. tostring(t))
|
||||||
|
@ -26,26 +26,3 @@ function table.pprint(t, options_in, ident_in, total_count_in)
|
||||||
end
|
end
|
||||||
return total_count
|
return total_count
|
||||||
end
|
end
|
||||||
|
|
||||||
function table.readonly(t)
|
|
||||||
return setmetatable({}, {
|
|
||||||
__index = t,
|
|
||||||
__newindex = function ()
|
|
||||||
error("Attempt to modify read-only table")
|
|
||||||
end,
|
|
||||||
__metatable = false
|
|
||||||
});
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.split(inputstr, sep)
|
|
||||||
if sep == nil then
|
|
||||||
sep = "%s"
|
|
||||||
end
|
|
||||||
local t={}
|
|
||||||
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
|
||||||
table.insert(t, str)
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue