Varnish and ESI HTTP AUTH - http

I'm very lost on this problem, and I don't know where could be the problem, so, I hope that you could help me.
I have an HTTP BASIC authentification with symfony, and I'm trying to reach an url which is protected by this auth, with an tag in a Drupal page. Every requests are send to a Varnish
I give username and password in the url like that :
<esi:include src="http://admin:adminpass#api.dev:8081/app.php/next"/>
In my varnish configuration file, I have only that lines for auth.http:
if (req.http.Authorization) {
return (pass);
}
My backend for Symfony is working well without http authentification, and the http authentification is working well when there's not Varnish and esi tag.
If anyone have an idea of the problem, please, tell me, even if it's wrong =)

ESI in varnish doesn't work like an iframe or link tag in a browser in that it doesn't connect to whatever url you give it. ESI just starts a new request within varnish and goes through the workflow (vcl_recv, etc).
You are expecting varnish to act like an http client, parsing the url, setting the authorization header, setting a host header to api.dev:8081 and initiating a new http connection/request which it will not. In this case, my guess is it starts a new req with req.url set to /app.php/next inheriting the headers from the request for the parent resource (containing the esi tag) or possibly just ignores the esi tag completely.
The way to accomplish what you want to do is (in vcl_recv):
if (req.esi_level > 0 && req.url == "/app.php/next") {
set req.http.Authorization = "BASIC [base64 encoded admin:adminpass]"
return (pass);
}
and then the esi tag should look like <esi:include src="/app.php/next" />
If you need the ESI request to hit a different backend server, you need to add that server as a different named backend:
backend authorization_needed {
.host = "api.dev";
.port = "8081";
}
and in vcl_recv, tell varnish to use it for esi requests:
if (req.esi_level > 0 && req.url == "/app.php/next") {
set req.http.Authorization = "BASIC [base64 encoded admin:adminpass]"
set req.backend = authorization_needed;
return (pass);
}
you may also need to set req.http.Host in that if block if the backend responds to a different virtual host than "api.dev".
Update:
Since basic authorization is coming from the client, and you are calling return (pass) when req.http.Authorization is present, varnish will not ESI process those pages. You must explicitly enable esi in vcl_fetch() which is not called when you pass.
So to pass authorization for the ESI fragments but not for the parent page, change in vcl_rev:
if (req.http.Authorization && req.esi_level == 0) {
set req.http.X-Esi-Authorization = req.http.Authorization;
unset req.http.Authorization;
}
else if (req.http.X-Esi-Authorization && req.esi_level > 0 ) {
set req.http.Authorization = req.http.X-Esi-Authorization;
return (pass);
}
And add to vcl_fetch:
if (req.http.X-Esi-Authorization) {
set beresp.do_esi = true;
}
The net effect is the parent response is cacheable and will process esi, the esi fragments themselves will always be passed to the backend with the client's authorization header.

Related

Varnish : Cluster - Purge cache on all varnish hosts

I am using the (free) version of varnsih (version 6).
Here is my configuration :
2 varnish hosts (varnish_A, varnish_B)
2 webs servers (node_A, node_B)
I only cache all the pages /app/api/client (GET) from my web servers and it's working very well
But I have a problem, when there is an update on the page (from a PUT) /app/api/client/test_data I added a BAN in the sub vcl_recv part
if (req.url ~ "^app/api/client($|/.*)" && ( req.method == "POST" || req.method == "PUT" )) {
ban("req.http.host == " + req.http.host + " && req.url == " + req.url);
std.syslog(180, "[debug]["+req.method+"] - Starting Purge of the cache for: " + req.http.host + req.url );
return (pass);
set req.backend_hint = web_node.backend();
}
The ban is working, but only on the current varnish host (for example varnish_B), so the other one (varnish_A) still has the old version of the object...
Does any one know how to "share" the ban on all host ?
Thank you !
Send a ban to both Varnish servers
It's just a matter of running the ban on both Varnish servers, rather than on one. You can probably build that into your content management flow where the 2 endpoints are called.
Varnish Broadcaster
Alternatively you could sign up for Varnish Enterprise and use the Varnish Broadcaster as your banning endpoint. It will distribute the request to all or to specific Varnish nodes. See https://docs.varnish-software.com/varnish-broadcaster/examples/ for examples.
Optimizing your bans
Just a sidenote, using req.url and req.http.host is not ideal for bans. When the ban is executed, the ban lurker doesn't have access to these variables, which means the banning is the responsibility of the next request.
A way to circumvent this limitation is by adding the following VCL code:
sub vcl_backend_response {
set beresp.http.x-url = bereq.url;
set beresp.http.x-host = bereq.http.host;
}
sub vcl_deliver {
unset resp.http.x-url;
unset resp.http.x-host;
}
You could then run the following ban:
ban("obj.http.x-host ~ " + req.http.host + " && obj.http.x-url == " + req.url);
See https://www.varnish-software.com/developers/tutorials/ban/ for a full tutorial on Varnish bans.

