Proxy testing with IP Namespaces and GitLab CI/CD


At work, I have a CLI tool I’ve been working on. It talks to the web and is used by customers all over the planet, some of them on networks with tighter restrictions than my own. Often those customers have an HTTP proxy of some sort and that means the CLI application needs to negotiate with it differently than it would directly with a web server.

So I need to test it somehow with a proxy environment. Installing a proxy service like Squid doesn’t sound like too big a deal but it needs to run in several configurations, at a very minimum these three:

  • no-proxy
  • authenticating HTTP proxy
  • non-authenticating HTTP proxy

I’m going to ignore HTTPS proxy for now as it’s not actually a common configuration for customers but I reckon it’s possible to do with mkcert or LetsEncrypt without too much work.

There are two other useful pieces of information to cover, firstly I use GitLab-CI to run the CI/CD test stages for the three proxy configurations in parallel. Secondly, and this is important, I must make sure that, once the test Squid proxy service is running, the web requests in the test only pass through the proxy and do not leak out of the GitLab runner. I can do this by using a really neat Linux feature called IP namespaces.

IP namespaces allow me to set up different network environments on the same machine, similar to IP subnets or AWS security groups. Then I can launch specific processes in those namespaces and network access from those processes will be limited by the configuration of the network namespace. That is to say, the Squid proxy can have full access but the test process can only talk to the proxy. Cool, right?

The GitLab CI/CD YAML looks like this (edited to protect the innocent)

- integration

.integration_common: &integration_common |
apt-get update
apt-get install -y iproute2

.network_ns: &network_ns |
ip netns add $namespace
ip link add v-eth1 type veth peer name v-peer1
ip link set v-peer1 netns $namespace
ip addr add dev v-eth1
ip link set v-eth1 up
ip netns exec $namespace ip addr add dev v-peer1
ip netns exec $namespace ip link set v-peer1 up
ip netns exec $namespace ip link set lo up
ip netns exec $namespace ip route add default via

image: ubuntu:18.04
stage: integration
- *integration_common
- test/end2end/cli

image: ubuntu:18.04
stage: integration
- *integration_common
- apt-get install -y squid apache2-utils
- mkdir -p /etc/squid3
- htpasswd -cb /etc/squid3/passwords testuser testpass
- *network_ns
- squid3 -f test/end2end/conf/squid.conf.auth && sleep 1 || tail -20 /var/log/syslog | grep squid
- http_proxy=http://testuser:testpass@ https_proxy=http://testuser:testpass@ ip netns exec $namespace test/end2end/cli
- ip netns del $namespace || true
namespace: proxyauth

image: ubuntu:18.04
stage: integration
- *integration_common
- apt-get install -y squid
- *network_ns
- squid3 -f test/end2end/conf/squid.conf.noauth && sleep 1 || tail -20 /var/log/syslog | grep squid
- http_proxy= https_proxy= test/end2end/cli
- ip netns del $namespace || true
namespace: proxynoauth

So there are five blocks here, with three stages and two common script blocks. The first common script block installs iproute2 which gives us the ip command.

The second script block is where the magic happens. It configures a virtual, routed subnet in the parameterised $namespace.

Following that we have the three test stages corresponding to the three proxy (or not) configurations I listed earlier. Two of them install Squid, one of those creates a test user for authenticating with the proxy. They all run the test script, which in this case is test/end2end/cli. When those three configs are modularised and out like this with the common net namespace script as well it provides a good deal of clarity to the test maintainer. I like it a lot.

So then the last remaining things are the respective squid configurations: proxyauth and proxynoauth. There’s a little bit more junk in these than there needs to be as they’re taken from the stock examples, but they look something like this:

 visible_hostname proxynoauth
acl localnet src # RFC1918 possible internal network
acl localnet src # RFC1918 possible internal network
acl localnet src # RFC1918 possible internal network
acl SSL_ports port 443
acl Safe_ports port 80 # http
acl Safe_ports port 443 # https
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access allow localnet
http_access allow localhost
http_access deny all
http_port 3128

and for authentication:

 visible_hostname proxyauth
acl localnet src # RFC1918 possible internal network
acl localnet src # RFC1918 possible internal network
acl localnet src # RFC1918 possible internal network
acl SSL_ports port 443
acl Safe_ports port 80 # http
acl Safe_ports port 443 # https
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager

auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid3/passwords
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED

http_access allow authenticated
http_access deny all
http_port 3128

And there you have it – network-restricted proxy testing with different proxy configurations. It’s the first time I’ve used ip net ns without being wrapped up in Docker, LXC, containerd or some other libvirt thing, but the feeling of power from my new-found network-god skills is quite something :)

