1
0

init commit

This commit is contained in:
2022-11-01 23:31:14 +08:00
commit 57c2c6a22e
26 changed files with 2208 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.o
*.dylib
/bin/*
/testbin/*
/luajit/*
# User-specific files
*.suo
*.user
*.sln.docstates
/.vscode
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# NuGet Packages Directory
packages/*
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
envoy-apisix

32
Makefile Normal file
View File

@@ -0,0 +1,32 @@
TARGET = envoy-apisix
LUA_VERSION = 2.1.0-beta3
SRCS = $(wildcard envoy/*.cpp)
OBJS = $(SRCS:.cpp=.o)
INCLUDES = -I./luajit/include/luajit-2.1
CXXFLAGS = -fPIC -O2 -Wall -std=c++11 $(INCLUDES)
LDFLAGS = -L./luajit/lib -lluajit-5.1 -ldl
.PHONY: all
all: run
$(TARGET): $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
luajit:
$(RM) -r /tmp/LuaJIT-$(LUA_VERSION)
curl -sL https://luajit.org/download/LuaJIT-$(LUA_VERSION).tar.gz | tar zxf - -C /tmp
make -C /tmp/LuaJIT-$(LUA_VERSION) install PREFIX=$(shell pwd)/luajit XCFLAGS=-DLUAJIT_ENABLE_LUA52COMPAT
.PHONY: run
run: ${TARGET}
@./$(TARGET) ./plugins/entry.lua
.PHONY: envoy
envoy:
docker run -it --rm --net host -v $(shell pwd):/etc/envoy -e "LUA_PATH=/etc/envoy/?.lua" envoyproxy/envoy-distroless:v1.24.0
.PHONY: clean
clean:
${RM} ${TARGET} ${OBJS}

22
envoy.sln Normal file
View File

@@ -0,0 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Express 2013 for Windows Desktop
VisualStudioVersion = 12.0.40629.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "envoy", "envoy\envoy.vcxproj", "{E152FBD5-2A29-400E-8E18-9C515986217C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E152FBD5-2A29-400E-8E18-9C515986217C}.Debug|Win32.ActiveCfg = Debug|Win32
{E152FBD5-2A29-400E-8E18-9C515986217C}.Debug|Win32.Build.0 = Debug|Win32
{E152FBD5-2A29-400E-8E18-9C515986217C}.Release|Win32.ActiveCfg = Release|Win32
{E152FBD5-2A29-400E-8E18-9C515986217C}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

126
envoy.yaml Normal file
View File

@@ -0,0 +1,126 @@
# 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
generate_request_id: false
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:
body_filter: |
for key, value in pairs(ctx.headers) do
log.info("user-code: ", key, " = ", value)
end
- 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: "Get Body\n"
metadata:
filter_metadata:
envoy.filters.http.lua:
plugins:
- name: user-code
conf:
access: |
ctx.var.path = ctx.headers:get(":path")
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
- match:
prefix: /
direct_response:
status: 200
body:
inline_string: "Lucky\n"
metadata:
filter_metadata:
envoy.filters.http.lua:
plugins:
- name: user-code
conf:
body_filter: |
for key, value in pairs(ctx.headers) do
ctx.log.info("header: ", key, " = ", value)
end
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/core/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

204
envoy/envoy.cpp Normal file
View File

@@ -0,0 +1,204 @@
#include "envoy.h"
#include <string>
#include <iostream>
namespace lua {
/**
* A wrapper for a buffer.
*/
int BufferWrapper::luaLength(lua_State* state) {
lua_pushnumber(state, 0);
return 1;
}
int BufferWrapper::luaGetBytes(lua_State* state) {
lua_pushlstring(state, "buffer", 6);
return 1;
}
int BufferWrapper::luaSetBytes(lua_State* state) {
lua_pushnumber(state, 0);
return 1;
}
/**
* Lua wrapper for a header map. Methods that will modify the map will call a check function
* to see if modification is allowed.
*/
int HeaderMapWrapper::luaAdd(lua_State* state) {
UNUSED(state);
return 0;
}
int HeaderMapWrapper::luaGet(lua_State* state) {
UNUSED(state);
return 0;
}
int HeaderMapWrapper::luaPairs(lua_State* state) {
if (iterator_.get() != nullptr) {
luaL_error(state, "cannot create a second iterator before completing the first");
}
iterator_.reset(HeaderMapIterator::create(state), true);
lua_pushcclosure(state, HeaderMapIterator::static_luaPairsIterator, 1);
return 1;
}
int HeaderMapIterator::luaPairsIterator(lua_State* state) {
UNUSED(state);
return 0;
}
/**
* Lua wrapper for a metadata map.
*/
int MetadataMapWrapper::luaGet(lua_State* state) {
const char* key = luaL_checkstring(state, 2);
UNUSED(key);
return 0;
}
int MetadataMapWrapper::luaPairs(lua_State* state) {
if (iterator_.get() != nullptr) {
luaL_error(state, "cannot create a second iterator before completing the first");
}
iterator_.reset(MetadataMapIterator::create(state), true);
lua_pushcclosure(state, MetadataMapIterator::static_luaPairsIterator, 1);
return 1;
}
int MetadataMapIterator::luaPairsIterator(lua_State* state) {
UNUSED(state);
return 0;
}
/**
* Lua wrapper for a stream info.
*/
int StreamInfoWrapper::luaProtocol(lua_State* state) {
const std::string protocol = "HTTP/1.1";
lua_pushlstring(state, protocol.data(), protocol.size());
return 1;
}
int StreamInfoWrapper::luaDynamicMetadata(lua_State* state) {
UNUSED(state);
return 0;
}
int StreamInfoWrapper::luaDownstreamSslConnection(lua_State* state) {
lua_pushnil(state);
return 1;
}
int StreamInfoWrapper::luaDownstreamLocalAddress(lua_State* state) {
const std::string local_address = "127.0.0.1:1234";
lua_pushlstring(state, local_address.data(), local_address.size());
return 1;
}
int StreamInfoWrapper::luaDownstreamDirectRemoteAddress(lua_State* state) {
const std::string direct_remote_address = "172.17.0.3:80";
lua_pushlstring(state, direct_remote_address.data(), direct_remote_address.size());
return 1;
}
int StreamInfoWrapper::luaRequestedServerName(lua_State* state) {
const std::string server_name = "envoy";
lua_pushlstring(state, server_name.data(), server_name.size());
return 1;
}
/**
* A wrapper for a currently running request/response. This is the primary handle passed to Lua.
* The script interacts with Envoy entirely through this handle.
*/
int StreamHandleWrapper::luaHeaders(lua_State* state) {
if (headers_wrapper_.get() != nullptr) {
headers_wrapper_.pushStack();
} else {
headers_wrapper_.reset(HeaderMapWrapper::create(state), true);
}
return 1;
}
int StreamHandleWrapper::luaBody(lua_State* state) {
UNUSED(state);
return 0;
}
int StreamHandleWrapper::luaBodyChunks(lua_State* state) {
lua_pushcclosure(state, static_luaBodyIterator, 1);
return 1;
}
int StreamHandleWrapper::luaBodyIterator(lua_State* state) {
LuaDeathRef<BufferWrapper> wrapper;
wrapper.reset(BufferWrapper::create(state), true);
return 1;
}
int StreamHandleWrapper::luaMetadata(lua_State* state) {
if (metadata_wrapper_.get() != nullptr) {
metadata_wrapper_.pushStack();
} else {
metadata_wrapper_.reset(MetadataMapWrapper::create(state), true);
}
return 1;
}
int StreamHandleWrapper::luaStreamInfo(lua_State* state) {
if (stream_info_wrapper_.get() != nullptr) {
stream_info_wrapper_.pushStack();
} else {
stream_info_wrapper_.reset(StreamInfoWrapper::create(state), true);
}
return 1;
}
int luaLog(lua_State* state, const char* prefix) {
size_t input_size = 0;
const char* input = luaL_checklstring(state, 2, &input_size);
std::string message(input, input_size);
std::cout << prefix << ": " << message << std::endl;
return 0;
}
int StreamHandleWrapper::luaLogTrace(lua_State* state) {
return luaLog(state, "trace");
}
int StreamHandleWrapper::luaLogDebug(lua_State* state) {
return luaLog(state, "debug");
}
int StreamHandleWrapper::luaLogInfo(lua_State* state) {
return luaLog(state, "info");
}
int StreamHandleWrapper::luaLogWarn(lua_State* state) {
return luaLog(state, "warn");
}
int StreamHandleWrapper::luaLogErr(lua_State* state) {
return luaLog(state, "error");
}
int StreamHandleWrapper::luaLogCritical(lua_State* state) {
return luaLog(state, "crit");
}
int StreamHandleWrapper::luaHttpCall(lua_State* state) {
UNUSED(state);
return 0;
}
int StreamHandleWrapper::luaRespond(lua_State* state) {
UNUSED(state);
return 0;
}
}

