This repository has been archived on 2023-11-13. You can view files and clone it, but cannot push or open issues or pull requests.
blog/_posts/2022-05-28-frp_and_acme.md
2023-06-03 15:58:09 +08:00

12 KiB
Raw Permalink Blame History

layout, title, subtitle, date, author, catalog, header-img, tags
layout title subtitle date author catalog header-img tags
post FRP&ACME——内网穿透+证书签发 Keep your data on your own machine 2022-05-28 10:03:29 Manford Fan false img/post-bg-universe.jpg
FRP
ACME

很久之前听说过花生壳可以让内网的设备在公网上访问当时还不知道什么叫域名什么是代理只是觉得很神奇。最近一年由于工作原因接触了很多跟web相关的东西恰巧自己也跟着搭建了一些网络服务器更巧的是家里有一台配置还行的内存不是很大的老笔记本——i7 6500u/4G DDR3 1600MHz/256G Nvme SSD就想着把搭建在腾讯云的服务挪到自己的笔记本上用FRP实现内网穿透因为花生壳收费【穷.jpg】而腾讯云的机器只做流量转发优点是真正实现了所有数据保存在自己的机器上安全性高缺点也很明显增加了访问链路长度带宽的损耗比可避免具体是多少还在验证中另外如果VPS是上下行计费的话流量消耗将是双倍再就是自己的笔记本不能断网断电不然服务就彻底嗝屁了这就涉及到本地网络带宽以及笔记本的功耗问题。

一、FRP —— A fast reverse proxy

FRP采用C/S模式将服务端部署在具有公网IP的机器上客户端部署在内网或防火墙内的机器上通过访问暴露在服务器上的端口反向代理到处于内网的服务。在此基础上frp支持 TCP/UDP/HTTP/HTTPS等多种协议提供了加密、压缩身份认证代理限速负载均衡等众多能力。

1. 原理

frp主要由客户端(frpc)和服务端(frps)组成服务端通常部署在具有公网IP的机器上客户端通常部署在需要穿透的内网服务所在的机器上。内网服务由于没有公网IP不能被非局域网内的其他用户访问。用户通过访问服务端的frps由frp负责根据请求的端口或其他信息将请求路由到对应的内网机器从而实现通信。在frp中一个代理对应一个需要暴露的内网服务一个客户端支持同时配置多个代理。其中支持的类型如下

类型 描述
tcp 单纯的TCP端口映射服务端会根据不同的端口路由到不同的内网服务
udp 单纯的UDP端口映射服务端会根据不同的端口路由到不同的内网服务
http 针对HTTP应用定制了一些额外的功能例如修改Host Header增加鉴权
https 针对HTTPS应用定制了一些额外的功能
stcp 安全的TCP内网代理需要在被访问者和访问者的机器上都部署frpc不需要在服务端暴露端口
sudp 安全的UDP内网代理需要在被访问者和访问者的机器上都部署frpc不需要在服务端暴露端口
xtcp 点对点内网穿透代理功能同stcp但是流量不需要经过服务器中转
tcpmux 支持服务端TCP端口的多路复用通过同一个端口访问不同的内网服务
kcp 基于udp比tcp多浪费10%~20%的带宽换取30%~40%的延时性能提升

2. 配置

frp由客户端和服务端构成通过客户端主动与服务端发起链接从而建立通信其配置文件分别是frpc.ini和frps.ini可以通过命令启动也可以配置systemd服务。

服务端

[common]
# FRP监听端口
bind_port = 8765
kcp_bind_port = 8765 # 一定要开启该端口的udp协议
max_pool_count = 100
tcp_mux = on

# http监听端口
vhost_http_port = 8888

# https监听端口
vhost_https_port = 54321

# domain域
subdomain_host = rustle.cc


# 授权码
token = rcAhsh$ub1lh

# frp管理后台端口
dashboard_port = 12345

# frp管理后台用户名和密码
dashboard_user = admin
dashboard_pwd = XXXXXXX
enable_prometheus = true

# frp日志配置需要提前创建日志文件并修改所有者和所属组为nobody与nogroup
log_file = /opt/logs/frps.log
log_level = trace
log_max_days = 3

客户端

# 客户端配置
[common]
server_addr = 1.23.4.56
server_port = 8765  # 对应bind_port
pool_count = 20
use_encryption = true
use_compression = true
protocol = kcp

# 授权码
token = rcAhsh$ub1lh

# 配置ssh服务
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 8888  # ssh服务必须要指定http(s)服务可以在nginx中指定

[homepage]
type = http        # http协议
local_port = 8001  # 本地监听8001端口
subdomain = www    # 域名是www.rsutle.cc 
custom_domains = rustle.cc  # 或者是 rustle.cc 

# frp日志配置要求同服务端但是好像客户端不需要配置...
log_file = /opt/logs/frps.log
log_level = trace
log_max_days = 3

3. 隐藏端口&开启https

通过frp转发的http服务都必须带端口访问比如"http://rustle.cc:8080/"看起来很丑还要记端口号很不友好。可以通过nginx进行反向代理把请求转发给frps监听的端口到达将服务端端口号隐藏掉的效果。另外现在很多浏览器都强制要求https协议访问更有些网站要求HSTS所以当用frp转发内网的web服务的时候也支持开启https协议。目前有两种方式一个是在服务端的nginx服务器配置证书第二个就是通过frp本身的https2http插件在客户端实现。

