• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

[SuProxy]Ngnix+Lua 实现SSH2,LDAP,ORACLE,SQLSERVER等TCP/IP协议分析,劫持,代理,会话及负载

开发技术 开发技术 1个月前 (10-27) 17次浏览

目录

目录
  • 目录
  • 前言
  • 介绍
  • 安装
    • 下载并拷贝
    • 使用LuaRocks安装
    • 运行测试
  • 使用简介
    • 处理器(processor)创建
    • 通道(channel)创建
    • 负载均衡
    • 会话信息和会话管理
    • Event Handling
      • NoReturnEvent 和 ReturnedEvent
      • addHandler 和 setHandler
      • Channel Event
        • OnConnectEvent
      • Processor Events
        • BeforeAuthEvent
        • OnAuthEvent
        • AuthSuccessEvent
        • AuthFailEvent
        • CommandEnteredEvent
        • CommandFinishedEvent
        • ContextUpdateEvent
      • Parser 及 Parser Events
        • 拦截,更改和停止转发
        • 读取数据与响应
  • Examples

前言

Nginx+Lua或Openresty已经是网关代理的流行架构,比如Openresty,KONG,API6等,现有大多数网关均在http协议上工作,虽然Nginx已经支持了TCP、IP stream,但由于对除http协议外,其他协议解析和分析模块的缺乏,使得其应用条件非常有限,除基本的代理功能外,其他一些在http下可以实现的功能,均无法在其他协议,比如在SSH2、TDS(SQL server)、TNS(ORACLE)、LDAP等协议上实现、劫持、修改、响应、负载及会话管理等能力。而对上述能力的支持,均需要建立在对各种协议的详尽解析之上。SuProxy正是在这个条件下出现,目标是建立一个基于nginx+lua的四层的协议分析与代理库,把nginx的稳定性能引入到除http外更广泛的领域,并作为进一步审计,安全攻防,身份管理,协议分析的基础组件。同时Suproxy也设计为一个能灵活的可扩展架构,第三方可以自行扩展协议解析,进一步丰富开源协议解析领域的公共知识。

SuProxy当前还在试验阶段,使用示例和文档将会陆续在博客园,CSDN博客,Git上发布,欢迎大家测试,并提出意见与建议,

项目Git地址:
https://github.com/yizhu2000/suproxy

文档地址(英文):
https://github.com/yizhu2000/suproxy/blob/main/readme.md

有问题可以在此留言,或直接在git上提交
https://github.com/yizhu2000/suproxy/issues

也可以通过邮件yizhu2000@hotmail.com进行沟通

介绍

SuProxy是事件驱动的Lua TCP/IP代理库,用于分析,拦截,负载平衡和会话管理。它提供以下API:

  • 身份验证拦截:在身份验证期间读取或更改凭据,或引入自定义身份验证器。
  • 命令输入拦截:监视,过滤或更改命令输入。
  • 命令输出拦截:监视,过滤或更改命令相应。
  • 上下文收集:获取网络、用户、客户端和服务器信息,例如IP、端口、版本等。
  • 会话管理:将会话存储在Redis中,提供会话列表,终止和搜索会话的API。
  • 协议解析器:解析和编码协议数据包。
  • 负载平衡:具有容错能力的负载均衡。

当前,支持的协议包括SSH2,ORACLE TNS,SQLSERVER TDS,LDAP。

SSH协议 SQL服务器 甲骨文 LDAP
获取用户名 Y [^ 1] Y [^ 2] Y Y [^ 6]
获取密码 Y [^ 1] Y [^ 2] N Y [^ 6]
变更使用者名称 Y Y Y [^ 4] Y
更改密码 Y Y N Y
第三方认证 Y Y Y [^ 5] Y
获取命令 Y Y Y Y [^ 7]
得到回复 Y Y N Y [^ 7]
变更指令 Y Y [^ 3] Y [^ 3] N
获取网络上下文 (IP,端口等)。 Y Y Y Y
获取客户端上下文 (客户端/服务器程序名称 和版本等) Y Y Y N
  • [^ 1]:仅密码认证
  • [^ 2]:获取SQL Server的用户名和密码会禁用SSL加密
  • [^ 3]:更改SQL命令未经过全面测试,某些更改(例如,更改选择命令到删除命令)可能无法成功
  • [^ 4]:不支持为oracle10更改用户名
  • [^ 5]:仅支持基于用户名的身份验证
  • [^ 6]:不支持SSL
  • [^ 7]:仅支持搜索请求和回复

SuProxy由纯粹的Lua编写,基于事件驱动模式,SuProxy库的使用和扩展很简单:启动侦听器通道并处理其事件。下面的示例显示如何启动SSH2侦听器并处理SSH连接的身份验证成功事件。

server {
    listen 22;
    content_by_lua_block {
        local ssh=require("suproxy.ssh2"):new()
        local channel=require("suproxy.channel"):new({{ip="192.168.1.135",port=22}},tds)
        channel:run()
		ssh.AuthSuccessEvent:addHandler(ssh,logAuth)
    }
}

SuProxy提供基本的负载平衡功能。以下示例显示了如何将多个upstream传递给通道。

package.loaded.my_SSHB=package.loaded.my_SSHB or
require ("suproxy.balancer.balancer"):new{
    {ip="127.0.0.1",port=2222,id="local",gid="linuxServer"},
    {ip="192.168.46.128",port=22,id="remote",gid="linuxServer"},
    {ip="192.168.1.121",port=22,id="UBUNTU14",gid="testServer"}
}
local channel=require("suproxy.channel"):new(package.loaded.my_SSHB,ssh)

SuProxy可以在内存或Redis中收集和维护会话上下文,以下是SuProxy在ssh连接中收集的信息

{
	"sid": "xxxxxxxxxxxx",
	"uid": "xxxx",
	"stype": "ssh2",
	"uptime": 1600831353.066,
	"ctime": 1600831353.066,
	"ctx": {
		"srvIP": "127.0.0.1",
		"client": "SSH-2.0-PuTTY_Release_0.74",
		"clientIP": "127.0.0.1",
		"clientPort": "56127",
        "username": "xxxx",
		"srvPort": 2222,
		"server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
	}
}

安装

下载并拷贝

从这里https://github.com/yizhu2000/suproxy/releases 下载tar或zip包,解压并复制到Openresty的lualib目录。

依赖的库包括

lua-resty-openssl> 0.6 https://github.com/fffonion/lua-resty-openssl

lua-resty-logger-socket(仅用于示例项目)https://github.com/cloudflare/lua-resty-logger-socket

使用LuaRocks安装

安装了luarocks环境的机器上可以使用下列命令安装本库及其依赖项

luarocks install suproxy

运行测试

luajit.exe ./suproxy/test.lua

使用简介

初始化Suproxy有4个步骤

  1. 创建处理器(processor),传入初始参数
  2. 创建通道(channel),传入upstream服务器列表及上一步创建的processor
  3. 处理处理器(processor)或通道(channel)的事件
  4. 启动通道(channel)

以下代码创建一个TNS通道并处理它的事件

--Create a TNS processor and passing server version to it
local tns=require("suproxy.tns"):new({oracleVersion=11})
--Create a channel with upstreams and TNS processor
local channel=require("suproxy.channel"):new({{ip="192.168.1.96",port=1521}},tns)
--Processing events
tns.AuthSuccessEvent:addHandler(tns,logAuth)
tns.CommandEnteredEvent:addHandler(tns,forbidden)
tns.CommandFinishedEvent:addHandler(tns,logCmd)
tns.BeforeAuthEvent:addHandler(tns,simpleUserPassOracle)
channel.OnConnectEvent:addHandler(channel,logConnect)
--start the channel
channel:run()

执行channel:run()之后,通道将在套接字上侦听。然后,事件将在不同的场合触发。用户程序应处理这些事件以完成其工作。通道和处理器都可以触发事件

处理器(processor)创建