Nginx returning 404 when accessing it through varnish server over public IP

I'm trying to serve static contents through varnish(client<-varnish<-nginx) but I'm having an issue accessing it through public IP. Varnish is working fine when accessing it locally (where varnish is running) but throws status 404 when accessing it through public IP.
My flow looks like this:
Client--> Caching_Server[NGINX(used for SSL support only) -> Varnish] --> Origin_Server[Nginx]
My varnish server config is as simple as:
probe healthcheck {
.url = "http://10.10.10.3/healthcheck";
.timeout = 2s;
.interval = 30s;
.window = 5;
.threshold = 3;
}
backend default {
.host = "10.10.10.3";
.port = "80";
.probe = healthcheck;
}
sub vcl_recv {
if (req.url ~ ".ts$") {
unset req.http.Cookie;
}
set req.backend_hint = default;
}
I also checked the Nginx logs and I see that it's throwing 404 which explains why varnish is responding with 404. But my question is why is it working when I test it locally using localhost.
The output from varnishlog -g request -q "ReqUrl eq '/'" will probably give you the answer to your question.
Run the command while sending a request internally, and a request from the internet. Compare the output, and see where there's a difference.
If you need help, add the logs of both requests to your question and I'll help you examine the situation.

Apache2.49 cookies not working via my ProxyPass VirtualHost

In the apache virtualHost i have these commands:
ProxyPass "/s" "http://127.0.0.1:3001"
ProxyPassReverse "/s" "http://127.0.0.1:3001"
RewriteRule ^/s/(.*) http://127.0.0.1:3001/$1 [P,L]
ProxyPassReverseCookiePath "/" "/"
The backend server is NodeJS. The proxy itself works fine. The problem is that the Node is sending a set-cookie in the HTTP header (session ID) but the browser seems to ignore it. I tested with Chromium and Firefox but none creates the cookie. I tried to change the virtualhost configuration but nothing appears to solve the problem The set-cookie command is:
set-cookie: sid=s%3AhgHWDO3D...BBUZbbOA; Path=/; HttpOnly; Secure;HttpOnly;Secure
I need your help to solve this problem. Thank you.
UPDATE
If the url is containing a direct request for the Node:
https://example.com/s/backend
it works. It creates the session is cookie. But if this URL is called from a AJAX request in the JS, it does not create the cookie.
The https://example.com load a HTML with a script load of a JS file. That JS file makes the AJAX call to the backend using the path https://example.com/s/something and in this case the cookie is never created.
Any suggestions?
UPDATE
I discovered that the problem is when i use the Fetch API to retrieve a JSON file. This code running does not create the session ID cookie:
fetch("https://localbestbatteriesonline.com/s/p.json?0103")
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
But if i have this code, it creates the cookie:
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
}
};
xhttp.open("GET", "https://localbestbatteriesonline.com/s/p.json?0103", true);
xhttp.send();
Analysing the requests, both are exactly the same. Both receive the cookie to create.
Any ideas why with the fetch does not work?
Problem solved. Using the Fetch API does not include the cookies exchange like it does in the XMLHttpRequest. Therefor, it does not create the session id cookie. To enable this, the Fetch call must have the option:
credentials:"same-origin".
fetch("https://localbestbatteriesonline.com/s/p.json?0103",{credentials:"same-origin"})
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
Now it works.

Varnish caching with cookies