Be aware that you might need to choose different subnet ranges if your regular LAN conflicts. Please let me know in the comments if you find this useful or if you had to modify things to work in your environment.

Systems & Security Tools du jour

I’ve been to two events in the past two weeks which have started me thinking harder about the way we protect and measure our enterprise systems.

The first of the two events was the fourth Splunk Live in St. Paul’s, London last week. I’ve been a big fan of Splunk for a few years but I’ve never really tried it out in production. The second was InfoSec at Earl’s Court. More about that one later.

What is Splunk?

To be honest, splunk is different things to different people. Since inception it’s had great value as a log collation and event alerting tool for systems administrators as that was what it was originally designed to do. However as both DJ Skillman and Godfrey Sullivan pointed out, Splunk has grown into a lot more than that. It solved a lot of “Big Data” (how I hate that phrase) problems before Big Data was trendy, taking arbitrary unstructured data sources structuring them in useful ways, indexing the hell out of them and adding friendly, near-real-time reporting and alerting on top. Nowadays, given the right data sources, Splunk is capable of providing across-the-board Operational Intelligence, yielding tremendous opportunities in measuring value of processes and events.

How does it work?

In order to make the most out of a Splunk installation you require at least three basic things :-

  1. A data source - anything from a basic syslog or Apache web server log to a live high level ERP logistics event feed or even entire code commits
  2. An enrichment process – something to tag packets, essentially to assign value to indexed fields, allowing the association of fields from different feeds, e.g. tallying new orders with a customer database with stock keeping perhaps.
  3. A report – a canned report, presented on a dashboard for your CFO for example, or an email alert to tell your IT manager that someone squirting 5 day experiments in at the head of the analysis pipeline is going to go over-budget on your AWS analysis pipeline in three days’ time.

How far can you go with it?

Well, here’s a few of the pick ‘n’ mix selection of things I’d like to start indexing as soon as we sort out a) the restricted data limits of our so-far-free Splunk installation and b) what’s legal to do

  • Door id access (physical site presence)
  • VPN logins (virtual site presence)
  • Wifi device registrations (guest, internal, whatever)
  • VoIP + PSTN call logs (number, duration)
  • Environmentals – temperature, humidity of labs, offices, server rooms
  • System logs for everything (syslog, authentication, Apache, FTPd, MySQL connections, Samba, the works)
  • SGE job logs with user & project accounting
  • Application logs for anything we’ve written in house
  • Experimental metadata (who ran what when, where, why)
  • Domains for all incoming + outgoing mail, plus mail/attachment weights (useful for spotting outliers exfiltrating data)
  • Firewall: accepted incoming connections
  • Continuous Integration test results (software project, timings, memory, cpu footprints)
  • SVN/Git code commits (yes, it’s possible to log the entire change set)
  • JIRA tickets (who, what, when, project, component, priority)
  • ERP logs (supply chain, logistics, stock control, manufacturing lead times)
  • CRM + online store logs (customer info, helpdesk cases, orders)
  • anything and everything else with vaguely any business value

I think it’s pretty obvious that all this stuff taken together constitutes what most people call Big Data these days. There’s quite a distinction between that sort of mixed relational data and the plainer “lots of data” I deal with day to day, experimental data in the order of a terabyte-plus per device per day.

SVN Server Integration with HTTPS, Active Directory, PAM & Winbind

Subversion on a whiteboard
Image CC by johntrainor
In this post I’d like to explain how it’s possible to integrate SVN (Subversion) source control using WebDAV and HTTPS using Apache and Active Directory to provide authentication and access control.

It’s generally accepted that SVN over WebDAV/HTTPS  provides finer granulation security controls than SVN+SSH. The problem is that SVN+SSH is really easy to set up, requiring knowledge of svnadmin and the filesystem and very little else but WebDAV+HTTPS requires knowledge of Apache and its modules relating to WebDAV, authentication and authorisation which is quite a lot more to ask. Add to that authenticating to AD and you have yourself a lovely string of delicate single point of failure components. Ho-hum, not a huge amount you can do about that but at least the Apache components are pretty robust.

For this article I’m using CentOS but everything should be transferrable to any distribution with a little tweakage.

Repository Creation

Firstly then, pick a disk or volume with plenty of space, we’re using make your repository – same as you would for svn+ssh:

svnadmin create /var/svn/repos

Apache Modules

Install the prerequisite Apache modules:

yum install mod_dav_svn