服务端实现

原理很简单就是用nginx服务监听80和443端口同时配置ssl证书即可LAN客户端无需做任何https相关的配置。

server {
    server_name *.rustle.cc;
    listen 80;
    listen 443 ssl http2;
    ssl_certificate /opt/cert/server.crt;
    ssl_certificate_key /opt/cert/server.key;

    proxy_connect_timeout 60;
    proxy_send_timeout 60;
    proxy_read_timeout 60;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";

    location / {
        proxy_pass http://localhost:8080;
    }
}

客户端实现

说是客户端实现其实服务端也需要做稍微的配置即开启https监听端口vhost_https_port = 54321其他的就交给LAN客户端即可。如下是客户端frpc.ini文件的相关配置

[test_https2http]
type = https
custom_domains = test.example.com

plugin = https2http
plugin_local_addr = 127.0.0.1:8001
plugin_crt_path = ./server.crt
plugin_key_path = ./server.key
plugin_host_header_rewrite = 127.0.0.1
plugin_header_X-From-Where = frp

个人更喜欢第一种方式在服务端配置同时监听80和443端口客户端实现方法也能达到同样的效果但是需要在服务端同时配置两个server块分别监听80和443并使用nginx分别转发到不同的frp端口稍微复杂一点对于这两种方法暂时没有对比转发效率的优劣。

4. 优化

经过frp转发之后带宽一定会有所损耗应该较小......可以通过开启kcp协议减缓在弱网环境效果尤其明显另外由于网络的复杂性frp服务经常会断开链接可以通过systemd服务让frp服务断开一定时间之后重新链接。以内网客户端为例

[Unit]
Description=Frp Client Service
After=network.target syslog.target
Wants=network.target

[Service]
Type=simple
User=www-data
Restart=always
RestartSec=5s
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target

另外当我们做frp性能调优的时候经常会尝试一些不同的配置很有可能会把客户端搞挂掉但是自己又不在家于是家里的服务器就失联了。这是很悲催的事情不过可以通过crontab服务来规避将验证过的有效的配置放在备份文件夹设置每30分钟或1个小时将该备份文件拷贝到frp配置文件夹这样就有30分钟或1个小时的时间进行调试实验即使调试过程中内网服务器挂掉了也不怕一段时间之后就自动恢复了调优成功之后记得及时把备份文件更新

功耗问题IdeaPad 710S-13ISK-ISE待机功耗一天差不多0.1-0.3度电 性能问题根据测试内网2M下载速率外网780K下载速率实时性能780K内网2M下载速率外网50M下载速率实时性能2M所以FRP的总体性能取决于内外网中性能较差的那个。

二、ACME —— 自动签发证书

既然要配置https访问肯定要申请相应的证书之前用certbot只能申请单域名证书,无法申请泛域名证书。所以这次使用acme.sh来申请泛域名证书,同时也大大简化了服务端nginx的配置。acme.sh支持多个CA机构签发免费证书但是只支持DV证书的签发也就是通过验证域名所有权然后签发该域名的证书。它支持两种验证方式一种是通过HTTP的方式验证另一种是通过DNS的方式验证只有第二种方式支持签发泛域名证书所以如下也是记录的该方式。

1. 准备工作

ACME支持cloudflare, dnspod, aliyun, cloudxns, godaddy以及ovh等数十种DNS解析商的自动集成脚本会根据提供的API的AK和SK自动的在解析中添加一条TXT解析并验证你对该域名具有所有权处理完之后在调用API自动的将该TXT解析删除。所以要先获取AK和SK并作相应的配置以阿里云为例https://usercenter.console.aliyun.com/#/manage/ak点击该链接申请创建AKSK并将其导入服务端环境变量定义如下

export Ali_Key="sddiwjedfasSDFSFsdaf"
export Ali_Secret="jlsdsddiwjedfasSDFSFkljlfdsaklkjflsa"

2. 开始安装

执行如下命令进行安装默认情况下acme.sh以隐藏文件夹的形式安装在用户的主目录ACME不要求root权限并且crontab会自动创建一条定时任务每天凌晨检查证书是否过期是否需要续签等。

$ cd ~
$ git clone https://github.com/acmesh-official/acme.sh.git
$ cd acme.sh
$ ./acme.sh --install -m  my@example.com

安装完成之后就开始申请证书,因为是签发,所以要使用issue参数;指明使用dns_ali作为验证方式;后面跟着的-d为指定证书中的域名,这里有一点需要注意的:**如果证书中只包含泛域名,那么签发出来的证书是没有根域的。所以需要额外添加一个根域。**等待23分钟证书会自动申请成功并存放在/.acme/rustle.cc/目录下。

$ acme.sh --issue --dns dns_ali -d rustle.cc -d *.rustle.cc

3. 配置证书

证书主要用在nginx服务中所以要使用包含中级CA的域名证书与私钥配置在NG服务配置文件即可。

ssl_buffer_size            16k;
ssl_protocols              TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ecdh_curve             X25519:P-256:P-384:P-224:P-521;
ssl_ciphers                TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
ssl_prefer_server_ciphers  on;
ssl_session_timeout        3h;
ssl_stapling               on;
ssl_session_tickets        on;

ssl_certificate /opt/cert/fullchain.cer;
ssl_certificate_key /opt/cert/rustle.cc.key;

三、参考文档