Re: [PATCH] relayd client certificate validation again

2021-12-20 Thread Markus Läll
Hi, we've got the patch ready for client certificate validation, cc'ing
related people.

The patch adds two features:
1. client certificate validation itself
2. passing on certificate and select fields in HTTP headers


## Brief description of client certificates (for whoever else is reading)

Client certificates, also known as mutual authentication, are the reverse
to TLS server certificates (like letsencrypt) where the server
authenticates the user instead of the other way around.

In principle client certificates work as follows:
- the server has keypair and a CA certificate created from this keypair
- the user has a keypair and submits their public key for certification (to
being signed by the CA)
- the server (relayd) has the CA certificate configured as 'client ca
"/path/to/ca.pem"'
- the user provides their certificate when connecting, the provided
certificate is validated against the CA certificate.

How this is set up in practice is up to whoever implements the
infrastructure. Client certificates can be installed to operating systems'
certificate stores (Windows, macOS) where browsers can use them, or into
browsers own certificate stores (Firefox has its own), or specified on the
command line (curl, wget) etc.


## Configuration

To turn on client certificate validation add

tls { client ca "/path/to/ca-cert.pem" }

to relayd.conf.

Add "optional" flag to make the certificate not required:

tls { client ca "/path/to/cert.pem" optional }

With "optional" relayd will succeed in the TLS handshake when no client
certificate is provided. But if a certificate *is* provided then it *must*
validate with the configured CA, otherwise the TLS handshake fails.


## Pass certificate on in HTTP header

To pass on the certificate in an url-encoded PEM:

match header set "ANY_HEADER_NAME" value "$CLIENT_CERT_CHAIN"

With this configuration the downstream can inspect the known-to-be-valid
certificate further (e.g extract identity or other info from x509
extensions).

There was discussion privately on is there any standard for putting
certificates in HTTP headers, repeating the reply here as well:

There appears to be no standard, but this is how other HTTP servers do it:
- nginx urlencodes the PEM file with $ssl_client_escaped_cert[1] (this is
what is done in this patch too). There is also the $ssl_client_cert
variable which adds a tab to each next new line, but this way of doing
multiline HTTP headers is deprecated[2]. There is also
$ssl_client_raw_cert, but the raw multiline PEM is invalid HTTP header;
- envoy also urlencodes the PEM[3];
- haproxy has only the binary DER, but base64 encoding it like
%[ssl_c_der,base64]) should result in PEM with no newlines and no headers;
- apache has the %{SSL_CLIENT_CERT} with raw (multiline) PEM[4], which is
invalid in HTTP headers, but this can be processed with the escape
function[5], something like "expr= %{escape:SSL_CLIENT_CERT}"

[1]
https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_escaped_cert
[2] https://tools.ietf.org/html/rfc7230#section-3.2.4
[3]
https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
[4] https://httpd.apache.org/docs/current/mod/mod_ssl.html#envvars
[5] https://httpd.apache.org/docs/current/expr.html#functions

In addition to extracting the entire certificate, subject and issuer can be
put to HTTP headers too for convenience:

match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"



-- 
Markus Läll

On Thu, Dec 16, 2021 at 11:23 PM rivo nurges  wrote:

> Hi!
>
> Here comes the support for relayd client certificate validation.
> Full certificate chain, subject and issuer can be passed over in http
> headers.
> It supports mandatory validation and optional validation(if client chooses
> to
> provide certificate it will be validated).
>
> Part of my sample config.
>
> http protocol test {
>match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
>match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
>match header set "CS_CERT" value "$CLIENT_CERT_CHAIN"
>pass
>tls {client ca "/tmp/easyrsa3/pki/ca.crt" optional }
> }
>
> This uses code from the patches submitted by Ashe Connor.
>
> Rivo
>
> diff refs/heads/master refs/heads/relay-clc3
> blob - a2f1c130d6b45e3082048218c11537dca485998a
> blob + 5070a7d48f58403f53d818231e1676db749aa9d7
> --- usr.sbin/relayd/config.c
> +++ usr.sbin/relayd/config.c
> @@ -954,6 +954,15 @@ config_setrelay(struct relayd *env, struct relay *rlay
> rlay->rl_conf.name);
> return (-1);
>

