折腾搬瓦工–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.pem与dst.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目录,关于目录信息可以查看之前的博客。我们需要关注的是该目录下的live与renewal**,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,外网的请求应该就能穿透进来了。