处理器使用协议特定的解析器解析网络流。

--xxx can be ssh2,ldap,tns,tds for now
require("suproxy.xxx"):new(options)

使用上面的代码可以创建处理器(将xxx更改为处理器名称)。目前,SSH,TDS,TNS和LDAP处理器已准备就绪。处理器是否可以具有自定义的初始选项。例如,TNS处理器可以接受两个参数oracleVersion及swapPass,oracleVersion指定服务器主版本,而swapPass则告诉处理器是否需要在登录时更改用户密码。有关详细信息,请参阅每个处理器的文档

通道(channel)创建

通道维护客户端和服务器之间的连接,从套接字读取数据并将手数据发送到不同的协议处理器以进行进一步处理,通道还负责将数据发送到上游服务器。

require("suproxy.channel"):new({{ip="192.168.1.97",port=1521}},tns)

上面的代码创建了一个具有一个后端服务器(upstream)和TNS协议处理器的通道。如果将多个上游传递到通道,则[默认balancer]将从这些上游中随机选择。请注意,只有在调用channel.run()之后,通道才会启动。

Channel.c2pSend将数据放入客户端代理套接字

Channel.p2sSend将数据发送到代理服务器套接字

Channel.c2pRead从客户端代理套接字读取数据

Channel.p2cRead从代理服务器套接字读取数据

如何使用它们,请参阅[阅读和响应]

channel.new方法可以接受额外的选项来设置套接字超时

options.c2pConnTimeout -- client-proxy connect timeout default 10000
options.c2pSendTimeout -- client-proxy send timeout default 10000
options.c2pReadTimeout -- client-proxy read timeout default 3600000
options.p2sConnTimeout --proxy-server connect timeout default 10000
options.p2sSendTimeout --proxy-server send timeout default 10000
options.p2sReadTimeout --proxy-server read timeout 3600000

require("suproxy.channel"):new(upstream,processor,options)

负载均衡