Re: [PATCH] relayd client certificate validation again

2021-12-16 Thread Brian Brombacher
Hi, sorry for being a moron.

I realize it’s already optional by not specifying client ca… sorry about the 
noise!


> On Dec 16, 2021, at 9:35 PM, Brian Brombacher  wrote:
> 
> Hi, not to interrupt development …
> 
> Can you make this completely optional from the servers perspective?  I don’t 
> want my endpoints validating anonymous client certificates when I run a 
> public endpoint.
> 
> I’ll just hack it out otherwise, but I think this opens a vector that should 
> be completely optional from a relay service.
> 
> 
>> On Dec 16, 2021, at 4:25 PM, rivo nurges  wrote:
>> 
>> Hi!
>> 
>> Here comes the support for relayd client certificate validation.
>> Full certificate chain, subject and issuer can be passed over in http 
>> headers.
>> It supports mandatory validation and optional validation(if client chooses to
>> provide certificate it will be validated).
>> 
>> Part of my sample config.
>> 
>> http protocol test {
>> match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
>> match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
>> match header set "CS_CERT" value "$CLIENT_CERT_CHAIN"
>> pass
>> tls {client ca "/tmp/easyrsa3/pki/ca.crt" optional }
>> }
>> 
>> This uses code from the patches submitted by Ashe Connor.
>> 
>> Rivo
>> 
>> diff refs/heads/master refs/heads/relay-clc3
>> blob - a2f1c130d6b45e3082048218c11537dca485998a
>> blob + 5070a7d48f58403f53d818231e1676db749aa9d7
>> --- usr.sbin/relayd/config.c
>> +++ usr.sbin/relayd/config.c
>> @@ -954,6 +954,15 @@ config_setrelay(struct relayd *env, struct relay *rlay
>>   rlay->rl_conf.name);
>>   return (-1);
>>   }
>> +if (rlay->rl_tls_client_ca_fd != -1 &&
>> +config_setrelayfd(ps, id, n, 0,
>> +rlay->rl_conf.id, RELAY_FD_CLIENTCACERT,
>> +rlay->rl_tls_client_ca_fd) == -1) {
>> +log_warn("%s: fd passing failed for "
>> +"`%s'", __func__,
>> +rlay->rl_conf.name);
>> +return (-1);
>> +}
>>   /* Prevent fd exhaustion in the parent. */
>>   if (proc_flush_imsg(ps, id, n) == -1) {
>>   log_warn("%s: failed to flush "
>> @@ -987,6 +996,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
>>   close(rlay->rl_s);
>>   rlay->rl_s = -1;
>>   }
>> +if (rlay->rl_tls_client_ca_fd != -1) {
>> +close(rlay->rl_tls_client_ca_fd);
>> +rlay->rl_tls_client_ca_fd = -1;
>> +}
>>   if (rlay->rl_tls_cacert_fd != -1) {
>>   close(rlay->rl_tls_cacert_fd);
>>   rlay->rl_tls_cacert_fd = -1;
>> @@ -1012,6 +1025,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
>>   cert->cert_ocsp_fd = -1;
>>   }
>>   }
>> +if (rlay->rl_tls_client_ca_fd != -1) {
>> +close(rlay->rl_tls_client_ca_fd);
>> +rlay->rl_tls_client_ca_fd = -1;
>> +}
>> return (0);
>> }
>> @@ -1034,6 +1051,7 @@ config_getrelay(struct relayd *env, struct imsg *imsg)
>>   rlay->rl_s = imsg->fd;
>>   rlay->rl_tls_ca_fd = -1;
>>   rlay->rl_tls_cacert_fd = -1;
>> +rlay->rl_tls_client_ca_fd = -1;
>> if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {
>>   if (rlay->rl_conf.proto == EMPTY_ID)
>> @@ -1163,6 +1181,9 @@ config_getrelayfd(struct relayd *env, struct imsg *ims
>>   case RELAY_FD_CAFILE:
>>   rlay->rl_tls_cacert_fd = imsg->fd;
>>   break;
>> +case RELAY_FD_CLIENTCACERT:
>> +rlay->rl_tls_client_ca_fd = imsg->fd;
>> +break;
>>   }
>> DPRINTF("%s: %s %d received relay fd %d type %d for relay %s", __func__,
>> blob - 22beb857229a16e5b2c17a25a2944231d41e7e08
>> blob + fe5e8ff4dfed10e8f09e3226bdfe33f8bc031c8e
>> --- usr.sbin/relayd/parse.y
>> +++ usr.sbin/relayd/parse.y
>> @@ -172,14 +172,14 @@ typedef struct {
>> %tokenCODE COOKIE DEMOTE DIGEST DISABLE ERROR EXPECT PASS BLOCK EXTERNAL
>> %tokenFILENAME FORWARD FROM HASH HEADER HEADERLEN HOST HTTP ICMP INCLUDE 
>> INET
>> %tokenINET6 INTERFACE INTERVAL IP KEYPAIR LABEL LISTEN VALUE LOADBALANCE 
>> LOG
>> -%tokenLOOKUP METHOD MODE NAT NO DESTINATION NODEL

Re: [PATCH] relayd client certificate validation again

2021-12-16 Thread Brian Brombacher
Hi, not to interrupt development …

Can you make this completely optional from the servers perspective?  I don’t 
want my endpoints validating anonymous client certificates when I run a public 
endpoint.

I’ll just hack it out otherwise, but I think this opens a vector that should be 
completely optional from a relay service.


> On Dec 16, 2021, at 4:25 PM, rivo nurges  wrote:
> 
> Hi!
> 
> Here comes the support for relayd client certificate validation.
> Full certificate chain, subject and issuer can be passed over in http headers.
> It supports mandatory validation and optional validation(if client chooses to
> provide certificate it will be validated).
> 
> Part of my sample config.
> 
> http protocol test {
>  match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
>  match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
>  match header set "CS_CERT" value "$CLIENT_CERT_CHAIN"
>  pass
>  tls {client ca "/tmp/easyrsa3/pki/ca.crt" optional }
> }
> 
> This uses code from the patches submitted by Ashe Connor.
> 
> Rivo
> 
> diff refs/heads/master refs/heads/relay-clc3
> blob - a2f1c130d6b45e3082048218c11537dca485998a
> blob + 5070a7d48f58403f53d818231e1676db749aa9d7
> --- usr.sbin/relayd/config.c
> +++ usr.sbin/relayd/config.c
> @@ -954,6 +954,15 @@ config_setrelay(struct relayd *env, struct relay *rlay
>rlay->rl_conf.name);
>return (-1);
>}
> +if (rlay->rl_tls_client_ca_fd != -1 &&
> +config_setrelayfd(ps, id, n, 0,
> +rlay->rl_conf.id, RELAY_FD_CLIENTCACERT,
> +rlay->rl_tls_client_ca_fd) == -1) {
> +log_warn("%s: fd passing failed for "
> +"`%s'", __func__,
> +rlay->rl_conf.name);
> +return (-1);
> +}
>/* Prevent fd exhaustion in the parent. */
>if (proc_flush_imsg(ps, id, n) == -1) {
>log_warn("%s: failed to flush "
> @@ -987,6 +996,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
>close(rlay->rl_s);
>rlay->rl_s = -1;
>}
> +if (rlay->rl_tls_client_ca_fd != -1) {
> +close(rlay->rl_tls_client_ca_fd);
> +rlay->rl_tls_client_ca_fd = -1;
> +}
>if (rlay->rl_tls_cacert_fd != -1) {
>close(rlay->rl_tls_cacert_fd);
>rlay->rl_tls_cacert_fd = -1;
> @@ -1012,6 +1025,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
>cert->cert_ocsp_fd = -1;
>}
>}
> +if (rlay->rl_tls_client_ca_fd != -1) {
> +close(rlay->rl_tls_client_ca_fd);
> +rlay->rl_tls_client_ca_fd = -1;
> +}
>  return (0);
> }
> @@ -1034,6 +1051,7 @@ config_getrelay(struct relayd *env, struct imsg *imsg)
>rlay->rl_s = imsg->fd;
>rlay->rl_tls_ca_fd = -1;
>rlay->rl_tls_cacert_fd = -1;
> +rlay->rl_tls_client_ca_fd = -1;
>  if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {
>if (rlay->rl_conf.proto == EMPTY_ID)
> @@ -1163,6 +1181,9 @@ config_getrelayfd(struct relayd *env, struct imsg *ims
>case RELAY_FD_CAFILE:
>rlay->rl_tls_cacert_fd = imsg->fd;
>break;
> +case RELAY_FD_CLIENTCACERT:
> +rlay->rl_tls_client_ca_fd = imsg->fd;
> +break;
>}
>  DPRINTF("%s: %s %d received relay fd %d type %d for relay %s", __func__,
> blob - 22beb857229a16e5b2c17a25a2944231d41e7e08
> blob + fe5e8ff4dfed10e8f09e3226bdfe33f8bc031c8e
> --- usr.sbin/relayd/parse.y
> +++ usr.sbin/relayd/parse.y
> @@ -172,14 +172,14 @@ typedef struct {
> %tokenCODE COOKIE DEMOTE DIGEST DISABLE ERROR EXPECT PASS BLOCK EXTERNAL
> %tokenFILENAME FORWARD FROM HASH HEADER HEADERLEN HOST HTTP ICMP INCLUDE 
> INET
> %tokenINET6 INTERFACE INTERVAL IP KEYPAIR LABEL LISTEN VALUE LOADBALANCE 
> LOG
> -%tokenLOOKUP METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON PARENT 
> PATH
> +%tokenLOOKUP METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON OPTIONAL 
> PARENT PATH
> %tokenPFTAG PORT PREFORK PRIORITY PROTO QUERYSTR REAL REDIRECT RELAY 
> REMOVE
> %tokenREQUEST RESPONSE RETRY QUICK RETURN ROUNDROBIN ROUTE SACK SCRIPT 
> SEND
> %tokenSESSION SOCKET SPLICE SSL STICKYADDR STRIP STYLE TABLE TAG TAGGED 
> TCP
> %tokenTIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT URL WITH TTL RTABLE
> %tokenMATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD 
> ECDHE
> 

[PATCH] relayd client certificate validation again

2021-12-16 Thread rivo nurges

Hi!

Here comes the support for relayd client certificate validation.
Full certificate chain, subject and issuer can be passed over in http headers.
It supports mandatory validation and optional validation(if client chooses to
provide certificate it will be validated).

Part of my sample config.

http protocol test {
  match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
  match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
  match header set "CS_CERT" value "$CLIENT_CERT_CHAIN"
  pass
  tls {client ca "/tmp/easyrsa3/pki/ca.crt" optional }
}

This uses code from the patches submitted by Ashe Connor.

Rivo

diff refs/heads/master refs/heads/relay-clc3
blob - a2f1c130d6b45e3082048218c11537dca485998a
blob + 5070a7d48f58403f53d818231e1676db749aa9d7
--- usr.sbin/relayd/config.c
+++ usr.sbin/relayd/config.c
@@ -954,6 +954,15 @@ config_setrelay(struct relayd *env, struct relay *rlay
rlay->rl_conf.name);
return (-1);
}
+   if (rlay->rl_tls_client_ca_fd != -1 &&
+   config_setrelayfd(ps, id, n, 0,
+   rlay->rl_conf.id, RELAY_FD_CLIENTCACERT,
+   rlay->rl_tls_client_ca_fd) == -1) {
+   log_warn("%s: fd passing failed for "
+   "`%s'", __func__,
+   rlay->rl_conf.name);
+   return (-1);
+   }
/* Prevent fd exhaustion in the parent. */
if (proc_flush_imsg(ps, id, n) == -1) {
log_warn("%s: failed to flush "
@@ -987,6 +996,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
close(rlay->rl_s);
rlay->rl_s = -1;
}
+   if (rlay->rl_tls_client_ca_fd != -1) {
+   close(rlay->rl_tls_client_ca_fd);
+   rlay->rl_tls_client_ca_fd = -1;
+   }
if (rlay->rl_tls_cacert_fd != -1) {
close(rlay->rl_tls_cacert_fd);
rlay->rl_tls_cacert_fd = -1;
@@ -1012,6 +1025,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
cert->cert_ocsp_fd = -1;
}
}
+   if (rlay->rl_tls_client_ca_fd != -1) {
+   close(rlay->rl_tls_client_ca_fd);
+   rlay->rl_tls_client_ca_fd = -1;
+   }
 
 	return (0);

 }
@@ -1034,6 +1051,7 @@ config_getrelay(struct relayd *env, struct imsg *imsg)
rlay->rl_s = imsg->fd;
rlay->rl_tls_ca_fd = -1;
rlay->rl_tls_cacert_fd = -1;
+   rlay->rl_tls_client_ca_fd = -1;
 
 	if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {

if (rlay->rl_conf.proto == EMPTY_ID)
@@ -1163,6 +1181,9 @@ config_getrelayfd(struct relayd *env, struct imsg *ims
case RELAY_FD_CAFILE:
rlay->rl_tls_cacert_fd = imsg->fd;
break;
+   case RELAY_FD_CLIENTCACERT:
+   rlay->rl_tls_client_ca_fd = imsg->fd;
+   break;
}
 
 	DPRINTF("%s: %s %d received relay fd %d type %d for relay %s", __func__,

blob - 22beb857229a16e5b2c17a25a2944231d41e7e08
blob + fe5e8ff4dfed10e8f09e3226bdfe33f8bc031c8e
--- usr.sbin/relayd/parse.y
+++ usr.sbin/relayd/parse.y
@@ -172,14 +172,14 @@ typedef struct {
 %token CODE COOKIE DEMOTE DIGEST DISABLE ERROR EXPECT PASS BLOCK EXTERNAL
 %token FILENAME FORWARD FROM HASH HEADER HEADERLEN HOST HTTP ICMP INCLUDE INET
 %token INET6 INTERFACE INTERVAL IP KEYPAIR LABEL LISTEN VALUE LOADBALANCE LOG
-%token LOOKUP METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON PARENT PATH
+%token LOOKUP METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON OPTIONAL 
PARENT PATH
 %token PFTAG PORT PREFORK PRIORITY PROTO QUERYSTR REAL REDIRECT RELAY REMOVE
 %token REQUEST RESPONSE RETRY QUICK RETURN ROUNDROBIN ROUTE SACK SCRIPT SEND
 %token SESSION SOCKET SPLICE SSL STICKYADDR STRIP STYLE TABLE TAG TAGGED TCP
 %token TIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT URL WITH TTL RTABLE
 %token MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDHE
 %token EDH TICKETS CONNECTION CONNECTIONS CONTEXT ERRORS STATE CHANGES CHECKS
-%token WEBSOCKETS
+%token WEBSOCKETS CLIENT
 %token STRING
 %token NUMBER
 %type  context hostname interface table value path
@@ -188,6 +188,7 @@ typedef struct {
 %type  opttls opttlsclient
 %type  redirect_proto relay_proto match
 %type  action ruleaf key_option
+%type  clientcaopt
 %typeport
 %typehost
 %type  

relayd client certificate validation

2021-05-23 Thread rivo nurges

Hi!

Here comes the support for relayd client certificate validation.
Full certificate chain, subject and issuer can be passed over in http headers.
It supports mandatory validation, optional validation(if client chooses to
provide certificate it will be validated) and no validation(cert is passed to
the backend but no validation will happen in relayd).

Part of my sample config.

http protocol test {
  match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
  match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
  match header set "CS_CERT" value "$CLIENT_CERT_CHAIN"
  pass
  tls client ca "/tmp/easyrsa3/pki/ca.crt"
  tls { client-insecure, client-optional }
}

This uses code from the patches submitted by Ashe Connor.

Rivo

diff 8c17c73d13a081367dafe608a20fbe1b0ed903ce /home/rix/src
blob - 3e60d63ef52437fed245ed2715e30a0b2bf7956b
file + usr.sbin/relayd/config.c
--- usr.sbin/relayd/config.c
+++ usr.sbin/relayd/config.c
@@ -956,6 +956,15 @@ config_setrelay(struct relayd *env, struct relay *rlay
rlay->rl_conf.name);
return (-1);
}
+   if (rlay->rl_tls_client_ca_fd != -1 &&
+   config_setrelayfd(ps, id, n, 0,
+   rlay->rl_conf.id, RELAY_FD_CLIENTCACERT,
+   rlay->rl_tls_client_ca_fd) == -1) {
+   log_warn("%s: fd passing failed for "
+   "`%s'", __func__,
+   rlay->rl_conf.name);
+   return (-1);
+   }
/* Prevent fd exhaustion in the parent. */
if (proc_flush_imsg(ps, id, n) == -1) {
log_warn("%s: failed to flush "
@@ -989,6 +998,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
close(rlay->rl_s);
rlay->rl_s = -1;
}
+   if (rlay->rl_tls_client_ca_fd != -1) {
+   close(rlay->rl_tls_client_ca_fd);
+   rlay->rl_tls_client_ca_fd = -1;
+   }
if (rlay->rl_tls_cacert_fd != -1) {
close(rlay->rl_tls_cacert_fd);
rlay->rl_tls_cacert_fd = -1;
@@ -1014,6 +1027,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
cert->cert_ocsp_fd = -1;
}
}
+   if (rlay->rl_tls_client_ca_fd != -1) {
+   close(rlay->rl_tls_client_ca_fd);
+   rlay->rl_tls_client_ca_fd = -1;
+   }
 
 	return (0);

 }
@@ -1036,6 +1053,7 @@ config_getrelay(struct relayd *env, struct imsg *imsg)
rlay->rl_s = imsg->fd;
rlay->rl_tls_ca_fd = -1;
rlay->rl_tls_cacert_fd = -1;
+   rlay->rl_tls_client_ca_fd = -1;
 
 	if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {

if (rlay->rl_conf.proto == EMPTY_ID)
@@ -1165,6 +1183,9 @@ config_getrelayfd(struct relayd *env, struct imsg *ims
case RELAY_FD_CAFILE:
rlay->rl_tls_cacert_fd = imsg->fd;
break;
+   case RELAY_FD_CLIENTCACERT:
+   rlay->rl_tls_client_ca_fd = imsg->fd;
+   break;
}
 
 	DPRINTF("%s: %s %d received relay fd %d type %d for relay %s", __func__,

blob - 5f7513e788d7ba0615942bd1efee8d9cff6e9941
file + usr.sbin/relayd/parse.y
--- usr.sbin/relayd/parse.y
+++ usr.sbin/relayd/parse.y
@@ -179,7 +179,7 @@ typedef struct {
 %token TIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT URL WITH TTL RTABLE
 %token MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDHE
 %token EDH TICKETS CONNECTION CONNECTIONS CONTEXT ERRORS STATE CHANGES CHECKS
-%token WEBSOCKETS
+%token WEBSOCKETS CLIENT
 %token STRING
 %token NUMBER
 %type  context hostname interface table value path
@@ -1353,6 +1353,16 @@ tlsflags : SESSION TICKETS { proto->tickets = 1; }
name->name = $2;
TAILQ_INSERT_TAIL(>tlscerts, name, entry);
}
+   | CLIENT CA STRING  {
+   if (strlcpy(proto->tlsclientca, $3,
+   sizeof(proto->tlsclientca)) >=
+   sizeof(proto->tlsclientca)) {
+   yyerror("tlsclientca truncated");
+   free($3);
+   YYERROR;
+   }
+   free($3);
+   }
| NO flag   { proto->tlsflags &a