Skip to content
Simon Urbanek edited this page Jan 4, 2025 · 6 revisions

R serve has built-in support for TLS/SSL. This page aims to document the concepts and details on how to use it.

Basics

Transport Layer Security (TLS) is a way to use encrypted channel for communication between two parties. All communication over this channel is encrypted and thus safe from eavesdropping. It is the successor of SSL (Secure Sockets Layer) and users typically encounter it as part of HTTPS which is a way to secure the HTTP protocol, but it can be used for any communication.

Rserve supports TLS encrypted channels in following ways:

  • QAP + TLS: encrypts the Rserve-native QAP protocol which is used to communicate between Rserve clients and the Rserve server. Configuration directive: qap.tls.port (and tls.port for compatibility but discouraged for its ambiguity)

  • QAP TLS upgrade: Rserve uses (unencrypted) QAP on the regular qap.port, but allows CMD_switch to switch from unencrypted to encrypted TLS communication. The switch is then optional and initiated by the client (discouraged, makes sense only if you need to support both clients with and without TLS support for historical reasons). Configuration directive: switch.qap.tls enable, uses regular qap.port

  • HTTPS (HTTP + TLS): Rserve acts as a secure web-server using the HTTP protocol over TLS channel. Configuration directive: http.tls.port (and https.port for compatibility)

  • WebSockets + TLS: encrypted WebSockets connection that uses QAP wrapped in WebSockets protocol (see rserve.js). Configuration directive: websockets.tls.port

All above servers can be enabled in addition to any other existing modes, so let's say if you want to only enable HTTPS on its default port would would use http.tls.port 443 and qap disable to disable the non-encrypted QAP.

Search for tls.port in the NEWS file for more details which serves as the official documentation.

Keys and Certificates

In addition to enabling the corresponding server by setting one of the *.tls.port options, you have to also set the following configuration options:

  • tls.key : mandatory, must point to the TLS private key in PEM format

  • tls.cert : mandatory, must point to the certificate that matches the above key, also in PEM format

  • tls.ca : optional, if you certificate requires additional certificate authority (CA) certificates then you must provide them here

If you are using a self-signed certificate then you only only need to set tls.key and tls.cert. Note, however, that the certificate validation will always fail, so, e.g., if you use RSclient::RS.connect you have to also set verify=FALSE (or set ca= to the same value as tls.cert), otherwise the connection will fail.

If you use a real certificate, you almost always have to also set tls.ca, because certificate authorities (CAs) no longer issue certificates that are issued by the root authorities. Therefore they will supply not only the certificate for your server, but also a chain of intermediate certificates. Those are simply concatenated certificates on the CA in one PEM file. If the CA gave you multiple, just create one file by concatenating all of them into one file.

Hint: very often nowadays the server certificate and the CA certificates are concatenated into a single file often called "full.pem" (e.g., if you use Let's Encrypt). In that case you can set both tls.cert and tls.ca to that same file as long as the server certificate is the first one.

Client certificate authentication

Since Rserve 1.8-8 client certificates are supported. Previous version would accept them but they were not checked. Starting with version 1.8-8 the tls.client configuration defines whether client certificates are validated, required or what conditions they have to fulfill. The most permissive setting is tls.client=none which does not request nor check the client certificate. This is appropriate if your server requires no client certificate authentication and where you want all clients to be accepted. The next levels is request which asks the client for a certificate, but does not require that it is present. require requests a client certificate and requires that it is valid using the server's chair of trust (see tls.ca above). Additional entries match:, prefix: and suffix: can be use to restrict which (valid) certificates are accepted based on their common name - see the NEWS entry for Rserve 1.8-8 for details.

Troubleshooting

First, your system must have the OpenSSL library and Rserve must be compiled against it in order to be able to use TLS support. When you install Rserve from sources you should see the following output during the installation:

checking openssl/rsa.h usability... yes
checking openssl/rsa.h presence... yes
checking for openssl/rsa.h... yes
checking for library containing RSA_generate_key... -lcrypto
checking openssl/ssl.h usability... yes
checking openssl/ssl.h presence... yes
checking for openssl/ssl.h... yes
checking for library containing SSL_CTX_load_verify_locations... -lssl

If any of the above fails, Rserve cannot use TLS. Rserve will fall back to unencrypted connections if the initialisation of TLS fails. This includes failures due invalid keys or certificates as well. Versions before Rserve 1.8-8 did so silently, so, unfortunately, there was no visual indication that TLS failed or TLS support was not present. From version 1.8-8 on there will be a warning, but Rserve will still fall back to unencrypted connections.

It is possible to test the TLS functionality using openssl s_client command line tool. For example, with qap.tls.port 6312 you can test with

openssl s_client my.server.com:6312

if you want to use local server but with valid certificate you can also use

openssl s_client -servername my.server.com 127.0.0.1:6312

For a QAP+TLS connection the output should end with:

---
read R BLOCK
Rsrv0103QAP1

--------------

If the client connection gets aborted immediately on connect, check your tls.client setting and set it to none if you want all TLS clients to be accepted.

Other options

For security reasons it is sometimes desirable to use separate modules to split risk across layered processes. For example, if Rserve is used in an enterprise environment where it has to run as root to switch to users according to authentication, it is ofter desirable to shield this process from network access. This is possible by using local unix sockets for communication to the main Rserve process and use a separate process for encryption and communication. Rserve provides a HTTP + WebSocket proxy for this purposes. The proxy implements the HTTP/WebSockets protocol and communicates with Rserve using local sockets and QAP protocol. The proxy also supports optional TLS layer in the same way as discussed above. The executable is called forward and it installed as part of Rserve. You can locate it using

system.file("libs", "forward", package="Rserve")

in R. For more details see

$ forward -h

 Usage: forward [-h] [-p <http-port>] [-w <ws-port>] [-s <QAP-socket>] [-R <Rscript-socket>] [-r <doc-root>]
        [-k <TLS-key-path> [-c <TLS-cert-path>] [-C <TLS-CA-path>]] [-u <ulog-socket>] [-S <host>[:<port>]]]