This should also install mod_authz_svn which we’ll also be making use of. Both should end up in Apache’s module directory, in this case /etc/httpd/modules/

Download and install mod_authnz_external from its Google Code page. This allows Apache basic authentication to hook into an external authentication mechanism. should end up in Apache’s module directory but in my case it ended up in its default location of /usr/lib/httpd/modules/.

Download and install the companion pwauth utility from its Google Code page. In my case it installs to /usr/local/sbin/pwauth and needs suexec permissions (granted using chmod +s).

Apache Configuration (HTTP)


Listen		*:80
NameVirtualHost *:80

User		nobody
Group		nobody

LoadModule setenvif_module	modules/
LoadModule mime_module		modules/
LoadModule log_config_module	modules/
LoadModule dav_module		modules/
LoadModule dav_svn_module	modules/
LoadModule auth_basic_module    modules/
LoadModule authz_svn_module	modules/
LoadModule authnz_external_module modules/

LogFormat	"%v %A:%p %h %l %u %{%Y-%m-%d %H:%M:%S}t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" clean
CustomLog	/var/log/httpd/access_log	clean

<virtualhost *:80>

	AddExternalAuth         pwauth  /usr/local/sbin/pwauth
	SetExternalAuthMethod   pwauth  pipe

	<location / >
		DAV			svn
		SVNPath			/var/svn/repos
		AuthType		Basic
		AuthName		"SVN Repository"
		AuthzSVNAccessFile	/etc/httpd/conf/authz_svn.acl
		AuthBasicProvider	external
		AuthExternal		pwauth
		Satisfy			Any

			Require valid-user

Network Time (NTP)

In order to join a Windows domain, accurate and synchronised time is crucial, so you’ll need to be running NTPd.

yum install ntp
chkconfig ntpd on
service ntpd start

Samba Configuration

Here’s where AD comes in and in my experience this is by far the most unreliable service. Install and configure samba:

yum install samba
chkconfig winbind on

Edit your /etc/samba/smb.conf to pull information from AD.

	workgroup = EXAMPLE
	realm = EXAMPLE.COM
	security = ADS
	allow trusted domains = No
	use kerberos keytab = Yes
	log level = 3
	log file = /var/log/samba/%m
	max log size = 50
	printcap name = cups
	idmap backend = idmap_rid:EXAMPLE=600-20000
	idmap uid = 600-20000
	idmap gid = 600-20000
	template shell = /bin/bash
	winbind enum users = Yes
	winbind enum groups = Yes
	winbind use default domain = Yes
	winbind offline logon = yes

Join the machine to the domain – you’ll need an account with domain admin credentials to do this:

net ads join -U administrator

Check the join is behaving ok:

[root@svn conf]# net ads info
LDAP server:
LDAP server name:
Bind Path: dc=EXAMPLE,dc=COM
LDAP port: 389
Server time: Tue, 15 May 2012 22:44:34 BST
KDC server:
Server time offset: 130

(Re)start winbind to pick up the new configuration:

service winbind restart

PAM & nsswitch.conf

PAM needs to know where to pull its information from, so we tell it about the new winbind service in /etc/pam.d/system-auth.

# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        required
auth        sufficient nullok try_first_pass
auth        requisite uid >= 500 quiet
auth        sufficient try_first_pass
auth        required

account     required broken_shadow
account     sufficient
account     sufficient uid < 500 quiet
account     [default=bad success=ok user_unknown=ignore]
account     required

password    requisite try_first_pass retry=3
password    sufficient md5 shadow nullok try_first_pass use_authtok
password    sufficient use_authtok
password    required

session     optional revoke
session     required
session     [success=1 default=ignore] service in crond quiet use_uid
session     required      /lib/security/ 
session     required
session     optional

YMMV with PAM. It can take quite a lot of fiddling around to make it work perfectly. This obviously has an extremely close correlation to how flaky users find the authentication service. If you’re running on 64-bit you may find you need to install 64-bit versions of pam modules, e.g. mkhomedir which aren’t installed by default.

We also modify nsswitch.conf to tell other, non-pam aspects of the system where to pull information from:

passwd:     files winbind
shadow:     files winbind
group:      files winbind

To check the authentication information is coming back correctly you can use wbinfo but I like seeing data by using getent group or getent passwd. The output of these two commands will contain domain accounts if things are working correctly and only local system accounts otherwise.

External Authentication

We’re actually going to use system accounts for authentication. To stop people continuing to use svn+ssh (and thus bypassing the authorisation controls) we edit /etc/ssh/sshd_config and use AllowUsers or AllowGroups and specify all permitted users. Using AllowGroups will also provide AD group control of permitted logins but as the list is small it’s probably overkill. My sshd_config list looks a lot like this:

