Relayd with SNI and TLS keypairs

· 5min · Dan F.

Back when this article was written, on setting up a relayd load-balancer with two back-end httpd servers, relayd did not have the capability of handling multiple tls relays with unique domain names. This meant that each tls relay required a unique IP per domain. This was in part due to the fact that relayd had no SNI support. I am happy to say that with OpenBSD 6.6, this is no longer the case.

Since the previously linked article already goes over the major steps in creating the load balancer, this article will only go over the changes needed to enable tls keypairs in relayd.conf and acme-client.conf.

In OpenBSD 6.5 and earlier, each unique domain in relayd required its own relay within relayd.conf, with each relayd listening on a unique IP. This was required because the referenced tls protocol directive within each domain's relay would look for the SSL certs for that domain based on the IP that the relay was listening on. This meant that if a load balancer was hosting multiple domains, the relayd.conf would become cluttered with numerous relays, and the server would have multiple IP's bound to the server's network interfaces.

This was not ideal, and thankfully, OpenBSD released a new feature within relayd called tls keypairs. What this essentially does is cut down the required relays and protocols configured in relayd.conf to just two; two relays and protocols for http/https. The tls keypair directive is set within the https protocol defined in relayd.conf:

load_balancer:/etc/relayd.conf

table <webservers> { 10.0.0.2, 10.0.0.3 }

log state changes
log connection

http protocol "http" {

    # Let's log various extra things to the log
    match header log "Host"
    match header log "X-Forwarded-For"
    match header log "User-Agent"
    match header log "Referer"
    match url log

    # Update headers passed to the httpd servers
    match request header set "X-Forwarded-For" value "$REMOTE_ADDR"
    match request header set "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"

    # Set recommended tcp options
    tcp { nodelay, socket buffer 65536, backlog 100 }

    block path "/cgi-bin/index.cgi" value "*command=*"
}

http protocol "https" {

    # Let's log various extra things to the log
    match header log "Host"
    match header log "X-Forwarded-For"
    match header log "User-Agent"
    match header log "Referer"
    match url log

    # Update headers passed to the httpd servers
    match header set "X-Forwarded-For" value "$REMOTE_ADDR"
    match header set "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
    match header set "Keep-Alive" value "$TIMEOUT"

    # Set recommended tcp options
    tcp { nodelay, socket buffer 65536, backlog 100 }

    tls no tlsv1.0
    tls ciphers "HIGH:!aNULL"

    # New tls keypair definitions
    tls keypair www.example.com
    tls keypair devel.example.com

    block path "/cgi-bin/index.cgi" value "*command=*"
}

relay "http_relay" {
    listen on 55.55.55.55 port 80
    protocol "http"
    forward to <webservers> port 80 mode loadbalance check tcp
}

relay "https_relay" {
    listen on 55.55.55.55 port 443 tls
    protocol "https"
    forward to <webservers> port 443 mode loadbalance check tcp
}

If you compare this relayd.conf to the config shown in the earlier linked article, you will see that the number of relays configured have been cut from four down to just two. The tls keypairs listed in the https protocol are loaded based on the domain requested by the client. This makes life very simple, as whenever a new domain is added to the load balancer, all that is required is the new tls keypair to be added to the https protocol block, as well as the backend httpd servers setup to host the new domain.

There is one last step needed before the above relayd.conf will be correct, and that is generating the certifications for the domain. This is rather simple, as it only requires the existing acme-client.conf to be tweaked slightly. Note that the configuration below is also using the new v2 api for let's encrypt.

/etc/acme-client.conf

authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/acme/letsencrypt-privkey.pem"
}

domain www.example.com {
    alternative names { example.com }
    domain key "/etc/ssl/private/www.example.com:443.key"
    domain full chain certificate "/etc/ssl/www.example.com:443.crt"
    sign with letsencrypt
}

domain devel.example.com {
    domain key "/etc/ssl/private/devel.example.com:443.key"
    domain full chain certificate "/etc/ssl/devel.example.com:443.crt"
    sign with letsencrypt
}

After the configuration has been modified, go ahead and generated the certs for your domains, using the new syntax for acme-client:

acme-client -v www.example.com
acme-client -v devel.example.com

At this point, test your new relayd.conf, then load it if the config is without err:

# Test the config without loading
relayd -nf /etc/relayd.conf

# Load the config if correct
relayd -f /etc/relayd.conf

If you are following this article to convert your existing load balancer to use SNI and tls keypairs, be sure to remove your existing aliases from your network interfaces that are no longer needed, as well as update your dns settings to point your domains all to the single IP. This completes the tutorial to convert relayd to use tls keypairs using the new SNI implementation released in OpenBSD 6.6.


Has been tested on OpenBSD 6.6