API服务监听 0.0.0.0 & 如何给本地localhost API使用HTTPS (API Gateway, SSH远程端口转发)

TL;DR

API 服务监听 0.0.0.0 IP地址,既可以被同一个局域网的主机通过改API主机的IP访问。

正文

后端开发中,各种API服务(比如,Django, Flask, Express.js)启动的时候,一般默认监听的都是127.0.0.1地址。

如果要使用DHCP分配的IP地址访问,可以把host改为0.0.0.0再启动API服务。这样同局域网的主机,比如说前端的同事就可以通过你的IP地址访问后端API了。

0.0.0.0的意思是分配给改主机的所有可能的IPv4地址。IPv6是::/0

可能分配给主机的IP地址是未知的,但是你启动API的时候是需要绑定一个IP地址的,如果你没有分配到IP地址,这个时候127.0.0.1就有用了,或者localhost也可以。

如果启动API的时候绑定不是你主机的IP会怎样?

你应该会有绑定失败,Django会抛出这个错误: Error: That IP address can't be assigned to.

所以,0.0.0.0就是有点类似通配符的效果。不需要指定Hard code 未知的地址。

竟然0.0.0.0这么方便,那些API框架怎么默认不使用?

我觉得是为了安全,0.0.0.0会直接让你服务器可能直接暴露在公网里面。一般开发过程中都是没有什么认证措施的,这个时候附近有个人端口扫描一下,可能就会泄露重要的资料。


为什么有了这个文章?

近期空闲时间在写Side project,需要暴露自己的API服务在公网,给一个SaaS服务回调。

当然我可以用ngrok,但是对比于4年前,这个服务现在越来越抠门。以前是可以单机多开,而且不限时长,现在玩了付费订阅制之后,限制越来越多。

我的解决方案是:

  1. API服务监听0.0.0.0端口,给路由器访问。
  2. 路由器做端口转发,让宽带公网IP的某个端口映射到本机API服务端口。
    1. 为什么不使用路由器DMZ?
    2. DMZ会直接暴露本机到公网。只使用端口转发所需的服务,这样安全点。

最后我把这个地址放到SaaS,即可访问http://58.123.123.123:8000/

延伸一下,如何给SaaS使用上HTTPS,而不是直接用本机公网地址?

方案一 AWS API Gateway Proxy :

SaaS Callback URL => AWS API Gateway => Public IP => Router Port forwarding => API (Listen 0.0.0.0)

  1. AWS API Gateway会给你分配一个HTTPS地址,你需要创建一个Stage才会有。
  2. 直接创建一个Resource,然后把所有路由都转发到本机的公网IP,比如上述的http://58.123.123.123:8000/
  3. 如果你不想使用AWS API Gateway分配的域名,你可以使用AWS ACM认证域名,然后在API Gateway创建一个漂亮的custom domain。
  4. 重要的是,上述的1 2 3都是免费的。如果超出了AWS free tier期限,AWS API Gateway是按需付费,零星的使用量基本上是免费的。
  5. ⚠️这个不是全程加密,API Gateway到本机的API服务之间的通信没有使用HTTPS。

方案二 本站Apache反向代理 + SSH远程转发(这个是我写这篇文章的时候想到的方法😂):

SaaS Callback URL => This WordPress Site Apache Reverse Proxy => SSH Remote forwarding => API (Listen on localhost)

  1. ✅ 这个是全程加密通信。
    1. SaaS到Apache是HTTPS,我还启用了Http2.0
    2. Apache到我本机的API服务是SSH。
  2. Apache的反向代理可以使用二级域名,然后创建一个VirtualHost。
  3. SSH使用的是远程转发。因为要远程主机访问本机服务,本机没有公网IP。

Apache VirtualHost代码:

<VirtualHost _default_:443>
  # 一个新的二级域名,记得配置DNS,还有SSL/TLS证书
  ServerName xx.adamliu.net
  SSLEngine on
  SSLCertificateFile "/opt/bitnami/apache2/conf/adamliu.net.crt"
  SSLCertificateKeyFile "/opt/bitnami/apache2/conf/adamliu.net.key"
  Protocols h2 h2c http/1.1

  ProxyPass "/"  "http://localhost:8000/"
  # 这句是让3xx之类的重定向地址可以被Apache捕获然后修改为apache的对外URL
  ProxyPassReverse "/"  "http://localhost:8000/"

  # Error Documents
  ErrorDocument 503 /503.html

