Image by Harry Fabel from Pixabay

relayd — Secure Web Front End

phbits
5 min readJan 18, 2021

Relayd is a free load-balancer, application layer gateway, transparent proxy, and SSL/TLS gateway. It’s safe to say I would avoid placing a web server directly on the internet without placing relayd infront of it. While it may not have all the functionality of competing products (e.g. F5, nginx, haproxy) it has the right balance.

The following relayd.conf is similar to what I use as it achieves these three goals:

  1. Performs connection termination, including TLS.
  2. Updates HTTP request header with client information (e.g. X-Forwarded-For).
  3. Performs basic HTTP security checks.

Tested on: OpenBSD 6.6 GENERIC

relayd.conf

As the title states, this is the relayd.conf. The next section will explain some of the configuration lines.

ext_addr = “1.1.1.1”
web_srvr = “10.1.1.10”
log state changes
log connection
http protocol httpFilter {
#return error
http headerlen 4096
match request header set “X-Forwarded-For” value “$REMOTE_ADDR”
match request header set “X-Forwarded-SPort” value “$REMOTE_PORT”
match request header set “X-Forwarded-DPort” value “$SERVER_PORT”
match response header remove “Server” value “*”
match header log “Host”
match header log “X-Forwarded-For”
match header log “User-Agent”
match header log “X-Req-Status”
match url log
match request tag “BAD_METHOD” match request method GET tag “OK_METH”
match request method HEAD tag “OK_METH”
block request quick tagged “BAD_METHOD” match request header “Host” value “domain.com” tag “OK_REQ”
match request header “Host” value “www.domain.com" tag “OK_REQ”
match request header “Host” value “another.domain.com” tag “OK_REQ”
block request quick tagged “OK_METH” tag “BAD_HH” block request quick path “*.php” tag NO_PHP
block request quick path “*.cgi” tag NO_CGI
block request quick path “*.js” tag NO_JS
block request quick path “*.asp” tag NO_ASP
block tag “BAD_REQ” pass request tagged “OK_REQ”
}
http protocol httpsFilter {
#return error
http headerlen 4096
tls ciphers “ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256”
tls edh match request header set “X-Forwarded-For” value “$REMOTE_ADDR”
match request header set “X-Forwarded-SPort” value “$REMOTE_PORT”
match request header set “X-Forwarded-DPort” value “$SERVER_PORT”
match response header remove “Server” value “*”
match header log “Host”
match header log “X-Forwarded-For”
match header log “User-Agent”
match header log “X-Req-Status”
match url log
match response header set “Strict-Transport-Security” value “max-age=63072000; includeSubdomains; preload” match request tag “BAD_METHOD” match request method GET tag “OK_METH”
match request method HEAD tag “OK_METH”
block request quick tagged “BAD_METHOD” match request header “Host” value “domain.com” tag “OK_REQ”
match request header “Host” value “www.domain.com" tag “OK_REQ”
match request header “Host” value “another.domain.com” tag “OK_REQ”
block request quick tagged “OK_METH” tag “BAD_HH” block request quick path “*.php” tag NO_PHP
block request quick path “*.cgi” tag NO_CGI
block request quick path “*.js” tag NO_JS
block request quick path “*.asp” tag NO_ASP
block tag “BAD_REQ” pass request tagged “OK_REQ”
}
relay www {
listen on $ext_addr port 80
protocol httpFilter
forward to $web_srvr port 80
}
relay wwwssl {
listen on $ext_addr port 443 tls
protocol httpsFilter
forward to $web_srvr port 80
}

Explaination

The recommended implementation of relayd is to have it listen on 127.0.0.1 and use a rdr-to rule in pf. The backend web server is using an RFC1918 address.

ext_addr = "127.0.0.1"
web_srvr = "10.1.1.10"

Helpful to log more information and identify those testing the TLS configuration.

log state changes
log connection

Uncomment this during testing and relayd will return an HTTP 403 Forbidden when it blocks a request. Commented out, or not present, relayd will close the connection by sending a TCP FIN. Appropriate action for all those scanners sending garbage requests.

#return error

Lower the HTTP header size as the default is quite generous.

http headerlen 4096

All the necessary ciphers to achieve an A+ on Qualys SSL Server Test.

tls ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"

Enable EDH-based cipher suites.

tls edh

Add information to the request header regarding the original client request. This is important since connections are being terminated by relayd. By using set, it ensures the client can't forge their own value for these headers.

match request header set "X-Forwarded-For" value "$REMOTE_ADDR"  match request header set "X-Forwarded-SPort" value "$REMOTE_PORT"  match request header set "X-Forwarded-DPort" value "$SERVER_PORT"

Remove the Server response header.

match response header remove "Server" value "*"

Have relayd log additional request fields.

match header log "Host"
match header log "X-Forwarded-For"
match header log "User-Agent"
match url log

Here we first tag all requests. Since only GET and HEAD requests are allowed, requests using those methods will have their tag changed to OK_METH. If the request retains BAD_METHOD, it wasn't using an approved method and will be immediately blocked.

match request tag "BAD_METHOD"
match request method GET tag "OK_METH"
match request method HEAD tag "OK_METH"
block request quick tagged "BAD_METHOD"

Many scanners submit requests using the destination IP address for the host header and thus should be blocked. An alternative is to redirect all of these requests to a landing page or the main company website. However in this instance they will be blocked. The following will tag a request using an approved hostname with OK_REQ. If the tag doesn't change, the request will be immediately blocked as it will have retained the former tag of OK_METH. Retagging the block with BAD_HH helps to show where the request was blocked when parsing logs.

match request header "Host" value "domain.com" tag "OK_REQ"
match request header "Host" value "www.domain.com" tag "OK_REQ"
match request header "Host" value "another.domain.com" tag "OK_REQ"
block request quick tagged "OK_METH" tag "BAD_HH"

The following are quick blocks for requests seeking pages that aren’t hosted on the destination. The following uses PHP and ASP as an example as any request using those file extension will be immediately blocked.

block request quick path "*.php" tag NO_PHP
block request quick path "*.asp" tag NO_ASP

Default block of a request.

block tag "BAD_REQ"

Allow requests tagged as OK_REQ.

pass request tagged "OK_REQ"

--

--