折腾搬瓦工–10–将内网服务暴露到外网

将内网服务暴露到外网?我实在想不出其他的好名字了。

在接入支付宝和微信支付时,由于需要一个公网的HTTPS回调地址,一开始很多支付只能在线上环境测试,涉及到金额的部分又没办法直接修改,于是想到把内网服务暴露到外网的方式,让外网请求可以进入到内网。这样在外网也可以访问到测试环境,同时也可以进行一些未上线功能的演示,或者一些需要特殊地理位置的功能测试。

假定我们需要配置的HTTPS回调地址为 https://api.wbuntu.com,先做好域名解析,再多解析一个子域名ike.wbuntu.com到主机上,用于配制和连接VPN。然后可以开始动手了,VPS与本地主机都为Ubuntu 16.04 64位系统。

1.IKEv2隧道配置

如果本地测试环境是在公司或者家中的Linux主机上,可以考虑使用VPN,如果是在自己的主机上开发,推荐使用frp,因为使用系统自带VPN工具的话,会接管本地的所有流量。

这里使用IKEv2协议建立连接,认证方式为EAP-MSCHAPv2,关于StrongSwan的安装,strongswan.conf,防火墙配置,可以参考我之前的配置:IPSecAndIKEv2VPNWithStrongswan

虽然重复配置VPN有点啰唆,不过既然需要使用HTTPS,肯定要用到Let's Encrypt,这次使用Let's Encrypt的证书来认证服务端。

1.服务端与客户端的StrongSwan编译安装(这次只使用eap-mschapv2模块来认证,openVZ主机需要附加kernel-libipsec模块)

1apt-get install build-essential libgmp3-dev libgmp-dev openssl libssl-dev -y
2wget https://download.strongswan.org/strongswan-5.5.1.tar.gz
3tar zxvf strongswan-5.5.1.tar.gz
4cd strongswan-5.5.1
5./configure --sysconfdir=/etc --enable-eap-mschapv2 --enable-eap-identity --enable-md4
6make && make install

2.证书及账号密码配置(这一小节主要讲证书怎么存放)

在服务端

/etc/ipsec.d/certs目录下存放fullchain.pem:软链接,指向最新的服务端证书/etc/letsencrypt/live/wbuntu.com/fullchain.pem,包含api和ike两个子域名;

/etc/ipsec.d/private目录下存放privkey.pem:软链接,指向最新的服务端证书私钥/etc/letsencrypt/live/wbuntu.com/private.pem

在客户端

/etc/ipsec.d/cacerts目录下存放两个证书

chain.pem:Let‘s Encrypt中间证书,由DST Root CA X3颁发,从Let's Encrypt生成证书时都会附带,如果没有的话,可以在这里下载:chain.pem

dst.pem:DST Root CA X3根证书,虽然系统已经内置了,但不放在该目录的话,还是会出现验证失败问题,通常可以在/etc/ssl/certs/DST_Root_CA_X3.pem位置找到,如果没有的话,可以从这里下载:DST_Root_CA_X3.pem

虽然我们使用的服务器证书fullchian.pem中包含了我们的域名证书和chain.pem,但StrongSwan似乎只能识别出第一条链,客户端必须在证书目录下存放chain.pemdst.pem才能完成完整的证书链认证。

完成后,在客户端下运行命令

1ipsec listcacerts

可以看到详细的证书信息

 1subject:  "C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3"
 2issuer:   "O=Digital Signature Trust Co., CN=DST Root CA X3"
 3validity:  not before Mar 18 00:40:46 2016, ok
 4           not after  Mar 18 00:40:46 2021, ok (expires in 1489 days)
 5serial:    0a:01:41:42:00:00:01:53:85:73:6a:0b:85:ec:a7:08
 6flags:     CA CRLSign
 7CRL URIs:  http://crl.identrust.com/DSTROOTCAX3CRL.crl
 8OCSP URIs: http://isrg.trustid.ocsp.identrust.com
 9pathlen:   0
