├── provision ├── skeleton │ ├── hitch │ │ └── etc │ │ │ ├── rsyslog.d │ │ │ └── 22-hitch.conf │ │ │ ├── hitch │ │ │ └── hitch.conf │ │ │ └── init.d │ │ │ └── hitch │ ├── README.md │ └── varnish │ │ └── etc │ │ ├── default │ │ ├── varnishncsa │ │ └── varnish │ │ ├── varnish │ │ └── default.vcl │ │ └── init.d │ │ ├── varnishncsa │ │ └── varnish ├── lighttpd.sh ├── tools.sh ├── base.sh ├── varnish.sh └── hitch.sh ├── screenshot.png ├── status.sh ├── wrapper-monitor.screenrc ├── mitmdump-enable.sh ├── mitmdump-disable.sh ├── wrapper-diff.screenrc ├── Vagrantfile ├── LICENSE ├── mitmdump-logger.py ├── analyze-flows.py └── README.md /provision/skeleton/hitch/etc/rsyslog.d/22-hitch.conf: -------------------------------------------------------------------------------- 1 | 22-hitch.conf 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finnigja/varnish-wrapper/HEAD/screenshot.png -------------------------------------------------------------------------------- /status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | psgrep() { ps up $(pgrep -f $@) 2>&-; } 4 | 5 | psgrep lighttpd 6 | psgrep hitch 7 | psgrep varnish 8 | 9 | sudo netstat -tlp 10 | -------------------------------------------------------------------------------- /provision/skeleton/README.md: -------------------------------------------------------------------------------- 1 | For Varnish and Hitch, we reuse init files from Debian Testing packages: 2 | - https://packages.debian.org/stretch/varnish 3 | - https://packages.debian.org/stretch/hitch 4 | 5 | -------------------------------------------------------------------------------- /wrapper-monitor.screenrc: -------------------------------------------------------------------------------- 1 | screen -t precache sudo mitmdump -p 80 -R http://localhost:8081 -d --no-http2 2 | split 3 | focus down 4 | screen -t postcache sudo mitmdump -p 8082 -R http://localhost:8080 -d --no-http2 5 | -------------------------------------------------------------------------------- /provision/lighttpd.sh: -------------------------------------------------------------------------------- 1 | #start with lighttpd as content back-end (port 8080) 2 | sudo apt-get install lighttpd -y 3 | sudo sed -i -e 's/= 80$/= 8080/g' /etc/lighttpd/lighttpd.conf 4 | sudo service lighttpd restart 5 | service lighttpd status 6 | -------------------------------------------------------------------------------- /provision/tools.sh: -------------------------------------------------------------------------------- 1 | #install security tools 2 | sudo apt-get install tcpdump nmap 3 | 4 | #add mitmproxy 5 | sudo apt-get install python-dev python-pip libxml2-dev libffi-dev libxslt1-dev libjpeg8-dev zlib1g-dev sqlite3 -y 6 | sudo pip install mitmproxy 7 | 8 | -------------------------------------------------------------------------------- /mitmdump-enable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #move varnish to 8081 4 | sudo sed -i -e 's/VARNISH_LISTEN_PORT\=[[:digit:]]\+$/VARNISH_LISTEN_PORT\=8081/g' /etc/default/varnish 5 | # point varnish to 8082 6 | sudo sed -i -e 's/\.port \= \"[[:digit:]]\+"\;$/.port \= \"8082\"\;/g' /etc/varnish/default.vcl 7 | sudo service varnish restart 8 | 9 | -------------------------------------------------------------------------------- /provision/base.sh: -------------------------------------------------------------------------------- 1 | #install needed packages 2 | sudo apt-get update 3 | sudo apt-get install vim curl git build-essential libtool automake pkg-config python-docutils libncurses-dev libpcre3-dev libreadline-dev libev-dev libssl-dev flex bison -y 4 | 5 | #symlink to scripts/configs that will be of use during testing 6 | ln -s /vagrant ~/varnish-wrapper 7 | -------------------------------------------------------------------------------- /mitmdump-disable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo killall mitmdump 4 | 5 | #move varnish to 80 6 | sudo sed -i -e 's/VARNISH_LISTEN_PORT\=[[:digit:]]\+/VARNISH_LISTEN_PORT\=80/g' /etc/default/varnish 7 | # point varnish to 8080 8 | sudo sed -i -e 's/\.port \= \"[[:digit:]]\+"\;$/.port \= \"8080\"\;/g' /etc/varnish/default.vcl 9 | sudo service varnish restart 10 | -------------------------------------------------------------------------------- /wrapper-diff.screenrc: -------------------------------------------------------------------------------- 1 | screen -t precache sudo mitmdump -p 80 -R http://localhost:8081 --host -s "mitmdump-logger.py precache" --no-http2 2 | split 3 | focus down 4 | screen -t postcache sudo mitmdump -p 8082 -R http://localhost:8080 --host -s "mitmdump-logger.py postcache" --no-http2 5 | split 6 | focus down 7 | screen -t differ bash -c 'while true; do sleep 5; python ~/varnish-wrapper/analyze-flows.py; done' 8 | focus top 9 | resize 10 10 | focus down 11 | resize 10 12 | focus down 13 | -------------------------------------------------------------------------------- /provision/varnish.sh: -------------------------------------------------------------------------------- 1 | #clone, build, install, and configure Varnish as front-end cache (port 80) 2 | cd 3 | git clone https://github.com/varnishcache/varnish-cache.git 4 | cd varnish-cache 5 | ./autogen.sh 6 | ./configure --prefix=/opt/varnish && make && sudo make install 7 | cd 8 | sudo cp -rv /vagrant/provision/skeleton/varnish/* / 9 | sudo addgroup --system varnish 10 | sudo adduser --system --disabled-login --no-create-home --ingroup varnish varnish 11 | sudo adduser --system --disabled-login --no-create-home --ingroup varnish vcache 12 | sudo adduser --system --disabled-login --no-create-home --ingroup varnish vlogger 13 | sudo install -m 0600 /proc/sys/kernel/random/uuid /etc/varnish/secret 14 | sudo service varnish start 15 | -------------------------------------------------------------------------------- /provision/hitch.sh: -------------------------------------------------------------------------------- 1 | #clone, build, install and configure Hitch for TLS termination (port 443) 2 | cd 3 | git clone https://github.com/varnish/hitch.git 4 | cd hitch 5 | ./bootstrap 6 | ./configure --prefix=/opt/hitch && make && sudo make install 7 | cd 8 | sudo cp -rv /vagrant/provision/skeleton/hitch/* / 9 | sudo addgroup --system hitch 10 | sudo adduser --system --disabled-login --no-create-home --ingroup hitch hitch 11 | cd /etc/hitch/certs 12 | sudo openssl genrsa -out varnish-wrapper.local.key 2048 > /dev/null 13 | sudo openssl req -new -x509 -key varnish-wrapper.local.key -out varnish-wrapper.local.cert -days 3650 -subj /CN=varnish-wrapper.local 14 | cat varnish-wrapper.local.key varnish-wrapper.local.cert | sudo tee varnish-wrapper.local.pem > /dev/null 15 | cd 16 | sudo service hitch start 17 | -------------------------------------------------------------------------------- /provision/skeleton/varnish/etc/default/varnishncsa: -------------------------------------------------------------------------------- 1 | # Configuration file for varnishncsa 2 | # 3 | # This shell script fragment is sourced by /etc/init.d/varnishncsa 4 | # 5 | # Note: If systemd is installed, this file is obsolete and ignored. You will 6 | # need to copy /lib/systemd/system/varnishncsa.service to /etc/systemd/system/ 7 | # and edit that file. 8 | # 9 | # Uncomment this to enable logging for varnish. Please make sure you have 10 | # enough disk space for significant amounts of log data. To disable logging, 11 | # set the variable to "0", "no", or leave it unset. 12 | # 13 | # NCSA log format, to be used by HTTP log analyzers 14 | # VARNISHNCSA_ENABLED=1 15 | # 16 | # If you want to add more arguments to varnishncsa, such as providing 17 | # a different log format, you can override the DAEMON_OPTS variable 18 | # from /etc/init.d/varnishncsa here. 19 | # DAEMON_OPTS="-a -w ${LOGFILE} -D -P ${PIDFILE}" 20 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "ubuntu/trusty64" 3 | config.vm.hostname = "varnish-wrapper.local" 4 | #config.vm.network :forwarded_port, guest: 80, host: 8080 5 | config.vm.guest = :ubuntu 6 | 7 | config.vm.provider "virtualbox" do |v| 8 | v.memory = 2048 9 | v.cpus = 2 10 | end 11 | 12 | config.vm.define :server do |server| 13 | server.vm.network :private_network, ip: "192.168.200.2", netmask: "255.255.255.0" 14 | server.vm.provision :shell, :privileged => false, :path => "provision/base.sh" 15 | server.vm.provision :shell, :privileged => false, :path => "provision/lighttpd.sh" 16 | server.vm.provision :shell, :privileged => false, :path => "provision/varnish.sh" 17 | server.vm.provision :shell, :privileged => false, :path => "provision/hitch.sh" 18 | server.vm.provision :shell, :privileged => false, :path => "provision/tools.sh" 19 | server.vm.provision :shell, :privileged => false, :path => "status.sh" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Jamie Finnigan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /provision/skeleton/varnish/etc/varnish/default.vcl: -------------------------------------------------------------------------------- 1 | # 2 | # This is an example VCL file for Varnish. 3 | # 4 | # It does not do anything by default, delegating control to the 5 | # builtin VCL. The builtin VCL is called when there is no explicit 6 | # return statement. 7 | # 8 | # See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/ 9 | # and https://www.varnish-cache.org/trac/wiki/VCLExamples for more examples. 10 | 11 | # Marker to tell the VCL compiler that this VCL has been adapted to the 12 | # new 4.0 format. 13 | vcl 4.0; 14 | 15 | # Default backend definition. Set this to point to your content server. 16 | backend default { 17 | .host = "127.0.0.1"; 18 | .port = "8080"; 19 | } 20 | 21 | sub vcl_recv { 22 | # Happens before we check if we have this in cache already. 23 | # 24 | # Typically you clean up the request here, removing cookies you don't need, 25 | # rewriting the request, etc. 26 | } 27 | 28 | sub vcl_backend_response { 29 | # Happens after we have read the response headers from the backend. 30 | # 31 | # Here you clean the response headers, removing silly Set-Cookie headers 32 | # and other mistakes your backend does. 33 | } 34 | 35 | sub vcl_deliver { 36 | # Happens when we have all the pieces we need, and are about to send the 37 | # response to the client. 38 | # 39 | # You can do accounting or modifying the final object here. 40 | } 41 | -------------------------------------------------------------------------------- /provision/skeleton/varnish/etc/init.d/varnishncsa: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: varnishncsa 5 | # Required-Start: $local_fs $remote_fs $network 6 | # Required-Stop: $local_fs $remote_fs $network 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Start HTTP accelerator log daemon 10 | # Description: This script provides logging for varnish 11 | ### END INIT INFO 12 | 13 | # Source function library 14 | . /lib/lsb/init-functions 15 | 16 | NAME=varnishncsa 17 | DESC="HTTP accelerator log deamon" 18 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 19 | DAEMON=/usr/bin/$NAME 20 | PIDFILE=/run/$NAME/$NAME.pid 21 | LOGFILE=/var/log/varnish/varnishncsa.log 22 | USER=varnishlog 23 | DAEMON_OPTS="-a -w ${LOGFILE} -D -P ${PIDFILE}" 24 | 25 | # Include defaults if available 26 | if [ -f /etc/default/$NAME ] ; then 27 | . /etc/default/$NAME 28 | fi 29 | 30 | # If unset, or set to "0" or "no", exit 31 | if [ -z "${VARNISHNCSA_ENABLED}" ] || \ 32 | [ "${VARNISHNCSA_ENABLED}" = "0" ] || \ 33 | [ "${VARNISHNCSA_ENABLED}" = "no" ]; then 34 | exit 0; 35 | fi 36 | 37 | test -x $DAEMON || exit 0 38 | 39 | start_varnishncsa() { 40 | output=$(/bin/tempfile -s.varnish) 41 | log_daemon_msg "Starting $DESC" "$NAME" 42 | create_pid_directory 43 | if start-stop-daemon --start --quiet --pidfile ${PIDFILE} \ 44 | --chuid $USER --exec ${DAEMON} -- ${DAEMON_OPTS} \ 45 | > ${output} 2>&1; then 46 | log_end_msg 0 47 | else 48 | log_end_msg 1 49 | cat $output 50 | exit 1 51 | fi 52 | rm $output 53 | } 54 | 55 | stop_varnishncsa(){ 56 | log_daemon_msg "Stopping $DESC" "$NAME" 57 | if start-stop-daemon --stop --quiet --pidfile $PIDFILE \ 58 | --retry 10 --exec $DAEMON; then 59 | log_end_msg 0 60 | else 61 | log_end_msg 1 62 | fi 63 | } 64 | 65 | reload_varnishncsa(){ 66 | log_daemon_msg "Reloading $DESC" "$NAME" 67 | if kill -HUP $(cat $PIDFILE) >/dev/null 2>&1; then 68 | log_end_msg 0 69 | else 70 | log_end_msg 1 71 | exit 1 72 | fi 73 | } 74 | 75 | status_varnishncsa(){ 76 | status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" 77 | exit $? 78 | } 79 | 80 | create_pid_directory() { 81 | install -o $USER -g $USER -d $(dirname $PIDFILE) 82 | } 83 | 84 | case "$1" in 85 | start) 86 | start_varnishncsa 87 | ;; 88 | stop) 89 | stop_varnishncsa 90 | ;; 91 | reload) 92 | reload_varnishncsa 93 | ;; 94 | status) 95 | status_varnishncsa 96 | ;; 97 | restart|force-reload) 98 | $0 stop 99 | $0 start 100 | ;; 101 | *) 102 | log_success_msg "Usage: $0 {start|stop|restart|force-reload|reload}" 103 | exit 1 104 | ;; 105 | esac 106 | -------------------------------------------------------------------------------- /mitmdump-logger.py: -------------------------------------------------------------------------------- 1 | from mitmproxy.script import concurrent 2 | import sqlite3 3 | import uuid 4 | 5 | # mitm-logger.py - an inline script for mitmproxy 6 | # 7 | # Logs request/response pairs passing through mitmproxy into SQLite. 8 | # Tag requests with x-mitm-uuid / x-mitm-time headers, to allow correlation 9 | # if recurring. 10 | # 11 | # TODO: rework header tagging, likely only need one 12 | # TODO: optionally restrict logging of response content per content-type 13 | # 14 | # Reference: http://docs.mitmproxy.org/en/stable/scripting/inlinescripts.html 15 | 16 | 17 | def start(context, argv): 18 | context.position = argv[1] 19 | context.content_limit = -1 # can use this to limit content stored 20 | context.db_conn = sqlite3.connect( 21 | 'flows.db', 22 | check_same_thread=False # allow reuse across mitmproxy instances 23 | ) 24 | c = context.db_conn.cursor() 25 | c.execute('''CREATE TABLE IF NOT EXISTS request ( 26 | position text, method text, scheme text, host text, 27 | port text, path text, headers blob, content blob, 28 | uuid text, time text)''') 29 | c.execute('''CREATE TABLE IF NOT EXISTS response (position text, 30 | http_version text, status_code text, reason text, 31 | headers blob, content blob, uuid text, time text)''') 32 | context.db_conn.commit() 33 | context.log('{0} initialized ({1})'.format(argv[0], argv[1])) 34 | 35 | 36 | def done(context): 37 | if context.db_conn is not None: 38 | context.db_conn.close() 39 | 40 | 41 | @concurrent 42 | def request(context, flow): 43 | c = context.db_conn.cursor() 44 | req_uuid = flow.request.headers.pop('x-mitm-uuid', str(uuid.uuid4())) 45 | req_time = flow.request.headers.pop('x-mitm-time', str( 46 | flow.request.timestamp_start) 47 | ) 48 | t = (context.position, flow.request.method, flow.request.scheme, 49 | flow.request.host, flow.request.port, flow.request.path, 50 | sqlite3.Binary(unicode(flow.request.headers)), 51 | sqlite3.Binary(flow.request.get_decoded_content()), 52 | req_uuid, req_time) 53 | c.execute('INSERT INTO request VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', t) 54 | context.db_conn.commit() 55 | flow.request.headers.set_all('x-mitm-uuid', (req_uuid,)) 56 | flow.request.headers.set_all('x-mitm-time', (req_time,)) 57 | 58 | 59 | @concurrent 60 | def response(context, flow): 61 | c = context.db_conn.cursor() 62 | req_uuid = flow.request.headers.pop('x-mitm-uuid') 63 | req_time = flow.request.headers.pop('x-mitm-time') 64 | content = flow.response.get_decoded_content()[0:context.content_limit] 65 | t = (context.position, flow.response.http_version, 66 | flow.response.status_code, flow.response.reason, 67 | sqlite3.Binary(unicode(flow.response.headers)), 68 | sqlite3.Binary(content), req_uuid, req_time) 69 | c.execute(u'INSERT INTO response VALUES (?, ?, ?, ?, ?, ?, ?, ?)', t) 70 | context.db_conn.commit() 71 | -------------------------------------------------------------------------------- /analyze-flows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sqlite3 4 | import difflib 5 | 6 | positions = ['precache', 'postcache'] 7 | types = [('request', '=>'), ('response', '<=')] 8 | 9 | db_conn = sqlite3.connect('flows.db', check_same_thread=False) 10 | db_conn.row_factory = sqlite3.Row 11 | 12 | 13 | def diff_http_elements(headers1, content1, headers2, content2): 14 | return { 15 | 'headers': difflib.SequenceMatcher(None, headers1, headers2).ratio(), 16 | 'content': difflib.SequenceMatcher(None, content1, content2).ratio() 17 | } 18 | 19 | 20 | def diff_text(position1, text1, position2, text2): 21 | return ( 22 | difflib.unified_diff( 23 | str(text1).splitlines(), 24 | str(text2).splitlines(), 25 | lineterm='', 26 | fromfile=position1, 27 | tofile=position2 28 | ) 29 | ) 30 | 31 | 32 | c1 = db_conn.cursor() 33 | for i, row in enumerate( 34 | c1.execute('''SELECT * FROM request GROUP BY uuid ORDER BY time''') 35 | ): 36 | uuid = row['uuid'] 37 | req_str = "{0} {1}://{2}:{3}{4}".format( 38 | row['method'], row['scheme'], row['host'], row['port'], row['path'] 39 | ) 40 | print("[-- {0} : {1} --]".format(i, req_str)) 41 | c2 = db_conn.cursor() 42 | positions = ['source',] 43 | results = [] 44 | for (type_text, type_arrow) in types: 45 | baseline = None 46 | for row in c2.execute( 47 | '''SELECT * FROM {0} WHERE uuid = ?'''.format(type_text), (uuid,) 48 | ): 49 | positions.append(row['position']) 50 | status_code = row['status_code'] if 'status_code' in row.keys() else '' 51 | if status_code != '': 52 | positions[-1] = '{0} ({1})'.format(positions[-1], status_code) 53 | if baseline is None: 54 | baseline = row 55 | else: 56 | diff_ratios = diff_http_elements( 57 | baseline['headers'], baseline['content'], 58 | row['headers'], row['content'] 59 | ) 60 | results.append("{0} {1}: headers {2}, content {3} ({4})".format( 61 | type_arrow, 62 | row['position'], 63 | "{0:.2f}".format(diff_ratios['headers']), 64 | "{0:.2f}".format(diff_ratios['content']), 65 | status_code 66 | )) 67 | for http_element in ['headers', 'content']: 68 | if diff_ratios[http_element] != 1.0: 69 | results.append(''.join(['\t{0}\n'.format(line) for line in ( 70 | diff_text( 71 | baseline['position'], baseline[http_element], 72 | row['position'], row[http_element] 73 | ) 74 | )])) 75 | positions.append('origin') if type_text == 'request' else positions.append('source') 76 | print(' => '.join(positions)) 77 | for r in results: 78 | print(r) 79 | 80 | if db_conn is not None: 81 | db_conn.close() 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Varnish Cache Test Wrapper 2 | 3 | Sometimes, it may be useful to be able to see how an HTTP cache or proxy 4 | modifies the traffic that passes through it. Use cases might include 5 | testing cache/proxy configurations (eg. Varnish VCL being applied), or 6 | evaluating how malformed requests are handled & forwarded. 7 | 8 | This Vagrant environment facilitates the creation of a Varnish Cache test 9 | environment, wrapping it with pre-/post-cache MITM capabilities. 10 | 11 | This creates a single virtual machine running Ubuntu Trusty 14.04 with: 12 | - Varnish (front-end cache) 13 | - lighttpd (back-end content server) 14 | - Hitch (TLS termination) 15 | - mitmproxy (HTTP/HTTPS man-in-the-middle) 16 | 17 | It should be somewhat straightforward to swap out Varnish for some other 18 | cache or proxy component, if a different target needs to be tested. 19 | 20 | 21 | Tested with VirtualBox 5.0 and Vagrant 1.7.4. 22 | 23 | ![screenshot](https://raw.githubusercontent.com/chair6/varnish-wrapper/master/screenshot.png) 24 | 25 | ## Standard Install 26 | 27 | Varnish and Hitch are both pulled from their Github repositories and built 28 | from source, in order to get most recent versions. 29 | 30 | Initially, the configuration deployed is as follows: 31 | ``` 32 | https-client http-client 33 | | | 34 | hitch:443 | 35 | \ | 36 | \ | 37 | varnish:80 38 | | 39 | lighttpd:8080 40 | ``` 41 | 42 | 43 | ## MITM Monitor Mode 44 | 45 | Scripts are provided to reconfigure into a pre- and post-cache MITM flow, 46 | using mitmproxy running in reverse proxy mode: 47 | ``` 48 | https-client http-client 49 | | | 50 | hitch:443 | 51 | \ | 52 | \ | 53 | mitmproxy:80 54 | | 55 | varnish:8081 56 | | 57 | mitmproxy:8082 58 | | 59 | lighttpd:8080 60 | ``` 61 | 62 | Running mitm-enable.sh will reconfigure Varnish in this mode. 63 | 64 | Running 'screen -c wrapper-monitor.screenrc' will start a screen session, 65 | with mitmdump processes at both pre-cache and post-cache positions in the 66 | flow. 67 | 68 | The commands in the screenrc that are run to initialize the two mitmdump 69 | processes could be modified, for example to run mitmproxy if the user wants 70 | interactive access to the MITMed flows. 71 | 72 | Test HTTP/HTTPS requests can be generated from within the virtual machine 73 | itself, or from the host system by targeting the Vagrant-configured IP 74 | (192.168.200.2 by default). Keeping it simple, curl works in both cases, 75 | or a browser on the host system. 76 | 77 | Running mitm-disable.sh will kill any remaining mitmdump processes and 78 | reconfigure Varnish to return the flow to original state. 79 | 80 | 81 | ## MITM Diff Mode 82 | 83 | Running 'screen -c wrapper-diff.screenrc' will start a screen session, with 84 | mitmdump processes at both pre-cache and post-cache positions. It will also 85 | enable an mitmproxy script (mitmdump-logger.py) that uses header injection to 86 | tie pre-/post-cache requests to each other, and persist the requests/responses 87 | in an SQLite database. 88 | 89 | Running analyze-flows.py will perform some basic analysis against the request/ 90 | response pairs in the SQLite database and indicate where differences are 91 | identified. 92 | 93 | ## Logging 94 | 95 | Logs for Hitch are dropped into the daemon syslog facility, which drop into 96 | /var/log/daemon.log on the system. 97 | 98 | Logs for Varnish can be accessed by executing /opt/varnish/bin/varnishlog, 99 | which is documented at: 100 | https://www.varnish-cache.org/docs/4.1/reference/varnishlog.html 101 | 102 | 103 | ## Author 104 | 105 | https://twitter.com/chair6 106 | 107 | -------------------------------------------------------------------------------- /provision/skeleton/varnish/etc/default/varnish: -------------------------------------------------------------------------------- 1 | # Configuration file for varnish 2 | # 3 | # /etc/init.d/varnish expects the variables $DAEMON_OPTS, $NFILES and $MEMLOCK 4 | # to be set from this shell script fragment. 5 | # 6 | # Note: If systemd is installed, this file is obsolete and ignored. Please see 7 | # /usr/share/doc/varnish/examples/varnish.systemd-drop-in.conf 8 | 9 | # Should we start varnishd at boot? Set to "no" to disable. 10 | START=yes 11 | 12 | # Maximum number of open files (for ulimit -n) 13 | NFILES=131072 14 | 15 | # Maximum locked memory size (for ulimit -l) 16 | # Used for locking the shared memory log in memory. If you increase log size, 17 | # you need to increase this number as well 18 | MEMLOCK=82000 19 | 20 | VARNISH_LISTEN_PORT=80 21 | 22 | # Default varnish instance name is the local nodename. Can be overridden with 23 | # the -n switch, to have more instances on a single server. 24 | # You may need to uncomment this variable for alternatives 1 and 3 below. 25 | # INSTANCE=$(uname -n) 26 | 27 | # This file contains 4 alternatives, please use only one. 28 | 29 | ## Alternative 1, Minimal configuration, no VCL 30 | # 31 | # Listen on port 6081, administration on localhost:6082, and forward to 32 | # content server on localhost:8080. Use a 1GB fixed-size cache file. 33 | # 34 | # This example uses the INSTANCE variable above, which you need to uncomment. 35 | # 36 | # DAEMON_OPTS="-a :6081 \ 37 | # -T localhost:6082 \ 38 | # -b localhost:8080 \ 39 | # -u varnish -g varnish \ 40 | # -S /etc/varnish/secret \ 41 | # -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G" 42 | 43 | 44 | ## Alternative 2, Configuration with VCL 45 | # 46 | # Listen on port 6081, administration on localhost:6082, and forward to 47 | # one content server selected by the vcl file, based on the request. 48 | # 49 | DAEMON_OPTS="-a :$VARNISH_LISTEN_PORT \ 50 | -T localhost:6082 \ 51 | -f /etc/varnish/default.vcl \ 52 | -S /etc/varnish/secret \ 53 | -s malloc,256m" 54 | 55 | 56 | ## Alternative 3, Advanced configuration 57 | # 58 | # This example uses the INSTANCE variable above, which you need to uncomment. 59 | # 60 | # See varnishd(1) for more information. 61 | # 62 | # # Main configuration file. You probably want to change it :) 63 | # VARNISH_VCL_CONF=/etc/varnish/default.vcl 64 | # 65 | # # Default address and port to bind to 66 | # # Blank address means all IPv4 and IPv6 interfaces, otherwise specify 67 | # # a host name, an IPv4 dotted quad, or an IPv6 address in brackets. 68 | # VARNISH_LISTEN_ADDRESS= 69 | # VARNISH_LISTEN_PORT=6081 70 | # 71 | # # Telnet admin interface listen address and port 72 | # VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1 73 | # VARNISH_ADMIN_LISTEN_PORT=6082 74 | # 75 | # # Cache file location 76 | # VARNISH_STORAGE_FILE=/var/lib/varnish/$INSTANCE/varnish_storage.bin 77 | # 78 | # # Cache file size: in bytes, optionally using k / M / G / T suffix, 79 | # # or in percentage of available disk space using the % suffix. 80 | # VARNISH_STORAGE_SIZE=1G 81 | # 82 | # # File containing administration secret 83 | # VARNISH_SECRET_FILE=/etc/varnish/secret 84 | # 85 | # # Backend storage specification 86 | # VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}" 87 | # 88 | # # Default TTL used when the backend does not specify one 89 | # VARNISH_TTL=120 90 | # 91 | # # DAEMON_OPTS is used by the init script. If you add or remove options, make 92 | # # sure you update this section, too. 93 | # DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \ 94 | # -f ${VARNISH_VCL_CONF} \ 95 | # -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \ 96 | # -t ${VARNISH_TTL} \ 97 | # -S ${VARNISH_SECRET_FILE} \ 98 | # -s ${VARNISH_STORAGE}" 99 | # 100 | 101 | 102 | ## Alternative 4, Do It Yourself 103 | # 104 | # DAEMON_OPTS="" 105 | -------------------------------------------------------------------------------- /provision/skeleton/hitch/etc/hitch/hitch.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Example configuration file for hitch(8). 3 | # 4 | 5 | # Listening address. REQUIRED. 6 | 7 | frontend = { 8 | host = "*" 9 | port = "443" 10 | pem-file = "/etc/hitch/certs/varnish-wrapper.local.pem" 11 | } 12 | 13 | # The following options can also be set in a frontend block, which 14 | # will configure the option for this specific frontend only: 15 | # 16 | # pem-file = "" 17 | # tls = on 18 | # ssl = off 19 | # ciphers = "" 20 | # prefer-server-ciphers = off 21 | # sni-nomatch-abort = off 22 | # match-global-certs = off 23 | # 24 | # See further explanation below for each specifc option. 25 | 26 | # Upstream server address. REQUIRED. 27 | # 28 | # type: string 29 | # syntax: [HOST]:PORT. 30 | backend = "[127.0.0.1]:80" 31 | 32 | # SSL x509 certificate file. REQUIRED. 33 | # List multiple certs to use SNI. Certs are used in the order they 34 | # are listed; the last cert listed will be used if none of the others match 35 | # 36 | # Also available in a frontend declaration, to make a certificate 37 | # only available for a specific listen endpoint. 38 | # 39 | # type: string 40 | pem-file = "" 41 | 42 | # SSL protocol. 43 | # 44 | tls = on 45 | ssl = off 46 | 47 | # List of allowed SSL ciphers. 48 | # 49 | # Run openssl ciphers for list of available ciphers. 50 | # 51 | # Option is also available in a frontend declaration. 52 | # 53 | # type: string 54 | ciphers = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH" 55 | 56 | # Enforce server cipher list order 57 | # 58 | # Option is also available in a frontend declaration. 59 | # 60 | # type: boolean 61 | prefer-server-ciphers = off 62 | 63 | # Use specified SSL engine 64 | # 65 | # type: string 66 | ssl-engine = "" 67 | 68 | # Number of worker processes 69 | # 70 | # type: integer 71 | workers = 1 72 | 73 | # Listen backlog size 74 | # 75 | # type: integer 76 | backlog = 100 77 | 78 | # TCP socket keepalive interval in seconds 79 | # 80 | # type: integer 81 | keepalive = 3600 82 | 83 | # Chroot directory 84 | # 85 | # type: string 86 | chroot = "" 87 | 88 | # Set uid after binding a socket 89 | # 90 | # type: string 91 | user = "" 92 | 93 | # Set gid after binding a socket 94 | # 95 | # type: string 96 | group = "" 97 | 98 | # Quiet execution, report only error messages 99 | # 100 | # type: boolean 101 | quiet = off 102 | 103 | # Use syslog for logging 104 | # 105 | # type: boolean 106 | syslog = on 107 | 108 | # Syslog facility to use 109 | # 110 | # type: string 111 | syslog-facility = "daemon" 112 | 113 | # Run as daemon 114 | # 115 | # type: boolean 116 | daemon = on 117 | 118 | # Report client address by writing IP before sending data 119 | # 120 | # NOTE: This option is mutually exclusive with option write-proxy-v2, write-proxy and proxy-proxy. 121 | # 122 | # type: boolean 123 | write-ip = off 124 | 125 | # Report client address using SENDPROXY protocol, see 126 | # http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt 127 | # for details. 128 | # 129 | # NOTE: This option is mutually exclusive with option write-proxy-v2, write-ip and proxy-proxy. 130 | # 131 | # type: boolean 132 | write-proxy-v1 = off 133 | 134 | # Report client address using SENDPROXY v2 binary protocol, see 135 | # http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt 136 | # for details. 137 | # 138 | # NOTE: This option is mutually exclusive with option write-ip, write-proxy-v1 and proxy-proxy. 139 | # 140 | # type: boolean 141 | write-proxy-v2 = off 142 | 143 | # Proxy an existing SENDPROXY protocol header through this request. 144 | # 145 | # NOTE: This option is mutually exclusive with option write-proxy-v2, write-ip and write-proxy-v1. 146 | # 147 | # type: boolean 148 | proxy-proxy = off 149 | 150 | # Abort handshake when the client submits an unrecognized SNI server name. 151 | # 152 | # Option is also available in a frontend declaration. 153 | # 154 | # type: boolean 155 | sni-nomatch-abort = off 156 | 157 | # EOF 158 | -------------------------------------------------------------------------------- /provision/skeleton/varnish/etc/init.d/varnish: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: varnish 5 | # Required-Start: $local_fs $remote_fs $network 6 | # Required-Stop: $local_fs $remote_fs $network 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Start HTTP accelerator 10 | # Description: This script provides a server-side cache 11 | # to be run in front of a httpd and should 12 | # listen on port 80 on a properly configured 13 | # system 14 | ### END INIT INFO 15 | 16 | # Source function library 17 | . /lib/lsb/init-functions 18 | 19 | NAME=varnishd 20 | DESC="HTTP accelerator" 21 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 22 | DAEMON=/opt/varnish/sbin/varnishd 23 | PIDFILE=/run/$NAME.pid 24 | 25 | test -x $DAEMON || exit 0 26 | 27 | # Include varnish defaults if available 28 | if [ -f /etc/default/varnish ] ; then 29 | . /etc/default/varnish 30 | fi 31 | 32 | # Open files (usually 1024, which is way too small for varnish) 33 | ulimit -n ${NFILES:-131072} 34 | 35 | # Maxiumum locked memory size for shared memory log 36 | ulimit -l ${MEMLOCK:-82000} 37 | 38 | # If $DAEMON_OPTS is not set at all in /etc/default/varnish, use minimal useful 39 | # defaults (Backend at localhost:8080, a common place to put a locally 40 | # installed application server.) 41 | DAEMON_OPTS=${DAEMON_OPTS:--b localhost} 42 | 43 | # Ensure we have a PATH 44 | export PATH="${PATH:+$PATH:}/usr/sbin:/usr/bin:/sbin:/bin" 45 | 46 | start_varnishd() { 47 | log_daemon_msg "Starting $DESC" "$NAME" 48 | output=$(/bin/tempfile -s.varnish) 49 | if start-stop-daemon \ 50 | --start --quiet --pidfile ${PIDFILE} --exec ${DAEMON} -- \ 51 | -P ${PIDFILE} ${DAEMON_OPTS} > ${output} 2>&1; then 52 | log_end_msg 0 53 | else 54 | log_end_msg 1 55 | cat $output 56 | exit 1 57 | fi 58 | rm $output 59 | } 60 | 61 | disabled_varnishd() { 62 | log_daemon_msg "Not starting $DESC" "$NAME" 63 | log_progress_msg "disabled in /etc/default/varnish" 64 | log_end_msg 0 65 | } 66 | 67 | stop_varnishd() { 68 | log_daemon_msg "Stopping $DESC" "$NAME" 69 | if start-stop-daemon \ 70 | --stop --quiet --pidfile $PIDFILE --retry 10 \ 71 | --exec $DAEMON; then 72 | log_end_msg 0 73 | else 74 | log_end_msg 1 75 | fi 76 | 77 | if test -r $PIDFILE; then 78 | read -r PID < $PIDFILE 79 | if test ! -d /proc/$PID ; then 80 | # stale pidfile 81 | unset PID 82 | rm -f $PIDFILE 83 | fi 84 | fi 85 | } 86 | 87 | reload_varnishd() { 88 | log_daemon_msg "Reloading $DESC" "$NAME" 89 | if /usr/share/varnish/reload-vcl -q; then 90 | log_end_msg 0 91 | else 92 | log_end_msg 1 93 | fi 94 | } 95 | 96 | status_varnishd() { 97 | start-stop-daemon \ 98 | --status --quiet --pidfile $PIDFILE \ 99 | --exec $DAEMON 100 | exit $? 101 | } 102 | 103 | configtest() { 104 | $DAEMON ${DAEMON_OPTS} -C -n /tmp > /dev/null 105 | } 106 | 107 | 108 | 109 | case "$1" in 110 | start) 111 | case "${START:-}" in 112 | [Yy]es|[Yy]|1|[Tt]|[Tt]rue) 113 | start_varnishd 114 | ;; 115 | *) 116 | disabled_varnishd 117 | ;; 118 | esac 119 | ;; 120 | stop) 121 | stop_varnishd 122 | ;; 123 | reload) 124 | reload_varnishd 125 | ;; 126 | status) 127 | status_varnishd 128 | ;; 129 | restart|force-reload) 130 | if status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" 1>/dev/null; then 131 | if ! configtest; then 132 | log_failure_msg "Syntax check failed, not restarting" 133 | exit 1 134 | fi 135 | fi 136 | $0 stop 137 | $0 start 138 | ;; 139 | configtest) 140 | configtest && log_success_msg "Syntax ok" 141 | ;; 142 | *) 143 | log_success_msg "Usage: $0 {start|stop|restart|reload|force-reload|configtest}" 144 | exit 1 145 | ;; 146 | esac 147 | -------------------------------------------------------------------------------- /provision/skeleton/hitch/etc/init.d/hitch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: hitch 4 | # Required-Start: $local_fs $network $remote_fs $syslog 5 | # Required-Stop: $local_fs $network $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: scalable TLS proxy 9 | # Description: hitch is a network proxy that terminates TLS/SSL 10 | # connections and forwards the unencrypted traffic to some 11 | # backend. It's designed to handle 10s of thousands of 12 | # connections efficiently on multicore machines. 13 | ### END INIT INFO 14 | 15 | # Author: Stig Sandbeck Mathisen 16 | 17 | # Do NOT "set -e" 18 | 19 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 20 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 21 | DESC="hitch" 22 | NAME=hitch 23 | DAEMON=/opt/hitch/sbin/hitch 24 | DAEMON_ARGS="--daemon --pidfile=/run/hitch.pid --user hitch --group hitch --config=/etc/hitch/hitch.conf" 25 | PIDFILE=/var/run/$NAME.pid 26 | SCRIPTNAME=/etc/init.d/$NAME 27 | 28 | # Exit if the package is not installed 29 | [ -x "$DAEMON" ] || exit 0 30 | 31 | # Read configuration variable file if it is present 32 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 33 | 34 | # Load the VERBOSE setting and other rcS variables 35 | . /lib/init/vars.sh 36 | 37 | # Define LSB log_* functions. 38 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 39 | # and status_of_proc is working. 40 | . /lib/lsb/init-functions 41 | 42 | # 43 | # Function that starts the daemon/service 44 | # 45 | do_start() 46 | { 47 | # Return 48 | # 0 if daemon has been started 49 | # 1 if daemon was already running 50 | # 2 if daemon could not be started 51 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 52 | || return 1 53 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ 54 | $DAEMON_ARGS \ 55 | || return 2 56 | # The above code will not work for interpreted scripts, use the next 57 | # six lines below instead (Ref: #643337, start-stop-daemon(8) ) 58 | #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \ 59 | # --name $NAME --test > /dev/null \ 60 | # || return 1 61 | #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \ 62 | # --name $NAME -- $DAEMON_ARGS \ 63 | # || return 2 64 | 65 | # Add code here, if necessary, that waits for the process to be ready 66 | # to handle requests from services started subsequently which depend 67 | # on this one. As a last resort, sleep for some time. 68 | } 69 | 70 | # 71 | # Function that stops the daemon/service 72 | # 73 | do_stop() 74 | { 75 | # Return 76 | # 0 if daemon has been stopped 77 | # 1 if daemon was already stopped 78 | # 2 if daemon could not be stopped 79 | # other if a failure occurred 80 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME 81 | RETVAL="$?" 82 | [ "$RETVAL" = 2 ] && return 2 83 | # Wait for children to finish too if this is a daemon that forks 84 | # and if the daemon is only ever run from this initscript. 85 | # If the above conditions are not satisfied then add some other code 86 | # that waits for the process to drop all resources that could be 87 | # needed by services started subsequently. A last resort is to 88 | # sleep for some time. 89 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 90 | [ "$?" = 2 ] && return 2 91 | # Many daemons don't delete their pidfiles when they exit. 92 | rm -f $PIDFILE 93 | return "$RETVAL" 94 | } 95 | 96 | # 97 | # Function that sends a SIGHUP to the daemon/service 98 | # 99 | do_reload() { 100 | # 101 | # If the daemon can reload its configuration without 102 | # restarting (for example, when it is sent a SIGHUP), 103 | # then implement that here. 104 | # 105 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME 106 | return 0 107 | } 108 | 109 | case "$1" in 110 | start) 111 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 112 | do_start 113 | case "$?" in 114 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 115 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 116 | esac 117 | ;; 118 | stop) 119 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 120 | do_stop 121 | case "$?" in 122 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 123 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 124 | esac 125 | ;; 126 | status) 127 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 128 | ;; 129 | reload|force-reload) 130 | # 131 | # If do_reload() is not implemented then leave this commented out 132 | # and leave 'force-reload' as an alias for 'restart'. 133 | # 134 | log_daemon_msg "Reloading $DESC" "$NAME" 135 | do_reload 136 | log_end_msg $? 137 | ;; 138 | restart) 139 | # 140 | # If the "reload" option is implemented then remove the 141 | # 'force-reload' alias 142 | # 143 | log_daemon_msg "Restarting $DESC" "$NAME" 144 | do_stop 145 | case "$?" in 146 | 0|1) 147 | do_start 148 | case "$?" in 149 | 0) log_end_msg 0 ;; 150 | 1) log_end_msg 1 ;; # Old process is still running 151 | *) log_end_msg 1 ;; # Failed to start 152 | esac 153 | ;; 154 | *) 155 | # Failed to stop 156 | log_end_msg 1 157 | ;; 158 | esac 159 | ;; 160 | *) 161 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 162 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 163 | exit 3 164 | ;; 165 | esac 166 | 167 | : 168 | --------------------------------------------------------------------------------