SuProxy提供基本的负载均衡功能。多个上游可以发送到通道。默认负载平衡器将从给定的上游服务器中随机选择一个。如果一个上游发生故障,负载均衡将暂时将该上游暂停一会儿。创建负载均衡:调用suproxy.balancer.balancer.new 方法,传入upstream list和suspendSpan(可选,默认为30秒,然后将负载均衡传递给通道的构造函数,如下所示:

--here use "package.loaded" to ensure balancer only init once across multiple request, cause balancer will maintain the state of those upstreams.
package.loaded.my_SSHB=package.loaded.my_SSHB or
require ("suproxy.balancer.balancer"):new({
    {ip="127.0.0.1",port=2222,id="local",gid="linuxServer"},
    {ip="192.168.46.128",port=22,id="remote",gid="linuxServer"},
    {ip="192.168.1.121",port=22,id="UBUNTU14",gid="testServer"}
},10)
local channel=require("suproxy.channel"):new(package.loaded.my_SSHB,ssh)

每个上游必须包括IP,端口。ID和GID是可选字段,ID代表此上游服务器的标识符,GID代表该服务器所属的组。事件处理器可以获取这两个参数

通过实现getBestblame方法,可以轻松编写自己的平衡器。有关更多信息,请参考balancer.balancer.lua。

会话信息和会话管理

SuProxy维护会话上下文包括:服务器IP,服务器端口,客户端IP,客户端端口,连接时间,用户名以及某些处理器特定的属性,例如客户端版本或连接字符串。以下是SSH2处理器的会话上下文:

 {
     "srvIP": "127.0.0.1",
     "client": "SSH-2.0-PuTTY_Release_0.74",
     "clientIP": "127.0.0.1",
     "clientPort": "56127",
     "username": "xxxx",
     "srvPort": 2222,
     "connTime":1600831353.066
     "server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}

这些信息将被传递到处理器的事件处理程序。

默认情况下,会话上下文存储在本地,因此不会在请求之间共享。提供了Redis会话管理器以支持Redis存储。Redis会话管理器还提供了简单的会话管理操作,例如获取活动会话列表和终止会话。更改默认会话管理器的方法如下:

local sessionManager= require ("suproxy.session.sessionManager"):new{ip="127.0.0.1",port=6379,expire=-1,extend=false,timeout=2000}
local channel=require("suproxy.channel"):new(package.loaded.my_OracleB,tns,{sessionMan=sessionManager})

其中IP端口是Redis服务器的地址。Expire设置会话默认值3600的默认过期时间跨度(以秒为单位),-1表示永不过期。extend指示从客户端发送新数据包后是否延长会话租约,timeout指示Redis超时(以毫秒为单位),默认为5000。

LUA代码example.session.lua显示了如何使用浏览器管理会话。将以下行添加到nginx config进行测试。

 server {
    listen       80;
    server_name  localhost;
    
    ...

    location /suproxy/manage{
        content_by_lua_file  lualib/suproxy/example/session.lua;
    }
}

http://localhost/suproxy/manage/session/all 列出所有会话

http://localhost/suproxy/manage/session/clear 杀死所有会话

http://localhost/suproxy/manage/session/kill?sid=xxxx 通过sessionID终止会话

http://localhost/suproxy/manage/session/kill?uid=xxxx 通过uid终止会话

http://localhost/suproxy/manage/session/get?sid=xxxx 通过sessionID获取会话

http://localhost/suproxy/manage/session/get?uid=xxxx 通过uid杀死会话

Event Handling

通道和处理器都会触发事件,为事件添加处理程序的方法如下

event:addHandler(context,handler)

context表示程序将在哪个对象上执行,处理程序可以访问上下文对象中定义的参数(self对象),handler是处理事件的函数。

典型的处理程序如下所示

function handler(context,eventSource,[other event params])
    -- handler logic here
end

处理程序至少会接收到两个参数:context和eventSource。context是由addHandler方法定义的执行上下文,eventSource是触发此事件的对象,大多数情况下是处理器本身。

NoReturnEvent 和 ReturnedEvent

事件有两种:NoReturnEventReturnedEventReturnedEvents的处理程序可以返回值,而NoReturn Event的处理程序则不能。NoReturnEvent可以有多个处理程序,但Returned Event可以只有一个。将处理程序添加到已经具有处理程序的Returned Event中将覆盖旧的处理程序。在相同情况下向NoReturnEvent添加更多处理程序将形成一个处理程序链,该链中的每个处理程序将一个接一个地执行。

addHandler 和 setHandler

对于NoReturnEvent,event:addHandler将新处理程序追加到处理程序链,setHandler方法清除该链并将处理程序追加到头部。调用setHandler方法可确保该事件有且仅有一个处理程序。

Channel Event

OnConnectEvent

此事件在刚建立连接时触发。handler参数为连接相关信息:

{
    clientIP, --client ip address
    clientPort, --client port if tcp is used
    srvIP, --upstream server ip
    srvPort=serverPort --upstream server port if tcp is used
}

这是处理通道的OnConnectEvent的示例,该事件将连接信息写入日志文件:

local function logConnect(context,source,connInfo)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."t" ,
        connInfo.clientIP..":"..connInfo.clientPort.."t",
        "connect to ",
        connInfo.srvIP..":"..connInfo.srvPort.."rn"
    }
    print(table.concat(rs))
end

结果如下

2020.09.24 18:28:03	127.0.0.1:60486	connect to 127.0.0.1:2222

Processor Events

不同的处理器可能具备不同的事件,但是所有处理器都实现以下的事件

  • BeforeAuthEvent
  • AuthSuccessEvent
  • AuthFailEvent
  • CommandEnteredEvent(对于ssh2Processor,这由commandCollector而非processor触发)
  • CommandFinishedEvent(对于ssh2Processor,这由commandCollector而非processor触发)
  • ContextUpdateEvent

每个处理器事件将不同的参数和一个会话上下文(session context)对象传递给其处理程序。会话上下文对象包含该会话中处理器收集的所有信息。以下是SSH2.0处理器中的典型会话上下文:

{
    "srvIP": "127.0.0.1",
    "client": "SSH-2.0-PuTTY_Release_0.74",
    "clientIP": "127.0.0.1",
    "clientPort": "56127",
    "username": "root"
    "srvPort": 2222,
    "server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}

会话上下文信息随处理器类型和连接阶段的不同而有所不同。某些处理器没有“client”字段或“server”字段(如LDAP),有些处理器有附加信息,例如连接字符串(比如TNS)。另外,在连接的某个阶段,用户名尚未传输,因此在此时,username不会出现在上下文信息中。但是,上下文至少包含srvIP,srvPort,clientIP,clientPort,connTime,并且username将在用户身份验证后立即添加到上下文中。

BeforeAuthEvent

此事件在用户身份验证之前触发,这是交换用户凭据的绝佳时机。传递给其处理程序的参数是认证凭证会话上下文对象,凭证对象定义如下(对于TNS处理器,除非满足某些版本条件,否则不提供密码字段)。

{
    username,--string, username entered by user
    password --string, password entered by user
}

此事件的事件处理程序可以返回新的凭据,如果返回了新的凭据,则新的凭据将转发到上游,而不是旧的凭据。

下面是将原始凭证传递到远程服务器并通过OAUTH2.0从中获取新凭证的示例(此实例中用到的OAUTH验证模块ssoProcessors尚在实验阶段,用户完全可以用成熟的OAUTH2.0客户端替代)

local function oauth(context,source,cred,session)
 --show how to get password with oauth protocal,using username as code, an app should be add and a password attributes should be add
    local param={
        ssoProtocal="OAUTH",
        validate_code_url="http://xxxxxxxxxxxx/oauth2/token",
        profile_url="http://xxxxxxxxxxxxx/oauth2/userinfo",
        client_secret="xxxxxxxxxxx",
    }
    local authenticator=authFactory.getAuthenticator(param)
    local result=authenticator:valiate({username=cred.username,password=cred.password})
    local newCred={}
    if result.status==ssoProcessors.CHECK_STATUS.SUCCESS then
        --confirm oauth password attributes correctly configged
        newCred.username=result.accountData.user
        newCred.password=result.accountData.attributes.password
    end
    if not newCred then return nil,"can not get cred from remote server" end
    return newCred
end

OnAuthEvent

该事件在认证通过时触发。这是交换用户凭证或引入自定义身份验证的绝佳时机。传递给其处理程序的参数是凭据(用户名和密码)和会话上下文对象,凭据对象定义如下(对于TNS处理器,除特定版本外无密码字段)。

BeforeAuthEvent和OnAuthEvent之间的区别在于:BeforeAuthEvent在凭据的任何部分(如用户名)传输到服务器之前触发,而OnAuthEvent在身份验证真正发生并将密码传输到服务器时触发。对于某些协议(例如LDAP和TDS),这两个时机是相同的;对于处理器(例如SSH2或TNS),用户名是在密码之前传输的。如果需要为这些通道更改用户名,则必须在BeforeAuthEvent处理程序中准备好新的用户名。

此事件的事件处理程序可以返回验证结果,错误消息和新凭据。使用新凭据字段在LDAP和TDS处理器中可以替代BeforeAuthEvent

本示例说明如何引入第三方认证。

local function authenticator(context,source,credential,session)
    --OAUTH or other auth protocol should be used to swap real credential in real world
    local result=credential.username=="test" and credential.password=="test"
	if result then
		return result
    else
		local message="login with "..credential.username.." failed"
		return result,message       
    end
end
ssh.OnAuthEvent:addHandler(tns,authenticator)

AuthSuccessEvent

用户身份验证成功完成后触发此事件,这是写入日志文件的正确时机。传递给其处理程序的参数是用户名会话上下文对象

下面是将登录操作写入日志文件的示例。

local function logAuth(context,source,username,session)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."t" ,
        session.clientIP..":"..session.clientPort.."t",
        username.."t",
        "login with ",
        (session and session.client) and session.client or "unknown client",
        (session and session.clientVersion) and session.clientVersion or ""
    }
    print(table.concat(rs))
end

结果如下

2020.09.24 19:03:40	127.0.0.1:60844	root	login with SSH-2.0-PuTTY_Release_0.74

AuthFailEvent

用户身份验证失败时触发此事件,这是写入日志文件的正确时机。传递给其处理程序的参数是failInfo会话上下文对象

failInfo定义如下

{
	username, --string, failed username 
	message --string, fail message passed
}

下面是将登录失败操作写入日志文件的示例。

local function logAuthFail(context,source,failInfo,session)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."t" ,
        session.clientIP..":"..session.clientPort.."t",
        failInfo.username.."t",
        "login fail, fail message:  ",
        failInfo.message
    }
    print(table.concat(rs))
