In this article I introduced how to use frp to establish an SSH connection to a local server. In fact, services running on a local server also can be exposed to the internet via frp's TCP forwarding. For instance, I ran a chat service on my old laptop without public IP address and made it accessible over the internet. However, a potential security issue is that the traffic between the local client and server is plaintext if you just use the tcp or http mode. This article records how to enable TLS feature.

Since the basic setup of frp was introduced previously, it does not receive description there. Check out this article if you haven't deployed frp on your hosts.

First of all, frp supports both HTTP and HTTPS transmission modes, and offers different services based on the domain name. However, I didn't get HTTPS to work properly (now I realized that maybe it is due to certificate issues). Thus I choose another way from the official document: encrypting with TLS. This is also the official recommended security configuration.

The content of this article is based on the official document. However, the document has some ambiguity and repeating parts, that spent me, a noob, a lot of time to figure out. I will explain them clearly and provide some examples.

Generate frps cert on client

Prepare openssl config file.

# show configuration directory
$ openssl version -d
OPENSSLDIR: "/usr/lib/ssl"

# copy to your directory
cp /usr/lib/ssl/openssl.cnf ./my-openssl.cnf

Build certs for server on client. Change IPADDRESS to server's IP address.

# generate ca certs
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt

# generate certs for server
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:IPADDRESS,DNS:example.server.com")) \
    -out server.csr

openssl x509 -req -days 3650 -sha256 \
    -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -extfile <(printf "subjectAltName=DNS:localhost,IP:IPADDRESS,DNS:example.server.com") \
    -out server.crt

The following figure shows an example.

Then, copy server.key and server.crt to the server using scp or something else.

Generate frpc cert on server

Similarly, generate ca certs according to the configuration file, then build certs for client on server. The IP address is not required in client cert, thus following codes can be run immediately without edit.

# show configuration directory
$ openssl version -d
OPENSSLDIR: "/usr/lib/ssl"

# copy to your directory
cp /usr/lib/ssl/openssl.cnf ./my-openssl.cnf

# generate ca serts
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt

# generate certs for server
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 3650 -sha256 \
    -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com") \
    -out client.crt

The following figure shows an example.

Then, copy client.key and client.crt to the client using scp or something else.

Edit configuration files

Configure following parameters under the [common] section to frpc.ini to enable TLS.

# enable tls
tls_enable = true
# specify the directory of client.* copied from server
tls_cert_file = path/client.crt
tls_key_file = path/client.key
# specify the directory of ca.crt
tls_trusted_ca_file = path/ca.crt

Configure following parameters under the [common] section to frps.ini to enable TLS.

# enforce tls to only accept TLS connections
tls_only = true
# enable tls
tls_enable = true
# specify the directory of server.* copied from client
tls_cert_file = path/server.crt
tls_key_file = path/server.key
# specify the directory of ca.crt
tls_trusted_ca_file = path/ca.crt

Finally, run ./frpc -c frpc.ini on client and ./frps -c frps.ini on server, to see if it works well. If everything is working properly, run them as system services using systemctl. The method for adding a daemon has been introduced in previous article.

In this way, communication between the client and server is encrypted with TLS.

Expose a local port to the server

After configuring the TLS encryption, you can use tcp mode to expose the local service to the server as follows, and all communication between frpc and frps are secured.

[rocketchat_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 3000
remote_port = 3000

Then, you can use nginx as reverse proxy to access this service through port 3000, like access a service deployed on the server.

Capture packets to see the difference

Before:

After:

Reference

https://link.toolin.cc/O1rka

https://link.toolin.cc/uuUAd

https://link.toolin.cc/LfRlj

Outline