原创

手把手教你给ngrok添加简单的用户验证

需求

有的朋友在工作、学习时需要在外网访问自己内网(微信开发、小程序开发、web演示等),然而没有固定IP,只能用自己的方式来解决了。
听到这个需求,我的第一反应就是内网穿透,然而市面上可以做内网穿透的程序很多,花生壳、N2N、Ngrok等等!

由于N2N年久失修,对Mac用户也不友好,花生壳又是那尿性(你懂的),推荐使用ngrok将端口暴露到外网服务器,直接用客户端就可以将流量转接到本地了(小米球也是基于Ngrok开源程序修改)。

首先我简单介绍一下ngrok:
ngrok
is a reverse proxy that creates a secure tunnel from a public endpoint to a locally running web service.
ngrok captures and analyzes all traffic over the tunnel for later inspection and replay.
简单来说,ngrok最主要的功能就是将本地的端口映射到外网的某个端口,以方便地完成很多需要外网访问却不希望deploy在服务器上的功能,比如现场demo、临时分享等。

目前ngrok有两种版本,一个是开源但是几近停止维护的ngrok 1.x,还有一个是闭源商用的ngrok 2.x。
这里我们选用1.x,方便self hosting部署以及自定义功能比如本文的标题:手把手教你给ngrok添加简单的用户验证。

我们可以根据Self Hosting Doc的文档完成最初的部署。
然而部署完我们会发现,原始版本的ngrok的server是没有身份验证功能的,也就是说任何人都可以通过ngrok 1.x的client使用我们的服务器,毕竟是私人的服务器,所以我们就希望给ngrok加上身份验证的功能。

分析

下面我们开始分析相关的ngrok的源代码。
src/ngrok/server/control.go#func NewControl

func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
    ...
    failAuth := func(e error) {
        _ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()})
        ctlConn.Close()
    }
    // register the clientid
    c.id = authMsg.ClientId
    if c.id == "" {
        // it's a new session, assign an ID
        if c.id, err = util.SecureRandId(16); err != nil {
            failAuth(err)
            return
        }
    }
    ...
}

我们发现这一段代码实现的正是某种访问控制的功能,这个authMsg里说不定包含了一些我们需要的验证信息,下面我们查看authMsg的定义。
src/ngrok/msg/msg.go#Auth struct

// When a client opens a new control channel to the server
// it must start by sending an Auth message.
type Auth struct {
    Version   string // protocol version
    MmVersion string // major/minor software version (informational only)
    User      string
    Password  string
    OS        string
    Arch      string
    ClientId  string // empty for new sessions
}

这里我们看到了User和Password,下意识觉得这里有戏,我们需要查证这两个值的来源。
我们可以在client中寻找对应的参数设定位置。

<过程略……>

最终我们会发现,Auth struct中的User对应的正是client命令行参数中的-authtoken
因此,我们可以在-authtoken中包含我们的验证信息。

实现

我们采用-authtoken='username:password'的方式来进行验证。具体实现的话只需要在src/ngrok/server/control.go#func NewControl中添加对Auth struct中的User验证的方法就行。

我的实现方案是,可以手动创建并用-secretPath指定(默认在/etc/ngrok-secrets)存储用户名和密码的文件的位置,格式为:

# username      password
  example-user  example-password

以后使用时只需要加上-authtoken参数即可,如:
$ ngrok -authtoken="username:password" -proto=tcp 8888

总结

我这里只是添加了很简单的一种身份验证方式,大家其实可以根据自己的需求与其他验证系统结合起来!

正文到此结束
本文目录