背景#
- FRP是一个很强大的穿透工具,无需多说。但在安全性上除使用frps自带的加密参数
use_encryption = true外,还可以将信道进行ssl加密,本文将介绍frp的自签名ssl加密实施。 - 传统的CommonName证书会遇到一个
x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0异常,是因为 go 1.15 版本开始废弃 CommonName,因此推荐使用 SAN 证书。 如果想兼容之前的方式,需要设置环境变量 GODEBUG 为x509ignoreCN=0。 - 对于强迫症来说,到处改配置文件肯定无法接受,所以本文整理一下本地生成自签名证书相关的流程和操作。
证书生成流程#
创建CA#
准备openssl.cnf#
-
找找
/etc/pki/tls/openssl.cnf目录下应该有一个openssl.cnf,默认的这个文件很长,可以把它copy到当前目录,如果没有的话就创建一个lite版的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25cat > my-openssl.cnf << EOF
[ ca ]
default_ca = CA_default
[ CA_default ]
x509_extensions = usr_cert
[ req ]
default_bits = 2048
default_md = sha256
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca
string_mask = utf8only
[ req_distinguished_name ]
[ req_attributes ]
[ usr_cert ]
basicConstraints = CA:FALSE
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true
EOF生成一个时间超长的CA证书#
1
2openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt生成 frps 的服务端证书:#
- 以下命令的
XXX.XXX.XXX.XXX替换为server的公网ip:
1
2
3
4
5
6
7
8
9
10
11
12openssl genrsa -out server.key 2048
openssl req -new -sha256 -key server.key \
-subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \
-reqexts SAN \
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:XXX.XXX.XXX.XXX,DNS:example.server.com")) \
-out server.csr
openssl x509 -req -days 365 \
-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:localhost,IP:XXX.XXX.XXX.XXX,DNS:example.server.com") \
-out server.crt生成frpc的客户端证书:#
- 生成客户端证书
1
2
3
4
5
6
7
8
9
10
11openssl genrsa -out client.key 2048
openssl req -new -sha256 -key client.key \
-subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com" \
-reqexts SAN \
-config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:client.com,DNS:example.client.com")) \
-out client.csr
openssl x509 -req -days 365 \
-in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com") \
-out client.crt配置frps和frpc的ssl#
- 以上操作完成后大概会生成以下文件:
1
ca.crt ca.key ca.srl client.crt client.csr client.key my-openssl.cnf server.crt server.csr server.key
- 其中,server.crt 和 client.crt 都是由默认 ca 签发的,因此他们对默认 ca 是合法的(合法: 如果证书是 ca 签发的,或者证书是在 ca 的信任链中,那即认为: 该证书对 ca 而言是合法的。)
- 一般可分为单向验证和双向验证。具体如下:
frpc 单向校验 frps 身份#
1
2
3
4
5
6
7
8
9# frpc.ini
[common]
tls_enable = true
tls_trusted_ca_file = /to/ca/path/ca.crt
# frps.ini
[common]
tls_cert_file = /to/cert/path/server.crt
tls_key_file = /to/key/path/server.keyfrpc 需要额外加载 ca 证书,frps 需要额外指定 TLS 配置。frpc 通过 ca 证书单向验证 frps 的身份。这就要求 frps 的
server.crt对 frpc 的 ca 是合法的。 - 以下命令的
frps 单向验证 frpc 的身份#
1 | # frpc.ini |
frpc 需要额外加载 TLS 配置,frps 需要额外加载 ca 证书。frps 通过 ca 证书单向验证 frpc 的身份。这就要求 frpc 的 client.crt 对 frps 的 ca 是合法的。
双向验证#
1 | # frpc.ini |
双向验证即 frpc 和 frps 通过本地 ca 证书去验证对方的身份(也就是对方证书是由本地ca链路认可签发的)。理论上 frpc 和 frps 的 ca 证书可以不同,只要能验证对方身份即可。
附:完整的双向验证配置:#
1 | # frpc.ini |
更安全的全链路加密:#
注意: 以上加密只能保证frpc=======frps之间是加密的,但是并不能保证client-----frps, 以及frpc-----局域网目标服务的加密。
- 比如我们经常使用frp的场景是:
用户client--------云服务器上的frps==========局域网的frpc-------局域网的server- 最后的局域网一般是安全的,但是前面用户client(如浏览器)到云服务器这一段还是不安全的裸奔.
- 所以,我建议在frps前面套一个nginx,弄一个ssl证书,就能保证全链路安全了,也就是:
用户client----(tls)----云服务器的nginx with tls------同一台云服务器上的frps====(tls+encryption)======局域网的frpc-------局域网的server
参考: