init commit
This commit is contained in:
7
plugins/core.lua
Normal file
7
plugins/core.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
return {
|
||||
crypto = require("core.crypto"),
|
||||
json = require("core.json"),
|
||||
table = require("core.table"),
|
||||
request = require("core.request"),
|
||||
}
|
||||
39
plugins/crypto.lua
Normal file
39
plugins/crypto.lua
Normal file
@@ -0,0 +1,39 @@
|
||||
local ffi = require('ffi')
|
||||
local crypto = ffi.load("crypto.so.1.1", true)
|
||||
local ffi_string = ffi.string
|
||||
|
||||
ffi.cdef[[
|
||||
char *MD5(const char *d, size_t n, char *md);
|
||||
int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);
|
||||
int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);
|
||||
]]
|
||||
|
||||
local _M = { version = 0.2 }
|
||||
|
||||
function string.tohex(str)
|
||||
return (str:gsub('.', function (c)
|
||||
return string.format('%02X', string.byte(c))
|
||||
end))
|
||||
end
|
||||
|
||||
function _M.md5(data)
|
||||
local buf = ffi.new("char[16]")
|
||||
crypto.MD5(data, #data, buf)
|
||||
return ffi_string(buf):tohex()
|
||||
end
|
||||
|
||||
function _M.decode_base64(data)
|
||||
local len = #data - #data/4
|
||||
local buf = ffi.new("unsigned char["..len.."]")
|
||||
len = crypto.EVP_DecodeBlock(buf, data, #data)
|
||||
return ffi_string(buf, len - 1)
|
||||
end
|
||||
|
||||
function _M.encode_base64(data)
|
||||
local len = #data + #data/4
|
||||
local buf = ffi.new("unsigned char["..len.."]")
|
||||
len = crypto.EVP_EncodeBlock(buf, data, #data)
|
||||
return ffi_string(buf, len - 1)
|
||||
end
|
||||
|
||||
return _M
|
||||
46
plugins/ctx.lua
Normal file
46
plugins/ctx.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
local _M = { version = 0.2 }
|
||||
|
||||
local function get_client_ip(stream)
|
||||
local ip = stream:downstreamLocalAddress()
|
||||
if ip then
|
||||
return ip
|
||||
end
|
||||
|
||||
ip = stream:downstreamDirectRemoteAddress()
|
||||
if ip then
|
||||
return ip
|
||||
end
|
||||
end
|
||||
|
||||
function _M.set_vars_meta(handle)
|
||||
local stream = handle:streamInfo()
|
||||
local meta = stream:dynamicMetadata()
|
||||
|
||||
local var = {}
|
||||
var._cache = meta:get("envoy.filters.http.lua") or {}
|
||||
var.remote_addr = get_client_ip(stream)
|
||||
|
||||
setmetatable(var, {
|
||||
__index = function(self, key)
|
||||
local cached = self._cache[key]
|
||||
if cached ~= nil then
|
||||
return cached
|
||||
end
|
||||
|
||||
return nil
|
||||
end,
|
||||
|
||||
__newindex = function(self, key, val)
|
||||
meta:set("envoy.filters.http.lua", key, val)
|
||||
self._cache[key] = val
|
||||
end,
|
||||
|
||||
__pairs = function (self)
|
||||
return next, self._cache, nil
|
||||
end,
|
||||
})
|
||||
|
||||
return var
|
||||
end
|
||||
|
||||
return _M
|
||||
15
plugins/entry.lua
Normal file
15
plugins/entry.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
local plugin = require("core.plugin")
|
||||
|
||||
function envoy_on_request(request_handle)
|
||||
local conf = request_handle:metadata():get("plugins")
|
||||
if conf then
|
||||
plugin.run(request_handle, "request", conf)
|
||||
end
|
||||
end
|
||||
|
||||
function envoy_on_response(response_handle)
|
||||
local conf = response_handle:metadata():get("plugins")
|
||||
if conf then
|
||||
plugin.run(response_handle, "response", conf)
|
||||
end
|
||||
end
|
||||
110
plugins/envoy.yaml
Normal file
110
plugins/envoy.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
# docker run --rm --network host -v $(pwd):/etc/envoy -w /etc/envoy -it envoyproxy/envoy-distroless:v1.24.0
|
||||
# curl -u 'admin:admin' -H 'Origin: http://lenovo.com' -i http://vcap.me:10000/ip
|
||||
|
||||
static_resources:
|
||||
listeners:
|
||||
- address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 10000
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
codec_type: AUTO
|
||||
stat_prefix: ingress_http
|
||||
strip_any_host_port: true
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: vcap_me
|
||||
domains:
|
||||
- vcap.me
|
||||
routes:
|
||||
- match:
|
||||
path: /ip
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: httpbin
|
||||
metadata:
|
||||
filter_metadata:
|
||||
envoy.filters.http.lua:
|
||||
plugins:
|
||||
- name: user-code
|
||||
conf:
|
||||
access: |
|
||||
ctx.var.requst = {
|
||||
path = ctx.headers:get(":path")
|
||||
}
|
||||
- name: basic-auth
|
||||
conf:
|
||||
username: admin
|
||||
password: admin
|
||||
- name: redirect
|
||||
conf:
|
||||
ret_code: 301
|
||||
uri: /headers
|
||||
headers:
|
||||
x-earth-token: xxx
|
||||
x-earth-project: yyy
|
||||
- match:
|
||||
path: /body
|
||||
direct_response:
|
||||
status: 200
|
||||
body:
|
||||
inline_string: "Body\n"
|
||||
metadata:
|
||||
filter_metadata:
|
||||
envoy.filters.http.lua:
|
||||
plugins:
|
||||
- name: user-code
|
||||
conf:
|
||||
access: |
|
||||
ctx.var.path = ctx.headers:get(":path")
|
||||
ctx.var.method = ctx.headers:get(":method")
|
||||
ctx.log.info("hit access")
|
||||
body_filter: |
|
||||
for key, value in pairs(ctx.var) do
|
||||
ctx.log.info("var: ", key, " = ", value)
|
||||
end
|
||||
ctx.log.info("hit body_filter")
|
||||
- match:
|
||||
path: /auth
|
||||
direct_response:
|
||||
status: 200
|
||||
body:
|
||||
inline_string: "Autherd\n"
|
||||
metadata:
|
||||
filter_metadata:
|
||||
envoy.filters.http.lua:
|
||||
plugins:
|
||||
- name: basic-auth
|
||||
conf:
|
||||
htpasswd: | # htpasswd -bnBC 10 admin admin
|
||||
admin:$2y$10$7y/gRzOG6zhB5WOnGp8xw.wMF9c4Fw6ZkPwaALHlMNFG5IZy1W3Um
|
||||
http_filters:
|
||||
- name: envoy.filters.http.lua
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
|
||||
default_source_code:
|
||||
filename: /etc/envoy/entry.lua
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
access_log:
|
||||
- name: envoy.access_loggers.stdout
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
|
||||
clusters:
|
||||
- name: httpbin
|
||||
type: LOGICAL_DNS
|
||||
load_assignment:
|
||||
cluster_name: httpbin
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: httpbin.org
|
||||
port_value: 80
|
||||
388
plugins/json.lua
Normal file
388
plugins/json.lua
Normal file
@@ -0,0 +1,388 @@
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
41
plugins/log.lua
Normal file
41
plugins/log.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local _M = { }
|
||||
|
||||
local log_levels = {
|
||||
crit = "logCritical",
|
||||
error = "logErr",
|
||||
warn = "logWarn",
|
||||
notice = "logTrace",
|
||||
info = "logInfo",
|
||||
debug = "logDebug",
|
||||
}
|
||||
|
||||
local _tostring = tostring
|
||||
local tostring = function(...)
|
||||
local t = {}
|
||||
for i = 1, select('#', ...) do
|
||||
local x = select(i, ...)
|
||||
if type(x) == "number" then
|
||||
x = round(x, .01)
|
||||
end
|
||||
t[#t + 1] = _tostring(x)
|
||||
end
|
||||
return table.concat(t, " ")
|
||||
end
|
||||
|
||||
function _M.new(handle)
|
||||
local o = {}
|
||||
setmetatable(o, {__index = function(self, cmd)
|
||||
local t = getmetatable(handle)
|
||||
local method = rawget(t, log_levels[cmd])
|
||||
if not method then
|
||||
return do_nothing
|
||||
end
|
||||
|
||||
return function(...)
|
||||
method(handle, tostring(...))
|
||||
end
|
||||
end})
|
||||
return o
|
||||
end
|
||||
|
||||
return _M
|
||||
62
plugins/plugin.lua
Normal file
62
plugins/plugin.lua
Normal file
@@ -0,0 +1,62 @@
|
||||
local log = require("core.log")
|
||||
local ctx = require("core.ctx")
|
||||
local json = require("core.json")
|
||||
|
||||
local _M = {version = 0.2}
|
||||
|
||||
local phases = {
|
||||
request = {
|
||||
'access',
|
||||
'rewrite'
|
||||
},
|
||||
response = {
|
||||
'header_filter',
|
||||
'body_filter'
|
||||
}
|
||||
}
|
||||
|
||||
function _M.run(handle, phase, plugins)
|
||||
if not plugins or #plugins == 0 then
|
||||
return ctx
|
||||
end
|
||||
|
||||
local resp_header = {}
|
||||
|
||||
local context = {
|
||||
headers = handle:headers(),
|
||||
body = handle:body(),
|
||||
log = log.new(handle),
|
||||
var = ctx.set_vars_meta(handle),
|
||||
}
|
||||
|
||||
function context.set_resp_header(key, value)
|
||||
resp_header[key] = value
|
||||
end
|
||||
|
||||
for _, plugin in ipairs(plugins) do
|
||||
local ok, plugin_object = pcall(require, "plugins." .. plugin.name)
|
||||
if ok then
|
||||
local earth_phases = phases[phase]
|
||||
for _, phase_name in ipairs(earth_phases) do
|
||||
local phase_func = plugin_object[phase_name]
|
||||
if type(phase_func) == "function" then
|
||||
handle:logTrace("phase_name: " .. plugin.name .. "." .. phase_name)
|
||||
local status, body = phase_func(plugin.conf, context)
|
||||
if status then
|
||||
resp_header[":status"] = status
|
||||
if type(body) == "table" then
|
||||
body = json.encode(body)
|
||||
end
|
||||
return handle:respond(resp_header, body)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
handle:logWarn("failed to load plugin ["..plugin.name.."] err: "..plugin_object)
|
||||
end
|
||||
end
|
||||
|
||||
return ctx
|
||||
end
|
||||
|
||||
return _M
|
||||
64
plugins/plugins/basic-auth.lua
Normal file
64
plugins/plugins/basic-auth.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
local core = require("core")
|
||||
|
||||
local plugin_name = "basic-auth"
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 2520,
|
||||
type = 'auth',
|
||||
name = plugin_name,
|
||||
}
|
||||
|
||||
local function extract_auth_header(auth)
|
||||
local obj = { username = "", password = "" }
|
||||
local userpass = auth:match("Basic%s+(.*)")
|
||||
if not userpass then
|
||||
return nil, "Invalid authorization header format"
|
||||
end
|
||||
|
||||
local decoded = core.crypto.decode_base64(userpass)
|
||||
if not decoded then
|
||||
return nil, "Failed to decode authentication header: " .. m[1]
|
||||
end
|
||||
|
||||
user, pass = decoded:match("([^:]*):(.*)")
|
||||
obj.username = user:gsub("%s+", "")
|
||||
obj.password = pass:gsub("%s+", "")
|
||||
return obj, nil
|
||||
end
|
||||
|
||||
function _M.rewrite(conf, ctx)
|
||||
ctx.log.info("plugin access phase, conf: ", core.json.encode(conf))
|
||||
|
||||
-- 1. extract authorization from header
|
||||
local auth_header = core.request.header(ctx, "Authorization")
|
||||
if not auth_header then
|
||||
ctx.set_resp_header("WWW-Authenticate", "Basic realm='.'")
|
||||
return 401, { message = "Missing authorization in request" }
|
||||
end
|
||||
|
||||
local user, err = extract_auth_header(auth_header)
|
||||
if err then
|
||||
ctx.log.warn("extract auth header: ", err)
|
||||
return 401, { message = "Invalid authorization in request" }
|
||||
end
|
||||
ctx.log.info("plugin access phase, authorization: ", user.username, ":", user.password)
|
||||
|
||||
-- 2. get user info from cache
|
||||
|
||||
-- 4. check the password is correct
|
||||
if conf.password ~= user.password then
|
||||
ctx.log.info("check: ["..type(conf.password).."], ["..type(user.password).."]")
|
||||
ctx.log.info("check: ["..#conf.password.."], ["..#user.password.."\0".."]")
|
||||
return 401, { message = "Invalid user authorization" }
|
||||
end
|
||||
|
||||
-- 5. hide `Authorization` request header if `hide_credentials` is `true`
|
||||
if conf.hide_credentials then
|
||||
ctx.headers:remove("Authorization")
|
||||
end
|
||||
|
||||
ctx.log.info("hit basic-auth access")
|
||||
end
|
||||
|
||||
return _M
|
||||
84
plugins/plugins/ldap-auth.lua
Normal file
84
plugins/plugins/ldap-auth.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
local core = require("core")
|
||||
local ldap = require("resty.ldap")
|
||||
|
||||
local plugin_name = "ldap-auth"
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 2540,
|
||||
type = 'auth',
|
||||
name = plugin_name,
|
||||
}
|
||||
|
||||
local function extract_auth_header(auth)
|
||||
local obj = { username = "", password = "" }
|
||||
local userpass = auth:match("Basic%s+(.*)")
|
||||
if not userpass then
|
||||
return nil, "Invalid authorization header format"
|
||||
end
|
||||
|
||||
local decoded = core.crypto.decode_base64(userpass)
|
||||
if not decoded then
|
||||
return nil, "Failed to decode authentication header: " .. m[1]
|
||||
end
|
||||
|
||||
user, pass = decoded:match("([^:]*):(.*)")
|
||||
obj.username = user:gsub("%s+", "")
|
||||
obj.password = pass:gsub("%s+", "")
|
||||
return obj, nil
|
||||
end
|
||||
|
||||
function _M.rewrite(conf, ctx)
|
||||
ctx.log.info("plugin rewrite phase, conf: ", core.json.encode(conf))
|
||||
|
||||
-- 1. extract authorization from header
|
||||
local auth_header = core.request.header(ctx, "Authorization")
|
||||
if not auth_header then
|
||||
ctx.set_resp_header("WWW-Authenticate", "Basic realm='.'")
|
||||
return 401, { message = "Missing authorization in request" }
|
||||
end
|
||||
|
||||
local user, err = extract_auth_header(auth_header)
|
||||
if err then
|
||||
ctx.log.warn(err)
|
||||
return 401, { message = "Invalid authorization in request" }
|
||||
end
|
||||
|
||||
-- 2. try authenticate the user against the ldap server
|
||||
local ldap_host, ldap_port = core.utils.parse_addr(conf.ldap_uri)
|
||||
|
||||
local userdn = conf.uid .. "=" .. user.username .. "," .. conf.base_dn
|
||||
local ldapconf = {
|
||||
timeout = 10000,
|
||||
start_tls = false,
|
||||
ldap_host = ldap_host,
|
||||
ldap_port = ldap_port or 389,
|
||||
ldaps = conf.use_tls,
|
||||
tls_verify = conf.tls_verify,
|
||||
base_dn = conf.base_dn,
|
||||
attribute = conf.uid,
|
||||
keepalive = 60000,
|
||||
}
|
||||
local res, err = ldap.ldap_authenticate(user.username, user.password, ldapconf)
|
||||
if not res then
|
||||
ctx.log.warn("ldap-auth failed: ", err)
|
||||
return 401, { message = "Invalid user authorization" }
|
||||
end
|
||||
|
||||
-- 3. Retrieve consumer for authorization plugin
|
||||
local consumer_conf = consumer_mod.plugin(plugin_name)
|
||||
if not consumer_conf then
|
||||
return 401, { message = "Missing related consumer" }
|
||||
end
|
||||
local consumers = lrucache("consumers_key", consumer_conf.conf_version,
|
||||
create_consumer_cache, consumer_conf)
|
||||
local consumer = consumers[userdn]
|
||||
if not consumer then
|
||||
return 401, {message = "Invalid user authorization"}
|
||||
end
|
||||
consumer_mod.attach_consumer(ctx, consumer, consumer_conf)
|
||||
|
||||
ctx.log.info("hit basic-auth access")
|
||||
end
|
||||
|
||||
return _M
|
||||
26
plugins/plugins/redirect.lua
Normal file
26
plugins/plugins/redirect.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
local core = require("core")
|
||||
|
||||
local plugin_name = "redirect"
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 900,
|
||||
name = plugin_name
|
||||
}
|
||||
|
||||
function _M.rewrite(conf, ctx)
|
||||
local ret_code = conf.ret_code
|
||||
local uri = conf.uri
|
||||
|
||||
if conf.http_to_https and ctx.var.scheme == "http" then
|
||||
uri = "https://$host$request_uri"
|
||||
ret_code = 301
|
||||
end
|
||||
|
||||
if uri and ret_code then
|
||||
ctx.set_resp_header("Location", uri)
|
||||
return ret_code
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
||||
29
plugins/plugins/user-code.lua
Normal file
29
plugins/plugins/user-code.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
local plugin_name = "user-code"
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 2520,
|
||||
name = plugin_name
|
||||
}
|
||||
|
||||
local function eval(equation, variables)
|
||||
if(type(equation) == "string") then
|
||||
local eval = load(equation);
|
||||
if(type(eval) == "function") then
|
||||
setfenv(eval, variables or {});
|
||||
return eval();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(_M, {__index = function(self, cmd)
|
||||
return function(conf, ctx)
|
||||
local equation = rawget(conf, cmd)
|
||||
return eval(equation, {
|
||||
pairs = pairs,
|
||||
ctx = ctx,
|
||||
})
|
||||
end
|
||||
end})
|
||||
|
||||
return _M
|
||||
30
plugins/request.lua
Normal file
30
plugins/request.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
local encode_json = require("core.json").encode
|
||||
local concat_tab = table.concat
|
||||
|
||||
local _M = { version = 0.1 }
|
||||
|
||||
function _M.header(ctx, name)
|
||||
return ctx.headers:get(name)
|
||||
end
|
||||
|
||||
function _M.set_header(ctx, name, value)
|
||||
return ctx.headers:replace(name, value)
|
||||
end
|
||||
|
||||
function _M.get_ip(ctx)
|
||||
return ctx.var.remote_addr or ctx.headers:get("x-forwarded-for")
|
||||
end
|
||||
|
||||
function _M.getbody(max_size, ctx)
|
||||
return ctx.body:getBytes(0, max_size)
|
||||
end
|
||||
|
||||
function _M.get_scheme(ctx)
|
||||
return ctx.headers:get(":scheme")
|
||||
end
|
||||
|
||||
function _M.get_host(ctx)
|
||||
return ctx.headers:get(":authority")
|
||||
end
|
||||
|
||||
return _M
|
||||
68
plugins/string.lua
Normal file
68
plugins/string.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You 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.
|
||||
--
|
||||
local error = error
|
||||
local type = type
|
||||
local str_find = string.find
|
||||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local ffi_cast = ffi.cast
|
||||
|
||||
|
||||
ffi.cdef[[
|
||||
int memcmp(const void *s1, const void *s2, size_t n);
|
||||
]]
|
||||
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
}
|
||||
|
||||
|
||||
setmetatable(_M, {__index = string})
|
||||
|
||||
|
||||
-- find a needle from a haystack in the plain text way
|
||||
function _M.find(haystack, needle, from)
|
||||
return str_find(haystack, needle, from or 1, true)
|
||||
end
|
||||
|
||||
|
||||
function _M.has_prefix(s, prefix)
|
||||
if type(s) ~= "string" or type(prefix) ~= "string" then
|
||||
error("unexpected type: s:" .. type(s) .. ", prefix:" .. type(prefix))
|
||||
end
|
||||
if #s < #prefix then
|
||||
return false
|
||||
end
|
||||
local rc = C.memcmp(s, prefix, #prefix)
|
||||
return rc == 0
|
||||
end
|
||||
|
||||
|
||||
function _M.has_suffix(s, suffix)
|
||||
if type(s) ~= "string" or type(suffix) ~= "string" then
|
||||
error("unexpected type: s:" .. type(s) .. ", suffix:" .. type(suffix))
|
||||
end
|
||||
if #s < #suffix then
|
||||
return false
|
||||
end
|
||||
local rc = C.memcmp(ffi_cast("char *", s) + #s - #suffix, suffix, #suffix)
|
||||
return rc == 0
|
||||
end
|
||||
|
||||
|
||||
return _M
|
||||
162
plugins/table.lua
Normal file
162
plugins/table.lua
Normal file
@@ -0,0 +1,162 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
-- contributor license agreements. See the NOTICE file distributed with
|
||||
-- this work for additional information regarding copyright ownership.
|
||||
-- The ASF licenses this file to You 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.
|
||||
--
|
||||
local newproxy = newproxy
|
||||
local getmetatable = getmetatable
|
||||
local setmetatable = setmetatable
|
||||
local select = select
|
||||
local new_tab = require("table.new")
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local string = string
|
||||
|
||||
|
||||
local _M = {
|
||||
version = 0.2,
|
||||
new = new_tab,
|
||||
clear = require("table.clear"),
|
||||
insert = table.insert,
|
||||
concat = table.concat,
|
||||
sort = table.sort,
|
||||
}
|
||||
|
||||
|
||||
setmetatable(_M, {__index = table})
|
||||
|
||||
|
||||
local nkeys
|
||||
do
|
||||
local ok, table_nkeys = pcall(require, 'table.nkeys')
|
||||
if ok then
|
||||
nkeys = table_nkeys
|
||||
else
|
||||
nkeys = function(t)
|
||||
local count = 0
|
||||
for _, _ in pairs(t) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
end
|
||||
end
|
||||
_M.nkeys = nkeys
|
||||
|
||||
|
||||
function _M.insert_tail(tab, ...)
|
||||
local idx = #tab
|
||||
for i = 1, select('#', ...) do
|
||||
idx = idx + 1
|
||||
tab[idx] = select(i, ...)
|
||||
end
|
||||
|
||||
return idx
|
||||
end
|
||||
|
||||
|
||||
function _M.set(tab, ...)
|
||||
for i = 1, select('#', ...) do
|
||||
tab[i] = select(i, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- only work under lua51 or luajit
|
||||
function _M.setmt__gc(t, mt)
|
||||
local prox = newproxy(true)
|
||||
getmetatable(prox).__gc = function() mt.__gc(t) end
|
||||
t[prox] = true
|
||||
return setmetatable(t, mt)
|
||||
end
|
||||
|
||||
|
||||
local function deepcopy(orig)
|
||||
local orig_type = type(orig)
|
||||
if orig_type ~= 'table' then
|
||||
return orig
|
||||
end
|
||||
|
||||
-- If the array-like table contains nil in the middle,
|
||||
-- the len might be smaller than the expected.
|
||||
-- But it doesn't affect the correctness.
|
||||
local len = #orig
|
||||
local copy = new_tab(len, nkeys(orig) - len)
|
||||
for orig_key, orig_value in pairs(orig) do
|
||||
copy[orig_key] = deepcopy(orig_value)
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
_M.deepcopy = deepcopy
|
||||
|
||||
local ngx_null = nil
|
||||
local function merge(origin, extend)
|
||||
for k,v in pairs(extend) do
|
||||
if type(v) == "table" then
|
||||
if type(origin[k] or false) == "table" then
|
||||
if _M.nkeys(origin[k]) ~= #origin[k] then
|
||||
merge(origin[k] or {}, extend[k] or {})
|
||||
else
|
||||
origin[k] = v
|
||||
end
|
||||
else
|
||||
origin[k] = v
|
||||
end
|
||||
elseif v == ngx_null then
|
||||
origin[k] = nil
|
||||
else
|
||||
origin[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return origin
|
||||
end
|
||||
_M.merge = merge
|
||||
|
||||
|
||||
local function patch(node_value, sub_path, conf)
|
||||
local sub_value = node_value
|
||||
local sub_paths = string.split(sub_path, "/")
|
||||
for i = 1, #sub_paths - 1 do
|
||||
local sub_name = sub_paths[i]
|
||||
if sub_value[sub_name] == nil then
|
||||
sub_value[sub_name] = {}
|
||||
end
|
||||
|
||||
sub_value = sub_value[sub_name]
|
||||
|
||||
if type(sub_value) ~= "table" then
|
||||
return 400, "invalid sub-path: /"
|
||||
.. _M.concat(sub_paths, 1, i)
|
||||
end
|
||||
end
|
||||
|
||||
if type(sub_value) ~= "table" then
|
||||
return 400, "invalid sub-path: /" .. sub_path
|
||||
end
|
||||
|
||||
local sub_name = sub_paths[#sub_paths]
|
||||
if sub_name and sub_name ~= "" then
|
||||
sub_value[sub_name] = conf
|
||||
else
|
||||
node_value = conf
|
||||
end
|
||||
|
||||
return nil, nil, node_value
|
||||
end
|
||||
_M.patch = patch
|
||||
|
||||
|
||||
return _M
|
||||
Reference in New Issue
Block a user