10certificatePolicies:
11           2.23.140.1.2.1
12           1.3.6.1.4.1.44947.1.1.1
13           CPS: http://cps.root-x1.letsencrypt.org
14authkeyId: c4:a7:b1:a4:7b:2c:71:fa:db:e1:4b:90:75:ff:c4:15:60:85:89:10
15subjkeyId: a8:4a:6a:63:04:7d:dd:ba:e6:d1:39:b7:a6:45:65:ef:f3:a8:ec:a1
16pubkey:    RSA 2048 bits
17keyid:     da:9b:52:a8:77:11:69:d3:13:18:a5:67:e1:dc:9b:1f:44:b5:b3:5c
18subjkey:   a8:4a:6a:63:04:7d:dd:ba:e6:d1:39:b7:a6:45:65:ef:f3:a8:ec:a1
19
20subject:  "O=Digital Signature Trust Co., CN=DST Root CA X3"
21issuer:   "O=Digital Signature Trust Co., CN=DST Root CA X3"
22validity:  not before Oct 01 05:12:19 2000, ok
23           not after  Sep 30 22:01:15 2021, ok (expires in 1686 days)
24serial:    44:af:b0:80:d6:a3:27:ba:89:30:39:86:2e:f8:40:6b
25flags:     CA CRLSign self-signed
26subjkeyId: c4:a7:b1:a4:7b:2c:71:fa:db:e1:4b:90:75:ff:c4:15:60:85:89:10
27pubkey:    RSA 2048 bits
28keyid:     a8:e3:02:96:70:a6:8b:57:eb:ec:ef:cc:29:4e:91:74:9a:d4:92:38
29subjkey:   c4:a7:b1:a4:7b:2c:71:fa:db:e1:4b:90:75:ff:c4:15:60:85:89:10

如果是在服务端执行的话,会看到服务端证书信息

 1subject:  "CN=wbuntu.com"
 2issuer:   "C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3"
 3validity:  not before Feb 04 20:51:00 2017, ok
 4           not after  May 05 20:51:00 2017, ok (expires in 77 days)
 5serial:    03:2b:7f:06:23:f2:d9:d1:8d:a4:7d:42:40:28:da:bb:51:3c
 6altNames:  wbuntu.com, api.wbuntu.com, ike.wbuntu.com
 7flags:     serverAuth clientAuth
 8OCSP URIs: http://ocsp.int-x3.letsencrypt.org/
 9certificatePolicies:
10           2.23.140.1.2.1
11           1.3.6.1.4.1.44947.1.1.1
12           CPS: http://cps.letsencrypt.org
13authkeyId: a8:4a:6a:63:04:7d:dd:ba:e6:d1:39:b7:a6:45:65:ef:f3:a8:ec:a1
14subjkeyId: b9:1e:ea:66:0f:08:cb:e8:f2:c8:30:9b:73:08:13:8e:ab:96:b9:18
15pubkey:    RSA 2048 bits, has private key
16keyid:     46:46:12:a0:39:32:b0:77:6e:8c:41:23:f8:44:d0:7f:b5:ed:a3:eb
17subjkey:   b9:1e:ea:66:0f:08:cb:e8:f2:c8:30:9b:73:08:13:8e:ab:96:b9:18

