Securing the WordPress admin interface using (Free!) SSL certificates

Posted: December 19th, 2011 | Filed under: Fedora | Tags: , , , , , , | 1 Comment »

Last year I migrated my website off Blogger to a WordPress installation hosted on my Debian server. Historically my website has only been exposed over plain old HTTP, which was fine since the Blogger publishing UI was running HTTPS. With the migration to WordPress install though, the publishing UI is now running on my own webserver and thus the lack of HTTPS on my server becomes a reasonably serious problem. Most people’s first approach to fixing this would be to just generate a self-signed certificate and deploy that for their server, but I rather wanted to have a x509 certificate that would be immediately trusted by any visiting browser.

Getting free x509 certificates from StartSSL

The problem is that the x509 certificate authority system is a bit of a protection racket with recurring fees that just cannot justify the level of integrity they provide. There is one exception to the norm though, StartSSL offer some basic x509 certificates at zero cost. In particular you can get Class1 web server certificates and personal client identity certificates. The web server certificates are restricted in that you can only include 2 domain names in them, your basic domain name & the same domain name with ‘www.’ prefixed. If you want wildcard domains, or multiple different domain names in a single certificate you’ll have to go for their pay-for offerings. For many people, including myself, this limitation will not be a problem.

StartSSL have a nice self-service web UI for generating the various certificates. The first step is to generate a personal client identity certificate, which the rest of their administrative control panel relies on for authentication. After generation is complete, it automatically gets installed into firefox’s certificate database. You are wisely reminded to export the database to a pkcs12 file and back it up somewhere securely. If you loose this personal client certificate, you will be unable to access their control panel for managing your web server certificates. The validation they do prior to issuing the client certificate is pretty minimal, but fully automated, in so much as they send a message to the email address you provide with a secret URL you need to click on. This “proves” that the email address is yours, so you can’t request certificates for someone else’s email address, unless you can hack their email accounts…

Generating certificates for web servers is not all that much more complicated. There are two ways to go about it though, either you can fill in their interactive web form & let their site generate the private key, or you can generate a private key offline and just provide them with a CSR (Certificate Signing Request). I tried todo the former first of all, but for some reason it didn’t work – it got stuck generating the private key, so I switched to generating a CSR instead. The validation they do prior to issuing a certificate for a web server is also automated. This time they do a whois lookup on the domain name you provide, and send a message with a secret URL to the admin, technical & owner email addresses in the whois record. This “proves” that the domain is yours, so you can’t requests certificates for someone else’s domain name, unless you can hack their whois data or admin/tech/owner email accounts…

Setting up Apache to enable SSL

The next step is to configure apache to enable SSL for the website as a whole. There are four files that need to be installed to provide the certificates to mod_ssl

  • ssl-cert-berrange.com.pem – this is the actual certificate StartSSL issued for my website, against StartSSL’s Class1 root certificate
  • ssl-cert-berrange.com.key – this is the private key I generated and used with my CSR
  • ssl-ca-start.com.pem – this is the master StartSSL CA certificate
  • ssl-ca-chain-start.com-class1-server.pem – this is the chain of trust between your website’s certificate and StartSSL’s master CA certificate, via their Class1 root certificate

On my Debian Lenny host, they were installed to the following locations

  • /etc/ssl/certs/ssl-cert-berrange.com.pem
  • /etc/ssl/private/ssl-cert-berrange.com.key
  • /etc/ssl/certs/ssl-ca-chain-start.com-class1-server.pem
  • /etc/ssl/certs/ssl-ca-start.com.pem

The only other bit I needed todo was to setup a new virtual host in the apache config file, listening on port 443

<VirtualHost *:443>
  ServerName www.berrange.com
  ServerAlias berrange.com

  DocumentRoot /var/www/berrange.com
  ErrorLog /var/log/apache2/berrange.com/error_log
  CustomLog /var/log/apache2/berrange.com/access_log combined

  SSLEngine on

  SSLCertificateFile    /etc/ssl/certs/ssl-cert-berrange.com.pem
  SSLCertificateKeyFile /etc/ssl/private/ssl-cert-berrange.com.key
  SSLCertificateChainFile /etc/ssl/certs/ssl-ca-chain-start.com-class1-server.pem
  SSLCACertificateFile /etc/ssl/certs/ssl-ca-start.com.pem
</VirtualHost>

After restarting Apache, I am now able to connect to https://berrange.com/ and that my browser trusts the site with no exceptions required.

Setting up Apache to require SSL client cert for WordPress admin pages

