Skip to main content

You are here

Varnish

Hide Server and Web Application Information

First and foremost, this is security over obscurity. No relevant security improvement is gained by this, but rather just annoy or hopefully prevent automated bots or script kiddies from doing any future damage. By NO means it should be the only defensive mechanism for any site!

This site runs on Drupal. Drupal, by default returns Drupal specific http headers on every request. So I wanted to disabled this completely at the server level, in addition to the PHP and Apache information that is part of a typical stock LAMP configuration.

First, we start with PHP. The PHP X-Powered-By header can be disabled by disabling the expose_php option:

expose_php = Off

Next, is updating the default Server header set by Apache:

ServerTokens Prod

Finally, it's time to remove the X-Generator and X-Drupal-Cache specific Drupal headers.
Using Apache via mod_headers module:

<IfModule mod_headers.c>
     Header unset X-Generator
     Header unset X-Drupal-Cache
</IfModule>

Using Nginx via the headers more module:

more_clear_headers 'x-generator';
more_clear_headers 'x-drupal-cache';

Why stop here when I can set custom headers. So as a joke, I want to tell the world that my sites are powered by Unicors and I’m the hacker being it. Doing so is dead simple.

Nginx

add_header              X-Powered-By "Unicorns";
add_header              X-hacker "Alpha01";

Varnish (vcl_deliver)

set resp.http.X-Powered-By = "Unicorns";
set resp.http.X-hacker = "Alpha01";

Now, let's view my new http headers

alpha03:~ tony$ curl -I https://www.rubysecurity.org
HTTP/1.1 200 OK
Date: Thu, 24 Nov 2016 03:53:40 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Set-Cookie: __cfduid=d8fbf8e2a27fac74e782224db3fd3c86c1479959620; expires=Fri, 24-Nov-17 03:53:40 GMT; path=/; domain=.rubysecurity.org; HttpOnly
Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
Expires: Sun, 19 Nov 1978 05:00:00 GMT
Cache-Control: public, max-age=300
X-Content-Type-Options: nosniff
Content-Language: en
X-Frame-Options: SAMEORIGIN
Last-Modified: Tue, 22 Nov 2016 06:55:27 GMT
Vary: Cookie,Accept-Encoding
Front-End-Https: on
X-Powered-By: Unicorns
X-hacker: Alpha01
Server: cloudflare-nginx
CF-RAY: 3069ea499a6320ba-LAX

References:
http://php.net/manual/en/ini.core.php#ini.expose-php
http://httpd.apache.org/docs/2.4/mod/core.html#servertokens
https://www.drupal.org/node/982034#comment-4719282
http://nginx.org/en/docs/http/ngx_http_headers_module.html

Programming: 

Awesome Applications: 

Redirect to www domain in Varnish

I’m a domain name hoarder, over twenty domain names and counting. I’ve done plenty of 301 domain name redirects in the past at the Apache level using mod_rewrite. Hell, just about every registrar now in days gives you the ability to this really easily through them.

In my case, I opted to simply point DNS directly to my server. Since I’m using Varnish (3.x), it’s more efficient to do the actual domain name redirect in Varnish itself rather than Apache. Also not to mention, the actual configuration is easier to understand in Varnish than using mod_rewrite in my opinion.

Example Varnish 3.x config:

# Tony's shitty portfolio site
sub vcl_recv {
  if (req.http.host ~ "(?i)^(www.)?(tony|antonio)baltazar\.(com|org|net)") {
       set req.http.host = "www.antoniobaltazar.com";
       error 750 "http://" + req.http.host + req.url;
   }
}

sub vcl_error {
  if (obj.status == 750) {
    set obj.http.Location = obj.response;
    set obj.status = 301;
    return(deliver);
  }
}

On this example, any incoming request matching (?i)^(www.)?(tony|antonio)baltazar\.(com|org|net) will have the host request header set to www.antoniobaltazar.com. The error function call with the status method of 750 is simply used internally by Varnish, so we can track it at the vcl_error subroutine. I can see why in Varnish 4.x this is handled differently since, this is basically a synthetic internal Varnish request that we're working on and should not be handled in vcl_error.

Example Varnish 4 config

sub vcl_recv {
  if (req.http.host ~ "(?i)^(www.)?(tony|antonio)baltazar\.(com|org|net)") {
    return (synth(750, ""));
   }
}

sub vcl_synth {
    if (resp.status == 750) {
        set resp.status = 301;
        set resp.http.Location = "http://www.antoniobaltazar.com" + req.url;
        return(deliver);
    }
}

So in Varnish 4, the configuration is even easier to understand.

An additional benefit of using this configuration is that now I’m consolidating and increasing Varnish cache hit rate. This means that Varnish won’t have two different caches for antoniobaltazar.com and www.antoniobaltazar.com.

Awesome Applications: 

Locking Down WordPress Access with Varnish 3.x

I have Varnish in front of all my WordPress sites and configured all /wp-admin traffic use https via Nginx. See https://www.rubysecurity.org/wordpress_admin-ssl

