Friday, May 17, 2013

OpenVPN on CentOS Running on Hyper-V Using Windows PKI

I have a knack for titles eh? (Just smile and nod) I've been very impressed by the scalability demonstrated by OpenVPN in applications such as proXPN. Because of this I decided to implement OpenVPN on my favorite Hyper-V friendly distro, CentOS (6.4) and utilize a Windows PKI infrastructure for issuing certificates. Wanna do that? Let's:


  • You're familiar with the basics of PKI and how to process certificate requests. For more information on how to setup Windows PKI, see my article here.
  • You're familiar with the basics of Linux administration including editing config files, etc. 
  • You'll use sudo where applicable. I don't put it on command references to keep them cleaner. 

VM Creation

Create a VM with the following minimal virtual hardware: 
  • 384MB RAM
  • 1 Virtual Proc
  • 10GB HD
  • 2 NICs, (native) one either public/DMZ and one on your internal network (or inside the same DMZ if you desire)
It's possible to setup OpenVPN with 1 NIC, but in most configurations it's less secure and I won't be covering it here.

CentOS Setup

  1. Download from here; minimal is all you'll need.
  2. Insert the ISO to the virtual optical drive on the VM and start it up.
  3. Install CentOS to the VM with desired settings; no special configuration is necessary.
  4. Setup the NICs with static IP addresses. ("/etc/sysconfig/network-scripts/ifcfg-eth0" and "eth1", more instructions here) The following combinations for connectivity are possible: External/Internal, DMZ/Internal, External/DMZ, DMZ/DMZ. While different options have different pros and cons I'm not going to say what you should use because your circumstances will dictate your direction. I am using DMZ/Internal.
  5. Enable ipv4 forwarding by editing "/etc/sysctl.conf" and setting the following value
    net.ipv4.ip_forward = 1
    (It's there and 0 by default)
  6. Enable the EPEL so we can grab OpenVPN from it. Ensure you get the correct version, it was 6.8 as of this writing.
    rpm -Uvh
  7. Make the directory for the keys:(We'll copy keys into this dir before install)
    mkdir /etc/openvpn/keys  
  8. Make sure SSH is installed; if not:
    yum -y install openssh-server
  9. Perform any other tasks you should find applicable to a OpenVPN server such as configuring auto-update (yum install vixie-cron -y needed on CentOS 5 minimal install) or installing any other supporting apps you should want. Just remember that any application you add is a potential security liability.

Create Certificate Templates

I won't be covering the basics of setting up certificate templates; for that see my article.We'll just cover the specifics of this implementation here.

Server Template

  1. Open the certificate template manager for you enterprise PKI, and duplicate the Computer template. 
  2. When prompted, select the "Windows 2003 Server, Enterprise Edition" and hit "OK". (The version of the OS determines the template version) 
  3. Change the template display and name to something sufficiently descriptive such as "Computer (CompanyName OpenVPN Server)"
  4. Up the Validity period should you desire. 
  5. On the "Request Handling" tab set the private key to be exportable. (We're putting this on a Linux server afterall)

  6. On the "Subject Name" tab change the option to "Supply in the request"
  7. On the "Issuance Requirements" tab check that the template requires CA certificate manager approval should you desire. 
  8. Change to the "Extensions" tab, select "Application Policies" and click "Edit". Change it so that the "Server Authentication" is the only listed policy. Do not mark the extension critical. 
  9. Everything else can be left the same unless you should want to change the permissions under security. 

Client Template

Repeat the steps for the Server template except change the name in 3: to a client name and 8: to "Client Authentication".

After creating your templates publish them to your issuing CA. Again, see my article for instructions.

Enroll For Server Certificate

Since this step is virtually the same for the client I'll be redirecting you here for that step as well. There are a few modifications listed to these steps to accommodate that; don't pay any attention to "For Clients:" on the first go-around.

  1. Open the MMC (mmc.exe) and add the certificates snap-in for the local computer.  This can be done on any machine since we'll be exporting it.
  2. Enroll in the server certificate template we created earlier. We need to specify a few options, so click "More information is required to enroll for this certificate.  Click here to configure settings." For Clients: use the client template
  3. On the subject tab under Subject name, set the "common name" to the FQDN of the OpenVPN server. If you're using split DNS, make sure you set this to the external FQDN. For Clients: use your domain FQDN and set the OU to something consistent for all certs so that you can use the tls-verify option down the road should you choose. I use " ClientCert"

  4. On the "General" tab set the friendly name and the description to whatever you desire. I copy the common name and append "OpenVPN server"
  5. On the "Extensions" tab change "Key Usage" to include only "Key agreement" and "Key encipherment". "Make these key usages critical" can be checked. Based on the template it should already have "Extended Key Usage" set to "Server Authentication". For Clients: Use "Client Authentication" rather than server authentication.

  6. Under "Basic Constraints" click "Enable this extension" and "Make the basic constraints extension critical"
  7. Under the "Private Key" tab select "Allow the private key to be archived" should you desire.
  8. Click "OK" and then "Enroll". After the enrollment finishes you will need to click "Finish"
  9. Go to your CA and complete the enrollment process, then export the public key and import it back on the requesting computer to complete the certificate request process. 

Export and Split the Certificate

Like the section above, this will be used for the client as well. See For Clients: for that.
  1. Using the MMC or command line/powershell, export the newly created certificate WITH the private key to a pkcs12 format. Do not include all certificates in the certification path. Make sure you remember the password.
  2. Use OpenSSL (you'll need to download/install from here) to convert the pfx file to standard text, then cut and split the file into a cert file and a key file. I'd walk you through this, but how about I give you a Powershell script to do it instead. :)  Make sure to set the variables at the top of the file to what suits you. If you don't want to use powershell, just follow the comments and the commands to convert and split the file. For Clients: You may want to change the variable definitions at the top of the file to reference client file names.

#set the output files, working file, and password for the PFX

#Set the location to the Openssl.exe file
$openSSLLoc="C:\Program Files\OpenSSL-Win32\bin"

#Execute OpenSSL to convert the PFX file to standard text
& $openSSLLoc\OpenSSL.exe pkcs12 -in $inputFile -nodes -out $tempFile -passin pass:$password

#Now we need to find where the key and cert start and end. 
#For non-script users the point here is that we chop this one text file into two files,
# one the public key and one the private key. The two of those are denoted by the patterns listed below. 
$beginKey=get-content $tempFile |select-string -Pattern "-----BEGIN PRIVATE KEY-----"
$endKey=get-content $tempFile |select-string -Pattern "-----END PRIVATE KEY-----"
$beginCert=get-content $tempFile |select-string -Pattern "-----BEGIN CERTIFICATE-----"
$endCert=get-content $tempFile |select-string -Pattern "-----END CERTIFICATE-----"
$length=Get-Content $tempFile |Measure-Object

$key=Get-Content $tempFile |Select-String "-----BEGIN PRIVATE KEY-----" -Context 0,($endKey-$beginKey)
$cert=Get-Content $tempFile |Select-String "-----BEGIN CERTIFICATE-----" -Context 0,($endCert-$beginCert)

#Write out the files. Make sure they're ASCII encoded!
$key.line| Out-File $outputPrivateKey -Encoding ASCII
$key.Context.DisplayPostContext | Out-File $outputPrivateKey -Encoding ASCII -Append
$cert.line| Out-File $outputPublicKey -Encoding ASCII
$cert.Context.DisplayPostContext | Out-File $outputPublicKey -Encoding ASCII -Append

#Get rid of the temporary working file. Not needed for those not using the script. 
Remove-Item $tempFile -Force

Even if you can't run that hopefully you can decipher what's going on. Let me know if you would like any help.

Export The CA Certs and Copy to Server

For Clients: You can use the CA cert you created earlier for the server on the client as well. Just include it in the keys directory with the other certs. 
  1. Using the MMC or cmdline/powershell, export the root and (if applicable) intermediate certs to base-64 encoded x.509. For specifics, see here.
  2. If using 2 or more tier PKI: You can only specify one CA cert so if you have more than one CA in your chain you need to "stack" them. To do so, pipe both into one file with the simple command from a command prompt: "type RootCA.cer IntermediateCA.cer > CAs.cer" where rootca.cer and intermediateca.cer are the exported certificates. Substitute cat for type if you're doing this on a Linux platform. This will join any listed certs into one chain for use with OpenVPN. 
  3. (For Clients: This step is not necessary.) Generate Diffe-Helman by executing
    openssl genpkey -genparam -algorithm DH -out dh.pem 1024
    This can be done on either the Windows machine you are on or directly on the OpenVPN server. Either way, just make sure you copy the dh.pem to /etc/openvpn/keys/ on the OpenVPN server. (see step below)
  4. Copy the server private key, public key (both created earlier) and (stacked) ca public key to "/etc/openvpn/keys" on the OpenVPN server. These keys will be referred to as server.cer, server.key, and ca.cer in the future. In order to do security correctly, make sure permissions are cranked down on these files as much as possible, especially the server.key file. (private key) On Windows I use pscp.exe which ships with Putty. For Clients: Just copy the certs/keys into a temp directory for now, we'll move them to the final location later.

Install/Configure OpenVPN on Server

  1. Make sure you enabled EPEL above and then execute the following to to install OpenVPN and LZO:
    yum install openvpn lzo -y
  2. Copy the sample configs as a starting point:
    cp /usr/share/doc/openvpn-2.2.2/server.conf /etc/openvpn/
    (Make sure the version number in the /usr/share/doc path hasn't changed since I wrote this) 

  3. Edit /etc/openvpn/server.conf and pay attention to the following lines: 
    1. Set the ipaddress to the address of the NIC you intend to bind to. This should be the NIC closest to the internet, be it an external or DMZ NIC. 
    2. Set the port to have OpenVPN listen on. 1194 is default and a firewall rule below assumes that, so be aware if you change it.
      port 1194
    3. Protocol, UDP vs. TCP. UDP is faster and the client machine will already be managing TCP retrans should they be needed. 
      proto UDP
    4. A bit on the the difference between tun and tap: tun is a layer 3 IP routing based solution, whereas tap is a level 2 based solution. Tap can shoot non-routable protocols over the tunnel, so if you're going to play Descent II this is the one you want. The problem (or not) with using tap for standard client connectivity is besides being more complex to setup it also routes broadcast traffic. This won't scale well. Generally tap is used for site-to-site connections. If you want to use tap, you'll need to setup Ethernet bridging. This guide assumes dev tun.
      dev tun
    5. Specify the keys we copied over earlier:
      ca /etc/openvpn/keys/ca.cer
      cert /etc/openvpn/keys/server.cer
      key /etc/openvpn/keys/server.key
    6. Specify Diffe-Helman copied over earlier:
      dh /etc/openvpn/keys/dh.pem
    7. Set the subnet you want to route clients to. This makes that network route through the OpenVPN connection. Note that if your IP range is the same on the close end you'll have problems. Feel free to add multiple lines for multiple subnets.
      push "route"
    8. Set the internal DNS servers you want to push to the clients to facilitate looking up servers on the other side of the VPN. Add one line for each DNS server you would like in the search order of the client. 
      push "dhcp-option DNS"
    9. If you uncomment client-to-client OpenVPN will allow clients to talk to each other. You'll also need to config firewall rules appropriately should you wish to do this.
    10. Set the maximum number of simultaneous clients.
      max-clients 100
    11. Uncomment the following lines to increase security on 'nix systems by lowering privilege level after program launch.
      user nobody
      group nobody
  4. Set OpenVPN to start on boot:
    chkconfig openvpn on
  5. Configure the IPTables firewall; assuming eth0 is your internal NIC and eth1 is your external NIC. Change the listed NICs in each rule if that assumption is not correct!
    1. Edit the IPTables rule by editing /etc/sysconfig/iptables . There are other ways to accomplish IPTables editing, but since I make few changes I would rather just work directly on the config.
    2. First, change your SSH rule to allow port 22 on the internal NIC only; you should already have a rule listing --dport 22. Find it and change it to:
      -A INPUT -i eth0 -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT"
    3. Allow VPN connections on the external interface. If you have changed from the default port make sure to change the port 1194 reference listed here: " -A INPUT -i eth1 -m state --state NEW -m udp -p udp --dport 1194 -j ACCEPT"
    4. Setup forwarding rules for this network:
      -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT"
      -A FORWARD -s -j ACCEPT
    5. Make sure the rules from 2 and 3 are listed before the -A INPUT -j REJECT and the rules from 4 are listed before the -A FORWARD -j REJECT default rejection rules or they'll be ignored. 
  6. Reboot the box and ensure that OpenVPN starts successfully. Logs will be written to /var/log/messages. If you have problems and need to troubleshoot you can up the "verb" value in /etc/openvpn/server.conf.
Alternatively, you may see rules listed such as iptables -A INPUT -i tun0 -j ACCEPT and iptables -A FORWARD -i tun0 -j ACCEPT listed elsewhere; these would work in lieu of the rule, but there are security tradeoffs. Feel free to try either if it suits you. For more information about iptables, see this.

Enroll for Client Certificate

For this section you can follow the same instructions as the section "Enroll for Server Ceritificate" with the modifications listed within those points. (Mods in point 2, 3, and 5)

After doing this refer to the Export and Split the Certificate section and use that logic to do the same task to the client cert as well.

Export the CA Certs and Copy to Client

You can use the CA cert you created earlier for the server on the client as well. Just include it in the keys directory with the other certs.

Install/Configure OpenVPN on Windows Clients

  1. Download and install from here
  2. In the install directory, copy the client.ovpn file from the sample-config folder to the config folder for a starting point.
  3. Copy the keys we created earlier to a folder on the client; I use (OpenVPN Install Directory)\Keys.
  4. Edit ".\config\client.ovpn" and pay attention to the following lines: 
    1. Again, we're going to cover tun here. More work will be necessary if you want to do tap. See the server section for an explanation of what these options are. 
      dev tun
    2. Enter the external FQDN and port for your OpenVPN server.
      remote 1194
    3. List your certs copied over earlier. Make sure you set the directory correctly and protect that directory relative to the fact that a private key resides within it. On windows you need to use double backslashes because "\" is an escape character. 
      ca C:\\InstallDirectory\\Keys\\ca.cer
      cert C:\\InstallDirectory\\Keys\\clientcert.cer
      key C:\\InstallDirectory\\Keys\\clientcert.key
    4. Remark out ns-cert-type server and add remote-cert-tls server in its place. ns-cert-type is deprecated and the template wasn't updated. See here for more info.
      # ns-cert-type server
      remote-cert-tls server
  5. After saving the file, launch OpenVPN GUI as Administrator.

  6. Connect and enjoy!


Obviously given the scope of this article there is substantially more depth one could go into; if you have any questions please hit me up in the comments. I'm sure you can see the possibilities of this combination as there is much more automation that could be done. Thus far I've been very impressed by the performance of OpenVPN and I look forward to scaling it out in the future.

References/Additional Reading

No comments: