From: Alex Rousskov Date: Sat, 3 Dec 2011 22:45:38 +0000 (-0700) Subject: Honor ssl-bump option for https_port. X-Git-Tag: BumpSslServerFirst.take01~8 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=379e8c1c4f4b91de5e03b49d2cb7d49699d2b50b;p=thirdparty%2Fsquid.git Honor ssl-bump option for https_port. Initial ssl-bump handling logic mimics that of http_port: If the option is set, check the slow ssl_bump ACL, and if there is a match, plug into switchToHttps() code path, generating a dynamic certificate and establishing a secure connection with the client. If there is no match, Squid becomes a TCP tunnel for the intercepted connection. For now, we use the destination IP address of the intercepted connection as the host name for the certificate (which will trigger browser warnings, of course). --- diff --git a/src/cache_cf.cc b/src/cache_cf.cc index 610ab87120..0234ec13fd 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -4054,6 +4054,16 @@ parse_https_port_list(https_port_list ** head) parse_http_port_option(s, token); } + /* ssl-bump requires tproxy and vice versa */ + if (s->sslBump && !s->spoof_client_ip) { + debugs(3, DBG_CRITICAL, "FATAL: ssl-bump on https_port requires tproxy which is missing."); + self_destruct(); + } + if (s->spoof_client_ip && !s->sslBump) { + debugs(3, DBG_CRITICAL, "FATAL: tproxy on https_port requires ssl-bump which is missing."); + self_destruct(); + } + while (*head) { http_port_list ** headTmp = &(*head)->http.next; head = (https_port_list **)headTmp; diff --git a/src/cf.data.pre b/src/cf.data.pre index 434e94b6d6..e400801cec 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1503,6 +1503,21 @@ DOC_START accel Accelerator / reverse proxy mode + tproxy Support Linux TPROXY for spoofing outgoing + connections using the client IP address. + NP: disables authentication and maybe IPv6 on the port. + + ssl-bump Intercept each SSL request matching ssl_bump ACL, + establish secure connection with the client and with + the server, decrypt HTTP messages as they pass through + Squid, and treat them as unencrypted HTTP messages, + becoming the man-in-the-middle. + + The ssl_bump option is required to fully enable + the SslBump feature. + + Requires tproxy. + Omitting the mode flag causes default forward proxy mode to be used. @@ -1573,6 +1588,25 @@ DOC_START sslcontext= SSL session ID context identifier. + generate-host-certificates[=] + Dynamically create SSL server certificates for the + destination hosts of bumped SSL requests.When + enabled, the cert and key options are used to sign + generated certificates. Otherwise generated + certificate will be selfsigned. + If there is CA certificate life time of generated + certificate equals lifetime of CA certificate. If + generated certificate is selfsigned lifetime is three + years. + This option is enabled by default when SslBump is used. + See the sslBump option above for more information. + + dynamic_cert_mem_cache_size=SIZE + Approximate total RAM size spent on cached generated + certificates. If set to zero, caching is disabled. The + default value is 4MB. An average XXX-bit certificate + consumes about XXX bytes of RAM. + DOC_END NAME: tcp_outgoing_tos tcp_outgoing_ds tcp_outgoing_dscp diff --git a/src/client_side.cc b/src/client_side.cc index 61e844ec43..22a13bf7e6 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -2089,15 +2089,15 @@ prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, char *url, int url_sz = strlen(url) + 32 + Config.appendDomainLen + strlen(host); http->uri = (char *)xcalloc(url_sz, 1); - snprintf(http->uri, url_sz, "http://%s%s", /*conn->port->protocol,*/ host, url); + snprintf(http->uri, url_sz, "%s://%s%s", conn->port->protocol, host, url); debugs(33, 5, "TRANSPARENT HOST REWRITE: '" << http->uri <<"'"); } else { /* Put the local socket IP address as the hostname. */ int url_sz = strlen(url) + 32 + Config.appendDomainLen; http->uri = (char *)xcalloc(url_sz, 1); http->getConn()->clientConnection->local.ToHostname(ipbuf,MAX_IPSTRLEN), - snprintf(http->uri, url_sz, "http://%s:%d%s", - // http->getConn()->port->protocol, + snprintf(http->uri, url_sz, "%s://%s:%d%s", + http->getConn()->port->protocol, ipbuf, http->getConn()->clientConnection->local.GetPort(), url); debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'"); } @@ -2541,8 +2541,8 @@ clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *c * from the port settings to the request. */ if (http->clientConnection != NULL) { - request->flags.intercepted = (http->clientConnection->flags & COMM_INTERCEPTION); - request->flags.spoof_client_ip = (http->clientConnection->flags & COMM_TRANSPARENT); + request->flags.intercepted = ((http->clientConnection->flags & COMM_INTERCEPTION) != 0); + request->flags.spoof_client_ip = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; } if (internalCheck(request->urlpath.termedBuf())) { @@ -3411,6 +3411,71 @@ clientNegotiateSSL(int fd, void *data) conn->readSomeData(); } +/** + * Initializes an ssl connection to squid. + * In the case the SSL_CTX is not given it calls the ConnStateData::switchToHttps method + * to start procedure to generate a dynamic SSL_CTX + */ +static void +httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext) +{ + SSL *ssl = NULL; + assert(connState); + const Comm::ConnectionPointer &details = connState->clientConnection; + + if (sslContext && !(ssl = httpsCreate(details, sslContext))) + return; + + typedef CommCbMemFunT TimeoutDialer; + AsyncCall::Pointer timeoutCall = JobCallback(33, 5, + TimeoutDialer, connState, ConnStateData::requestTimeout); + commSetConnTimeout(details, Config.Timeout.request, timeoutCall); + + if (ssl) + Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); + else { + char buf[MAX_IPSTRLEN]; + debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX"); + connState->switchToHttps(details->local.NtoA(buf, sizeof(buf))); + } +} + +/** + * A callback function to use with the ACLFilledChecklist callback. + * In the case of ACCES_ALLOWED answer initializes an ssl bumped connection, + * else revert the connection to tunnel mode. + */ +static void +httpsSslBumpAccessCheckDone(allow_t answer, void *data) +{ + ConnStateData *connState = (ConnStateData *) data; + + //if connection closed/closing just return. + if (!connState->isOpen()) + return; + + if (answer == ACCESS_ALLOWED) { + debugs(33, 2, HERE << " sslBump done data: " << connState->clientConnection); + httpsEstablish(connState, NULL); + } else { + // fake a CONNECT request to force connState to tunnel + + debugs(33, 2, HERE << " normal SSL connection: " << connState->clientConnection << " revert to tunnel mode"); + static char ip[MAX_IPSTRLEN]; + static char reqStr[MAX_IPSTRLEN + 80]; + connState->clientConnection->local.NtoA(ip, sizeof(ip)); + snprintf(reqStr, sizeof(reqStr), "CONNECT %s:%d\r\n\r\n", ip, connState->clientConnection->local.GetPort()); + bool ret = connState->handleReadData(reqStr, strlen(reqStr)); + if (ret) + ret = connState->clientParseRequests(); + + if (!ret) { + debugs(33, 2, HERE << "Failed to start fake CONNECT request for ssl bumped connection: " << connState->clientConnection); + connState->clientConnection->close(); + } + } +} + /** handle a new HTTPS connection */ static void httpsAccept(const CommAcceptCbParams ¶ms) @@ -3423,11 +3488,6 @@ httpsAccept(const CommAcceptCbParams ¶ms) return; } - SSL_CTX *sslContext = s->staticSslContext.get(); - SSL *ssl = NULL; - if (!(ssl = httpsCreate(params.conn, sslContext))) - return; - debugs(33, 4, HERE << params.conn << " accepted, starting SSL negotiation."); fd_note(params.conn->fd, "client https connect"); @@ -3440,12 +3500,33 @@ httpsAccept(const CommAcceptCbParams ¶ms) // Socket is ready, setup the connection manager to start using it ConnStateData *connState = connStateCreate(params.conn, &s->http); - typedef CommCbMemFunT TimeoutDialer; - AsyncCall::Pointer timeoutCall = JobCallback(33, 5, - TimeoutDialer, connState, ConnStateData::requestTimeout); - commSetConnTimeout(params.conn, Config.Timeout.request, timeoutCall); + if (s->sslBump) { + assert(params.conn->flags & COMM_TRANSPARENT); + + debugs(33, 5, "httpsAccept: accept transparent connection: " << params.conn); - Comm::SetSelect(params.conn->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); + if (!Config.accessList.ssl_bump) { + httpsSslBumpAccessCheckDone(ACCESS_DENIED, connState); + return; + } + + // Create a fake HTTP request for ssl_bump ACL check, + // using tproxy-provided destination IP and port. + HttpRequest *request = new HttpRequest(); + static char ip[MAX_IPSTRLEN]; + request->SetHost(params.conn->local.NtoA(ip, sizeof(ip))); + request->port = params.conn->local.GetPort(); + request->myportname = s->name; + + ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL); + acl_checklist->src_addr = params.conn->remote; + acl_checklist->my_addr = s->s; + acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, connState); + return; + } else { + SSL_CTX *sslContext = s->staticSslContext.get(); + httpsEstablish(connState, sslContext); + } } void diff --git a/src/client_side_request.cc b/src/client_side_request.cc index d4bbe39ce3..27b8e842d7 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -1257,6 +1257,7 @@ bool ClientRequestContext::sslBumpAccessCheck() { if (http->request->method == METHOD_CONNECT && + !http->request->flags.spoof_client_ip && // is not a fake ssl-bumped request from a https port Config.accessList.ssl_bump && http->getConn()->port->sslBump) { debugs(85, 5, HERE << "SslBump possible, checking ACL"); diff --git a/src/tunnel.cc b/src/tunnel.cc index 3344f79926..03a1d814eb 100644 --- a/src/tunnel.cc +++ b/src/tunnel.cc @@ -479,11 +479,24 @@ TunnelStateData::copyRead(Connection &from, IOCB *completion) comm_read(from.conn, from.buf, from.bytesWanted(1, SQUID_TCP_SO_RCVBUF), call); } +/** + * Set the HTTP status for this request and sets the read handlers for client + * and server side connections. + */ +static void +tunnelStartShoveling(TunnelStateData *tunnelState) +{ + *tunnelState->status_ptr = HTTP_OK; + if (cbdataReferenceValid(tunnelState)) { + tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer); + tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); + } +} + /** * All the pieces we need to write to client and/or server connection * have been written. - * - Set the HTTP status for this request. - * - Start the blind pump. + * Call the tunnelStartShoveling to start the blind pump. */ static void tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t flag, int xerrno, void *data) @@ -497,11 +510,7 @@ tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t return; } - *tunnelState->status_ptr = HTTP_OK; - if (cbdataReferenceValid(tunnelState)) { - tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer); - tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); - } + tunnelStartShoveling(tunnelState); } /* @@ -512,9 +521,14 @@ tunnelConnected(const Comm::ConnectionPointer &server, void *data) { TunnelStateData *tunnelState = (TunnelStateData *)data; debugs(26, 3, HERE << server << ", tunnelState=" << tunnelState); - AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", - CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); - Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL); + + if (tunnelState->request && tunnelState->request->flags.spoof_client_ip) + tunnelStartShoveling(tunnelState); // ssl-bumped connection, be quiet + else { + AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", + CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); + Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL); + } } static void