130
envoy/envoy.h Normal file
View File

@@ -0,0 +1,130 @@
#pragma once
#include "lua.h"
namespace lua {
class BufferWrapper : public BaseLuaObject<BufferWrapper> {
public:
static ExportedFunctions exportedFunctions() {
return {{"length", static_luaLength},
{"getBytes", static_luaGetBytes},
{"setBytes", static_luaSetBytes}};
}
private:
DECLARE_LUA_FUNCTION(BufferWrapper, luaLength);
DECLARE_LUA_FUNCTION(BufferWrapper, luaGetBytes);
DECLARE_LUA_FUNCTION(BufferWrapper, luaSetBytes);
};
class HeaderMapIterator : public BaseLuaObject<HeaderMapIterator> {
public:
static ExportedFunctions exportedFunctions() { return {}; }
DECLARE_LUA_CLOSURE(HeaderMapIterator, luaPairsIterator);
};
class HeaderMapWrapper : public BaseLuaObject<HeaderMapWrapper> {
public:
static ExportedFunctions exportedFunctions() {
return {
{ "add", static_luaAdd },
{ "get", static_luaGet },
{ "__pairs", static_luaPairs },
};
}
private:
DECLARE_LUA_FUNCTION(HeaderMapWrapper, luaAdd);
DECLARE_LUA_FUNCTION(HeaderMapWrapper, luaGet);
DECLARE_LUA_FUNCTION(HeaderMapWrapper, luaPairs);
LuaDeathRef<HeaderMapIterator> iterator_;
};
class MetadataMapIterator : public BaseLuaObject<MetadataMapIterator> {
public:
static ExportedFunctions exportedFunctions() { return {}; }
DECLARE_LUA_CLOSURE(MetadataMapIterator, luaPairsIterator);
};
class MetadataMapWrapper : public BaseLuaObject<MetadataMapWrapper> {
public:
static ExportedFunctions exportedFunctions() {
return {{"get", static_luaGet}, {"__pairs", static_luaPairs}};
}
private:
DECLARE_LUA_FUNCTION(MetadataMapWrapper, luaGet);
DECLARE_LUA_FUNCTION(MetadataMapWrapper, luaPairs);
LuaDeathRef<MetadataMapIterator> iterator_;
};
class StreamInfoWrapper : public BaseLuaObject<StreamInfoWrapper> {
public:
static ExportedFunctions exportedFunctions() {
return {
{ "protocol", static_luaProtocol },
{ "dynamicMetadata", static_luaDynamicMetadata },
{ "downstreamLocalAddress", static_luaDownstreamLocalAddress },
{ "downstreamDirectRemoteAddress", static_luaDownstreamDirectRemoteAddress },
{ "downstreamSslConnection", static_luaDownstreamSslConnection },
{ "requestedServerName", static_luaRequestedServerName }
};
}
private:
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaProtocol);
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDynamicMetadata);
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDownstreamSslConnection);
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDownstreamLocalAddress);
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaDownstreamDirectRemoteAddress);
DECLARE_LUA_FUNCTION(StreamInfoWrapper, luaRequestedServerName);
};
class StreamHandleWrapper : public BaseLuaObject<StreamHandleWrapper> {
public:
static ExportedFunctions exportedFunctions() {
return {
{ "headers", static_luaHeaders },
{ "body", static_luaBody },
{ "bodyChunks", static_luaBodyChunks },
{ "metadata", static_luaMetadata },
{ "streamInfo", static_luaStreamInfo },
{ "logTrace", static_luaLogTrace },
{ "logDebug", static_luaLogDebug },
{ "logInfo", static_luaLogInfo },
{ "logWarn", static_luaLogWarn },
{ "logErr", static_luaLogErr },
{ "logCritical", static_luaLogCritical },
{ "httpCall", static_luaHttpCall },
{ "respond", static_luaRespond },
};
}
private:
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaHeaders);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaBody);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaBodyChunks);
DECLARE_LUA_CLOSURE(StreamHandleWrapper, luaBodyIterator);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaMetadata);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaStreamInfo);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogTrace);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogDebug);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogInfo);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogWarn);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogErr);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogCritical);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaHttpCall);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaRespond);
LuaDeathRef<HeaderMapWrapper> headers_wrapper_;
LuaDeathRef<MetadataMapWrapper> metadata_wrapper_;
LuaDeathRef<StreamInfoWrapper> stream_info_wrapper_;
};
}