AllowUsers	root rmp contractor itadmin

To test external authentication run /usr/local/sbin/pwauth as below. “yay” should be displayed if things are working ok. Note the password here is displayed in clear-text:

[root@svn conf]# pwauth && echo 'yay' || echo 'nay'

Access Controls

/etc/httpd/authz_svn.conf is the only part which should require any modifications over time – the access controls specify who is allowed to read and/or write to each svn project, in fact as everything’s a URL now you can arbitrarily restrict subfolders of projects too but that’s a little OTT. It can be arbitrarily extended and can take local and active directory usernames. I’m sure mod_authz_svn has full documentation about what you can and can’t put in here.

# Allow anonymous read access to everything by default.
* = r
rmp = rw

rmp = rw
bob = rw



So far that’s all the basic components. The last piece in the puzzle is enabling SSL for Apache. I use the following /etc/httpd/httpd.conf:


Listen		*:80
NameVirtualHost *:80

User		nobody
Group		nobody

LoadModule setenvif_module	modules/
LoadModule mime_module		modules/
LoadModule log_config_module	modules/
LoadModule proxy_module		modules/
LoadModule proxy_http_module	modules/
LoadModule rewrite_module	modules/
LoadModule dav_module		modules/
LoadModule dav_svn_module	modules/
LoadModule auth_basic_module    modules/
LoadModule authz_svn_module	modules/
LoadModule ssl_module		modules/
LoadModule authnz_external_module modules/

Include conf.d/ssl.conf

LogFormat	"%v %A:%p %h %l %u %{%Y-%m-%d %H:%M:%S}t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" clean
CustomLog	/var/log/httpd/access_log	clean

<virtualhost *:80>

	Rewrite		/	[R=permanent,L]

<virtualhost *:443>

	AddExternalAuth         pwauth  /usr/local/sbin/pwauth
	SetExternalAuthMethod   pwauth  pipe

	SSLEngine on
	SSLProtocol all -SSLv2

	SSLCertificateFile	/etc/httpd/conf/svn.crt
	SSLCertificateKeyFile	/etc/httpd/conf/svn.key

	<location />
		DAV			svn
		SVNPath			/var/svn/repos
		AuthType		Basic
		AuthName		"SVN Repository"
		AuthzSVNAccessFile	/etc/httpd/conf/authz_svn.acl
		AuthBasicProvider	external
		AuthExternal		pwauth
		Satisfy			Any

			Require valid-user

/etc/httpd/conf.d/ssl.conf is pretty much the unmodified distribution ssl.conf and looks like this:

LoadModule ssl_module modules/

Listen 443

AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl    .crl

SSLPassPhraseDialog  builtin

SSLSessionCache         shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout  300

SSLMutex default

SSLRandomSeed startup file:/dev/urandom  256
SSLRandomSeed connect builtin

SSLCryptoDevice builtin

SetEnvIf User-Agent ".*MSIE.*" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0

You’ll need to build yourself a certificate, self-signed if necessary, but that’s a whole other post. I recommend searching the web for “openssl self signed certificate” and you should find what you need. The above httpd.conf references the key and certificate under /etc/httpd/conf/svn.key and /etc/httpd/conf/svn.crt respectively.

The mod_authnz_external+pwauth combination can be avoided if you can persuade mod_authz_ldap to play nicely. There are a few different ldap modules around on the intertubes and after a lot of trial and even more error I couldn’t make any of them work reliably if at all.

And if all this leaves you feeling pretty nauseous it’s quite natural. To remedy this, go use git instead.

Phish Anatomy

I receive four or five of these sorts of phishing emails a week so I thought I’d take a quick look at one and see how it’s put together.

Firstly a poorly constructed message from my- or more often someone else’s bank/tax office. Note capitalisation, lack of whitespace after fullstop in the first sentence, no currency denomination (e.g. £) for the amount but a realistic sum of money, definitely not $10,000,000 from the office of former attorney general Utoula of Lagos. Also note the threat of a deadline, even though none is stated.

Dear Applicant:

we have reviewed your tax return and our calculations of your last years accounts a tax refund of 178.25 is due.Please submit the tax refund request and allow us 3-6 days in order to process it.

A refund can be delayed for a variety of reasons.
For example submitting invalid records or applying after the deadline.

Submit the form attached to your email in order to verify your card.

with an attachment: return_form.html . Who sends a plain text email with an attached HTML file? Nobody except scammers, that’s who. Saving out return_form.html (without the .html extension, for safety) and having a look I found this at the top:

<script type="text/javascript" language="JavaScript">// < ![CDATA[
// Copyright © 2005 Voormedia - WWW.VOORMEDIA.COM
var i,y,x="3c21444f43545950452048544d4c205055424c494320222d2f2f5733432f2f44544420485

and this at the bottom:

for(i=0;i < x.length;i+=2){y+=unescape('%'+x.substr(i,2));}document.write(y);
// ]]>

(I’ve cut out the middle section because it’s long and I’m only interested in the techniques.)

So this is an obfuscated html page, entirely URL-encoded and embedded in a javascript string with a little bit of decoding tacked on the end. This is simple, but quite neat. Not a technique I’ve ever used to do anything “production” with. I cut the string out, saved it to a file and decoded it on the command line using

perl -MCGI -e '$str= <>;for (my $i=0;$i < length $str;$i+=2){
  print CGI::unescape(sprintf q[%%%s], substr $str, $i,2)
}' < > return_form.decoded

The decoded page contains an HTML form requesting name, email address, physical address, card number, mother’s maiden name, phone number, national insurance number and bank account details where refund payment is to be made, including CVV. It posts all that delicious data over to … woah hold on, that’s not the HMRC is it?

<td><form name="processForm" method="post" action="" OnSubmit="return go_step2();">

So who is it?

host domain name pointer

An Italian Vodafone DSL customer, probably a hacked home PC, most likely part of a botnet infected by a virus of some sort.

Let’s try poking the service:

wget -O-
--2011-10-25 13:40:49--
Connecting to connected.
HTTP request sent, awaiting response... 302 Found
Location: [following]

ok, that was a GET request and the script expects a POST, but it still bounces us straight out to, presumably logging whatever data was sent back in a database or IRC channel somewhere whilst leaving the unsuspecting user none the wiser.

What about running services? Ok, let’s use nmap:

nmap -PN

Starting Nmap 5.51 ( ) at 2011-10-25 13:44 BST
Nmap scan report for (
Host is up (0.10s latency).
Not shown: 990 closed ports
80/tcp open http
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds
1027/tcp open IIS
1244/tcp open isbconference1
1433/tcp open ms-sql-s
1720/tcp filtered H.323/Q.931
3306/tcp open mysql
3389/tcp open ms-term-serv

Nmap done: 1 IP address (1 host up) scanned in 7.69 seconds

So it’s running a few bits and pieces, things you wouldn’t normally open up given the choice..

wget -O- -q | grep -i title
<title>WAMPSERVER Homepage</title>

WAMP is a LAMP software stack built for Windows consisting of Apache, MySQL and PHP which explains some of the services this thing is running.

Here I paused and went back to look at the message headers.

Return-Path: < hmrc>
< snip >
Received: from User ([]) by with Microsoft SMTPSVC(6.0.3790.4675);
	 Tue, 25 Oct 2011 15:54:42 +0400
From: "HMRC"< hmrc>
Subject: ***SPAM*** We have reviewed your tax return
Date: Tue, 25 Oct 2011 07:54:42 -0400
MIME-Version: 1.0
Content-Type: multipart/mixed;
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2600.0000
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2600.0000
Message-ID: < maindclqgdyxsvvr0ws0000024a>
X-OriginalArrivalTime: 25 Oct 2011 11:54:42.0679 (UTC) FILETIME=[E1F60870:01CC930C]
To: undisclosed-recipients:;

A few things to highlight here – firsly the return address is (probably fictitious) not, as doing so could generate a large number of bounced messages sent back to HMRC and alerting them that there’s a phisher out there. Not that they can really do anything about it beyond cyber-investigation, but always good to keep things on the QT.

Ignoring the fact that my MTA has flagged the subject as SPAM, the original SMTP server shows up as . Riiight, a UK Tax email sent through a mail server in Russia.

Back to the spam detection. The headers injected by my MTA look like this:

X-Virus-Scanned: Debian amavisd-new at
X-Spam-Flag: YES
X-Spam-Score: 6.105
X-Spam-Level: ******
X-Spam-Status: Yes, score=6.105 tagged_above=-9999 required=4.8
	T_FROM_MISSPACED=0.01] autolearn=no

Good. Forged Mail User Agent, which isn’t something you might easily spot visually, and blacklisted in spamcop to boot.

Conclusions? Firstly don’t open attachments from untrusted sources. Duh, like I needed to tell you that. Secondly this is a UK-targetted scam, hosted on an Italian computer (probably) originating from Russia. This stuff is real