笔记 ngrok 内网穿透及其身份认证 authtoken 配置

0. 系统环境

  • 腾讯云(带公网 IP)
  • centos 7.6

参考网址: https://www.vediotalk.com/archives/336
参考网址: https://studygolang.com/articles/15122
参考网址: https://github.com/prikevs/ngrok/commit/aa9b88d4e070069db6d8f88aa82526bcbcd1d0b6

1. 准备安装包

  • git-2.9.0.tar.gz
  • go1.9.2.linux-amd64.tar.gz
  • ngrok: git clone https://github.com/inconshreveable/ngrok.git

go 和 git 的下载: 链接:https://pan.baidu.com/s/1o23GK9VlNRXhg_IDJyGofQ
提取码:wbyj
复制这段内容后打开百度网盘手机App,操作更方便哦

go 语言不要太新版的,也不要太旧版的,容易出莫名其妙的问题
git 版本不清楚,尽量是 2.0 以上的吧

2. 域名泛解析

笔记 ngrok 内网穿透及其身份认证 authtoken 配置_第1张图片

3. go 环境搭建

下载对应系统的安装包 go1.9.2.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.9.2.linux-amd64.tar.gz
root 下执行
mkdir $HOME/go
echo 'export GOROOT=/usr/local/go'>> ~/.bashrc
echo 'export GOPATH=$HOME/go'>> ~/.bashrc
echo 'export PATH=$PATH:$GOROOT/bin'>> ~/.bashrc
source $HOME/.bashrc

4. git 安装

下载安装包
tar -zxvf git-2.9.0.tar.gz
cd git-2.9.0
./configure --prefix=/usr/local/git
make && make install
export PATH="/usr/local/git/bin:$PATH" >> ~/.bashrc
source $HOME/.bashrc
git --version

5. 获取 ngrok 源码

cd /usr/local
git clone https://github.com/inconshreveable/ngrok.git
1. 修改 ~/.bashrc
# ngrok
# 修改为自己解析的域名
export NGROK_DOMAIN="ngrok.chenzhi.website"
export GOPATH="/usr/local/ngrok/"
2. source 一下
source ~/.bashrc
3. 生成自签名 ssl 证书
cd /usr/local/ngrok

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

# 查看一下
ls -l
  • 多了 6 个证书,少了请重新操作
-rw-r--r-- 1 root root 1679 Dec 30 21:37 rootCA.key
-rw-r--r-- 1 root root 1139 Dec 30 21:37 rootCA.pem
-rw-r--r-- 1 root root   17 Dec 30 21:37 rootCA.srl
-rw-r--r-- 1 root root 1021 Dec 30 21:37 server.crt
-rw-r--r-- 1 root root  911 Dec 30 21:37 server.csr
-rw-r--r-- 1 root root 1679 Dec 30 21:37 server.key
4. 替换证书,询问是否确认替换,全部 yes
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key

6. 交叉编译

0. 解决 /root/go1.4/bin 的错误
rm -r /root/go1.4
cp /usr/local/go /root/go1.4 -r
1. windows 客户端编译
  • 首先
cd /usr/local/go/src/
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./make.bash
  • 再然后
cd /usr/local/ngrok
GOOS=windows GOARCH=amd64 make release-server release-client
2. mac 客户端编译
  • 首先
cd /usr/local/go/src/
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 ./make.bash
  • 再然后
cd /usr/local/ngrok
GOOS=darwin GOARCH=amd64 make release-server release-client
3. linux 客户端编译
  • 首先
cd /usr/local/go/src/
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 ./make.bash
  • 再然后
cd /usr/local/ngrok
GOOS=linux GOARCH=amd64 make release-server release-client
4. arm 客户端编译
  • 首先
cd /usr/local/go/src/
GOOS=linux GOARCH=arm CGO_ENABLED=0 ./make.bash
  • 再然后
cd /usr/local/ngrok
GOOS=linux GOARCH=arm make release-server release-client

8. ngrokd 服务端启动

  • 临时启动
