Hi Willy-- Here's my latest on extending Proxy Protocol V2. I'm still testing this, but I would like to solicit any feedback that you may have. I believe I have incorporated all of your comments to date. So far, I have implemented CN as a first sub-vector. I'm willing to write a couple others, if you would like to suggest any. Thanks, --Dave
diff --git a/include/proto/connection.h b/include/proto/connection.h index 8609f17..0db677e 100644 --- a/include/proto/connection.h +++ b/include/proto/connection.h @@ -41,7 +41,9 @@ 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 server *srv, struct connection *remote); +int make_proxy_line_v1(char *buf, int buf_len, struct sockaddr_storage *src, struct sockaddr_storage *dst); +int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connection *remote); /* 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..454edd5 100644 --- a/include/proto/ssl_sock.h +++ b/include/proto/ssl_sock.h @@ -40,6 +40,10 @@ int ssl_sock_prepare_srv_ctx(struct server *srv, struct proxy *px); void ssl_sock_free_all_ctx(struct bind_conf *bind_conf); const char *ssl_sock_get_cipher_name(struct connection *conn); const char *ssl_sock_get_proto_version(struct connection *conn); +int ssl_sock_is_ssl(struct connection *conn); +int ssl_sock_get_cert_used(struct connection *conn); +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..953cb16 100644 --- a/include/types/connection.h +++ b/include/types/connection.h @@ -265,6 +265,87 @@ struct connection { } addr; /* addresses of the remote side, client for producer and server for consumer */ }; +/* proxy protocol v2 definitions */ +#define PP2_SIGNATURE_LEN 12 +#define PP2_HEADER_LEN 16 +#define PP2_VERSION 0x20 +#define PP2_CMD_LOCAL 0x00 +#define PP2_CMD_PROXY 0x01 +#define PP2_FAM_UNSPEC 0x00 +#define PP2_FAM_INET 0x10 +#define PP2_FAM_INET6 0x20 +#define PP2_FAM_UNIX 0x30 +#define PP2_TRANS_UNSPEC 0x00 +#define PP2_TRANS_STREAM 0x01 +#define PP2_TRANS_DGRAM 0x02 + +#define PP2_ADDR_LEN_UNSPEC 0 +#define PP2_ADDR_LEN_INET 12 +#define PP2_ADDR_LEN_INET6 36 +#define PP2_ADDR_LEN_UNIX 216 + +#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC) +#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET) +#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6) +#define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX) + +struct proxy_hdr_v2 { + uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ + uint8_t cmd; /* protocol version and command */ + uint8_t fam; /* protocol family and transport */ + uint16_t len; /* number of following bytes part of the header */ +}; + +union proxy_addr { + struct { /* for TCP/UDP over IPv4, len = 12 */ + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ipv4_addr; + struct { /* for TCP/UDP over IPv6, len = 36 */ + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ipv6_addr; + struct { /* for AF_UNIX sockets, len = 216 */ + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unix_addr; +}; + +#define PP2_TYPE_SSL 0x20 +#define PP2_TYPE_SSL_CN 0x21 +#define PP2_TYPE_SSL_DN 0x22 + +struct tlv { + uint16_t length; + uint8_t type; + uint8_t value[0]; +}__attribute__((packed)); + +struct tlv_ssl { + struct tlv tlv; + uint32_t version; + uint32_t client; + uint32_t verify; + uint8_t sub_tlv[0]; +}__attribute__((packed)); + +#define PP2_CLIENT_SSL 0x00000001 +#define PP2_CLIENT_CERT 0x00000002 + +struct tlv_cn { + struct tlv tlv; + uint8_t cn[0]; +}__attribute__((packed)); + +struct tlv_dn { + struct tlv tlv; + uint8_t dn[0]; +}__attribute__((packed)); + #endif /* _TYPES_CONNECTION_H */ /* diff --git a/include/types/server.h b/include/types/server.h index 54ab813..8c4c784 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -57,6 +57,12 @@ #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 */ +/* configured server options for send-proxy */ +#define SRV_PP_V2 0x0001 /* proxy protocol version 2 */ +#define SRV_PP_V2_SSL 0x0002 /* proxy protocol version 2 with SSL*/ +#define SRV_PP_V2_SSL_CN 0x0004 /* proxy protocol version 2 with SSL and CN*/ +#define SRV_PP_V2_SSL_DN 0x0008 /* proxy protocol version 2 with SSL and DN*/ + /* function which act on servers need to return various errors */ #define SRV_STATUS_OK 0 /* everything is OK. */ #define SRV_STATUS_INTERNAL 1 /* other unrecoverable errors. */ @@ -106,6 +112,7 @@ struct server { int rdr_len; /* the length of the redirection prefix */ char *cookie; /* the id set in the cookie */ char *rdr_pfx; /* the redirection prefix */ + int pp_opts; /* proxy protocol options */ struct proxy *proxy; /* the proxy this server belongs to */ int served; /* # of active sessions currently being served (ie not pending) */ diff --git a/src/connection.c b/src/connection.c index 1483f18..13abc16 100644 --- a/src/connection.c +++ b/src/connection.c @@ -437,6 +437,23 @@ int conn_recv_proxy(struct connection *conn, int flag) return 0; } +int make_proxy_line(char *buf, int buf_len, struct server *srv, struct connection *remote) +{ + int ret = 0; + + if (srv && (srv->pp_opts & SRV_PP_V2)) { + ret = make_proxy_line_v2(buf, buf_len, srv, remote); + } + else { + if (remote) + ret = make_proxy_line_v1(buf, buf_len, &remote->addr.from, &remote->addr.to); + else + ret = make_proxy_line_v1(buf, buf_len, NULL, NULL); + } + + return ret; +} + /* Makes a PROXY protocol line from the two addresses. The output is sent to * buffer <buf> for a maximum size of <buf_len> (including the trailing zero). * It returns the number of bytes composing this line (including the trailing @@ -444,7 +461,7 @@ 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_v1(char *buf, int buf_len, struct sockaddr_storage *src, struct sockaddr_storage *dst) { int ret = 0; @@ -516,3 +533,96 @@ int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src, struct } return ret; } + +int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connection *remote) +{ + const char pp2_signature[12] = {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}; + int ret = 0; + struct proxy_hdr_v2 *hdr_p = (struct proxy_hdr_v2 *)buf; + union proxy_addr *addr_p = (union proxy_addr *)(buf + PP2_HEADER_LEN); + struct sockaddr_storage null_addr = {0}; + struct sockaddr_storage *src = &null_addr; + struct sockaddr_storage *dst = &null_addr; + + if (buf_len < PP2_HEADER_LEN) + return 0; + memcpy(hdr_p->sig, pp2_signature, PP2_SIGNATURE_LEN); + + if (remote) { + src = &remote->addr.from; + dst = &remote->addr.to; + } + if (src && dst && src->ss_family == dst->ss_family && src->ss_family == AF_INET) { + if (buf_len < PP2_HDR_LEN_INET) + return 0; + hdr_p->cmd = PP2_VERSION | PP2_CMD_PROXY; + hdr_p->fam = PP2_FAM_INET | PP2_TRANS_STREAM; + addr_p->ipv4_addr.src_addr = ((struct sockaddr_in *)src)->sin_addr.s_addr; + addr_p->ipv4_addr.dst_addr = ((struct sockaddr_in *)dst)->sin_addr.s_addr; + addr_p->ipv4_addr.src_port = ((struct sockaddr_in *)src)->sin_port; + addr_p->ipv4_addr.dst_port = ((struct sockaddr_in *)dst)->sin_port; + ret = PP2_HDR_LEN_INET; + } + else if (src && dst && src->ss_family == dst->ss_family && src->ss_family == AF_INET6) { + if (buf_len < PP2_HDR_LEN_INET6) + return 0; + hdr_p->cmd = PP2_VERSION | PP2_CMD_PROXY; + hdr_p->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM; + memcpy(addr_p->ipv6_addr.src_addr, &((struct sockaddr_in6 *)src)->sin6_addr, 16); + memcpy(addr_p->ipv6_addr.dst_addr, &((struct sockaddr_in6 *)dst)->sin6_addr, 16); + addr_p->ipv6_addr.src_port = ((struct sockaddr_in6 *)src)->sin6_port; + addr_p->ipv6_addr.dst_port = ((struct sockaddr_in6 *)dst)->sin6_port; + ret = PP2_HDR_LEN_INET6; + } + else { + if (buf_len < PP2_HDR_LEN_UNSPEC) + return 0; + hdr_p->cmd = PP2_VERSION | PP2_CMD_LOCAL; + hdr_p->fam = PP2_FAM_UNSPEC | PP2_TRANS_UNSPEC; + ret = PP2_HDR_LEN_UNSPEC; + } + +#ifdef USE_OPENSSL + if (srv->pp_opts & SRV_PP_V2_SSL) { + int ssl_tlv_len = 0; + if ((buf_len - ret) < sizeof(struct tlv_ssl)) + return 0; + struct tlv_ssl *tlv = (struct tlv_ssl *)&buf[ret]; + memset(tlv, 0, sizeof(struct tlv_ssl)); + ssl_tlv_len += sizeof(struct tlv_ssl); + tlv->tlv.type = PP2_TYPE_SSL; + if (ssl_sock_is_ssl(remote)) { + tlv->client |= PP2_CLIENT_SSL; + if (ssl_sock_get_cert_used(remote)) { + tlv->client |= PP2_CLIENT_CERT; + tlv->verify = htonl(ssl_sock_get_verify_result(remote)); + } + if (srv->pp_opts & SRV_PP_V2_SSL_CN) { + char *cn = ssl_sock_get_common_name(remote); + if (cn) { + int cn_len = strlen(cn); + if ((buf_len - ret - ssl_tlv_len) < cn_len + sizeof(struct tlv_cn)) + return 0; + struct tlv_cn *tlv_cn = (struct tlv_cn *)&buf[ret+ssl_tlv_len]; + memset(tlv_cn, 0, sizeof(struct tlv_cn)); + tlv_cn->tlv.type = PP2_TYPE_SSL_CN; + tlv_cn->tlv.length = htons(cn_len + sizeof(tlv_cn)); + memcpy(tlv_cn->cn, cn, cn_len); + ssl_tlv_len += cn_len + sizeof(tlv_cn); + } + } + + if (srv->pp_opts & SRV_PP_V2_SSL_DN) { + + } + + } + tlv->tlv.length = htons((uint16_t)ssl_tlv_len); + ret += ssl_tlv_len; + } +#endif + + hdr_p->len = htons((uint16_t)(ret - PP2_HEADER_LEN)); + + return ret; +} diff --git a/src/server.c b/src/server.c index 5ac5e37..cd651a0 100644 --- a/src/server.c +++ b/src/server.c @@ -615,6 +615,11 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr newsrv->state |= SRV_SEND_PROXY; cur_arg ++; } + else if (!defsrv && !strcmp(args[cur_arg], "send-proxy-v2")) { + newsrv->state |= SRV_SEND_PROXY; + newsrv->pp_opts |= SRV_PP_V2; + cur_arg ++; + } else if (!defsrv && !strcmp(args[cur_arg], "check-send-proxy")) { newsrv->check.send_proxy = 1; cur_arg ++; diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 525c7b5..49e3c06 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1896,6 +1896,80 @@ 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 */ +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; +} + +/* returns 1 if client passed a certificate, 0 if not */ +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; + } +} + +/* returns result from SSL verify */ +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 *****/ @@ -3605,6 +3679,34 @@ static int srv_parse_no_tls_tickets(char **args, int *cur_arg, struct proxy *px, newsrv->ssl_ctx.options |= SRV_SSL_O_NO_TLS_TICKETS; return 0; } +/* parse the "send-proxy-v2-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->pp_opts |= SRV_PP_V2; + newsrv->pp_opts |= SRV_PP_V2_SSL; + return 0; +} + +/* parse the "send-proxy-v2-ssl-cn" server keyword */ +static int srv_parse_send_proxy_cn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->state |= SRV_SEND_PROXY; + newsrv->pp_opts |= SRV_PP_V2; + newsrv->pp_opts |= SRV_PP_V2_SSL; + newsrv->pp_opts |= SRV_PP_V2_SSL_CN; + return 0; +} + +/* parse the "send-proxy-v2-ssl-dn" server keyword */ +static int srv_parse_send_proxy_dn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->state |= SRV_SEND_PROXY; + newsrv->pp_opts |= SRV_PP_V2; + newsrv->pp_opts |= SRV_PP_V2_SSL; + newsrv->pp_opts |= SRV_PP_V2_SSL_DN; + 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 +3886,9 @@ 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-v2-ssl", srv_parse_send_proxy_ssl, 0, 0 }, /* send PROXY protocol header v2 with SSL info */ + { "send-proxy-v2-ssl-cn", srv_parse_send_proxy_cn, 0, 0 }, /* send PROXY protocol header v2 with CN */ + { "send-proxy-v2-ssl-dn", srv_parse_send_proxy_dn, 0, 0 }, /* send PROXY protocol header v2 with DN */ { "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..67a5234 100644 --- a/src/stream_interface.c +++ b/src/stream_interface.c @@ -422,11 +422,7 @@ int conn_si_send_proxy(struct connection *conn, unsigned int flag) if (conn->data == &si_conn_cb) { 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); - else - ret = make_proxy_line(trash.str, trash.size, NULL, NULL); + ret = make_proxy_line(trash.str, trash.size, objt_server(conn->target), remote); } else { /* The target server expects a LOCAL line to be sent first. Retrieving @@ -440,7 +436,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, objt_server(conn->target), conn); } if (!ret)