1. 背景
倘若你在本地Mac或者Win环境开发过程中,恰好有一个DEMO或者撰写了一个Markdwon文档,想让另外的同事们看到你的内容,你不想一个个的叫他们到你电脑面前来,你会怎么办?
- 截图然后通过IM工具发送,不够直观;
- 发布你的代码到公共服务器,改动不够同步实时;
- 开发你本地的本地IP和端口网络服务,可能存在网络连通问题;
- 开放远程电脑服务,还需要开启相关RDP服务,这个算较为通用的方式,适合多对一的场景,倘若希望多个用户在自己的工位也可以看到,这个就有点困难了;
本文介绍一种将本地网络服务代理到公共网络,然后其他用户可以通过特定的IP或域名,外加端口访问你本地的网络服务。
该行为类似于ssh -R
,指定要将远程(服务器)主机上的给定端口,转发到本地端的给定主机和端口。
1.1. ssh的代理/端口转发模型
简单了解下ssh的3个常用代理/端口转发模型:
-L
: 指定要将本地(客户端)主机上的给定端口转发到远程端上的给定主机和端口;-R
: 指定要将远程(服务器)主机上的给定端口转发到本地端的给定主机和端口;-D
: 指定本地“动态”应用程序级端口转发。目前支持SOCKS4和SOCKS5协议,并且ssh将充当SOCKS服务器;
可以自行通过man ssh
查看!
1.2. Ngrok
一款流行的TCP网络服务代理工具,但目前官方应用部署由于国内网络问题无法访问;
1.3. Hugo
基于Golang编写的一款静态HTML生成工具,支持将MD文件转换成HTML文件,同时支持多套模板功能。
模板参考: https://themes.gohugo.io/tags/minimal/
1.4. ECS
云服务器,或者公司内部大家都可以公共访问的Linux服务器。
2. 解决方案
- ECS上面部署
ngrokd
服务,提供网络代理功能,目前ngrokd开源的是1.7版本的: https://github.com/inconshreveable/ngrok; - MAC机器部署
hugo
Web服务; - MAC机器部署
ngrok
的客户端,加入相关通道配置,将本地的http://127.0.0.1:1313
,代理到公网;
3. 安装和使用hugo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // 安装
$ brew install hugo
// 初始化MD站点
$ cd /tmp
$ hugo new site quickstart
// 初始化一个主题样式
$ cd quickstart
$ git init
$ git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke
// 将样式加入配置文件中
$ echo 'theme = "ananke"' >> config.toml
// 创建一个hello.md
$ hugo new posts/hello.md
/tmp/quickstart/content/posts/hello.md created
// 编辑hello.md
$ echo "## Welcome\n> First hugo page" >> ./content/posts/hello.md
// 运行Hugo服务
$ hugo server -D -w -v --debug
...
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
|
到此,hugo这块基本可以提供服务了,相关的内容可以通过hugo new
在content目录下进行创建新的Markdown
文件,然后正常编写即可!
请求http://localhost:1313/
,就应该可以访问到刚刚我们初始化好的Page
有问题的话,还可以参考官方文档: https://gohugo.io/getting-started/quick-start/
4. ngrokd - 服务端部署
4.1. 证书问题
由于ngrokd
是基于Golang开发的,编译过程依赖GO编译环境,另外由于HTTPS代理的话,涉及TLS过程,需要提供证书。
证书的解决方案有几种:
- 自签名证书,支持自定义签发时间,但存在其他用户访问提升CA不可信问题,需要让其他用户加入信任CA,有关自签名证书可以查看: 自签名证书
- 购买通配符域名,贵+繁琐;
- 利用
Let's Encrypt
生成自签名证书,较繁琐;
4.2. https自签名的证书生成
如果不想使用开源库中server生成服务端公钥和私钥, snakeoil.crt, snakeoil.key
需要自行生成,同时还会有自签名证书受信问题!
服务端的证书用于https的TLS的过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // 1. 下载编译安装ngrokd
$ cd /usr/local/src
$ git clone https://github.com/inconshreveable/ngrok.git
// 2. 创建一个新的CA证书,稍后用于签发服务端证书
$ cd ngrok && make tls
$ NGROK_DOMAIN="ioio.cool"
$ openssl req -new -x509 -newkey rsa:2048 -keyout ngrok-ca.key -nodes -days 36500 -subj "/CN=$NGROK_DOMAIN" -out ngrok-ca.cert
// 检视证书
$ openssl x509 -in ngrok-ca.cert -text -noout
// 3. 创建一个服务端的证书(先生成私钥,再生成CSR证书签发请求)
$ openssl req -new -newkey rsa:2048 -keyout srv-ngrok.key -nodes -subj "/CN=$NGROK_DOMAIN" -out srv-ngrok.csr
// 检视证书请求
$ openssl req -in srv-ngrok.csr -text -noout
// 4. 利用自己生成的证书,签发证书请求
$ openssl x509 -req -in srv-ngrok.csr -CA ngrok-ca.cert -CAkey ngrok-ca.key -CAcreateserial -days 3650 -out srv-ngrok.cert
Signature ok
subject=/CN=ioio.cool
Getting CA Private Key
// 5. 查看
ngrok-ca.cert: 生成的ca证书
ngrok-ca.key: 生成的ca证书私钥
ngrok-ca.srl: 证书签发序列号(用于吊销,这块暂时可以忽略)
srv-ngrok.csr: 服务端证书请求(签发服务端证书模板用途,签发后可以忽略)
srv-ngrok.cert: 服务端证书(用于HTTPS服务的部署,ngrokd会用到)
srv-ngrok.key: 附带证书私钥(用于HTTPS服务端部署,ngrokd会用到)
|
4.3. ngrokd的编译
查看Makefile
, 直接执行make,则会运行make all标签,即all: fmt client server
构建客户端和服务端!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 直接执行make,则会运行make all质量
# make
go fmt ngrok/...
bin/go-bindata -nomemcopy -pkg=assets -tags=debug \
-debug=true \
-o=src/ngrok/client/assets/assets_debug.go \
assets/client/...
bin/go-bindata -nomemcopy -pkg=assets -tags=debug \
-debug=true \
-o=src/ngrok/server/assets/assets_debug.go \
assets/server/...
go get -tags 'debug' -d -v ngrok/...
go install -tags 'debug' ngrok/main/ngrok
go install -tags 'debug' ngrok/main/ngrokd
// 服务启动
./bin/ngrokd -tlsCrt ./tls/srv-ngrok.cert -tlsKey ./tls/srv-ngrok.key -domain=ioio.cool -httpAddr=:8480 -httpsAddr=:8433 -tunnelAddr=:8422
// 后台运行
/bin/ngrokd -tlsCrt ./tls/srv-ngrok.cert -tlsKey ./tls/srv-ngrok.key -domain="ioio.cool" -httpAddr=":8480" -httpsAddr=":8433" -tunnelAddr=":8422" >> /var/log/ngrokd.log&
|
到此,服务端HTTP、HTTPS、以及TCP通道基本都OK了,但注意以下几点:
ngrokd
的编译可以是在MAC,也可以是在ECS。操作系统不同,可以通过直接在ECS上面进行直接编译或者在MAC交叉编译(比如在MAC下指定GOOS=linux, darwin, windows, netbsd
,GOARCH=amd64, 386, arm, ppc64
)后,将服务端的程序和证书同步到ECS;- 运行
ngrokd
的话,注意将服务端证书srv-ngrok.key
和srv-ngrok.cert
一起放到ECS上; - 客户端方面需要加入对生成的CA证书的认证;
1
2
3
4
5
6
7
8
| // 交叉编译
GOOS="darwin" GOARCH="amd64" make
GOOS="linux" GOARCH="amd64" make
// 检索
# file ./bin/darwin_amd64/ngrok
./bin/darwin_amd64/ngrok: Mach-O 64-bit executable
# file ./bin/ngrok
./bin/ngrok: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
|
4.4. ngrok客户端使用
假定上面一切都OK,同时ngrokd
在我们的ECS服务端(Linux环境)也正常跑起来了,现在是客户端(MAC)部分;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // 1. 客户端创建~/.ngrok.yml文件,编辑完后保存
server_addr: ioio.cool:8422
trust_host_root_certs: false
tunnels:
hugo:
subdomain: hugo
proto:
http: 1313
https: 1313
181-ssh:
subdomain: ssh181
proto:
tcp: 22
mac-http:
subdomain: mac80
proto:
http: 80
// 默认情况,ngrok 1.x客户端是读取~/.ngrok,我们可以做一个软连,这样不用每次都指定!
ln -s ~/.ngrok.yml ~/.ngrok
// 2. 运行之前编译好的MAC环境支持的ngrok:
$ ngrok list
hugo
181-ssh
mac-http
// 3. 启动我们的hugo通道代理(HTTP和HTTPS)
$ ngrok start hugo
...
Forwarding http://hugo.ioio.cool:8480 -> 127.0.0.1:1313
Forwarding https://hugo.ioio.cool:8480 -> 127.0.0.1:1313
|
4.5. 加入到Systemd系统启动中
注意这里我们为了让我的应用日志输出到/var/log/ngrokd.log
,而不是系统的默认journal
日志系统,我们采用了下面是shell
重定向!
另外,考虑到服务安全问题,我们创建了一个系统用户ngrok
专用于该ngrokd转发服务!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // 新增ngrok用户
useradd -r ngrok
// 配置启动服务相关, 将以下内容加入到`/usr/lib/systemd/system/ngrokd.service`
[Unit]
Description=ngrokd server
[Service]
Type=forking
User=ngrok
Group=ngrok
ExecStart=/bin/sh -c '/usr/local/ngrok/bin/ngrokd \
-tlsCrt /usr/local/ngrok/tls/srv-ngrok.cert \
-tlsKey /usr/local/ngrok/tls/srv-ngrok.key \
-domain="ioio.cool" \
-httpAddr=:8480 \
-httpsAddr=:8433 \
-tunnelAddr=:8422 >> /var/log/ngrokd.log 2>&1 &'
Restart=always
KillMode=process
[Install]
WantedBy=multi-user.target
// 执行链接 & 启动服务 & 查看日志
systemctl link ngrokd.service
systemctl start ngrokd.service
systemctl status ngrokd.service
systemctl enable ngrokd.service
// 不出意外,可以看到以下内容(21706是sh启动进程,fork出ngrokd子进程21707启动后退出):
# systemctl status ngrokd.service
● ngrokd.service - ngrokd server
Loaded: loaded (/usr/lib/systemd/system/ngrokd.service; enabled; vendor preset: disabled)
Active: active (running) since Wed 2019-11-13 18:59:15 CST; 2s ago
Process: 21706 ExecStart=/bin/sh -c /usr/local/ngrok/bin/ngrokd -tlsCrt /usr/local/ngrok/tls/srv-ngrok.cert -tlsKey /usr/local/ngrok/tls/srv-ngrok.key -domain="ioio.cool" -httpAddr=:8480 -httpsAddr=:8433 -tunnelAddr=:8422 >> /var/log/ngrokd.log 2>&1 & (code=exited, status=0/SUCCESS)
Main PID: 21707 (ngrokd)
CGroup: /system.slice/ngrokd.service
└─21707 /usr/local/ngrok/bin/ngrokd -tlsCrt /usr/local/ngrok/tls/srv-ngrok.c...
Nov 13 18:59:15 tkstorm_web systemd[1]: Starting ngrokd server...
Nov 13 18:59:15 tkstorm_web systemd[1]: Started ngrokd server.
|
4.6. 到此我们的ngrok以及可以通过外网访本地的mac服务了
但我们还有一个问题,CA的问题,我们上面的配置是trust_host_root_certs: true
,其源码,这块后续再来分析其内部实现,暂时先到此!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| ...
var (
rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt", "assets/client/tls/snakeoilca.crt"}
)
...
// configure TLS
if config.TrustHostRootCerts {
m.Info("Trusting host's root certificates")
m.tlsConfig = &tls.Config{}
} else {
m.Info("Trusting root CAs: %v", rootCrtPaths)
var err error
if m.tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil {
panic(err)
}
}
|
5. 小结
概要叙述了问题背景,即想开发或者代理本地网络服务到公网,提供给到其他用户使用!
我们采用了hugo构建一个简单web实现,同时利用ngrok部署了一个网络代理服务,提供给到公网访问!
针对后续这块,我自己还有个Idea,结合https://github.com/webslides/webslides
做一些更简单的事情!:)