还是只能看到第一条链,但以本站点为例(本站点也使用了Let's Encrypt证书),如果使用openssl命令,模拟握手,可以看到详细的证书链,命令和部分输出如下

 1openssl s_client -servername wbuntu.com -connect wbuntu.com:443 < /dev/null
 2
 3CONNECTED(00000003)
 4depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
 5verify return:1
 6depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
 7verify return:1
 8depth=0 CN = wbuntu.com
 9verify return:1
10---
11Certificate chain
12 0 s:/CN=wbuntu.com
13   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
14 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
15   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
16---

3.配置StrongSwan

配置服务端 ipsec.conf

我们将为客户端分配一个固定内网IP:10.0.0.1,一个固定eap_identity和帐号alice。

 1config setup
 2        uniqueids=no
 3conn %default
 4        ikelifetime=60m
 5        keylife=20m
 6        rekeymargin=3m
 7        keyingtries=1
 8        keyexchange=ike
 9        compress = yes
10        forceencaps=yes
11        fragmentation=yes
12conn ikev2-eap-mschapv2
13        keyexchange=ikev2
14        leftauth=pubkey
15        leftcert=fullchain.pem
16        [email protected]
17        leftsendcert=always
18        left=%defaultroute
19        leftsubnet=0.0.0.0/0
20        rightauth=eap-mschapv2
21        right=%any
22        rightid=alice
23        rightsourceip=10.0.0.1/32
24        eap_identity=alice
25        auto=add

配置客户端ipsec.conf

注意最后两行,closeaction设置为restart可以让客户端在服务端服务重启或断开连接后尝试重新连接,但如果重连次数过多且不断失败,客户端就会断开连接,不再主动发起连接,而auto设置为start则表示客户端启动ipsec服务时,主动向服务器发起连接。

 1config setup
 2        uniqueids=no
 3conn %default
 4        ikelifetime=60m
 5        keylife=20m
 6        rekeymargin=3m
 7        keyingtries=1
 8        keyexchange=ike
 9        compress = yes
10        forceencaps=yes
11        fragmentation=yes
12conn ikev2-eap-mschapv2
13        keyexchange=ikev2
14        left=%defaultroute
15       	leftid=alice
16       	leftsourceip=%any
17        leftauth=eap-mschapv2
18        right=ike.wbuntu.com
19       	rightid=ike.wbuntu.com
20        rightauth=pubkey
21       	eap_identity=alice
22       	closeaction=restart
23        auto=start

配置ipsec.secrets

服务端配置,需要引入服务端私钥 privkey.pem

1: RSA privkey.pem
2alice : EAP "WbdoabHKtNomDdp"

客户端配置

1alice : EAP "WbdoabHKtNomDdp"

推荐使用openssl生成密码

分别在客户端与服务端启动StrongSwan,应该是能够顺利完成连接,客户端被映射为服务端的一个内网主机。

2.HTTPS证书及Nginx配置

虽然之前的一篇文章里已经写了关于如何配置HTTPS证书:配置Let’s Encrypt证书

由于是从StartSSL证书升级过来的,所以并没有多讲其他内容,这次我们的环境中有了Nginx作反向代理,也有Nginx运行在Docker容器中的情况,因此需要考虑到在在反向代理状态下时,不停止Nginx,如何生成,配置,以及更新HTTPS证书。下面是正文。

假定客户端上的api服务端口为2333,则Nginx只需要将所有请求转发到10.0.0.1:2333即可。

首先当然是做好域名解析,其次把certbot项目克隆到VPS,首次执行certbot目录下的cerbot-auto程序,它会检测运行环境,并配置好一切依赖软件

1git clone https://github.com/certbot/certbot.git
2cd certbot
3./certbot-auto

假定已经配置好隧道,并把80端口的请求转发给了内网主机,那么Nginx的conf应该如下所示

1server {
2  listen 80;
3  server_name api.wbuntu.com;
4  location / {
5    proxy_pass http://10.0.0.1:2333;
6    proxy_set_header X-Forwarded-For $remote_addr;
7 }

按照certbot的示例指令来生成证书

1./certbot-auto certonly --standalone --email [email protected] -d wbuntu.com -d api.wbuntu.com -d ike.wbuntu.com

Nginx未启用状态下,它会暂时占用80端口,在指定目录下生成认证文件,然后向Let's Encrypt申请生成证书,默认会以首个域名创建文件夹和更新配置文件。执行完成后,可以发现新增了**/etc/letsencrypt目录,关于目录信息可以查看之前的博客。我们需要关注的是该目录下的liverenewal**,live目录中存放着以首个域名为名字的文件夹,在其中存放指向最新证书的软连接,renewal目录下存放着用于更新证书的配置信息。我们在命令中输入的邮箱会在证书即将过期时收到邮件提醒,我上次收到的时间是证书过期前第19天。

Nginx启用状态下,会提示80或443端口已被占用,这个时候需要手动指定网页目录。

1./certbot-auto certonly --webroot -w /pathToWebHostRoot --email [email protected] -d wbuntu.com -d api.wbuntu.com -d ike.wbuntu.com

但是这个命令还是会提示错误,因为我们配置了nginx,把发送给80端口的所有请求都转发给内网主机了,而certbot需要一个目录来生成认证文件,因此可以修改下conf文件,添加认证路径,默认为格式为 域名/.well-known,映射到本地目录,目录可随意指定,cerbot-auto会在给出的目录下建立**.well-known**文件夹。修改后conf文件如下

 1server {
 2  listen 80;
 3  server_name api.wbuntu.com;
 4  location / {
 5    proxy_pass http://10.0.0.1:2333;
 6    proxy_set_header X-Forwarded-For $remote_addr;
 7 }
 8  location /.well-known {
 9    alias /pathToWebHostRoot/.well-known;
10 }
11}

执行 **nginx -s reload **命令,应用修改,然后再执行生成证书的命令即可。

对于docker容器中的nginx,我们可以把本地的一个**/pathToWebHost目录挂载到容器中的同名路径。这样在执行cerbot-auto**命令时,在宿主机上的修改可以被容器中nginx实时读取,也可以完成认证。

如果后期更新了证书,只需要执行

1./certbot-auto renew
2nginx -s reload

cerbot-auto会读取**/etc/letsencrypt/renewal**目录下的配置文件,更新所有证书即将过期证书,nginx可平滑更新证书,不会影响到已建立的连接。

下面是最终完成后的nginx配置文件,我们获取到证书后,重新修改配置,把所有的http请求重定向到https地址。同样执行nginx -s reload应用修改。

 1server {
 2  listen 80;
 3  server_name api.wbuntu.com;
 4  location /.well-known {
 5    alias /pathToWebHostRoot/.well-known;
 6 }
 7  location / {
 8    proxy_pass http://10.0.0.1:2333;
 9    proxy_set_header X-Forwarded-For $remote_addr;
10 }
11}
12
13server {
14  listen 443 ssl http2;
15  server_name api.wbuntu.com;
16
17  ssl_certificate /etc/letsencrypt/live/wbuntu.com/fullchain.pem;
18  ssl_certificate_key /etc/letsencrypt/live/wbuntu.com/privkey.pem;
19
20  location / {
21    proxy_pass http://10.0.0.1:2333;
22    proxy_set_header X-Forwarded-For $remote_addr;
23  }
24}

3.使用FRP

在前一篇中就讲过要尝试一下frp,下面是正文。

由于服务端处理了所有请求的HTTPS握手,在转发给客户端时,内容是明文的HTTP报文,如果内容没有加密就直接传输给客户端的话,我觉得还是不太合适。IKEv2完善的加密和认证体系可以保证客户端与服务端间的通信安全,至于FRP就不太清楚了,但它也带有加密功能。项目链接:https://github.com/fatedier/frp

幸运的是FRP是国人开发的,中文文档十分完善。下载对应release包,解压后内容如下:

1frp_0.9.3_linux_amd64/
2frp_0.9.3_linux_amd64/frpc_min.ini
3frp_0.9.3_linux_amd64/LICENSE
4frp_0.9.3_linux_amd64/frpc.ini
5frp_0.9.3_linux_amd64/frps.ini
6frp_0.9.3_linux_amd64/frpc
7frp_0.9.3_linux_amd64/frps_min.ini
8frp_0.9.3_linux_amd64/frps

对于VPS端,我们可以从frps_min.ini文件修改,内容如下:

1[common]
2bind_addr = 127.0.0.1
3bind_port = 7000
4
5[api]
6type = tcp
7auth_token = 123
8bind_addr = 127.0.0.1
9listen_port = 6000

对于客户端,我们可以从frpc_min.ini文件修改,内容如下:

 1[common]
 2server_addr = wbuntu.com
 3server_port = 7000
 4auth_token = 123
 5
 6[api]
 7type = tcp
 8local_ip = 127.0.0.1
 9local_port = 2333
10remote_port = 6000

虽然换了一种方式,但套路还是相同的,外网https请求依次经过

nginx:443 -> frps:6000 -> frpc:7000 ->  localservice->local:2333

然后修改Nginx配置文件中转发目标的端口号为6000,应用修改。分别在服务端与客户端运行frp,外网的请求应该就能穿透进来了。