So to lock down access to my WordPress site's requires both Varnish and Nginx configs to be modified.

Block at the http Varnish level:

sub vcl_recv {
    if ((req.url ~ "wp-(login|admin)") && (client.ip !~ MY-IP-ADDRESS)) {
                error 403 "Fuck off";
        }
}

Block at the https Nginx level (using shit.alpha01.org as an example):

                location /wp-admin {
                        allow   MY-IP-ADDRESS;
                        deny all;
                        proxy_pass https://shit.alpha01.org/wp-admin;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header Host $http_host;
                }
                location /wp-login.php {
                        allow MY-IP-ADDRESS;
                        deny all;
                        proxy_pass https://shit.alpha01.org/wp-login.php;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header Host $http_host;
                }

Linux: 

Awesome Applications: 

Varnish VCL Syntax Check

[[email protected] varnish]# varnishd -C -f default.vcl
Message from VCC-compiler:
Expected an action, 'if', '{' or '}'
('input' Line 156 Pos 17)
erro 403 "Fuck off";
----------------####----------------

Running VCC-compiler failed, exit 1

VCL compilation failed

Awesome Applications: 

Varnish WordPress Performance Testing

Thanks to my new job, I've been working a lot with Varnish. Man, Varnish is one kick ass HTTP web accelerator! A few months back I ran a few performance Apache performance tests on my WordPress site with different layers of caching enabled:
https://www.rubysecurity.org/php_xcache
https://www.rubysecurity.org/apache_stress-testing

So now I wanted to see how the results may differ using Varnish.

Configuration:
At a bare minimal, Varnish needs to be configured to remove cookies set by WordPress in order to make the content cacheable.

sub vcl_recv {
  # Drop any cookies sent to Wordpress.
  if (!(req.url ~ "wp-(login|admin)") && req.http.host ~ "rubyninja.org") {
    unset req.http.cookie;
  }
}

sub vcl_fetch {
  # Drop any cookies Wordpress tries to send back to the client.
  if (!(req.url ~ "wp-(login|admin)") && req.http.host ~ "rubyninja.org") {
    unset beresp.http.set-cookie;
  }
}

With this configuration enabled, I ran the same identical ab tests that used to benchmark my server previously.

[[email protected] ~]# ab -n 1000 -c 5 http://www.rubyninja.org/index.php
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking www.rubyninja.org (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Finished 1000 requests

Server Software: Apache/2.2.15
Server Hostname: www.rubyninja.org
Server Port: 80

Document Path: /index.php
Document Length: 0 bytes

Concurrency Level: 5
Time taken for tests: 39.528015 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Non-2xx responses: 1001
Total transferred: 374139 bytes
HTML transferred: 0 bytes
Requests per second: 25.30 [#/sec] (mean)
Time per request: 197.640 [ms] (mean)
Time per request: 39.528 [ms] (mean, across all concurrent requests)
Transfer rate: 9.23 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 87 100 134.1 93 3092
Processing: 88 95 10.7 91 220
Waiting: 87 94 10.6 90 218
Total: 177 196 134.4 186 3183

Percentage of the requests served within a certain time (ms)
50% 186
66% 192
75% 196
80% 199
90% 207
95% 217
98% 234
99% 251
100% 3183 (longest request)

The requests per second handled by the webserver weren't much different from the caching layer that I already had enabled previously, which is the WordPress W3 Total Cache plugin configured with Page, Database, Object, and Browser cache enabled using APC, and mod_pagespeed.

However the huge difference is that all requests never reached the Apache backend. Varnish cached all content and served all the requests directly. Varnish is just fucking awesome.

Awesome Applications: 

Leaving Gmail and Google Apps: Part I

Since I'm paying for essentially unmaged dedicated hosting so I can run my mail server. I opted to consolidate my personal web applications to the same physical box. This is why I created a KVM guest that would solely be used for my web traffic. One of the main challenges I'm faced is the fact that I only have one public IP address. This means that all of my KVM guests have been configured using the default NAT networking.

For all http traffic I'm using Varnish as the proxy and caching server and for https traffic I'm using Nginx.
http_architecture

First thing that broke using this different architecture on my sites are the mod_access IP restrictions that I originally had set in place previously. This is becuase the Apache backend see's all requests originating from the Varnish and Nginx proxies. Luckily both Varnish and Nginx have really simple access control mechanism built in them.

For example, in Varnish I can create a list of IPs that I can use to their block or grant access to certain URLs.

acl admin {
   "localhost";
    "MyPublicIPAddress";
} 

sub vcl_recv {
  # Only allow access to the admin ACL
  if (req.url ~ "^/secureshit" && req.http.host ~ "rubysecurity.org") {
    if (client.ip ~ admin) {
      return(pass);
    } else {
      error 403 "Not allowed in admin area.";
    }
  }
}

Nginx acl

location  /secureshit {
  allow MyPublicIPAddres;
  deny all;
  proxy_pass https://www.rubysecurity.org/secureshit;
}

Linux: 

Awesome Applications: 

Premium Drupal Themes by Adaptivethemes