From 0d901ef43d218267e44c578fa2f67634de00663c Mon Sep 17 00:00:00 2001 From: Steve Hill Date: Sun, 12 May 2013 21:57:03 -0600 Subject: [PATCH] Add spoof_client_ip access control ... to control whether TPROXY requests have their source IP address spoofed by Squid. The ACL defaults to allow (i.e. the current behaviour), but using an ACL that results in a deny result will disable spoofing for that request. Example config to disable spoofing for all requests: spoof_client_ip deny all Also, polish the TPROXY related flags: 1. The flags.spoofClientIp flag was a general "this is a TPROXY request" flag, which was a bit confusing given the name of the flag. So the flags.spoofClientIp flag now only indicates whether we want to spoof the source IP or not. 2. The flags.interceptTproxy is added to flag whether the request was received on a "tproxy" port or not. --- src/HttpRequest.cc | 2 +- src/RequestFlags.h | 6 +++++- src/SquidConfig.h | 4 ++++ src/acl/DestinationIp.cc | 2 +- src/auth/Acl.cc | 2 +- src/cache_cf.cc | 3 +-- src/cf.data.pre | 21 +++++++++++++++++++++ src/client_side.cc | 28 +++++++++++++++++++++++----- src/client_side_request.cc | 4 ++-- src/format/Format.cc | 2 +- src/forward.cc | 2 +- src/peer_select.cc | 4 ++-- src/tunnel.cc | 2 +- 13 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/HttpRequest.cc b/src/HttpRequest.cc index c064124bba..f718da1bdf 100644 --- a/src/HttpRequest.cc +++ b/src/HttpRequest.cc @@ -590,7 +590,7 @@ HttpRequest::maybeCacheable() // Because it failed verification, or someone bypassed the security tests // we cannot cache the reponse for sharing between clients. // TODO: update cache to store for particular clients only (going to same Host: and destination IP) - if (!flags.hostVerified && (flags.intercepted || flags.spoofClientIp)) + if (!flags.hostVerified && (flags.intercepted || flags.interceptTproxy)) return false; switch (protocol) { diff --git a/src/RequestFlags.h b/src/RequestFlags.h index a6e36abe65..f8ab00f958 100644 --- a/src/RequestFlags.h +++ b/src/RequestFlags.h @@ -87,7 +87,11 @@ public: bool intercepted :1; /** set if the Host: header passed verification */ bool hostVerified :1; - /** request to spoof the client ip */ + /// Set for requests handled by a "tproxy" port. + bool interceptTproxy :1; + /// The client IP address should be spoofed when connecting to the web server. + /// This applies to TPROXY traffic that has not had spoofing disabled through + /// the spoof_client_ip squid.conf ACL. bool spoofClientIp :1; /** set if the request is internal (\see ClientHttpRequest::flags.internal)*/ bool internal :1; diff --git a/src/SquidConfig.h b/src/SquidConfig.h index b64ae89356..3d3cfaaf3b 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -403,6 +403,10 @@ public: #if ICAP_CLIENT acl_access* icap; #endif + + /// spoof_client_ip squid.conf acl. + /// nil unless configured + acl_access* spoof_client_ip; } accessList; AclDenyInfoList *denyInfoList; diff --git a/src/acl/DestinationIp.cc b/src/acl/DestinationIp.cc index 86825b74f4..c2a4f53259 100644 --- a/src/acl/DestinationIp.cc +++ b/src/acl/DestinationIp.cc @@ -56,7 +56,7 @@ ACLDestinationIP::match(ACLChecklist *cl) // Bypass of browser same-origin access control in intercepted communication // To resolve this we will force DIRECT and only to the original client destination. // In which case, we also need this ACL to accurately match the destination - if (Config.onoff.client_dst_passthru && (checklist->request->flags.intercepted || checklist->request->flags.spoofClientIp)) { + if (Config.onoff.client_dst_passthru && (checklist->request->flags.intercepted || checklist->request->flags.interceptTproxy)) { assert(checklist->conn() && checklist->conn()->clientConnection != NULL); return ACLIP::match(checklist->conn()->clientConnection->local); } diff --git a/src/auth/Acl.cc b/src/auth/Acl.cc index e388d68496..8f664d54eb 100644 --- a/src/auth/Acl.cc +++ b/src/auth/Acl.cc @@ -34,7 +34,7 @@ AuthenticateAcl(ACLChecklist *ch) } else if (request->flags.accelerated) { /* WWW authorization on accelerated requests */ headertype = HDR_AUTHORIZATION; - } else if (request->flags.intercepted || request->flags.spoofClientIp) { + } else if (request->flags.intercepted || request->flags.interceptTproxy) { debugs(28, DBG_IMPORTANT, "NOTICE: Authentication not applicable on intercepted requests."); return ACCESS_DENIED; } else { diff --git a/src/cache_cf.cc b/src/cache_cf.cc index dc7d899356..6d15433564 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -3601,8 +3601,7 @@ parse_port_option(AnyP::PortCfg * s, char *token) s->flags.tproxyIntercept = true; Ip::Interceptor.StartTransparency(); /* Log information regarding the port modes under transparency. */ - debugs(3, DBG_IMPORTANT, "Starting IP Spoofing on port " << s->s); - debugs(3, DBG_IMPORTANT, "Disabling Authentication on port " << s->s << " (IP spoofing enabled)"); + debugs(3, DBG_IMPORTANT, "Disabling Authentication on port " << s->s << " (TPROXY enabled)"); if (!Ip::Interceptor.ProbeForTproxy(s->s)) { debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: TPROXY support in the system does not work."); diff --git a/src/cf.data.pre b/src/cf.data.pre index 808063e669..5ea1568b9b 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1224,6 +1224,26 @@ DOC_START sources is required to prevent abuse of your proxy. DOC_END +NAME: spoof_client_ip +TYPE: acl_access +LOC: Config.accessList.spoof_client_ip +DEFAULT: none +DEFAULT_DOC: Allow spoofing on all TPROXY traffic. +DOC_START + Control client IP address spoofing of TPROXY traffic based on + defined access lists. + + spoof_client_ip allow|deny [!]aclname ... + + If there are no "spoof_client_ip" lines present, the default + is to "allow" spoofing of any suitable request. + + Note that the cache_peer "no-tproxy" option overrides this ACL. + + This clause supports fast acl types. + See http://wiki.squid-cache.org/SquidFaq/SquidAcl for details. +DOC_END + NAME: http_access TYPE: acl_access LOC: Config.accessList.http @@ -2960,6 +2980,7 @@ DOC_START no-tproxy Do not use the client-spoof TPROXY support when forwarding requests to this peer. Use normal address selection instead. + This overrides the spoof_client_ip ACL. proxy-only objects fetched from the peer will not be stored locally. diff --git a/src/client_side.cc b/src/client_side.cc index f9061a3caf..18eadc8912 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -2766,7 +2766,16 @@ clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *c */ if (http->clientConnection != NULL) { request->flags.intercepted = ((http->clientConnection->flags & COMM_INTERCEPTION) != 0); - request->flags.spoofClientIp = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; + request->flags.interceptTproxy = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; + if (request->flags.interceptTproxy) { + if (Config.accessList.spoof_client_ip) { + ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.spoof_client_ip, http); + request->flags.spoofClientIp = (checklist->fastCheck() == ACCESS_ALLOWED); + delete checklist; + } else + request->flags.spoofClientIp = true; + } else + request->flags.spoofClientIp = false; } if (internalCheck(request->urlpath.termedBuf())) { @@ -3658,7 +3667,7 @@ httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext, Ssl::BumpMode bum else { char buf[MAX_IPSTRLEN]; assert(bumpMode != Ssl::bumpNone && bumpMode != Ssl::bumpEnd); - HttpRequest *fakeRequest = new HttpRequest; + HttpRequest::Pointer fakeRequest(new HttpRequest); fakeRequest->SetHost(details->local.NtoA(buf, sizeof(buf))); fakeRequest->port = details->local.GetPort(); fakeRequest->clientConnectionManager = connState; @@ -3667,10 +3676,19 @@ httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext, Ssl::BumpMode bum fakeRequest->indirect_client_addr = connState->clientConnection->remote; #endif fakeRequest->my_addr = connState->clientConnection->local; - fakeRequest->flags.spoofClientIp = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; + fakeRequest->flags.interceptTproxy = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; fakeRequest->flags.intercepted = ((connState->clientConnection->flags & COMM_INTERCEPTION) != 0); + fakeRequest->myportname = connState->port->name; + if (fakeRequest->flags.interceptTproxy) { + if (Config.accessList.spoof_client_ip) { + ACLFilledChecklist checklist(Config.accessList.spoof_client_ip, fakeRequest.getRaw(), NULL); + fakeRequest->flags.spoofClientIp = (checklist.fastCheck() == ACCESS_ALLOWED); + } else + fakeRequest->flags.spoofClientIp = true; + } else + fakeRequest->flags.spoofClientIp = false; debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX"); - connState->switchToHttps(fakeRequest, bumpMode); + connState->switchToHttps(fakeRequest.getRaw(), bumpMode); } } @@ -4205,7 +4223,7 @@ clientListenerConnectionOpened(AnyP::PortCfg *s, const Ipc::FdNoteId portTypeNot debugs(1, DBG_IMPORTANT, "Accepting " << (s->flags.natIntercept ? "NAT intercepted " : "") << - (s->flags.tproxyIntercept ? "TPROXY spoofing " : "") << + (s->flags.tproxyIntercept ? "TPROXY intercepted " : "") << (s->flags.tunnelSslBumping ? "SSL bumped " : "") << (s->flags.accelSurrogate ? "reverse-proxy " : "") << FdNote(portTypeNote) << " connections at " diff --git a/src/client_side_request.cc b/src/client_side_request.cc index 97f858d39d..4d5054a97d 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -670,7 +670,7 @@ ClientRequestContext::hostHeaderVerify() } debugs(85, 3, HERE << "validate host=" << host << ", port=" << port << ", portStr=" << (portStr?portStr:"NULL")); - if (http->request->flags.intercepted || http->request->flags.spoofClientIp) { + if (http->request->flags.intercepted || http->request->flags.interceptTproxy) { // verify the Host: port (if any) matches the apparent destination if (portStr && port != http->getConn()->clientConnection->local.GetPort()) { debugs(85, 3, HERE << "FAIL on validate port " << http->getConn()->clientConnection->local.GetPort() << @@ -970,7 +970,7 @@ clientHierarchical(ClientHttpRequest * http) const wordlist *p = NULL; // intercepted requests MUST NOT (yet) be sent to peers unless verified - if (!request->flags.hostVerified && (request->flags.intercepted || request->flags.spoofClientIp)) + if (!request->flags.hostVerified && (request->flags.intercepted || request->flags.interceptTproxy)) return 0; /* diff --git a/src/format/Format.cc b/src/format/Format.cc index 4ea43ff474..09d976088d 100644 --- a/src/format/Format.cc +++ b/src/format/Format.cc @@ -383,7 +383,7 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS case LFT_LOCAL_LISTENING_IP: { // avoid logging a dash if we have reliable info const bool interceptedAtKnownPort = al->request ? - (al->request->flags.spoofClientIp || + (al->request->flags.interceptTproxy || al->request->flags.intercepted) && al->cache.port : false; if (interceptedAtKnownPort) { diff --git a/src/forward.cc b/src/forward.cc index d872d347c4..8ada85e5c0 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -151,7 +151,7 @@ void FwdState::start(Pointer aSelf) // Bug 3243: CVE 2009-0801 // Bypass of browser same-origin access control in intercepted communication // To resolve this we must force DIRECT and only to the original client destination. - const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.spoofClientIp); + const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.interceptTproxy); const bool useOriginalDst = Config.onoff.client_dst_passthru || (request && !request->flags.hostVerified); if (isIntercepted && useOriginalDst) { selectPeerForIntercepted(); diff --git a/src/peer_select.cc b/src/peer_select.cc index 897a06fe67..a1be9d5942 100644 --- a/src/peer_select.cc +++ b/src/peer_select.cc @@ -237,7 +237,7 @@ peerSelectDnsPaths(ps_state *psstate) // on intercepted traffic which failed Host verification const HttpRequest *req = psstate->request; const bool isIntercepted = !req->flags.redirected && - (req->flags.intercepted || req->flags.spoofClientIp); + (req->flags.intercepted || req->flags.interceptTproxy); const bool useOriginalDst = Config.onoff.client_dst_passthru || !req->flags.hostVerified; const bool choseDirect = fs && fs->code == HIER_DIRECT; if (isIntercepted && useOriginalDst && choseDirect) { @@ -339,7 +339,7 @@ peerSelectDnsResults(const ipcache_addrs *ia, const DnsLookupDetails &details, v if (psstate->paths->size() >= (unsigned int)Config.forward_max_tries) break; - // for TPROXY we must skip unusable addresses. + // for TPROXY spoofing we must skip unusable addresses. if (psstate->request->flags.spoofClientIp && !(fs->_peer && fs->_peer->options.no_tproxy) ) { if (ia->in_addrs[n].IsIPv4() != psstate->request->client_addr.IsIPv4()) { // we CAN'T spoof the address on this link. find another. diff --git a/src/tunnel.cc b/src/tunnel.cc index af4711824c..ec050ac299 100644 --- a/src/tunnel.cc +++ b/src/tunnel.cc @@ -553,7 +553,7 @@ tunnelConnected(const Comm::ConnectionPointer &server, void *data) TunnelStateData *tunnelState = (TunnelStateData *)data; debugs(26, 3, HERE << server << ", tunnelState=" << tunnelState); - if (tunnelState->request && (tunnelState->request->flags.spoofClientIp || tunnelState->request->flags.intercepted)) + if (tunnelState->request && (tunnelState->request->flags.interceptTproxy || tunnelState->request->flags.intercepted)) tunnelStartShoveling(tunnelState); // ssl-bumped connection, be quiet else { AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", -- 2.47.2