Just new to Varnish. It's getting hard, more than expected :-(
I'm trying to improve some php code, developed some time ago, using varnish.
This code uses just two cookies: PHPSESSID and LANGUAGE
All pages set PHPSESSID cookie if it's not defined. However this cookie for anonymous sessions it's only used in one page.
Let's say I have Page1, Page2, Page3 and Page4. My configuration should be as follows:
Page1, Page2 and Page3 need LANGUAGE cookie and should be cached with that cookie: one cache for each language and page.
Page4 needs PHPSESSID and LANGUAGE cookies, and shouldn't be cached as it's specific for each user.
My default.vlc is not working properly, so any orientation would be really apreciated. Maybe I've missunderstood some concepts.
sub vcl_init {
# When requests come to Varnish I need to remove PHPSESSID so it's not used for the hash in caching. Page4 doesn't need caching as it's specific for each user:
if (req.http.host ~ "Page4") {
return(pass);
}
# remove PHPSESSID so pages1, 2, and 3 get cached just once for everyuser but in all languages.
if ((req.url !~ "page4")) {
set req.http.Cookie = regsuball(req.http.Cookie, "PHPSESSID=[^;]+(; )?", "");
}
return (hash);
}
I need to cache webpages with LANGUAGE cookie so I include it in vcl_hash:
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
# hash cookies for requests that have them
if (req.http.Cookie) {
hash_data(req.http.Cookie);
}
}
How can I remove just PHPSESSIONID?
sub vcl_backend_response {
# Called after the response headers has been successfully retrieved from the backend.
if (!(bereq.url ~ "Page4")) {
unset beresp.http.set-cookie;
}
return (deliver);
}
You are on the right track. If I understand your question, the thing that does not work is that you remove all cookies server response (if it's not Page4) instead of just removing PHPSESSID.
You can do a regexp in your sub vcl_backend_response to remove only phpsessionid if url is not Page4.
beresp.http.set-cookie = regsuball(beresp.http.set-cookie, "PHPSESSID=[^;]+(; )?", "")
Alternatively, if you use varnish 4 or above, you should use the vmod cookie which makes cookie handling much easier (no more need for regexp).

Varnish returning error too many redirects

I'm attempting to get Varnish to cache two different domains with blogs, but upon adding the second one, the previous one stops working,
The basic setup is as following:
backend default {
.host = "127.0.0.1";
.port = "81";
}
backend onedomain {
.host = "127.0.0.1";
.port = "81";
}
backend newdomain {
.host = "127.0.0.1";
.port = "81";
}
acl purge {
"localhost";
}
sub vcl_recv {
# Happens before we check if we have this in cache already.
#
# Typically you clean up the request here, removing cookies you don't need,
# rewriting the request, etc.
#Bypass large files
if (req.http.x-pipe-mark && req.restarts > 0) {
return(pipe);
}
# all domains in here will return a "pass" which means they won't be cached
if (req.http.host ~ "(www\.)?(domain1.com|domain2.com)") {
return (pass);
}
#else check if something we're going to cache
else if(req.http.host ~ "(www\.)?(onedomain.nu)") {
set req.http.host = "onedomain.com";
set req.backend_hint = onedomain;
}
else if(req.http.host ~ "(www\.)?(newdomain.com)") {
set req.http.host = "newdomain.com";
set req.backend_hint = newdomain;
}
else {
return (pass);
}
Newdomain loads fine while domain4 just sends me to an infinite redirect loop (according to the chrome error)
I added the full config in a pastebin: http://pastebin.com/J1Hb76dZ
I realize Varnish doesn't send any redirect commands itself, the site works on the old configuration, it's only when I try this that the redirect issue arises on one of the websites.
Is there anyone that has experience with this happening and can suggest what to do?
Old question, but try modifying the vcl_hash subroutine. This worked for me on a single site, that included multiple redirects for http -> https and domain.com -> www.domain.com. It should also configure the hash to tell your different domains apart, as that was necessary for me to store all the redirects separate from the site data that caused the dreaded "too many redirects" errors. You may need to adjust/remove the X-Forwarded-Proto header as I am behind a load balancer.
sub vcl_hash {
hash_data(req.http.host);
hash_data(req.url);
hash_data(req.http.X-Forwarded-Proto);
return(hash);
}

Resources