Finally, another option often used with HTTP and WebSockets is nginx reverse-proxy. It can be used to handle the encryption as well as load-balancing if multiple Rserves are required. Note that this only works for WebSocket connections, not for "naked" QAP protocol.

Sample nginx configuration (from load-balanced RCloud deployment):

map $http_upgrade $connection_upgrade {
    	default	upgrade;
	''	close;
}

upstream rcloud_servers {
    # least_conn is load balancing based on selecting a server from the pool which has the
    # least active sessions from NginX. Default is round robin.
    least_conn;
    # List all of the rcloud server pool in the format <FQDN>:<PORT>
    include /etc/rcloud-servers;
}

server {
	listen 443;
	server_name <<your-server-name-here>>;

	location / {
		try_files $uri $uri/ @proxy;
	}

	location @proxy {
		proxy_pass		http://rcloud_servers;
		proxy_set_header	X-Real-IP  $remote_addr;

		# allow connection upgrade to WebSockets
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection $connection_upgrade;

		# we may serve long-running queries,
		# so we set this really high for now ...
		proxy_read_timeout 30m;
	}

	ssl on;
	ssl_certificate /etc/.../fullchain.pem;
	ssl_certificate_key /etc/.../privkey.pem;
}

The list of servers is expected in /etc/rcloud-servers in the form <host>:<port> and they are typically the forward proxy setup to connect to local Rserve with unix sockets locally.

Java client

The REngine Java API does not have specific facilities for TLS support, but the RConnection(Socket) API can use used to use regular Java TLS facilities with Rserve. For example:

SSLContext ctx = SSLContext.getInstance("TLS");
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket)factory.createSocket("myserver", 6312);
socket.startHandshake();
RConnection c = new RConnection(socket);

will connect to a TLS-enabled instance of Rserve, e.g.:

R CMD Rserve --RS-set qap=false --RS-set tls.key=server.key \
  --RS-set tls.cert=server.crt --RS-set tls.ca=ca.cert.pem \
  --RS-set qap.tls.port=6312

Refer to Java documentation on key stores and trust stores on how to manage them.

If you don't want to use public key infrastructure and manage your own keys and certificates instead, here is a simple example using openssl and keytool:

## The first two steps establish your own Certificate Authority (needed only once)
## generate CA key
openssl genrsa -out ca.key 4096
## create a CA certificate
openssl req -new -x509 -days 1825 -key ca.key -out ca.cert.pem

## create a Java trust store which trusts your CA
keytool -importcert -file ca.cert.pem -alias ca -storepass changeit -keystore truststore.jks
## NOTE: the password is not a joke, that is the default trust store password in Java
## You can use this trust store with:
## java -Djavax.net.ssl.trustStore=truststore.jks ...

## now you can generate new server (or client) certificates
## generate server key
openssl genrsa -out server.key 2048
## generate certificate signing request (CSR)
## common name (CN) should match the host name you will use
openssl req -new -key server.key -out server.csr
## sign that certificate with your CA key and certificate
openssl x509 -req -days 365 -in server.csr -CA ca.cert.pem -CAkey ca.key -CAcreateserial -out server.crt