/usr/local/ngrok/bin/ngrokd -domain="$NGROK_DOMAIN" -httpAddr=":80"
/usr/local/ngrok/bin/ngrokd -domain="$NGROK_DOMAIN" -httpAddr=":80" -httpsAddr=":443" -tunnelAddr=":4443"
  • 后台启动
nohup /usr/local/ngrok/bin/ngrokd -domain="$NGROK_DOMAIN" -httpAddr=":80" &
setsid /usr/local/ngrok/bin/ngrokd -domain="$NGROK_DOMAIN" -httpAddr=":80" -httpsAddr=":443" -tunnelAddr=":4443"
  • 想要结束进程
# 得到 PID
ps -aux | grep ngrokd
# kill PID
kill xxxx
  • 说明
(2)参数说明:3 个端口可随意配置(不能和现有服务端口冲突,阿里云服务器需打开对应端口)

# -domain         访问 ngrok 是所设置的服务地址生成证书时那个域名
# -httpAddr       http 协议端口 默认为 80
# -httpsAddr      https 协议端口 默认为 443 (可配置 https 证书)
# -tunnelAddr     通道端口,默认 4443

(3)注意:
所有涉及的端口,都需在阿里云(腾讯云)上设置规则,即打开端口

9. ngrok 启动客户端

  • 将 ngrok 客户端拷贝到需要穿透的服务器
  • 创建 ngrok.cfg 配置文件,只使用 ssh 登录
server_addr: "ngrok.chenzhi.website:4443"  # 远程地址
trust_host_root_certs: false
tunnels:  # 隧道
  ssh:  # 隧道名称
    remote_port: 9022       # 绑定到远程地址的那个端口
    proto:
      tcp: 22               # 本地的 TCP 22端口
  • 一个网上的 ngrok.cfg
server_addr: "ngrok.yanlongfei.top:4443"   //注意域名一定要和服务器端一致
trust_host_root_certs: false
tunnels:
  web:
    subdomain: zhiyong47    # 域名前缀  zhiyong47.ngrok.yanlongfei.top
    remote_port: 9024
    proto:
      http: 80              # 本地的 HTTP 80 端口
      
  https:
    subdomain: "ssl"
    proto:
      https: 443
      
  open:
    remote_port: 5555       # 映射的端口
    proto:
      tcp: 1194
    
  ssh:
    remote_port: 2222
    proto:
      tcp: 22
  • 临时启动
./ngrok -config=ngrok.cfg start http        #启动 web 服务 
./ngrok -config=ngrok.cfg start tcp         #启动 tcp 服务 
./ngrok -config=ngrok.cfg start http tcp    #同时启动两个服务 
./ngrok -config=ngrok.cfg start ssh         #同时启动 shh 
./ngrok -config=ngrok.cfg start-all         #启动所有服务

  • 后台启动
# 在上面每一条命令加
setsid  ****************** 
# 比如
setsid ./ngrok -config=ngrok.cfg start ssh




\





\

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

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()
}
2. 在 bin 中创建 authtokens.txt
[root@VM-8-5-centos ngrok]# tree bin
bin
|-- authtokens.txt
|-- go-bindata
|-- linux_amd64_linux
|   |-- ngrok
|   `-- ngrokd
|-- linux_arm
|   |-- ngrok
|   `-- ngrokd
|-- ngrok
|-- ngrokd
|-- nohup.out
`-- windows_amd64
    |-- ngrokd.exe
    `-- ngrok.exe

3 directories, 11 files
[root@VM-8-5-centos ngrok]# cat bin/authtokens.txt 
username:passwd
3. 重新按上述流程编译服务端和客户端
4. 修改客户端配置文件 ngrok.cfg, 关键是添加第 3
server_addr: ngrok.testdomain.com:4443
trust_host_root_certs: true
auth_token: username:password			# 关键是添加这一行
tunnels:
  	kian:
    	subdomain: kian
    	proto:
      		http: "80"
      		https: "8080"
5. 重新启动服务端和客户端

你可能感兴趣的:(linux,linux)