为frp添加ssl自签名证书

背景#

  • 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 证书。 如果想兼容之前的方式,需要设置环境变量 GODEBUGx509ignoreCN=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
    25
    cat > 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
    2
    openssl 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
    12
    openssl 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
    11
    openssl 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.key

    frpc 需要额外加载 ca 证书,frps 需要额外指定 TLS 配置。frpc 通过 ca 证书单向验证 frps 的身份。这就要求 frps 的 server.crt 对 frpc 的 ca 是合法的。

frps 单向验证 frpc 的身份#

1
2
3
4
5
6
7
8
9
# frpc.ini
[common]
tls_enable = true
tls_cert_file = /to/cert/path/client.crt
tls_key_file = /to/key/path/client.key

# frps.ini
[common]
tls_trusted_ca_file = /to/ca/path/ca.crt

frpc 需要额外加载 TLS 配置,frps 需要额外加载 ca 证书。frps 通过 ca 证书单向验证 frpc 的身份。这就要求 frpc 的 client.crt 对 frps 的 ca 是合法的。

双向验证#

1
2
3
4
5
6
7
8
9
10
11
12
# frpc.ini
[common]
tls_enable = true
tls_cert_file = /to/cert/path/client.crt
tls_key_file = /to/key/path/client.key
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.key
tls_trusted_ca_file = /to/ca/path/ca.crt

双向验证即 frpc 和 frps 通过本地 ca 证书去验证对方的身份(也就是对方证书是由本地ca链路认可签发的)。理论上 frpc 和 frps 的 ca 证书可以不同,只要能验证对方身份即可。

附:完整的双向验证配置:#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# frpc.ini
[common]
server_addr = xx.xx.xx.xx
# server bind port
server_port = 9999
token = XXXXXXXXXXX
authentication_method = token
authenticate_new_work_conns = true

tls_enable = true
tls_cert_file = /path/to/frpc_client.crt
tls_key_file = /path/to/frpc_client.key
tls_trusted_ca_file = /path/to/ca.crt


[local_web]
type = tcp
use_encryption = true
use_compression = true
local_ip = 127.0.0.1
local_port = 8080
remote_port = 18888






# frps.ini
[common]
bind_port = 9999

authentication_method = token
authenticate_new_work_conns = true
token = XXXXXXXXXXX

use_encryption = true
use_compression = true

# server的证书
tls_cert_file=/path/to/tx_nginx_and_frps.crt
tls_key_file=/path/to/tx_nginx_and_frps.key
tls_trusted_ca_file=/path/to/ca.crt

更安全的全链路加密:#

注意: 以上加密只能保证frpc=======frps之间是加密的,但是并不能保证client-----frps, 以及frpc-----局域网目标服务的加密。

  • 比如我们经常使用frp的场景是:
    • 用户client--------云服务器上的frps==========局域网的frpc-------局域网的server
    • 最后的局域网一般是安全的,但是前面用户client(如浏览器)到云服务器这一段还是不安全的裸奔.
  • 所以,我建议在frps前面套一个nginx,弄一个ssl证书,就能保证全链路安全了,也就是:
    • 用户client----(tls)----云服务器的nginx with tls------同一台云服务器上的frps====(tls+encryption)======局域网的frpc-------局域网的server

参考:

https://www.gongyipu.com/20210324/365.html#:~:text=为此 frp 支持 frpc 和 frps 之间的流量通过 TLS,common 中 tls_enable %3D true 时,表示开启 TLS 协议加密。