105
envoy/envoy.vcxproj Normal file
View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{E152FBD5-2A29-400E-8E18-9C515986217C}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>envoy</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros">
<NuGetPackageImportStamp>3c953278</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Configuration)\$(PlatformTarget)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Configuration)\$(PlatformTarget)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Configuration)\$(PlatformTarget)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Configuration)\$(PlatformTarget)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="envoy.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="envoy.h" />
<ClInclude Include="lua.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\luajit.native.2.0.5\build\native\luajit.native.targets" Condition="Exists('..\packages\luajit.native.2.0.5\build\native\luajit.native.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。启用“NuGet 程序包还原”可下载这些程序包。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\luajit.native.2.0.5\build\native\luajit.native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\luajit.native.2.0.5\build\native\luajit.native.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="envoy.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="envoy.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="lua.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

292
envoy/lua.h Normal file
View File

@@ -0,0 +1,292 @@
#pragma once
#include <vector>
#include <memory>
#include <lua.hpp>
#define ASSERT(...)
#define UNUSED(x) (void)(x)
#ifndef alignof
#define alignof(T) std::alignment_of<T>::value
#endif
namespace lua {
/**
* Base macro for declaring a Lua/C function. Any function declared will need to be exported via
* the exportedFunctions() function in BaseLuaObject. See BaseLuaObject below for more
* information. This macro declares a static "thunk" which checks the user data, optionally checks
* for object death (again see BaseLuaObject below for more info), and then invokes a normal
* object method. The actual object method needs to be implemented by the class.
* @param Class supplies the owning class name.
* @param Name supplies the function name.
* @param Index supplies the stack index where "this" (Lua/C userdata) is found.
*/
#define DECLARE_LUA_FUNCTION_EX(Class, Name, Index) \
static int static_##Name(lua_State* state) { \
Class* object = lua::alignAndCast<Class>( \
luaL_checkudata(state, Index, typeid(Class).name())); \
object->checkDead(state); \
return object->Name(state); \
} \
int Name(lua_State* state);
/**
* Declare a Lua function in which userdata is in stack slot 1. See DECLARE_LUA_FUNCTION_EX()
*/
#define DECLARE_LUA_FUNCTION(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, 1)
/**
* Declare a Lua function in which userdata is in upvalue slot 1. See DECLARE_LUA_FUNCTION_EX()
*/
#define DECLARE_LUA_CLOSURE(Class, Name) DECLARE_LUA_FUNCTION_EX(Class, Name, lua_upvalueindex(1))
/**
* Calculate the maximum space needed to be aligned.
*/
template <typename T> size_t maximumSpaceNeededToAlign() {
// The allocated memory can be misaligned up to `alignof(T) - 1` bytes. Adding it to the size to
// allocate.
return sizeof(T) + alignof(T) - 1;
}
template <typename T> inline T* alignAndCast(void* mem) {
size_t size = maximumSpaceNeededToAlign<T>();
return static_cast<T*>(std::align(alignof(T), sizeof(T), mem, size));
}
/**
* Create a new user data and assign its metatable.
*/
template <typename T> inline T* allocateLuaUserData(lua_State* state) {
void* mem = lua_newuserdata(state, maximumSpaceNeededToAlign<T>());
luaL_getmetatable(state, typeid(T).name());
ASSERT(lua_istable(state, -1));
lua_setmetatable(state, -2);
return alignAndCast<T>(mem);
}
/**
* This is the base class for all C++ objects that we expose out to Lua. The goal is to hide as
* much ugliness as possible. In general, to use this, do the following:
* 1) Make your class derive from BaseLuaObject<YourClass>
* 2) Define methods using DECLARE_LUA_FUNCTION* macros
* 3) Export your functions by declaring a static exportedFunctions() method in your class.
* 4) Optionally manage "death" status on your object. (See checkDead() and markDead() below).
* 5) Generally you will want to hold your objects inside a LuaRef or a LuaDeathRef. See below
* for more information on those containers.
*
* It's very important to understand the Lua memory model: Once an object is created, *it is
* owned by Lua*. Lua can GC it at any time. If you want to make sure that does not happen, you
* must hold a ref to it in C++, generally via LuaRef or LuaDeathRef.
*/
template <class T> class BaseLuaObject {
public:
using ExportedFunctions = std::vector<std::pair<const char*, lua_CFunction>>;
virtual ~BaseLuaObject() = default;
/**
* Create a new object of this type, owned by Lua. This type must have previously been registered
* via the registerType() routine below.
* @param state supplies the owning Lua state.
* @param args supplies the variadic constructor arguments for the object.
* @return a pair containing a pointer to the new object and the state it was created with. (This
* is done for convenience when passing a created object to a LuaRef or a LuaDeathRef.
*/
template <typename... ConstructorArgs>
static std::pair<T*, lua_State*> create(lua_State* state, ConstructorArgs&&... args) {
// Memory is allocated via Lua and it is raw. We use placement new to run the constructor.
T* mem = allocateLuaUserData<T>(state);
return {new (mem) T(std::forward<ConstructorArgs>(args)...), state};
}
/**
* Register a type with Lua.
* @param state supplies the state to register with.
*/
static void registerType(lua_State* state) {
std::vector<luaL_Reg> to_register;
// Fetch all of the functions to be exported to Lua so that we can register them in the
// metatable.
ExportedFunctions functions = T::exportedFunctions();
for (auto function : functions) {
to_register.push_back({function.first, function.second});
}
// Always register a __gc method so that we can run the object's destructor. We do this
// manually because the memory is raw and was allocated by Lua.
to_register.push_back(
{"__gc", [](lua_State* state) {
T* object = alignAndCast<T>(luaL_checkudata(state, 1, typeid(T).name()));
object->~T();
return 0;
}});
// Add the sentinel.
to_register.push_back({nullptr, nullptr});
// Register the type by creating a new metatable, setting __index to itself, and then
// performing the register.
luaL_newmetatable(state, typeid(T).name());
lua_pushvalue(state, -1);
lua_setfield(state, -2, "__index");
luaL_register(state, nullptr, to_register.data());
}
/**
* This function is called as part of the DECLARE_LUA_FUNCTION* macros. The idea here is that
* we cannot control when Lua destroys things. However, we may expose wrappers to a script that
* should not be used after some event. This allows us to mark objects as dead so that if they
* are used again they will throw a Lua error and not reach our code.
* @param state supplies the calling LuaState.
*/
int checkDead(lua_State* state) {
if (dead_) {
return luaL_error(state, "object used outside of proper scope");
}
return 0;
}
/**
* Mark an object as dead so that a checkDead() call will throw an error. See checkDead().
*/
void markDead() {
dead_ = true;
onMarkDead();
}
/**
* Mark an object as live so that a checkDead() call will not throw an error. See checkDead().
*/
void markLive() {
dead_ = false;
onMarkLive();
}
protected:
/**
* Called from markDead() when an object is marked dead. This is effectively a C++ destructor for
* Lua/C objects. Objects can perform inline cleanup or mark other objects as dead if needed. It
* can also be used to protect objects from use if they get assigned to a global variable and
* used across coroutines.
*/
virtual void onMarkDead() {}
/**
* Called from markLive() when an object is marked live. This is a companion to onMarkDead(). See
* the comments there.
*/
virtual void onMarkLive() {}
private:
bool dead_{};
};
/**
* This is basically a Lua smart pointer. The idea is that given a Lua object, if we want to
* guarantee that Lua won't destroy it, we need to reference it. This wraps the reference
* functionality. While a LuaRef owns an object it's guaranteed that Lua will not GC it.
* TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state.
*/
template <typename T> class LuaRef {
public:
/**
* Create an empty LuaRef.
*/
LuaRef() { reset(); }
/**
* Create a LuaRef from an object.
* @param object supplies the object. Generally this is the return value from a Object::create()
* call. The object must be at the top of the Lua stack.
* @param leave_on_stack supplies whether to leave the object on the stack or not when the ref
* is constructed.
*/
LuaRef(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
reset(object, leave_on_stack);
}
~LuaRef() { unref(); }
T* get() { return object_.first; }
/**
* Same as the LuaRef non-default constructor, but post-construction.
*/
void reset(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
unref();
if (leave_on_stack) {
lua_pushvalue(object.second, -1);
}
object_ = object;
ref_ = luaL_ref(object_.second, LUA_REGISTRYINDEX);
ASSERT(ref_ != LUA_REFNIL);
}
/**
* Return a LuaRef to its default/empty state.
*/
void reset() {
unref();
object_ = std::pair<T*, lua_State*>{};
ref_ = LUA_NOREF;
}
/**
* Push the referenced object back onto the stack.
*/
void pushStack() {
ASSERT(object_.first);
lua_rawgeti(object_.second, LUA_REGISTRYINDEX, ref_);
}
protected:
void unref() {
if (object_.second != nullptr) {
luaL_unref(object_.second, LUA_REGISTRYINDEX, ref_);
}
}
std::pair<T*, lua_State*> object_;
int ref_;
};
/**
* This is a variant of LuaRef which also marks an object as dead during destruction. This is
* useful if an object should not be used after the scope of the pcall() or resume().
* TODO(mattklein123): Add dedicated unit tests. This will require mocking a Lua state.
*/
template <typename T> class LuaDeathRef : public LuaRef<T> {
public:
~LuaDeathRef() { markDead(); }
void markDead() {
if (this->object_.first) {
this->object_.first->markDead();
}
}
void markLive() {
if (this->object_.first) {
this->object_.first->markLive();
}
}
void reset(const std::pair<T*, lua_State*>& object, bool leave_on_stack) {
markDead();
LuaRef<T>::reset(object, leave_on_stack);
}
void reset() {
markDead();
LuaRef<T>::reset();
}
};
}

41
envoy/main.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "envoy.h"
int callLua(lua_State* state, const char *func)
{
lua::LuaDeathRef<lua::StreamHandleWrapper> handle;
lua_getglobal(state, func);
if (lua_isfunction(state, -1)) {
handle.reset(lua::StreamHandleWrapper::create(state), true);
lua_call(state, 1, 0);
}
lua_pop(state, 1);
return 0;
}
int main(int argc, char *argv[])
{
if (argc < 1) {
printf("usage: envoy-apisix [lua file]\n");
return 0;
}
lua_State* state = luaL_newstate();
luaL_openlibs(state);
lua::BufferWrapper::registerType(state);
lua::HeaderMapIterator::registerType(state);
lua::HeaderMapWrapper::registerType(state);
lua::MetadataMapWrapper::registerType(state);
lua::MetadataMapIterator::registerType(state);
lua::StreamInfoWrapper::registerType(state);
lua::StreamHandleWrapper::registerType(state);
if (luaL_dofile(state, argv[1])) {
printf("script load error: %s\n", lua_tostring(state, -1));
return 0;
}
callLua(state, "envoy_on_request");
lua_close(state);
return 0;
}

4
envoy/packages.config Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="luajit.native" version="2.0.5" targetFramework="Native" />
</packages>

7
plugins/core.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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

View 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

View 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

View 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
View 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
View 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
View 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