分享
  1. 首页
  2. 文章

Linux搭建Ngrok服务器及身份认证实现内网穿透

双流小二 · · 1605 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1. 前言

前段时间因为测试一些东西,嫌上传程序到服务器麻烦,就想在腾讯云上搭建一个Ngrok服务器用于内网穿透,这样就可以在外网访问本地Web网站。
更多关于ngrok可以查看百度百科 ngrok

2. 搭建步骤

2.1 安装go get工具

yum install mercurial git bzr subversion golang

2.2 git版本需要在1.7.9.5以上,如果不符合条件需要将git版本升级。

yum --disablerepo=base,updates --enablerepo=rpmforge-extras update git

2.3 获取ngrok源码

mkdir /root/Ngrok
cd /root/Ngrok
git clone https://github.com/tutumcloud/ngrok.git ngrok
export GOPATH=~/ngrok

2.4 生成自签名证书

cd ngrok
NGROK_DOMAIN="ngrok.testdomain.com"
openssl genrsa -out base.key 2048
openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pem
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csr
openssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt
#在ngrok目录下就会新生成6个文件
ls -lt 
总用量 56
-rw-r--r-- 1 root root 973 3月 23 11:23 server.crt
-rw-r--r-- 1 root root 17 3月 23 11:23 base.srl
-rw-r--r-- 1 root root 891 3月 23 11:23 server.csr
-rw-r--r-- 1 root root 1675 3月 23 11:23 server.key
-rw-r--r-- 1 root root 1115 3月 23 11:23 base.pem
-rw-r--r-- 1 root root 1679 3月 23 11:23 base.key

2.5 替换文件

Ngrok通过bindata将ngrok源码目录下的assets目录(资源文件)打包到可执行文件(ngrokd和ngrok)中去,assets/client/tls和assets/server/tls下分别存放着用于ngrok和ngrokd的默认证书文件,我们需要将它们替换成我们自己生成的

cp base.pem assets/client/tls/ngrokroot.crt
cp server.crt assets/server/tls/snakeoil.crt
cp server.key assets/server/tls/snakeoil.key

2.6 编译Linux服务端和客户端

make release-server release-client

2.7 编译window版本客户端

GOOS=windows GOARCH=amd64 make release-client
## 会生成 /root/Ngrok/ngrok/bin/windows_amd64/ngrok.exe

2.8 设置域名解析

在自己的域名控制台上添加两条A记录:ngrok.testdomain.com和*ngrok.testdomain.com,指向所在的Ngrok服务器ip

2.9 启动服务端

nohup ./bin/ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain=ngrok.testdomain.com -httpAddr=:80 -httpsAddr=:443 -tunnelAddr=:4443 &

2.10 使用windows客户端

拷出./ngrok/bin/windows_amd64/ngrok.exe到windows下
同目录新建ngrok.cfg文件

server_addr: "ngrok.testdomain.com:4443"
trust_host_root_certs: false

启动客户端

ngrok.exe -config=ngrok.cfg -subdomain kian 8000

3. 添加身份认证

当前只要知道地址,拥有客户端都可以使用,所以我们要添加一个简单的认证。

3.1.1 修改源码ngrok/src/ngrok/server/control.go为