The next phase is to mandate use of a client certificate when accessing any of the WordPress administration pages. Should there be any future security flaws in the WordPress admin UI, this will block any would be attackers since they will not have the requisite client SSL certificate. Mnadating use of client certificates is done with the “SSLVerifyClient require” directive in Apache. This allows the client to present any client certificate that is signed by the CA configured earlier – that is potentially any user of StartSSL.  My intention is to restrict access exclusively to the certificate that I was issued. This requires specification of some match rules against various fields in the certificate. First lets see the Apache virtual host configuration additions:

<Location /wp-admin>
  SSLVerifyClient require
  SSLVerifyDepth  3
  SSLRequire %{SSL_CLIENT_I_DN_C} eq "IL" and \
             %{SSL_CLIENT_I_DN_O} eq "StartCom Ltd." and \
             %{SSL_CLIENT_I_DN_OU} eq "Secure Digital Certificate Signing" and \
             %{SSL_CLIENT_I_DN_CN} eq "StartCom Class 1 Primary Intermediate Client CA" and \
             %{SSL_CLIENT_S_DN_CN} eq "dan@berrange.com" and \
             %{SSL_CLIENT_S_DN_Email} eq "dan@berrange.com"
</Location>

The first 4 match rules here are saying that the client certificate must have been issued by the StartSSL Class1 client CA, while the last 2 matches are saying that the client certificate must contain my email address. The security thus relies on StartSSL not issuing anyone else a certificate using my email address. The whole lot appears inside a location match against ‘/wp-admin’ which is the URL prefix all the WordPress administration pages have. The entire block must also be duplicated using a location match against ‘/wp-login.php’ to protect the user login page too.

<Location /wp-login.php>
  SSLVerifyClient require
  SSLVerifyDepth  3
  SSLRequire %{SSL_CLIENT_I_DN_C} eq "IL" and \
             %{SSL_CLIENT_I_DN_O} eq "StartCom Ltd." and \
             %{SSL_CLIENT_I_DN_OU} eq "Secure Digital Certificate Signing" and \
             %{SSL_CLIENT_I_DN_CN} eq "StartCom Class 1 Primary Intermediate Client CA" and \
             %{SSL_CLIENT_S_DN_CN} eq "dan@berrange.com" and \
             %{SSL_CLIENT_S_DN_Email} eq "dan@berrange.com"
</Location>

Preventing access to the WordPress admin pages via non-HTTPS connections.

Finally, to ensure the login & admin pages cannot be accessed over plain HTTP, it is necessary to alter the virtual host config for port 80, to include

RewriteEngine On
RewriteRule ^(/wp-admin/.*) https://www.berrange.com$1 [L,R=permanent]
RewriteRule ^(/wp-login.php.*) https://www.berrange.com$1 [L,R=permanent]

To be honest, I should just put a redirect on ‘/’ to prevent any use of the plain HTTP site at all, but I want to test how well my tiny virtual server copes with the load before enabling HTTPs for everything.

Hopefully this blog post has demonstrated that setting up your personal webserver with certificates that any browser will trust, is both easy and cheap (free), so there is no reason to use self-signed certificates unless you need multiple domain names / wildcard addresses in your certificates and you’re unwilling to pay money for them.

Porting NetCF to Debian/Ubuntu, Suse and Windows

Posted: September 28th, 2011 | Filed under: Fedora, libvirt, Virt Tools | Tags: , , , , , | 4 Comments »

The NetCF library provides a simple API for managing network interface configuration files. Libvirt has used NetCF for several releases now to provide the internal implementation of the virInterface APIs. This allows libvirt based applications to configure plain ethernet devices, bridging, bonding and VLANs, which covers all the network configurations required for a typical virtualization host. The problem is that nearly every single OS distro has a different format for its configuration files, and NetCF has only had an implementation that works with Fedora and RHEL. This has led to a perception that NetCF is a project that is “Red Hat specific”, which is not at all the intention when NetCF was created. To try to address this problem, I have spent the last couple of weeks hacking on a driver for NetCF that knows how to deal with the Debian /etc/network/interfaces file format. As of last night, I pushed the code into the upstream NetCF git repository, so Debian & Ubuntu users have something nice to try out in the next release. Indeed, it would be good if any interested persons were to try out the latest NetCF GIT code before the next release to make sure it works for someone other than myself :-)

In the course of porting to Debian, we also became aware that there was a port of NetCF to Suse distributions, found as a patch in the netcf RPM for OpenSuse 11. We have not had chance to test it ourselves yet, but on the assumption that it must have been at least partially functional when added to OpenSuse 11 RPMs, we have merged that patch into the latest NetCF GIT. If anyone is using Suse and wants to try it out and report what works & what doesn’t, we’d appreciate the feedback.  If someone wants to actually do further development work for the Suse driver to finish it off, that would be even better !