end

结果如下

2020.09.24 19:27:48	127.0.0.1:61020	zhuyi	login fail, fail message: wrong password

CommandEnteredEvent

该事件将在命令发送到服务器之前触发。如果是数据库或LDAP协议,则命令是SQL命令或LDAP请求;如果是SSH连接,则命令是shell命令。这是执行命令检查或更改某些命令的最佳时机。

传递给其处理程序的参数是命令字符串会话上下文对象。处理程序可能会返回新命令错误消息。如果返回新命令,则新命令将取代原始命令被发送到服务器。如果返回错误消息,则该命令将不会执行,处理器将通知客户端错误消息。(通知客户端的方式随处理器而异,有些可能会提示消息,有些可能没有)以下是检查命令并禁止命令中使用某些关键字的示例。

local function forbidden(context,source,command,session)
    if command:match("forbidden")then
        print("forbidden command triggered")
        return nil,{message=command.." is a forbidden command"}
    end
    return command
end

以下是sql执行对sqlserver的影响

[SuProxy]Ngnix+Lua 实现SSH2,LDAP,ORACLE,SQLSERVER等TCP/IP协议分析,劫持,代理,会话及负载

以下是putty的响应

[SuProxy]Ngnix+Lua 实现SSH2,LDAP,ORACLE,SQLSERVER等TCP/IP协议分析,劫持,代理,会话及负载