</VirtualHost>

SSH远程端口转发:

# ServerAliveInterval和ServerAliveCountMax是为了保持session常驻
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=5  -R localhost:8000:localhost:8000 ubuntu@18.123.123.123

[AWS] 如何给EC2使用HTTPS?

现在已经是全民HTTPS时代了。不使用HTTPS协议基本就是等于在公网上裸奔。目前会有些地区故意不推HTTPS,这样就可以更方便监控网民的上网行为。

有个小科普

HTTPS是HTTP协议的加密版本,是由网络协议应用层的HTTP协议加上传输层的TLS或者SSL协议组成的。

同理,FTPS协议其实FTP + TLS/SSL协议。注意SFTP是另外一种实现方法,是属于SSH家族的。

SSL协议已经被淘汰了,现在一般使用TLS1.2以上的版本。比如我这个网站是有apache服务器驱动,就完全弃用了SSL协议。

⚠️等等! 我知道这些有啥用。

没啥,如果你爹妈用IE6或者IE8打开某些只支持HTTPS的网站,他们会问你怎么会“无法打开网页”。其实这就说明了浏览器可能完全不支持TLS(IE6),支持的TLS版本太低(IE8)。你可以点这里了解更多

没啥,如果你做后端开发。在使用一些数据库第三方连接库的时候,可能会遇到一些TLS/SSL协议版本的问题。这个问题可能是你的宿主机底层的OpenSSL太高或则太低,导致和你的第三方连接库代码不兼容。你可以点这里,看看自己能不能感受这个酸度,特别是部署代码的时候。

那问题来了,有了TLS就安全了么?来看一看2014年的心脏出血漏洞(Heartbleed bug)。现在大部分的操作系统都是使用开源的OpenSSL库来实现TLS,比如Linux,macOS等。所以那个年代如果你的服务器不使用OpenSSL库其实应该是没有这个漏洞的。

正文: 如何才能低成本使用HTTPS协议呢?

当然要看使用HTTPS协议是具体什么服务。

方法一:

如果你是使用EC2类似的VM,你可以找个免费证书(let’s encrypt)然后在你的服务器安装Nginx或Apache反向代理到你的HTTP程序。这种方式耗时耗力,还要打一堆命令。Let’s encrypt只能免费使用3个月,你还要安装Certbot做自动Review。

我这个博客网站就是按照上述做法做的,具体是使用了bitnami一条龙服务

💰💰费用:只需要给EC2的钱 (当然流量也要钱,流入免费,流出1月有1G免费流量,具体看这里

方法二:

如果你是使用EC2类似的VM,但是你只是想让API服务支持HTTPS。你可以按照这个方法,把AWS API Gateway直接做成纯代理,把所有请求都fowrad到你的EC2端口。

⚠️注意:这里创建的是RESTful API,而不是HTTP API。关于这两个有什么不同,你可以看这篇文章。HTTP API是纯Proxy,更便宜性能更好,但是对比于RESTful API会少很多功能,所以才便宜点。

API Gateway的强大之处在于,你把流量导向哪里都可以。我这里是导向了一个EC2的端口上面。

记得要在API Gateway 创建一个Stage,这样你就有一个默认的HTTPS URL了,这个URL是HTTPS的。

API Gateway功能太强大了,给你Throttling,RateLimit,还可以有针对不同的User Plan给不同的用量配置。看看SDK Generation,你只要给OPEN API Schema,就能生成几乎所有主流语言的SDK。醉了。。

💰💰费用:EC2的钱 + API Gateway的钱(前3百万的每一百万请求1美金左右)

总结一下:

方法一是最通用的。方法二只适合API(Restful)之类的HTTP服务。

突然要感谢一下前后端分离这个设计,这样可以使用API Gateway服务搞定API,静态资源直接用S3 + CloutFront托管就可以了。

完。

本文写作耗时约2小时。