Finally, a few months ago, there was work on creating a Windows driver for NetCF. This was posted a few times to the NetCF mailing lists, but was never completed because the original submitter ran out of time to work on it. In the hope that it will be a useful starting point for other interested developers, we have also merged the most recent Windows patch into the NetCF GIT repository. This is by no means useful yet, only able to list interfaces and bring them up/down – it can’t report their config, or create new interfaces.

Supported Debian driver configurations

Back to the Debian driver now. The Debian /etc/network/interfaces configuration file is quite nicely structured and reasonably well documented, but one of the problems faced is that there is often more than one way to get to the same end result. To make the development of a Debian driver a tractable problem, I decided to pick one specific configuration approach for each desired network interface arrangement. So while NetCF should be able to read back any configuration that it wrote itself, it may not be able to correctly read arbitrary configurations that a user created manually. I expect that over time the driver will iteratively improve its configuration parsing to cope with other styles.

AFAICT, the Debian best practice for setting up vlans, bonding & bridging is to use the extra configuration syntax offered by certain add in packages, rather than custom post scripts. So for the NetCF Debian driver to work, it is necessary to have the following DPKGs installed:

  - ifenslave    (required for any bonding config)
  - bridge-utils (required for any bridging config)
  - vlan         (required for any vlan config)

Ethernet Devices

Loopback:

  auto lo
  iface lo inet loopback

DHCP:

  auto eth0
  iface eth0 inet dhcp

Static config:

  auto eth0
  iface eth0 inet static
     address 196.168.1.1
     netmask 255.255.255.0
     gateway 196.168.1.255

No IP config

  auto eth0
  iface eth0 inet manual

Config with MTU / MAC addres

  auto eth0
  iface eth0 inet dhcp
     hwaddr ether 00:11:22:33:44:55
     mtu 150

Bonding

With miimon

  iface bond0 inet dhcp
     bond_slaves eth1 eth2
     bond_primary eth1
     bond_mode active-backup
     bond_miimon 100
     bond_updelay 10
     bond_use_carrier 0

With arpmon

  iface bond2 inet dhcp
     bond_slaves eth6
     bond_primary eth6
     bond_mode active-backup
     bond_arp_interval 100
     bond_arp_ip_target 198.168.1.1

VLANs

  auto eth0.42
  iface eth0.42 inet static
     address 196.168.1.1
     netmask 255.255.255.0
     vlan_raw_device eth0

Bridging

With single interface and IP addr

  auto br0
  iface br0 inet static
     address 192.68.2.3
     netmask 255.255.255.0
     mtu 1500
     bridge_ports eth3
     bridge_stp off
     bridge_fd 0.01

With no IP addr

  auto br0
  iface br0 inet manual
     bridge_ports eth3
     bridge_stp off
     bridge_fd 0.01

With multiple interfaces

  auto br0
  iface br0 inet static
     address 192.68.2.3
     netmask 255.255.255.0
     mtu 1500
     bridge_ports eth3 eth4
     bridge_stp off
     bridge_fd 0.01

With no interface or addr

  auto br0
  iface br0 inet manual
     mtu 1500
     bridge_ports none
     bridge_stp off
     bridge_fd 0.01

Complex Bridging

Bridging a bond:

  auto br1
  iface br1 inet manual
     mtu 1500
     bridge_ports bond1
     bridge_stp off
     pre-up ifup bond1
     post-down ifdown bond1
  iface bond1 inet manual
     bond_slaves eth4
     bond_primary eth4
     bond_mode active-backup
     bond_miimon 100
     bond_updelay 10
     bond_use_carrier 0

Bridging a VLAN:

  auto br2
  iface br2 inet manual
     mtu 1500
     bridge_ports eth0.42
     bridge_stp off
  iface eth0.42 inet manual
     vlan_raw_device eth0

IPv6

Static addressing, with multiple addresses:

  auto eth5
  iface eth5 inet6 static
     address 3ffe:ffff:0:5::1
     netmask 128
     pre-up echo 0 > /proc/sys/net/ipv6/conf/eth5/autoconf
     post-down echo 1 > /proc/sys/net/ipv6/conf/eth5/autoconf
     up /sbin/ifconfig eth5 inet6 add 3ffe:ffff:0:5::5/128
     down /sbin/ifconfig eth5 inet6 del 3ffe:ffff:0:5::5/128

Stateless autoconf

  auto eth5
  iface eth5 inet6 manual

DHCPv6 with autoconf

  auto eth5
  iface eth5 inet6 dhcp

DHCPv6 without autoconf

  auto eth5
  iface eth5 inet6 dhcp
     pre-up echo 0 > /proc/sys/net/ipv6/conf/eth5/autoconf
     post-down echo 1 > /proc/sys/net/ipv6/conf/eth5/autoconf

The most recent set of example configurations can be found in the documentation in GIT.