CommandFinishedEvent

从服务器回复命令时触发此事件,如果是数据库/ LDAP协议,则命令可能是SQL/ LDAP请求,如果是SSH连接,则命令可能是shell命令。这是编写命令及其在文件中回复的最佳时机。

传递给其处理程序的参数是命令字符串回复字符串上下文对象。某些处理器中可能没有答复字符串(在当前版本中未收集TNS(ORACLE)答复),以下是记录命令和答复的示例

local function logCmd(context,source,command,reply,session)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."t" ,
        session.clientIP..":"..session.clientPort.."t",
        username.."t",
        command.."rn"
    }
    if reply then
        rs[#rs+1]="------------------reply--------------------rn"
        rs[#rs+1]=reply
        rs[#rs+1]="rn----------------reply end------------------rn")
    end
    print(table.concat(rs))
end

日志文件如下

2020.09.24 19:28:43	127.0.0.1:61020	root	ls
------------------reply--------------------

libc6_2.31-0ubuntu8+lp1871129~1_amd64.deb

----------------reply end------------------

ContextUpdateEvent

处理器在会话中收集新信息时触发此事件。听此事件,程序可以获取上下文信息并构造自己的会话状态。以下示例显示如何使用resty.redis库将上下文记录到Redis中

function contextHandler(self,source,ctx) 
    local red = require ("resty.redis"):new()
    red:set_timeouts(10000, 10000, 10000) 
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR,"failed to connect: ", err)
        return
    end
    red:set(ctx.clientIP..ctx.clientPort,cjson.encode(ctx))
end

Parser 及 Parser Events

每个处理器都有2个解析器c2pParser和s2pParser。c2pParser负责解析客户端与代理间的通讯,s2pParser负责解析服务端和代理间的通讯。当这些解析器成功解析一个数据包时,将触发一个解析器事件。用户程序可以侦听这些事件以进行拦截,更改发送到服务器的数据包或停止转发。

传递给数据包事件处理程序的参数是解析器构造的数据包,其中包含协议特定的信息。处理程序可以获取或设置其字段以获取或更改发送到服务器的内容。例如,可以像这样更改TNS连接中的版本。

function _M:ConnectHandler(src,packet)
    packet:setTnsVersion(314)
    packet:pack()
end

拦截,更改和停止转发

设置packet的allBytes属性可以修改包的内容,这意味着计算字节长度,重新生成数据包头和将发送到服务器的字节流。每次更改字段时,都必须调用packet.pack()。字节流可以通过packet.allBytes访问。

如果我们不希望我们的数据包再转发。(例如,处理程序已作出手动响应),只需将packet.allBytes字段设置为“”即可。

p.allBytes=""
p:pack()

读取数据与响应

处理程序可以使用Channel提供的4种方法来读取并响应客户端或服务器:

Channel.c2pSend将数据发送到c2p套接字

Channel.p2sSend把数据P2S插座

Channel.c2pRead从c2p套接字读取数据

Channel.p2cRead读取P2S套接字数据

这四个方法包装了lua stream socket。参数和返回值与socket.receive和send相同,xxxSendMehod采用一个参数:要发送的字节。接收方法的输入参数是读取的长度或模式。

以下是拦截LDAP SearchRequest和构造响应的示例。

local function ldap_SearchRequestHandler(context,src,p)
    if context.command:match("pleasechangeme") then
        local packets=require("suproxy.ldap.ldapPackets")
        local response=packets.SearchResultEntry:new()
        local done=packets.SearchResultDone:new()
        response.objectName="cn=admin,dc=www,dc=test,dc=com"
        response.messageId=p.messageId
        response.attributes={
            {attrType="objectClass",values={"posixGroup","top"}},
            {attrType="cn",values={"group"}},
            {attrType="memberUid",values={"haha","zhuyi","joeyzhu"}},
            {attrType="gidNumber",values={"44789"}},
            {attrType="description",values={"group"}}
        }
        done.resultCode=packets.ResultCode.success
        done.messageId=p.messageId
        response:pack() done:pack()
        context.channel:c2pSend(response.allBytes..done.allBytes)
        --stop forwarding
        p.allBytes=""
    end
end

local ldap=require("suproxy.ldap"):new()
ldap.c2pParser.events.SearchRequest:addHandler(ldap,ldap_SearchRequestHandler)

Examples

example.gateway演示为TNS,TDS,SSH2,LDAP协议实现了一个简单的网关。要测试此演示,请修改nginx配置,将以下部分添加到您的配置文件中。确保commands.log文件路径有效。

stream {
	init_by_lua_file lualib/suproxy/init.lua;
    lua_code_cache off;
    #mock logserver if you do not have one
    server {
		listen 12080;
		content_by_lua_block {
            ngx.log(ngx.DEBUG,"logserver Triggerred")
            local reqsock, err = ngx.req.socket(true)
            reqsock:settimeout(100)
            while(not err) do
                local command,err=reqsock:receive()
                if(err) then ngx.exit(0) end
                local f = assert(io.open("/data/logs/commands.log", "a"))
                if(command) then
                    f:write(command .. "n")
                    f:close()
                end
            end
        }
	}
	#listen on ports
    server {
        listen 389;
		listen 1521;
		listen 22;
		listen 1433;
        content_by_lua_file lualib/suproxy/example/gateway.lua;
    }
}
#Session manager interfaces. if you want to view and manage your session 
#over http, this should be set.
http {
    include       mime.types;
	lua_code_cache off;
    server {
        listen       80;
        server_name  localhost;
		default_type text/html;
		location /suproxy/manage{
			content_by_lua_file  lualib/suproxy/example/session.lua;
		}
}

关于会话管理的接口,请参阅会话管理。


喜欢 (0)