package server
import (
 "fmt"
 "io"
 "ngrok/conn"
 "ngrok/msg"
 "ngrok/util"
 "ngrok/version"
 "runtime/debug"
 "strings"
 "time"
 "os"
 "bufio"
)
const (
 pingTimeoutInterval = 30 * time.Second
 connReapInterval = 10 * time.Second
 controlWriteTimeout = 10 * time.Second
 proxyStaleDuration = 60 * time.Second
 proxyMaxPoolSize = 10
)
type Control struct {
 // auth message
 auth *msg.Auth
 // actual connection
 conn conn.Conn
 // put a message in this channel to send it over
 // conn to the client
 out chan (msg.Message)
 // read from this channel to get the next message sent
 // to us over conn by the client
 in chan (msg.Message)
 // the last time we received a ping from the client - for heartbeats
 lastPing time.Time
 // all of the tunnels this control connection handles
 tunnels []*Tunnel
 // proxy connections
 proxies chan conn.Conn
 // identifier
 id string
 // synchronizer for controlled shutdown of writer()
 writerShutdown *util.Shutdown
 // synchronizer for controlled shutdown of reader()
 readerShutdown *util.Shutdown
 // synchronizer for controlled shutdown of manager()
 managerShutdown *util.Shutdown
 // synchronizer for controller shutdown of entire Control
 shutdown *util.Shutdown
}
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
 var err error
 // create the object
 c := &Control{
 auth: authMsg,
 conn: ctlConn,
 out: make(chan msg.Message),
 in: make(chan msg.Message),
 proxies: make(chan conn.Conn, 10),
 lastPing: time.Now(),
 writerShutdown: util.NewShutdown(),
 readerShutdown: util.NewShutdown(),
 managerShutdown: util.NewShutdown(),
 shutdown: util.NewShutdown(),
 }
 failAuth := func(e error) {
 _ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()})
 ctlConn.Close()
 }
 readLine := func(token string, filename string) (bool, error) {
 if token == "" {
 return false, nil;
 }
 f, err := os.Open(filename)
 if err != nil {
 return false, err
 }
 buf := bufio.NewReader(f)
 for {
 line, err := buf.ReadString('\n')
 line = strings.TrimSpace(line)
 if line == token {
 return true, nil
 }
 if err != nil {
 if err == io.EOF {
 return false, nil
 }
 return false, err
 }
 }
 return false, nil
 }
 // 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
 }
 }
 // set logging prefix
 ctlConn.SetType("ctl")
 ctlConn.AddLogPrefix(c.id)
 if authMsg.Version != version.Proto {
 failAuth(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), authMsg.Version))
 return
 }
 authd, err := readLine(authMsg.User, "authtokens.txt")
 if authd != true {
 failAuth(fmt.Errorf("authtoken %s invalid", "is"));
 return
 }
 // register the control
 if replaced := controlRegistry.Add(c.id, c); replaced != nil {
 replaced.shutdown.WaitComplete()
 }
 // start the writer first so that the following messages get sent
 go c.writer()
 // Respond to authentication
 c.out <- &msg.AuthResp{
 Version: version.Proto,
 MmVersion: version.MajorMinor(),
 ClientId: c.id,
 }
 // As a performance optimization, ask for a proxy connection up front
 c.out <- &msg.ReqProxy{}
 // manage the connection
 go c.manager()
 go c.reader()
 go c.stopper()
}
// Register a new tunnel on this control connection
func (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) {
 for _, proto := range strings.Split(rawTunnelReq.Protocol, "+") {
 tunnelReq := *rawTunnelReq
 tunnelReq.Protocol = proto
 c.conn.Debug("Registering new tunnel")
 t, err := NewTunnel(&tunnelReq, c)
 if err != nil {
 c.out <- &msg.NewTunnel{Error: err.Error()}
 if len(c.tunnels) == 0 {
 c.shutdown.Begin()
 }
 // we're done
 return
 }
 // add it to the list of tunnels
 c.tunnels = append(c.tunnels, t)
 // acknowledge success
 c.out <- &msg.NewTunnel{
 Url: t.url,
 Protocol: proto,
 ReqId: rawTunnelReq.ReqId,
 }
 rawTunnelReq.Hostname = strings.Replace(t.url, proto + "://", "", 1)
 }
}
func (c *Control) manager() {
 // don't crash on panics
 defer func() {
 if err := recover(); err != nil {
 c.conn.Info("Control::manager failed with error %v: %s", err, debug.Stack())
 }
 }()
 // kill everything if the control manager stops
 defer c.shutdown.Begin()
 // notify that manager() has shutdown
 defer c.managerShutdown.Complete()
 // reaping timer for detecting heartbeat failure
 reap := time.NewTicker(connReapInterval)
 defer reap.Stop()
 for {
 select {
 case <-reap.C:
 if time.Since(c.lastPing) > pingTimeoutInterval {
 c.conn.Info("Lost heartbeat")
 c.shutdown.Begin()
 }
 case mRaw, ok := <-c.in:
 // c.in closes to indicate shutdown
 if !ok {
 return
 }
 switch m := mRaw.(type) {
 case *msg.ReqTunnel:
 c.registerTunnel(m)
 case *msg.Ping:
 c.lastPing = time.Now()
 c.out <- &msg.Pong{}
 }
 }
 }
}
func (c *Control) writer() {
 defer func() {
 if err := recover(); err != nil {
 c.conn.Info("Control::writer failed with error %v: %s", err, debug.Stack())
 }
 }()
 // kill everything if the writer() stops
 defer c.shutdown.Begin()
 // notify that we've flushed all messages
 defer c.writerShutdown.Complete()
 // write messages to the control channel
 for m := range c.out {
 c.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout))
 if err := msg.WriteMsg(c.conn, m); err != nil {
 panic(err)
 }
 }
}
func (c *Control) reader() {
 defer func() {
 if err := recover(); err != nil {
 c.conn.Warn("Control::reader failed with error %v: %s", err, debug.Stack())
 }
 }()
 // kill everything if the reader stops
 defer c.shutdown.Begin()
 // notify that we're done
 defer c.readerShutdown.Complete()
 // read messages from the control channel
 for {
 if msg, err := msg.ReadMsg(c.conn); err != nil {
 if err == io.EOF {
 c.conn.Info("EOF")
 return
 } else {
 panic(err)
 }
 } else {
 // this can also panic during shutdown
 c.in <- msg
 }
 }
}
func (c *Control) stopper() {
 defer func() {
 if r := recover(); r != nil {
 c.conn.Error("Failed to shut down control: %v", r)
 }
 }()
 // wait until we're instructed to shutdown
 c.shutdown.WaitBegin()
 // remove ourself from the control registry
 controlRegistry.Del(c.id)
 // shutdown manager() so that we have no more work to do
 close(c.in)
 c.managerShutdown.WaitComplete()
 // shutdown writer()
 close(c.out)
 c.writerShutdown.WaitComplete()
 // close connection fully
 c.conn.Close()
 // shutdown all of the tunnels
 for _, t := range c.tunnels {
 t.Shutdown()
 }
 // shutdown all of the proxy connections
 close(c.proxies)
 for p := range c.proxies {
 p.Close()
 }
 c.shutdown.Complete()
 c.conn.Info("Shutdown complete")
}
func (c *Control) RegisterProxy(conn conn.Conn) {
 conn.AddLogPrefix(c.id)
 conn.SetDeadline(time.Now().Add(proxyStaleDuration))
 select {
 case c.proxies <- conn:
 conn.Info("Registered")
 default:
 conn.Info("Proxies buffer is full, discarding.")
 conn.Close()
 }
}
// Remove a proxy connection from the pool and return it
// If not proxy connections are in the pool, request one
// and wait until it is available
// Returns an error if we couldn't get a proxy because it took too long
// or the tunnel is closing
func (c *Control) GetProxy() (proxyConn conn.Conn, err error) {
 var ok bool
 // get a proxy connection from the pool
 select {
 case proxyConn, ok = <-c.proxies:
 if !ok {
 err = fmt.Errorf("No proxy connections available, control is closing")
 return
 }
 default:
 // no proxy available in the pool, ask for one over the control channel
 c.conn.Debug("No proxy in pool, requesting proxy from control . . .")
 if err = util.PanicToError(func() {
 c.out <- &msg.ReqProxy{}
 }); err != nil {
 return
 }
 select {
 case proxyConn, ok = <-c.proxies:
 if !ok {
 err = fmt.Errorf("No proxy connections available, control is closing")
 return
 }
 case <-time.After(pingTimeoutInterval):
 err = fmt.Errorf("Timeout trying to get proxy connection")
 return
 }
 }
 return
}
// Called when this control is replaced by another control
// this can happen if the network drops out and the client reconnects
// before the old tunnel has lost its heartbeat
func (c *Control) Replaced(replacement *Control) {
 c.conn.Info("Replaced by control: %s", replacement.conn.Id())
 // set the control id to empty string so that when stopper()
 // calls registry.Del it won't delete the replacement
 c.id = ""
 // tell the old one to shutdown
 c.shutdown.Begin()
}

大概修改就是从本地authtokens.txt中获取字符串和客户端传来的进行比对,不熟悉golang。

3.2 在bin中创建authtokens.txt

username:password

3.3 重新编译服务端和客户端

3.4 修改客户端配置文件ngrok.cfg

server_addr: ngrok.testdomain.com:4443
trust_host_root_certs: true
auth_token: username:password
tunnels:
 kian:
 subdomain: kian
 proto:
 http: "80"
 https: "8080"

启动客户端

.\ngrok.exe -log ngrok.log -config ngrok.cfg start kian
Tunnel Status online
Version 1.7/1.7
Forwarding http://kian.ngrok.testdomain.com -> 127.0.0.1:80
Forwarding https://kian.ngrok.testdomain.com -> 127.0.0.1:8080
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

4. 参考资料


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:双流小二

查看原文:Linux搭建Ngrok服务器及身份认证实现内网穿透

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1605 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