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)