Hello--
Part of my solution uses a non-HTTP protocol. My backend server need
L3/L4 information, so the PROXY protocol is a perfect fit. In addition to
TCP and IP addresses, my backend server needs information from the client
SSL connection. So, I would like to extend the PROXY protocol.
I want to keep the protocol header small, extensible, and backwards
compatible.
The current version 1 header looks like:
PROXY TCP4 1.2.3.4 2.3.4.5 51111 443\r\n
It is sent when the server keyword "send-proxy" is configured.
I propose a Version 1-SSL that can be sent when a new server keyword
"send-proxy-ssl" is configured.
It would look like:
PROXY SSL4 1.2.3.4 2.3.4.5 51111 443 1 0 CN="certificate common name"\r\n
The rules to follow would be:
1) If the connection is not SSL, then the original TCP4 format is used
2) The first integer after the destination port is:
0 if not client certifcate was sent
1 if a client certificate was sent
3) The second integer after destination port is:
0 if the certificate verified
X the return code from the SSL verify() function if the certificate
did not verify
4) Delimited by quotation marks, following CN=, is the Common Name from the
client certificate.
My hope is these values will solve most people's needs, and if not, this
format can be easily extended.
I intend this email to start a discussion, but I am also attaching a code
patch implementing the above.
My To Do List:
1) remove printf() in src/stream_interface.c left in for ease of testing
2) update doc/configuration.txt
3) update doc/proxy-protocol.txt
Please let me know your feedback.
--Dave
diff --git a/include/proto/connection.h b/include/proto/connection.h
index 8609f17..0fa12a9 100644
--- a/include/proto/connection.h
+++ b/include/proto/connection.h
@@ -41,7 +41,8 @@ int conn_fd_handler(int fd);
/* receive a PROXY protocol header over a connection */
int conn_recv_proxy(struct connection *conn, int flag);
-int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst);
+int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst,
+ int ssl_flag, int ssl_c_used, int ssl_result, const char
*ssl_cn);
/* returns true is the transport layer is ready */
static inline int conn_xprt_ready(const struct connection *conn)
diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h
index 9d891d9..51627ea 100644
--- a/include/proto/ssl_sock.h
+++ b/include/proto/ssl_sock.h
@@ -38,8 +38,12 @@ void ssl_sock_free_certs(struct bind_conf *bind_conf);
int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf, struct proxy *px);
int ssl_sock_prepare_srv_ctx(struct server *srv, struct proxy *px);
void ssl_sock_free_all_ctx(struct bind_conf *bind_conf);
+int ssl_sock_is_ssl(struct connection *conn);
const char *ssl_sock_get_cipher_name(struct connection *conn);
const char *ssl_sock_get_proto_version(struct connection *conn);
+int ssl_sock_get_cert_used(struct connection *conn);
+const char *ssl_sock_get_common_name(struct connection *conn);
+unsigned int ssl_sock_get_verify_result(struct connection *conn);
#endif /* _PROTO_SSL_SOCK_H */
diff --git a/include/types/connection.h b/include/types/connection.h
index 5341a86..c87d1a3 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -183,6 +183,14 @@ enum {
CO_SFL_STREAMER = 0x0002, /* Producer is continuously streaming
data */
};
+/* PROXY protocol versions */
+enum {
+ CO_PPV_NONE = 0x0001, /* no PROXY protocol */
+ CO_PPV_V1 = 0x0002, /* PROXY protocol version 1 */
+ CO_PPV_V1_SSL = 0x0003, /* PROXY protocol version 1 with SSL info */
+ CO_PPV_V2 = 0x0004, /* PROXY protocol version 2 */
+};
+
/* xprt_ops describes transport-layer operations for a connection. They
* generally run over a socket-based control layer, but not always. Some
* of them are used for data transfer with the upper layer (rcv_*, snd_*)
@@ -245,6 +253,7 @@ struct connection {
enum obj_type obj_type; /* differentiates connection from applet
context */
unsigned char err_code; /* CO_ER_* */
signed short send_proxy_ofs; /* <0 = offset to (re)send from the end,
>0 = send all */
+ unsigned short send_proxy_version; /* PROXY protocol v1, v1_ssl, v2 */
unsigned int flags; /* CO_FL_* */
const struct protocol *ctrl; /* operations at the socket layer */
const struct xprt_ops *xprt; /* operations at the transport layer */
diff --git a/include/types/server.h b/include/types/server.h
index 54ab813..ead19d7 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -56,6 +56,7 @@
/* unused: 0x0200, 0x0400 */
#define SRV_SEND_PROXY 0x0800 /* this server talks the PROXY protocol */
#define SRV_NON_STICK 0x1000 /* never add connections allocated to this
server to a stick table */
+#define SRV_SEND_PROXY_SSL 0x2000 /* this server talks the PROXY protocol
with SSL information */
/* function which act on servers need to return various errors */
#define SRV_STATUS_OK 0 /* everything is OK. */
diff --git a/src/backend.c b/src/backend.c
index d878028..a3643bd 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1061,6 +1061,10 @@ int connect_server(struct session *s)
srv_conn->send_proxy_ofs = 0;
if (objt_server(s->target) && (objt_server(s->target)->state &
SRV_SEND_PROXY)) {
srv_conn->send_proxy_ofs = 1; /* must compute size */
+ if ((objt_server(s->target)->state &
SRV_SEND_PROXY_SSL))
+ srv_conn->send_proxy_version = CO_PPV_V1_SSL;
+ else
+ srv_conn->send_proxy_version = CO_PPV_V1;
cli_conn = objt_conn(s->req->prod->end);
if (cli_conn)
conn_get_to_addr(cli_conn);
diff --git a/src/connection.c b/src/connection.c
index 1483f18..186d950 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -444,12 +444,17 @@ int conn_recv_proxy(struct connection *conn, int flag)
* TCP6 and "UNKNOWN" formats. If any of <src> or <dst> is null, UNKNOWN is
* emitted as well.
*/
-int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst)
+int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst,
+ int ssl_flag, int ssl_c_used, int ssl_result, const char
*ssl_cn)
{
int ret = 0;
if (src && dst && src->ss_family == dst->ss_family && src->ss_family ==
AF_INET) {
- ret = snprintf(buf + ret, buf_len - ret, "PROXY TCP4 ");
+ if (ssl_flag) {
+ ret = snprintf(buf + ret, buf_len - ret, "PROXY SSL4 ");
+ } else {
+ ret = snprintf(buf + ret, buf_len - ret, "PROXY TCP4 ");
+ }
if (ret >= buf_len)
return 0;
@@ -472,14 +477,30 @@ int make_proxy_line(char *buf, int buf_len, struct
sockaddr_storage *src, struct
return 0;
/* source and destination ports */
- ret += snprintf(buf + ret, buf_len - ret, " %u %u\r\n",
+ ret += snprintf(buf + ret, buf_len - ret, " %u %u",
ntohs(((struct sockaddr_in *)src)->sin_port),
ntohs(((struct sockaddr_in *)dst)->sin_port));
if (ret >= buf_len)
return 0;
+
+ /* conditionally, SSL info */
+ if (ssl_flag) {
+ ret += snprintf(buf + ret, buf_len - ret, " %d %d
CN=\"%s\"", ssl_c_used, ssl_result, ssl_cn?ssl_cn:"");
+ if (ret >= buf_len)
+ return 0;
+ }
+
+ /* end of header marker */
+ ret += snprintf(buf+ret, buf_len - ret, "\r\n");
+ if (ret >= buf_len)
+ return 0;
}
else if (src && dst && src->ss_family == dst->ss_family &&
src->ss_family == AF_INET6) {
- ret = snprintf(buf + ret, buf_len - ret, "PROXY TCP6 ");
+ if (ssl_flag) {
+ ret = snprintf(buf + ret, buf_len - ret, "PROXY SSL6 ");
+ } else {
+ ret = snprintf(buf + ret, buf_len - ret, "PROXY TCP6 ");
+ }
if (ret >= buf_len)
return 0;
@@ -502,11 +523,23 @@ int make_proxy_line(char *buf, int buf_len, struct
sockaddr_storage *src, struct
return 0;
/* source and destination ports */
- ret += snprintf(buf + ret, buf_len - ret, " %u %u\r\n",
+ ret += snprintf(buf + ret, buf_len - ret, " %u %u",
ntohs(((struct sockaddr_in6 *)src)->sin6_port),
ntohs(((struct sockaddr_in6 *)dst)->sin6_port));
if (ret >= buf_len)
return 0;
+
+ /* conditionally, SSL info */
+ if (ssl_flag) {
+ ret += snprintf(buf + ret, buf_len - ret, " %d %d
CN=\"%s\"", ssl_c_used, ssl_result, ssl_cn?ssl_cn:"");
+ if (ret >= buf_len)
+ return 0;
+ }
+
+ /* end of header marker */
+ ret += snprintf(buf+ret, buf_len - ret, "\r\n");
+ if (ret >= buf_len)
+ return 0;
}
else {
/* unknown family combination */
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 525c7b5..96d2836 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1897,6 +1897,79 @@ ssl_sock_get_dn_oneline(X509_NAME *a, struct chunk *out)
return 1;
}
+/* boolean, returns true if connection is over SSL */
+int ssl_sock_is_ssl(struct connection *conn)
+{
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return 0;
+ else
+ return 1;
+}
+
+/* returns common name, NULL terminated, from client certificate, or NULL if
none */
+const char *ssl_sock_get_common_name(struct connection *conn)
+{
+ X509 *crt = NULL;
+ X509_NAME *name;
+ struct chunk *trash;
+ const char find_cn[] = "CN";
+ const struct chunk find_cn_chunk = {
+ .str = (char *)&find_cn,
+ .len = sizeof(find_cn)-1
+ };
+ char *result = NULL;
+
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return NULL;
+
+ /* SSL_get_peer_certificate, it increase X509 * ref count */
+ crt = SSL_get_peer_certificate(conn->xprt_ctx);
+ if (!crt)
+ goto out;
+
+ name = X509_get_subject_name(crt);
+ if (!name)
+ goto out;
+
+ trash = get_trash_chunk();
+ if (ssl_sock_get_dn_entry(name, &find_cn_chunk, 1, trash) <= 0)
+ goto out;
+ trash->str[trash->len] = '\0';
+ result = trash->str;
+
+ out:
+ if (crt)
+ X509_free(crt);
+
+ return result;
+}
+
+int ssl_sock_get_cert_used(struct connection *conn)
+{
+ X509 *crt = NULL;
+
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return 0;
+
+ /* SSL_get_peer_certificate, it increase X509 * ref count */
+ crt = SSL_get_peer_certificate(conn->xprt_ctx);
+ if (crt) {
+ X509_free(crt);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+unsigned int ssl_sock_get_verify_result(struct connection *conn)
+{
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return (unsigned int)X509_V_ERR_APPLICATION_VERIFICATION;
+
+ return (unsigned int)SSL_get_verify_result(conn->xprt_ctx);
+}
+
+
/***** Below are some sample fetching functions for ACL/patterns *****/
/* boolean, returns true if client cert was present */
@@ -3606,6 +3679,14 @@ static int srv_parse_no_tls_tickets(char **args, int
*cur_arg, struct proxy *px,
return 0;
}
+/* parse the "send-proxy-ssl" server keyword */
+static int srv_parse_send_proxy_ssl(char **args, int *cur_arg, struct proxy
*px, struct server *newsrv, char **err)
+{
+ newsrv->state |= SRV_SEND_PROXY;
+ newsrv->state |= SRV_SEND_PROXY_SSL;
+ return 0;
+}
+
/* parse the "ssl" server keyword */
static int srv_parse_ssl(char **args, int *cur_arg, struct proxy *px, struct
server *newsrv, char **err)
{
@@ -3784,6 +3865,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "no-tlsv11", srv_parse_no_tlsv11, 0, 0 }, /* disable
TLSv11 */
{ "no-tlsv12", srv_parse_no_tlsv12, 0, 0 }, /* disable
TLSv12 */
{ "no-tls-tickets", srv_parse_no_tls_tickets, 0, 0 }, /* disable
session resumption tickets */
+ { "send-proxy-ssl", srv_parse_send_proxy_ssl, 0, 0 }, /* send
PROXY protocol header v1 with SSL info */
{ "ssl", srv_parse_ssl, 0, 0 }, /* enable
SSL processing */
{ "verify", srv_parse_verify, 1, 0 }, /* set SSL
verify method */
{ "verifyhost", srv_parse_verifyhost, 1, 0 }, /* require
that SSL cert verifies for hostname */
diff --git a/src/stream_interface.c b/src/stream_interface.c
index f23a9b0..7d315d6 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -33,6 +33,10 @@
#include <proto/stream_interface.h>
#include <proto/task.h>
+#ifdef USE_OPENSSL
+#include <proto/ssl_sock.h>
+#endif
+
#include <types/pipe.h>
/* socket functions used when running a stream interface as a task */
@@ -423,10 +427,21 @@ int conn_si_send_proxy(struct connection *conn, unsigned
int flag)
struct stream_interface *si = conn->owner;
struct connection *remote =
objt_conn(si->ob->prod->end);
- if (remote)
- ret = make_proxy_line(trash.str, trash.size,
&remote->addr.from, &remote->addr.to);
+ if (remote) {
+#ifdef USE_OPENSSL
+ if ((conn->send_proxy_version == CO_PPV_V1_SSL)
&& ssl_sock_is_ssl(remote)) {
+ ret = make_proxy_line(trash.str,
trash.size, &remote->addr.from, &remote->addr.to,
+ 1,
ssl_sock_get_cert_used(remote), ssl_sock_get_verify_result(remote),
ssl_sock_get_common_name(remote));
+ } else {
+#endif
+ ret = make_proxy_line(trash.str,
trash.size, &remote->addr.from, &remote->addr.to, 0, 0, 0, NULL);
+#ifdef USE_OPENSSL
+ }
+#endif
+ printf("%s", trash.str);
+ }
else
- ret = make_proxy_line(trash.str, trash.size,
NULL, NULL);
+ ret = make_proxy_line(trash.str, trash.size,
NULL, NULL, 0, 0, 0, NULL);
}
else {
/* The target server expects a LOCAL line to be sent
first. Retrieving
@@ -440,7 +455,7 @@ int conn_si_send_proxy(struct connection *conn, unsigned
int flag)
if (!(conn->flags & CO_FL_ADDR_TO_SET))
goto out_wait;
- ret = make_proxy_line(trash.str, trash.size,
&conn->addr.from, &conn->addr.to);
+ ret = make_proxy_line(trash.str, trash.size,
&conn->addr.from, &conn->addr.to, 0, 0, 0, NULL);
}
if (!ret)