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)

Reply via email to