1

I have a private intranet style website, where everyone must login before seeing any content. Once they're logged in, every page is the same - so I want these pages cached in Varnish, but still with a quick user access check.

So I was thinking to do this, I would create a rule in my VCL file that rewrites each incoming request to one file. This file is not cached and checks if the user is valid, if so it prints a esi include of the cached page.

This is all okay, except for identifying the second request for the cached page as being authenticated. I was thinking of adding a query string to the request, and checking for that. Or maybe there's a way to check if a request has been made through an esi:include. Perhaps I'm approaching this in the wrong way?

Any suggestions?

leon.nk
  • 437
  • 2
  • 6
  • 15

3 Answers3

1

If you don't want to use an authenticated request with an auth token in the URL, you can check req.esi_level and ensure that it's greater than 0 for the resources that require login.

if (req.esi_level == 0 && req.url ~ "^/private/.*" ) {
    error (403);
}

The caveat is that you need to prevent access to your backend from everything except varnish -- which you were probably doing anyway but it's worth noting.

Johnny C
  • 1,799
  • 1
  • 16
  • 27
  • Thanks, that is good to know about req.esi_level. Wouldn't another caveat be this would break if you were using esi for anything else? – leon.nk Aug 09 '13 at 10:27
  • 1
    I ended up going with a solution based on this: http://joshwaihi.com/content/authenticated-page-caching-varnish-drupal where the request is diverted to another url that isn't cached and returns a header that determines authentication. The request is then restarted within vcl and the page can be served from cache. – leon.nk Aug 09 '13 at 10:28
  • This wouldn't "break" anything else, as it only intervenes when a client requests /private/* directly-- which is what you stated that you were trying to prevent. All other scenarios will not match the if statement. If your business rules are more complex, you'll need to update the VCL to match. I'm glad you found another solution, but it seems pretty complicated for what you are trying to do imho. – Johnny C Aug 09 '13 at 16:46
  • Well there are a few more requirements that I hadn't mentioned in my original post. One of which was that the 403 page should be served from Apache. Plus the solution fits nicely with the application (which has already been built) requiring minimal modification. – leon.nk Aug 15 '13 at 12:34
0

If you only want basic auth check you may use plain HTTP authentication as described on http://blog.tenya.me/blog/2011/12/14/varnish-http-authentication/ :

User:pass

$echo -n "foo:bar" | base64

VCL

sub vcl_fetch {
# ...
  if (! req.http.Authorization ~ "Basic Zm9vOmJhcgo=")
  {
    error 401 "Restricted";
  }
# ...
}

sub vcl_error {
# ...
  if (obj.status == 401) {
    set obj.http.Content-Type = "text/html; charset=utf-8";
    set obj.http.WWW-Authenticate = "Basic realm=Secured";
    synthetic {" 
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
    <HTML>
    <HEAD>
    <TITLE>Error</TITLE>
    <META HTTP-EQUIV='Content-Type' CONTENT='text/html;'>
    </HEAD>
    <BODY><H1>401 Unauthorized (varnish)</H1></BODY>
    </HTML>
    "};
    return (deliver);
  }
# ...
}

If you prefer your current approach you always can set a cookie on the first request and later check if the cookie is present.

NITEMAN
  • 1,236
  • 10
  • 11
0

Here's what I've tried in a dev env, I'm not yet sure if we'll use this in production.

Varnish config

probe checkslash {
    .url = "/robots.txt";
    .interval = 500s;
    .timeout = 10s;
}    

include "backends.vcl";

/** generic config from here down */
sub vcl_recv{

    /* if the drupals are down, this is how long we cache for */
    set req.grace = 6h;

    /* Make sure we direct 443 traffic to the secure drupal */
    if (server.port == 443 ) {
      set req.backend = drpau_ssl_director;

   } else {
      /* port 80 traffic goes to the correct LB */
      set req.backend = drpau_director;
    }
 # just pass through non-page files, and the login page
   if (req.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|csv|png|gif|jpeg|jpg|ico|swf|css|js|htc|ejs)(\?.*)?$") {
       } else if (req.url ~ "(?i)(sites/default/files)|(js/)|(/login)" ) { 
    } else if (req.esi_level == 0 ) {
     # pass regular pages to a spoecial url
     set req.url = "/esi" + req.url;
   }
    return (lookup);
}



sub vcl_fetch {

 if (req.url ~ "/esi/" && req.esi_level == 0 ) {
       set beresp.do_esi = true; /* Do ESI processing               */ 
    }

}

Then in apache I redirect all requests for pages that come via the esi prefix

RewriteRule ^esi/(.*)$ test.php [L]

and test php is

<?php 
define('DRUPAL_ROOT', getcwd());
// We prepare only a minimal bootstrap.
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
global $user;
$roles = user_roles();

if (in_array('anonymous user', $user->roles)) {
  $uri = preg_replace('#^/esi#', '', $_SERVER[REQUEST_URI]);
  echo "<esi:include src=\"http://$_SERVER[SERVER_NAME]$uri\"/>";
} else {
    header("Location: https://$_SERVER[SERVER_NAME]/login");
}
Sean Burlington
  • 873
  • 10
  • 13