Dienstag, 24. Dezember 2013

On the way to deterministic binariy (gcc) output

In some projects (actually it should be in general) it is necessary to prove that two releases from the same input (source + configuration) generate the same output. This property is useful because it allows one to compare the binary output of a compilation/linking step. If there is no difference at all, one can be sure that there was no change on source code either and that the behaviour of the software doesn't change (as long as one trusts the compiler). It also allows one to prove that changes in the build infrastructure/system doesn't change the output of a build, that an archiving concept works, etc.

There are several aspects that should be considered on the way to deterministic binary output:
  1. absolute paths which are compiled into the binary code (mainly for debugging)
  2. compiler sometimes decide randomly e.g., which optimization to use, which path to choose, or how to mangle a specific function in anonymous namespaces. Of course, this has no influence on the functional properties of the code, the binaries are (they should ;-) ) always be functional equivalent.
  3. timestamps, uuid in object files, libraries, etc.
  4. timestamps, dates generated by __DATE__, __TIME__, __TIMESTAMP__ macros

An example where the first point comes into play is the __FILE__ macro which is often used for debugging purposes. The implementation of how this macro gets expanded depends from compiler to compiler. For example Microsofts C++ Compiler uses an FC flag which allows to control if the macro expansion to absolute or relative paths. Of course, the question regarding absolute or relative paths is only of value if you have multiple build machines with different location of workspaces or you care about information of your workspace that gets delivered to your customer. You can easily check if there are any path informations like that in the binary by searching for the workspace path in the binary.

strings binary.out | grep workspace

If there is any output that contains full paths to your sourcecode files then you would have to take care of this problem. For me, I have discovered two solutions:
  • Using compiler switches to make sure that paths are relative
  • Making sure that the build environment/workspaces are on the same absolute paths independent of the actual build machine(master, slave jenkins)
The next point is quite interesting. The average programmer would expect that given a specific piece of code and a set of rules for the compiler and linker, the outcome would be always the same. Well this is (normally) true for the functional behaviour of the piece of code. However, this is not true when comparing the two binaries on byte level. You can easily compare two binaries by using the


cmp -b -l b1 b2
 
This will show you all binary differences with location and difference for b1 vs. b2.
The binary incompatibility has several reasons: one is for examples how gcc mangles functions in anonymous namespaces. A part of this name mangling is randomized by using a random generator. If you have taken care of our first point and your object files still differ, then you can use a special gcc parameter. The -frandom-seed=<string> allows one to specify a string which will be used to initialize the random generator. The documentation for this option tells us...

       -frandom-seed=string
           This option provides a seed that GCC uses when it would otherwise
           use random numbers.  It is used to generate certain symbol names
           that have to be different in every compiled file.  It is also used
           to place unique stamps in coverage data files and the object files
           that produce them.  You can use the -frandom-seed option to produce
           reproducibly identical object files.

           The string should be different for every file you compile.

That means that we have to provide random strings for each file we will compile. I have found one solution to this problem in this blogpost by Jörg Förstner. He suggested to use the md5 hash of the source file as input to the -frandom-seed. This is sufficient as it will change for different source files and vice versa will provide the same seed if the source hasn't changed. He suggested to use the following compile parameters...

           $(CC) -frandom-seed=$(shell md5sum $< | sed 's/\(.*\) .*/\1/') $(CCFLAGS) -c $< -o $@

The seed is constructed by calculating the md5sum of the source code file (e.g. test.cpp).

          md5sum $<
          b61f78373a5b404a027c533b9ca6280f  test.cpp

This result is piped into sed (sed 's/\(.*\) .*/\1/') to cut away the filename part behind the actual md5 sum.

The problem described by the third point (timestamps, uuids) is created by some linkers in the linking step. For example when building object files/static libraries/archives with the ar tool, ar will also insert timestamps, uuids and other stuff which will change from build to build. You can easily try this out by executing ar two times and comparing by comparing the generated output. However, for the ar tool there is a simple solution to this problem, ar comes with the -D option which will turn ar into deterministic mode. The documentation for -D tells us...

       D   Operate in deterministic mode.  When adding files and the archive
           index use zero for UIDs, GIDs, timestamps, and use consistent file
           modes for all files.  When this option is used, if ar is used with
           identical options and identical input files, multiple runs will
           create identical output files regardless of the input files'
           owners, groups, file modes, or modification times. 

My command line for building a static library looks like:
      ar Drvs <output> <input> 

In some cases, for example when using a cross-compiler tool chain, you cannot easily change the bin-utils version to get an ar version that supports the deterministic option. This was the motivation for someone to write a tool that wipes out the timestamps in the generated archive files. You can find this tool at github under the following url: https://github.com/nh2/ar-timestamp-wiper/tree/master. If you use cmake as part of your build system, you can link in the tool in the finish step of the archive generation.
          SET(CMAKE_C_ARCHIVE_FINISH "ar-timestamp-wiper ")
          SET(CMAKE_CXX_ARCHIVE_FINISH ${CMAKE_C_ARCHIVE_FINISH})
          SET(CMAKE_C_ARCHIVE_FINISH ${CMAKE_C_ARCHIVE_FINISH})   

The last point (timestamps, dates introduced by macros like __DATE__, __TIME__, __TIMESTAMP__) can addressed by specifying a deterministic/known value for the corresponding build. I know at least two ways how to do this, both work in general, but sometimes one approach is easier to use then the other.
  1. faketime/libfaketime
  2. overriding the macros by compiler defines  
The first approach works by calling the build step/executable using faketime. Faketime then uses the LD_PRELOAD mechanism to override some of the syscalls to pretend a specific time.
          apt-get install faketime
          faketime '2014-01-09 00:00:00' /usr/bin/date
The second approach works by adding for example -D__DATE__="'Jan 9 2014'" -D__TIME__="'12:00:00'" to your buildstep. You have to take care that that you specify a valid date and time according to the expected return values of __DATE__ and __TIME__.

 References:

  • http://cmake.3232098.n2.nabble.com/How-to-calculate-a-value-quot-on-the-fly-quot-for-use-with-gcc-compiler-option-td3277077.html
  • http://stackoverflow.com/questions/14653874/deterministic-binary-output-with-g
  • https://wiki.debian.org/ReproducibleBuilds

Freitag, 13. Dezember 2013

KVM networking with guests having static public ip addresses

In this blogpost I will show you how to setup kvm networking in the way that guest IPs are mapped to the local network. This way it is possible have guests running on the host with external/public IP addresses. At first kvm should be already installed, additionally we should check if bridge-utils is already installed.
 
sudo apt-get install bridge-utils

Assuming the following scenario:
  • host system ip configuration:
    •   IP: 10.10.10.5
    • netmask: 255.255.0.0
    • Gateway: 10.10.0.1
    • DNS: 10.10.0.250, 10.10.0.251
  • guest ip configuration:
    • IP: 10.10.10.10
    • netmask: 255.255.0.0
    • Gateway: 10.10.0.1
    • DNS: 10.10.0.250, 10.10.0.251
We have to edit /etc/network/interfaces to enable the bridges. Currently my interfaces file looks like that:

auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
       address 10.10.10.5
       netmask 255.255.0.0
       gateway 10.10.0.1
       dns-nameservers 10.10.0.250 10.10.0.251 


This has to be changed in the following way:
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet manual

auto br0
iface br0 inet static
       address 10.10.10.5
       netmask 255.255.0.0
       gateway 10.10.0.1
       dns-nameservers 10.10.0.250 10.10.0.251 
       bridge_ports eth0
       bridge_stp off
       bridge_fd 0
       bridge_maxwait 0
Now we have to restart networking to incorporate the changes.
/etc/init.d/networking restart

After that we can run vmbuilder to create a new machine with the corresponding client ip configuration.
vmbuilder kvm ubuntu --suite=precise --flavour=virtual --arch=amd64 
--install-mirror=http://apt-cacher-ng:3142/ubuntu -o --libvirt=qemu:///system 
--ip=10.10.10.10 --gw=10.10.10.1 --part=vmbuilder.partition  --templates=templates/ 
--user=admin --name=admin --pass=pass --addpkg=acpid 
--firstboot=/opt/kvm/images/vslave-001/vmbuilder.boot.sh 
--mem=4096 --hostname=build-vslave-001 --bridge=br0 


  • https://help.ubuntu.com/community/KVM/Networking
  • http://docwiki.cisco.com/wiki/OpenStack:VM_Build
  • http://foswiki.org/Support/UbuntuVmBuilder
  • http://www.fak-online.net/?p=22
  • http://www.linux-kvm.org/page/Networking
  • http://blog.braastad.org/?p=128
  • http://www.howtoforge.com/virtualization-with-kvm-on-ubuntu-12.04-lts

Donnerstag, 12. Dezember 2013

changing ulimits for jenkins (daemons started by start-stop-daemon)

Our build infrastructure consists of several build systems running jenkins as a CI platform. The hardware that hosts these servers is very powerful, that means large and fast disks and up to 48 cores and 128GB of RAM. Having such a hardware allows to us to run a higher number of parallel build tasks which is the cause why we have hit the 4k limit for open files (Ubuntu 12.04LTS with default settings). Using ulimit -a will show you all limit settings for the current user.

root@system:# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 1031036
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1031036
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

This output shows that the maximum number of open files (soft limit) is 1024, the hard limit can be show with ulimit -a -H giving the following output.

root@system:# ulimit -a -H
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 1031036
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 4096
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) unlimited
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1031036
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

A user can increase its ulimits only until the hard limit has been reached by using the ulimit command. Ulimits can be specified system wide by using the /etc/security/limits.conf configuration file. However, these limits can only be applied to sessions that use pam daemon. We will come to this later...
In the following we will change the ulimits for a jenkins server in 2 steps:
  1. setting limits for user jenkins in /etc/security/limits.conf by adding the following two lines to the end of this file...
  2. jenkins soft nofile 64000
    jenkins hard nofile 64000
    
  3. setting limits for the jenkins process started by start-stop-daemon
  4. RUN_AS=jenkins
    COMMAND=start_jenkins.sh
    d_start() {
            ulimit -n 64000
            start-stop-daemon --start --quiet --background --chuid $RUN_AS --exec $COMMAND
    }
    
    The important part is that you have to specify the ulimits, e.g., for the number of open files before start-stop-daemon is called. The reason is that start-stop-daemon doesn't consider pam and hence will not find the limits which have been specified in /etc/security/limits.conf. Once the limits have been changed, one can check this by running a test job in jenkins that uses a shell as build step in which ulimit -a is executed...

Resources:

  • http://superuser.com/questions/454465/make-ulimits-work-with-start-stop-daemon
  • http://www.windowslinuxosx.com/q/answers-values-set-in-limits-conf-are-not-working-678783.html
  • http://posidev.com/blog/2009/06/04/set-ulimit-parameters-on-ubuntu/
  • http://serverfault.com/questions/472904/how-to-diagnose-ulimit-enforcement
  • http://www.ovirt.org/Jenkins#change_open_files
  • http://nishal-tech.blogspot.de/2013/07/how-to-set-ulimit-in-ubuntudebian-linux.html
  • http://www.altj.com/tag/ubuntu/

nagios: check number of threads,open files and http for service using check_by_ssh and a simple bash script

Recently, I had the problem that some of our jenkins servers in our build farm sometimes spawned up to 16k threads. In order to diagnose the problem and in order to monitor the services for availability issues I have created some monitoring scripts for nagios. This tutorial will show you how to monitor jenkins using nagios. Nagios is a nice tool for monitoring infrastructure and services. We will use the check_by_ssh service to execute the checks on the target system.

We will cover the following steps:
  1. Install/configure check_by_ssh plugin
  2. Configure service check_http to check jenkins web interface availability
  3. Configure service to check for jenkins number of open files (ulimit issue)
  4. Configure service to check for jenkins number of threads
1.Install/Configure check_by_ssh
  •  client side
    • install client package
      • apt-get install nagios-nrpe-server
    • enable nagios shell
      • usermod -s /bin/bash nagios
  • server side
    • install server package
      • apt-get install nagios3
    • enable nagios shell
      • usermod -s /bin/bash nagios
    • create ssh keypair
      • su - nagios
      • ssh-keygen -N ""
    • copy public key to clients using scp or other methods e.g. salt, chef
      •  add public key to ~nagios/.ssh/authorized_keys
  • check that the ssh connection is working
    • su - nagios
    • ssh <client-ip-address>
  • check that check_by_ssh is working
    • /usr/lib/nagios/plugins/check_by_ssh -l nagios -H <client-dns-name> -C "hostname"
      • output: <client-dns-name>
 2. Configure service check_http to check jenkins web interface availability
For the http check I was using the check_http command which is already provided as a nagios plugin (/usr/lib/nagios/plugins/). The corresponding nagios service section (services_nagios2.cfg) looks like this:

define service {
        hostgroup_name                  jenkins-servers
        service_description             jenkins nginx http redirect
        check_command                   check_http!-p 8080
        use                             generic-service
        notification_interval           0;
}
Actually in my case I have nginx proxies in front of the jenkins servers to do the https stuff and to redirect from non http addresses to https. This check here will check if the http redirect which is installed on port 8080 is available. However, this can be also used to check for normal jenkins instances running.

3.  Configure service to check for jenkins number of open files (ulimit issue)

Checking the number of open files for a specific user is a little bit trickier. I was using a perl script found at http://exchange.nagios.org/directory/Plugins/Uncategorized/Operating-Systems/Linux/check-open-files/details which I have placed in a plugin sub folder in the nagios home directory (on the clients). Then I have added a check_by_ssh_open_files custom command to the custom_commands.cfg nagios configuration file. The command uses the check_by_ssh command to call the plugin which has been installed on the client side.
define command {
        command_name    check_by_ssh_open_files
        command_line    $USER1$/check_by_ssh -o StrictHostKeyChecking=no -l nagios -H $HOSTADDRESS$ -C "/var/lib/nagios/plugins/check_unix_open_files.pl -a $ARG1$ -w $ARG2$,$ARG2$ -c $ARG3$,$ARG3$"
        }
After that I have defined a custom service that uses the check_by_ssh_open_files command with the username jenkins and warning level set to 2048 and critical level set to 4096 threads. Note that on standard installations the ulimits have been typically set to 4096 open files at most.
define service {
        hostgroup_name                  jenkins-servers
        service_description             check open files jenkins
        check_command                   check_by_ssh_open_files!jenkins!2048!4096
        use                             generic-service
        notification_interval           0;
}

4.Configure service to check for jenkins number of threads
Checking the number of threads for a specific process is even more complex. I found two solutions to do that, the first is to use the check_proc plugin which is part of nagios. The problem is that you have to recompile that plugin with a special ps-command syntax to display also all threads of a process instead of only the processes. Also I figured out how to download, configure and compile the plugin code, I wasn't able to figure out the specific ps options. You somehow have to define the command, parse paremeters and so on... I have found the following parameters on the web:

    --with-ps-command="/bin/ps -eo 's uid pid ppid vsz rss pcpu etime comm args'" \
    --with-ps-format='%s %d %d %d %d %d %f %s %s %n' \
    --with-ps-cols=10 \
    --with-ps-varlist='procstat,&;procuid,&;procpid,&;procppid,&;procvsz,&;procrss,&;procpcpu,procetime,procprog,&pos'

After wasting more than one hour with that I decided to write a simple bash script which will suffice my and nagios requirements. Here it is, my first nagios checker script...


#!/bin/bash

RET_OK=0
RET_WARN=1
RET_CRIT=2
RET_UNKNOWN=3

user="$1"
warn="$2"
crit="$3"

id=`id -u $user`
if [ $? -ne 0 ]
then
echo "UNKNOWN: USAGE ./check_threads.sh   "
fi
count=`ps auxH | grep $user | wc -l`
if [ $? -ne 0 ]
then
echo "UNKNOWN: USAGE ./check_threads.sh   "
fi
if [ $count -lt $warn ]
then
echo "THREADS OK: $count processes/threads with UID = $id ($user)"
exit $RET_OK
elif [ $count -lt $crit ]
then
echo "WARNING - $count threads processes/threads with UID = $id ($user)"
exit $RET_WARN
else
echo "CRITICAL - $count threads processes/threads with UID = $id ($user)"
exit $RET_CRIT
fi
This is the corresponding custom command...
define command {
        command_name    check_by_ssh_threads
        command_line    $USER1$/check_by_ssh -o StrictHostKeyChecking=no -l nagios -H $HOSTADDRESS$ -C "/var/lib/nagios/plugins/check_threads.sh $ARG1$ $ARG2$ $ARG3$"
        }

and this is the corresponding service that will check if the number of jenkins threads exceed 512 (warning) or 1024 (critical).
 
define service {
        hostgroup_name                  jenkins-servers
        service_description             check number threads jenkins
        check_command                   check_by_ssh_threads!jenkins!512!1024
        use                             generic-service
        flap_detection_enabled          0
        notification_interval           0;
}

Resources: 

  • http://www.nagios-wiki.de/nagios/plugins/check_by_ssh
  • http://exchange.nagios.org/directory/Plugins/Uncategorized/Operating-Systems/Linux/check-open-files/details
  • http://esisteinfehleraufgetreten.wordpress.com/2009/09/25/installing-nagios-or-icinga/
  • http://www.nagios-wiki.de/nagios/plugins/check_http
  • http://www.nagios.org/documentation

Donnerstag, 28. November 2013

SALT: changing a minions hostname

Recently I had the requirement to change a minions hostname. One might think that changing the hostname in /etc/hostname and/or the FQDN in /etc/hosts is sufficient, but you have to do some more steps.
  1. update /etc/hostname and /etc/hosts
  2. stop service
  3. change minion id in /etc/salt/minion_id
  4. start service
That's it...

Mittwoch, 27. November 2013

Constructing and building a Hexacopter with Openscad

Hi, this is my first x-copter post and also my first blog post in english. (Wenn Sie den Blog lieber in deutsch lesen wollen, so lassen Sie es mich wissen - Danke.)

In this post I will show you how one can construct a multicopter (hexacopter) with Openscad. Openscad is a free software, available for Linux, Windows and Mac OS X, which can be used to create solid 3D CAD models. Despite other software, such as Blender, it's focus is on construction aspects and not on artistic design aspects. In this way it is comparable to Catia, Solidworks, AutoCad, etc. However, the difference to the aforementioned modelling tools is that Openscad is less interactive, that means your 3D models are created more like computer programs and are rendered by a compiler. At first hand, this sounds weird and difficult, but after time one learns to love the new flexibility. Another important aspect of Openscad is it's ability to export to various file formats, e.g., DXF and STL. This way, one can easily create models for 3D printers or cnc routers.

The idea behind this tutorial is to help others to get started doing something useful with Openscad. For me it seemed a littlebit difficult to construct nice looking shapes and to fully utilize the tool chain (3D printing and CNC routing). Please feel free to improve my approach to Openscad! Please read the offical Openscad tutorials before following my tutorial.

Okay, so let's get started. First, I want to explain what I want to build. My goal is to build a hexacopter which will be my experimental platform for testing equipment etc. It should be capable of carrying neat stuff like FPV equipment (osd, cam, transmitter), GPS, larger batteries, gimbals, etc.

So how do we get started, well, we first need a center plate. So lets create one... First we need something where we can mount the six beams. I will call that area beam support.

1: beamsupport = 30;
2: square([beamsupport,beamsupport],center=true);


Next, let's duplicate and rotate that area to create six support areas for the six beams.
 
1:  baseplatedia = 140;
2:  beamsupport = 30;
3:  for(i=[0:5]) {
4:   rotate([0,0,360/6*i])
5:   translate([baseplatedia/2-beamsupport/2,0,0])
6:    square([beamsupport,beamsupport],center=true);
7:  }
 

On line 1 and 2 we have introduced some variables, baseplatedia which is the maximum dia of the base plate and beamsupport which is the above mentioned support area. Then we have created a loop (line 3) that will rotate (line 4) our axes by multiples of 60 degrees (at most 6 times). This way it is possible to place the beam support areas. We have also added a translation to move the support areas to the outer diameters of our base plate (line 5). On line 6 the actual support area has been drawn. Now we have 6 independent and unconnected regions. In the next step we will connect these regions to the base plate by using the hull() function (see below line 3).
 
1:  baseplatedia = 140;
2:  beamsupport = 30;
3:  hull() {
4:   for(i=[0:5]) {
5:    rotate([0,0,360/6*i])
6:    translate([baseplatedia/2-beamsupport/2,0,0])
7:     square([beamsupport,beamsupport],center=true);
8:   }
9:  }


Now, we already have a simple base plate. Next we will round the corners. To round the corners the minkowski() function will be exploited. With minkowski it is possible to trace one object around the shape of another. That means if we trace a circle around a square, then the result will be a (larger) square with rounded corners. So let's do this.
 
1:  baseplatedia = 140;
2:  beamsupport = 30;
3:  corners = 5;
4:  hull() {
5:   for(i=[0:5]) {
6:    rotate([0,0,360/6*i])
7:    translate([baseplatedia/2-beamsupport/2-corners,0,0])
8:     minkowski() {
9:      circle(corners);
10:     square([beamsupport,beamsupport],center=true);
11:    }
12:  }
13: }


We have added the minkowski function (line 8) which traces a circle with radius corners (line 9) around the square (line 10). We have removed the size of the circle object from the desired translation of the support areas to get the requested overall diameter. Now we already have a nice looking basis for a center plate. So far, the complete object is a 2D object. In the next step we will extrude this object to a 3D object to get advantage of full 3D modelling. So let's do this.

1:  baseplatedia = 140;
2:  beamsupport = 30;
3:  corners = 5;
4:  linear_extrude(height=5){
5:   hull() {
6:    for(i=[0:5]) {
7:     rotate([0,0,360/6*i])
8:     translate([baseplatedia/2-beamsupport/2-corners,0,0])
9:      minkowski() {
10:      circle(corners);
11:      square([beamsupport,beamsupport],center=true);
12:     }
13:   }
14:  }
15: }



Now, we have a full 3D model and can start to add/place the beams, mounting holes etc. to finally arrive at my (not yet finished) final version.


Resources:

Freitag, 22. November 2013

python jenkinsapi unsigned SSL certificate workaround/disable cert check

Einer meiner Kunden nutzt zur Unterstützung seiner Softwareentwicklung ein Jenkins basiertes Build Cluster. Um den darauf laufenden Buildprozess weitestgehend zu automatisieren bietet Jenkins selbst eine API Schnittstelle an. Zu dieser Schnittstelle gibt es diverse Libraries in mehreren Sprachen, u.a. Python (pip install jenkinsapi). Aufgrund einer Integration der Jenkins Server ins LDAP wollte dieser Kunde nun seine Jenkins Instanzes auf SSL umstellen. Dabei sollten selbst signierte SSL Zertifikate/Root-Zertifikate zum Einsatz kommen.

Versucht man sich nun via JenkinsAPI (siehe Codeausschnitt) mit den Server jenkins-master zu verbinden, so bekommt man folgende Fehlermeldung.

from jenkinsapi.jenkins import Jenkins
jenkins = Jenkins('https://jenkins-master:8080')
 
Meldung/Fehler:
SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

Die Meldung wird durch den verwendeten SSL Layer erzeugt, welcher nicht in der  Lage war das verwendete Zertifikat zu verifizieren. Es gibt nun verschiedene Möglichkeiten um dem Problem zu begegnen:
  1. offizielle SSL Zertifikate erwerben
  2. selbst signierte Zertifikate als vertrauenswürdige Zertifikate im SSL Layer installieren
  3. Überprüfung des Zertifikates für diese Verbindung deaktivieren.
Option 1 kam aufgrund der hohen Kosten und der angestrebten internen verwendung nicht in betracht. Auf Option 2 werde ich in einem anderen Blogpost näher eingehen. Vorerst soll uns aber mit Option 3 geholfen werden - meist ist dies auch ausreichend.
Nach einer Analyse der JenkinsAPI und der darunterliegenden requests API bietet es sich an die SSL Zertifikat Überprüfung über eine eigene Requester Klasse zu deaktivieren. Dies kann man wie folgt erreichen.
from jenkinsapi.jenkins import Jenkins
from jenkinsapi.utils.requester import Requester

class SSLRequester(Requester):
    def __init__(self, username=None, password=None):
        super(SSLRequester, self).__init__(username, password)

    def get_request_dict(self, url, params, data, headers):
        requestKWargs = super(SSLRequester, self).get_request_dict(url, params, data, headers)
        requestKWargs['verify'] = False
        return requestKWargs

jenkins = Jenkins('https://jenkins-master:8080', requester=SSLRequester())

Die Klasse SSLRequester überschreibt hierbei die in der Klasse Requester definierte get_request_dict Methode in der Art und Weise das zusätzlich zu den url, params, data, headers Parametern noch die Option verify=false als die darunterliegende requests Klasse/Methode übergeben wird. Nun wird die Jenkins Instanz ohne Fehlermeldungen über eine "sichere" SSL Verbindung erzeugt. Über dieselbe Art und Weise, mit Hilfe der SSLRequester Klasse kann man auch einen Pfad zu einer sogenannten "trusted certificate base" (Vertrauenswürdige Basiszertifikate) übergeben um dann mit Hilfe dieser Zertifikate seine selbst signierten Zertifikate zu verfifizieren.

Mittwoch, 20. November 2013

VirtualBox startet nicht (hangs during startup) - kvm issue

Auf einen Laptop mit Virtualisierungsunterstützung hatte ich neulich folgendes Problem: Scheinbar von einen Start zum nächsten wollte VirtualBox keine virtuellen Maschinen mehr starten. Meine Maschinen blieben alle während des Startups bei 0% stehen. Im Hintergrund kam dann immer ein 2. Fenster mit Fortschrittsbalken welches bei 20% hängen blieb. Im Log der virtuellen Mascine fand sich als letzte Meldung folgendes:

No VT-x or AMD-V CPU extension found.
Nach prüfen das die VT-x Erweiterungen auch wirklich noch im Bios aktiviert sind entschloss ich mich für eine Neuinstallation des Kerneltreibers.

/etc/init.d/vboxdrv setup
Leider schlug die Neuinstallation der Kerneltreiber mit der Meldung fehl das diese noch in Verwendung sind... Nach einigen weiteren rumprobieren entschloss ich mich für die Neuinstallation von VirtualBox.

apt-get remove virtualbox-4.2
Anschliessend habe ich mir die neueste Version von VirtualBox besorgt und mit dpkg installiert.Nach lösen eines Abhängigkeitsproblems mit einer Bibliothek (libvpx0) verlief die Installation problemlos. Leider war dies noch immer nicht die Lösung für mein Problem, wenn auch jetzt eine richtige/aussagekräftigere Fehlermeldung erschien.


VirtualBox can't operate in VMX root mode. VBox status code: -4011 (VERR_VMX_IN_VMX_ROOT_MODE).
Dieser Meldung nach wird die VMX Erweiterung bereits durch ein anderes Modul verwendet. Nach einigen suchen auf meinen Laptop war klar das irgendwie/irgendwann die kvm Kernelmodule installiert wurden.

lsmod | grep kvm
rmmod kvm_intel
rmmod kvm
Nach erfolgreichen entfernen dieser Module verlief der Startup aller virtuellen Maschinen wieder erfolgreich. Um ein erneutes Laden der Module zu verhindern kann man diese ggf. auch Blacklisten (/etc/modprobe.d/blacklist.conf). Bzw. die eigentliche Ursache für das Laden der Module finden (kvm selbst war bei mir nicht installiert...)

Quellen: 

  •  https://forums.virtualbox.org/viewtopic.php?t=7796
  • Google :)

Mittwoch, 18. September 2013

ISO Image zu libvirt Storage Pool hinzufügen

Ich verwende momentan einen libvirt Storage Pool (Verzeichnis im Host Dateisystem, z.B. /home/libvirt/disk-images) um meine ISO Installer Images zu verwalten.
Wenn man nun im laufenden Betrieb neue Images hinzufügen möchte, so stellt sich die Frage wie man dies ohne Neustart von libvirt realisieren kann. Mit
virsh pool-list
Name                 State      Autostart
-----------------------------------------
virtual-machine-images active     yes      
virtual-machine-install-images active     yes      
können wir uns die genaue Storage Pool Bezeichnung anzeigen lassen. Mein Storage Pool für die Installer Images heisst also virtual-machine-install-images. Wir verwenden diesen später um den Pool zu aktualisieren.

Man geht hierfür also wie folgt vor:
  1. cp <image> /home/libvirt/disk-images
  2. virsh pool-refresh virtual-machine-install-images
Nun können wir die neu hinzugefügten Images verwenden.

Dienstag, 17. September 2013

Increase Attachment Size, Anhanggrösse erweitern mit Squirrelmail, lighttpd, nginx (Debian Wheezy)

Ich verwende momentan Squirrelmail als Mail Frontend welches durch lighttpd bereitgestellt wird. nginx dient als http Reverse Proxy um auf die entsprechende Maschine zugreifen zu können.
Um Anhänge grösser als 2MB (Defaultwert aus php.ini) über Squirrelmail versenden zu können müssen wir folgende Anpassungen durchführen:
  1. Änderung des Wertes upload_max_filesize auf z.B. 10MB
  2. Änderung des Wertes post_max_size auf einen Wert > upload_max_filesize
  3. Änderung des Wertes memory_limit auf einen Wert > post_max_size
  4. Restart lighttpd
    service lighttpd restart
  5. Lokalisieren der nginx.conf (/etc/nginx/conf.d/default) und einfügen des folgenden Wertes in die "server" Sektion
    client_max_body_size 12M;
Die Änderung unter Punkt 5 ist nur notwendig wenn nginx als Proxy vor lighttpd/apache geschalten wurde. Man erhält in diesem Falle nämlich folgende Fehlermeldung:
Nginx: 413 Request Entity Too Large Error

Montag, 16. September 2013

Continuous Integration für CMake basierte Projekte mit Jenkins und CMakeBuilder Plugin

Wer bereits jenkins/hudson als Continuous Integration System für seine Java? Projekte verwendet, der fragt sich sicherlich früher oder später ob man dieses System nicht auch zum Bauen von C/C++ Projekten einsetzen kann. Dies ist im Prinzip schon out-of-the-box möglich, noch einfacher und wesentlich besser zu integrieren ist dies allerdings bei CMake basierten Projekten mit Hilfe des cmakebuilder Plugins möglich.

Hierzu installiert man im jenkins des cmakebuilder Plugin und erhält dann einen neuen Build-Step (CMake Build). Hier konfiguriert man das Plugin entsprechend der Anleitung/Beschreibung des Autors.
Bei mir habe ich als Source Directory ".", als Build Directory "./build" und als Install Directory "./install" angegeben. Somit erfolgt das komplette bauen und installieren in den Workspace Verzeichnis des Builds.
Selbstverständlich muss man noch sicherstellen das die entsprechenden Build Tools (cmake, make, gcc, ...) auf den Build Node zur Verfügung stehen. Unter Debian kann man diese ggf. so nachinstallieren.
apt-get install cmake build-essential

Referenzen:

  • https://wiki.jenkins-ci.org/display/JENKINS/cmakebuilder+Plugin
  • http://schneide.wordpress.com/2009/11/09/cmake-builder-plugin-reloaded/

Sonntag, 15. September 2013

Hyrbid LVM (Logical Volume Manager) Partitionierung für KVM Gäste (Debian Wheezy)

Dieser Blogpost beschreibt die Verwendung von einer sogenannten "Hybrid LVM Partitionierung" wie zuerst in diesem Blogpost gefunden. Bei dieser Art von Partitionierung ist jedes logische LVM Volume auf dem Host ein vollständiges Dateisystem im Gast. Einzige Ausnahme ist ein kleines logisches Volume welches als Boot Festplatte (mit kleiner Boot Partition) im Gast erscheint.
Diese Art der Partitionierung hat ein paar Vorteile:
  • einfaches Online Backup über LVM Snapshot auf den Host
  • Partitionen können ohne Probleme (zusätzliches Mapping durch kpartx, usw.) am Host gelesen werden
  • sehr einfaches Online Resizing (vergrössern) der Partitionen durch ändern der Grösse des logischen Volumes und vergrössern des Dateisystems im Gast.
  • sehr effizient, z.B. im Vergleich zu reinen Dateisystem Image Dateien


In unserem Beispiel möchte ich eine 32bit virtuelle Maschine erstellen, welche als Jenkins Slave mein Build Cluster um ein 32bit Debian System ergänzen soll. Die Maschine soll ein logisches Volume für root, swap und boot erhalten.

#Mapping: Host -> Gast
/dev/vg0/vmi-jenkins-slave-debian32-boot -> /dev/vda
/dev/vg0/vmi-jenkins-slave-debian32-root -> /dev/vdb
/dev/vg0/vmi-jenkins-slave-debian32-swap -> /dev/vdc
Im Gast werden die virtuellen Disks wie folgt verwendet.
/dev/vda -> Partitionstabelle
         -> + Boot Partition (/dev/vda1)
/dev/vdb -> Mount als ext4 Dateisystem
/dev/vdc -> Mount als Swap Partition
Im folgenden werden wir nun die benötigten logischen Volumes erzeugen. Mein Host System sieht wie folgt aus.
root@xx:~# vgs
  VG   #PV #LV #SN Attr   VSize VFree
  vg0    1  11   0 wz--n- 1,73t 1,56t
Wir haben also eine Volume Gruppe (vg0) welche wir als Storage für unsere virtuellen Maschinen verwenden können. Es empfiehlt sich jedoch eine eigene exklusive Volume Gruppe nur für virtuelle Maschinen zu haben.
Als erstes erzeugen wir  uns nun das Volume (vmi-jenkins-slave-debian32-boot)für die Boot Festplatte (/dev/vda) mit einer Grösse von 256MB.
lvcreate -n vmi-jenkins-slave-debian32-boot -L 256M /dev/vg0
Nun partitionieren wir das Volume mit fdisk mit folgenden Script wie folgt.
fdisk /dev/vg0/vmi-jenkins-slave-debian32-boot << __END__
n
p
1


a
1
w
__END__
Zuerst wird also eine neue (n) primäre (p) 1.Partition (1) erzeugt. Anschliessend bestätigen wir die beiden default Werte (Startsektor und Grösse). Die so erzeugte Partition wird dann noch aktiv geschalten (a 1) und die Partitionsdaten geschrieben (w).
Nun Mappen wir die erste Partition über kpartx und erzeugen ein ext2 Dateisystem für die Bootpartition.
#map/reload partitions
kpartx -a /dev/vg0/vmi-jenkins-slave-debian32-boot
#erzeugt Dateisystem auf erster Partition
mkfs.ext2
/dev/vg0/vmi-jenkins-slave-debian32--boot1
#unmap partitions
kpartx -d
/dev/vg0/vmi-jenkins-slave-debian32-boot
Jetzt müssen wir nur noch die logischen Volumes für die root und swap Partition erzeugen und formatieren.
lvcreate -n vmi-jenkins-slave-debian32-root -L 8G /dev/vg0
mkfs.ext4 /dev/vg0/vmi-jenkins-slave-debian32-root
lvcreate -n vmi-jenkins-slave-debian32-swap -L 2G /dev/vg0
mkswap /dev/vg0/vmi-jenkins-slave-debian32-swap
Wir haben nun 3 logische Volumes in der Volume Gruppe vg0. Das root und das swap Volume beinhalten komplette "Dateisysteme" ohne Partitionstabellen. Das boot Volume ist das einzige Volume welches selbst partitioniert ist und eine ext2 boot Partition beinhaltet. Um die Volumes verwenden zu können müssen wir diese noch in der entsprechenden libvirt Gast Beschreibung bekannt machen. Dies geht in der device Sektion wie folgt...
<disk type='block' device='disk'>
  <driver name='qemu' type='raw' cache='none'/>
  <source dev='/dev/vg0/vmi-jenkins-slave-debian32-boot'/>
  <target dev='vda' bus='virtio'/>
</disk>
<disk type='block' device='disk'>
  <driver name='qemu' type='raw' cache='none'/>
  <source dev='/dev/vg0/vmi-jenkins-slave-debian32-root'/>
  <target dev='vdb' bus='virtio'/>
</disk>
<disk type='block' device='disk'>
  <driver name='qemu' type='raw' cache='none'/>
  <source dev='/dev/vg0/vmi-jenkins-slave-debian32-swap'/>
  <target dev='vdc' bus='virtio'/>
</disk>
Im folgenden Abschnitt werden wir nun den 32bit Gast erzeugen. Hierzu definieren wir zuerst den Gast über ein libvirt xml Template und verbinden uns anschliessend über virt-manager mit dem qemu Hypervisor um den Gast zu installieren. Als erstes erzeugen wir uns ein Template von einen bereits bestehenden Gast.
virsh dumpxml <gast> > vm-templ.xml
In diesem Template machen wir dann folgende Anpassungen.
  • Änderung des Namens unter /domain/name auf den neuen Gast jenkins-slave-debian32
  • Entfernen der UUID unter /domain/uuid da diese bereits vorhanden ist und bei fehlen automatisch neu generiert wird
  • Anpassung/Ergänzen der disk Sektionen unter domain/devices/disk entsprechend obiger Beschreibung
  • Entfernen/Anpassung der MAC Adresse unter /domain/interface/mac
Jetzt können wir mittels des so erstellten Templates die neue virtuelle Maschine erzeugen. Wir verwenden hierzu define damit die virtuelle Maschine noch nicht automatisch startet.
virsh define vm-templ.xml
Nun verwenden wir den virt-manager um mit diesem schnell und komfortabel die Maschine für den initialen Boot vorzubereiten.
virt-manager -c qemu+ssh://root@vm-host/system
Nun nutzen wir den virt-manager um die Bootreihenfolge auf CD-ROM zu ändern, sowie um die Installations-CD zu mounten. Hierzu gehen wir auf Virtual Machine Details und dann auf Anzeige -> Details und ändern die Bootreihenfolge auf CD-ROM, ausserdem verbinden wir ein ISO-Image mit unseren CD Laufwerk. Jetzt kann die Installation des Gasts erfolgen, abschliessend trennen wir wieder das CD Image und ändern die Bootreihenfolge auf HD.
In einem anderen Post werde ich auf eine automatisierte Variante zum Bauen der Gäste eingehen.

Referenzen:

  • http://blog.gadi.cc/better-lvm-for-kvm/

Mittwoch, 21. August 2013

OpenVPN Debian Wheezy + Seagate Dockstar

Dieser Blogpost beschreibt die Einrichtung einer sicheren Verbindung zwischen 2 gesicherten Netzwerken - kurz eines VPN-Netzwerks. Das folgende Bild beschreibt meinen Aufbau.

Wir haben 2 Netzwerke (Netzwerk A: 192.168.100.0/24 und Netzwerk B: 192.168.0.0/24) welche wir via VPN miteinander verbinden wollen. Im Netzwerk A gibt es einen öffentlich erreichbaren Rechner der als Router, Firewall und VPN Gateway funktioniert. Im Netzwerk B gibt es eine Router&Firewall Box, ebenfalls mit öffentlicher IP, welche die Verbindung zum Internet über einen gängigen Provider herstellt. Im Netzwerk B verwenden wir den Dockstar als VPN Gateway, da die Router&Firewall Box keine VPN Funktionalität bietet. In beiden Netzwerken befinden sich weitere Rechner welche von jeder Seite aus erreichbar sein sollen. Im folgenden werden wir Schritt für Schritt ein Zertifikat basiertes VPN installieren und konfigurieren.
OpenVPN unterscheidet in seiner Konfiguration zwischen Clients und Servern. Wir werden daher den OpenVPN Server auf den Gateway in Netzwerk A und den Client auf der Dockstar in Netzwerk B installieren.

Installation und Konfiguration (Netzwerk A)


Als erstes installieren wir das openvpn Packet mittels apt-get.
apt-get install openvpn
Nun nutzen wir easy-rsa um die benötigten Zertifikate für den Server zu erstellen. Wir erstellen uns als erstes ein Zertifikat Verzeichnis /etc/openvpn/easy-rsa und kopieren dann den Inhalt von easy-rsa in dieses Verzeichnis.
mkdir -p /etc/openvpn/easy-rsa
cd /etc/openvpn/easy-rsa
cp /usr/share/doc/openvpn/examples/easy-rsa/2.0/* .
Nun editieren wir die vars Datei entsprechend unseren Bedürfnissen und passen folgende Werte an.
export KEY_SIZE=2048
export KEY_COUNTRY="DE"
export KEY_PROVINCE="yourProvince"
export KEY_CITY="yourCity"
export KEY_ORG="yourOrganization"
export KEY_EMAIL="yourEMail"
Anschliessend erzeugen wir als erstes unsere Certificate Authority (ca).
. ./vars
./clean-all
./build-ca
Es werden nun hauptsächlich die default Werte aus der vars verwendet. Bei der Frage nach den common-name geben wir unseren Servernamen ein, z.B. "vpn.example.com". Nun erzeugen wir noch ein Zertifikat für den Server und unsere Client Zertifikate.
./build-key-server vpn.example.com
Bei der Abfrage nach den common Name geben wir erneut "vpn.example.com" ein. Anschliessend bestätigen wir noch die beiden Fragen "Sign the certificate? [y/n]" und "1 out of 1 certificate requests certified, commit? [y/n]" jeweils mit y. Um ein Client Zertifikat für "client1" zu erzeugen geben wir nun noch folgendes Kommando ein. Bei der Frage nach dem common Name geben wir wieder "vpn.example.com" ein.
./build-key client1
Als nächstes erzeugen wir nun die Diffie-Hellman Parameter. Dieser Vorgang kann eine Weile dauern...
./build-dh
Wir haben jetzt alle benötigten Zertifikate und Schlüsselim Verzeichnis /etc/openvpn/easy-rsa/keys vorliegen. Wir haben ebenfalls bereits für einen Client (client1) ein Zertifikat erstellt. Ein Client (clientX) benötigt bei sich lokal dann jeweils 3 Dateien (clientX.crt, clientX.key und die ca.crt).

Erstellen der Konfigurationsdatei für Netzwerk A

Wir erstellen uns nun eine serverseitige openvpn Konfiguration. Dazu erstellen wir uns eine networkA.conf Datei im /etc/openvpn Verzeichnis.
touch /etc/openvpn/networkA.conf
Anschliessend editieren wir die Datei wie folgt...
#dynamic tun device
dev tun
# P2P local VPN endpoint is 10.0.0.1, remote VPN endpoint is 10.0.0.2
ifconfig 10.0.0.1 10.0.0.2
#SSL/TLS key exchange
tls-server
#Certs/Keys/DH parameters
dh /etc/openvpn/easy-rsa/keys/dh2048.pem
ca /etc/openvpn/easy-rsa/keys/ca.crt
cert /etc/openvpn/easy-rsa/keys/vpn.mindfab.net.crt
key /etc/openvpn/easy-rsa/keys/vpn.mindfab.net.key

#downgrade to UID,GID nobody after initialization
user nobody
group nobody
#enable LZO compression
comp-lzo
#ping every 15 seconds (keep statefull firewalls open)
ping 15
#verbosity level (3 medium)
verb 3


#push our own network (192.168.100.0/24 - see picture above) to the client
push "route 192.168.100.0 255.255.255.0"

#allow routing to the client network (192.168.0.0/24)
route 192.168.0.0 255.255.255.0

#openvpn 2.0 introduced script security, we need at least level 2.
script-security 2
#execute external script to add tun routing...
up ./networkA.up
Die einzelnen Parameter sind in den openvpn Beispielen ganz gut dokumentiert - siehe auch /usr/share/doc/openvpn/examples/sample-config-files/. Als Anhaltspunkt kann hierzu tls-office.conf dienen. Nun erstellen wir noch die networkA.up Datei welche die Routing Tabelle beim aufbauen der VPN Verbindung anpassen soll.
touch networkA.up; chmod +x networkA.up
Wir fügen folgende Route hinzu:
#!/bin/sh
route add -net 10.0.0.0 netmask 255.255.255.0 gw $5
Der interessante Teil unsere Konfiguration ist der, wo wir die verschiedenen Netzwerke für den Client verfügbar machen. Dies geschieht über den push "route 192.168.100.0 255.255.255.0" Befehl. Dieser Befehl veranlasst den Client bei Verbindungsaufbau lokal bei sich eine Route (route add -net 192.168.100.0 netmask 255.255.255.0 gw 10.0.0.1) einzufügen. Der Befehl route 192.168.0.0 255.255.255.0 veranlasst den Server einen entsprechenden Routing Eintrag in seine Routing Tabelle einzufügen. Somit kann dann Netzwerk A nach Netzwerk B als auch umgekehrt Netzwerk B nach Netzwerk A routen.

Achtung, bitte auch die Anmerkungen ganz unten lesen...

Installation und Konfiguration (Netzwerk B)

Als erstes installieren wir das benötigte openvpn Packet auf der Dockstar. Anschliessend kopieren wir die erzeugten Client Schlüssel von den Gateway aus Netzwerk A auf den VPN Gateway(Dockstar) in Netzwerk B.
root@dockstar: cd /etc/openvpn
scp root@222.100.100.222:/etc/openvpn/easy-rsa/keys/client1.crt .
scp root@222.100.100.222:/etc/openvpn/easy-rsa/keys/client1.key .
scp root@222.100.100.222:/etc/openvpn/easy-rsa/keys/ca.crt .

Erstellen der Konfigurationsdatei für Netzwerk B

Auf unserer Dockstar erstellen wir nun eine Client Konfigurationsdatei...
touch /etc/openvpn/client.conf
und editieren diese wie folgt.
client
dev tun
remote 222.100.100.222 1194
resolv-retry infinite
nobind
user nobody
group nobody
persist-key
persist-tun
ca ca.crt
cert client1.crt
key client1.key
ns-cert-type server
comp-lzo
verb 3
ifconfig 10.0.0.2 10.0.0.1

Firewall/Gateway Anmerkungen

In unseren Setup sind wir auf die Konfiguration (öffnen der Firewalls) nicht eingegangen. In unseren Beispiel ist es daher noch nötig die Firewall auf den Gateway in Netzwerk A auf UDP Port 1194 (default OpenVPN Port) zu öffnen. Weiterhin ist es notwendig, das Packete von den tun Interfaces auf das lokale Netzwerk geroutet werden dürfen (Forwarding chain), sowie das IP Forwarding aktiviert ist (siehe auch hier unter Punk 2) - dieser Punkt muss für beide Gateways erfüllt sein.
Da auf der Client Seite (Netzwerk B) der VPN Server nicht auf den Gateway liegt bedarf es hier noch weiterer Anpassungen bzgl. des Routings. In der Firewall&Gateway Box fügen wir daher noch 2 weitere (erweiterte) Routing Einstellungen hinzu.

route add -net 10.0.0.0 netmask 255.255.255.0 gw 192.168.0.2
route add -net 192.168.100.0 netmask 255.255.255.0 gw 192.168.0.2
Mit diesen Einstellungen werden Packete die für Netzwerk A bestimmt sind an unseren VPN Gateway 192.168.0.2 weitergeleitet.

Testen der Konfiguration

Wir starten nun den openvpn Daemon auf beiden Gateways...
service openvpn start #restart
Debug Informationen können wir über die daemon.log erhalten.
tail -f /var/log/daemon.log
Wir sollten nun die Gegenseiten der tun Devices pingen können.
root@dockstar: ping 10.0.0.1
ping 192.168.100.100

root@workstation1: ping 192.168.0.2
ping 192.168.0.1
ping 192.168.0.100

Referenzen

  • http://openvpn.net/index.php/open-source/documentation/howto.html
  • http://dev.shyd.de/2011/02/dockstar-howto-setup-openvpn-debian/

Dienstag, 13. August 2013

fetchmail + POP3/IMAP + SSL/TLS + CERTCK (Debian Wheezy)

Im folgenden möchte ich beschreiben wie man fetchmail so einrichtet, das die E-Mails über eine gesicherte Verbindung (SSL-Verschlüsselung) abgeholt werden. Fetchmail soll ausserdem die Zertifikate prüfen um Man-In-The-Middle Attacks zu verhindern. Obwohl ich diese Installation und Konfiguration auf einen Debian Wheezy System ausgeführt habe sollte diese auch problemlos auf anderen Linux Derivaten/Versionen funktionieren.
Fetchmail soll in dieser Konfiguration die empfangenen E-Mails an procmail übergeben. Procmail realisiert eine einfache Filterung und übergibt die E-Mails dann an cyrus IMAP.

Installation der Packete

Als erstes Installieren wir die benötigten Packete/bzw. stellen sicher das diese bereits installiert sind (z.B. openssl).
apt-get install fetchmail procmail openssl
Nun beginnen wir damit procmail zu konfigurieren. Wir erstellen uns eine einfache Konfiguration welche E-Mails für nur einen Nutzer entgeben nimmt und diese direkt an cyrus übergibt. Später kann die procmail Konfiguration um E-Mail Filter/weitere Nutzer erweitert  werden. Wir erstellen und editieren daher die Datei /etc/procmailrc wie folgt.
DELIVER="/usr/sbin/cyrdeliver"
LOGFILE="/var/log/procmail.robert"
VERBOSE=on

:0w
| $DELIVER -a -q robert robert
Wir haben nun einen procmail Filter erstellt welcher alle eingehenden E-Mails an die Mailbox robert auf den cyrus IMAP Server via cyrdeliver ausliefert. Als nächstes konfigurieren wir den fetchmail Daemon.

Konfiguration fetchmail Daemon

Als erstes teilen wir fetchmail mit das dieser als Daemon/Service laufen soll. Wir editieren dazu die Datei /etc/default/fetchmail und ändern das START_DAEMON flag von no auf yes.
START_DAEMON=yes
Um fetchmail Scripte zu debuggen kann man in dieser Datei unter Options beispielsweise das verbose Flag setzen. Sobald die Scripte laufen sollte man die Optionen aber wieder löschen/auskommentieren.
OPTIONS=-v
Nun erstellen wir uns die Fetchmail Konfigurationsdatei. Wir legen dazu eine zentrale Konfigurationdatei unter /etc/fetchmailrc an. Wichtig ist auch das wir die Datei entsprechend schützen da hier später u.a. die Passwörter für unsere Postfächer stehen.
touch /etc/fetchmailrc
chmod 0600 /etc/fetchmailrc
chown fetchmail. /etc/fetchmailrc
Wir konfigurieren nun ein POP3 GMX Postfach. Als Beispiel soll die E-Mail Adresse example@gmx.de mit Passwort "secret" dienen. Die E-Mails sollen über POP3s (POP3 over SSL) abgeholt werden. Wir editieren die /etc/fetchmailrc wie folgt.
set daemon 300
set syslog
set postmaster root
poll pop.gmx.de with protocol POP3
       user 'example@gmx.de' there with password 'secret'
       mda '/usr/bin/procmail' options
       keep
       ssl sslcertck sslcertpath /etc/ssl/certs
       sslfingerprint ""
In den ersten drei Zeilen definieren wir den Poll Intervall (Abfrage der E-Mails aller 300 Sekunden, wir logen nach /var/log/syslog und definieren den postmaster.
Weiterhin haben wir jetzt ein gmx POP3 Postfach mit den Nutzer 'example@gmx.de' definiert. Die abgerufenen E-Mails werden alle an procmail übergeben. Für Testzwecke haben wir die Option keep angegeben. Diese Option verhindert das löschen der E-Mails von den POP3 Server, diese Option können wir später (sobald alles funktioniert) durch fetchall ersetzen. Die folgenden Optionen (ssl, sslcertck, sslcertpath und sslfingerprint) teilen fetchmail mit das SSL zu verwenden ist und das ausserdem die Zertifikate zu prüfen sind. Im folgenden werden wir nun noch den Eintrag für den sslfingerprint bestimmen.

Fetchmail sslfingerprint bestimmen

Wir werden nun für das gmx.de Postfach den md5 sslfingerprint bestimmen. Wir verwenden hierzu die openssl Tools.
root@xx:~# openssl s_client -connect pop.gmx.de:995 | openssl x509 -md5 -fingerprint
MD5 Fingerprint=3D:0C:9A:45:53:86:0B:5B:C8:65:0A:63:9D:EF:F3:C9
-----BEGIN CERTIFICATE-----
MIIEoDCCA4igAwIBAgIQQpEipkhuiS++nRBu
...
...
N+wf54qfy6f6vHW18AA1W3cshTXO6FmC805IA2lstqYjX9nc
-----END CERTIFICATE-----
Wir verbinden uns hierzu mit den SSL-Client von openssl mit den POP3 Server von gmx.de. Wichtig, wir verbinden uns auf Port 995 (Pop3s). Die Ausgabe des ersten openssl aufrufes "pipen" wir als Eingabe in den 2.Aufruf, in welchen wir jetzt den md5 fingerprint des Server Zertifikates extrahieren. Wir erhalten dann eine Ausgabe mit den Fingerprint und den Zertifikat. Wir kopieren den so erhaltenen fingerprint nun in unsere fetchmailrc.
poll pop.gmx.de with protocol POP3
       user 'example@gmx.de' there with password 'secret'
       mda '/usr/bin/procmail' options
       keep
       ssl sslcertck sslcertpath /etc/ssl/certs
       sslfingerprint "3D:0C:9A:45:53:86:0B:5B:C8:65:0A:63:9D:EF:F3:C9"
Nun können wir unseren Daemon testen...
service fetchmail start

tail -f /var/log/syslog

Referenzen

  • http://blog.busstra.net/?p=648
  •  http://www.chambreuil.com/2008/01/19/fetchmail-procmail-cyrus-sasl-ldap-roundcube-getlive
  • http://email.about.com/od/gmxmailtips/f/GMX_Mail_IMAP_Server_Settings.htm
  • http://www.madboa.com/geek/openssl/
  • http://www.gagravarr.org/writing/openssl-certs/others.shtml#ca-openssl

Montag, 12. August 2013

Squirrelmail + Cyrus + Sieve + avelsieve (auf Debian Wheezy)

Im folgenden möchte ich die Installation des avelsieve Plugins für Squirrelmail unter Debian Wheezy mit cyrus imapd beschreiben. In einen anderen Post hatte ich bereits beschrieben wie man Squirrelmail unter lighttpd installiert. Aufbauend auf diesen Post und auf unserer Cyrus Imapd Installation werden wir nun avelsieve installieren.

Installation des Plugins

Als ersten besorgen wir uns das Plugin aus dem Subversion Repository (siehe auch avelsieve Webseite) da es u.a. in Revision 1091 einige Patches für PHP 5.4 gab und diese Patches noch nicht im stable oder devel Release sind.
  1. Nun editieren wir die config.php und machen Debian Wheezy spezifische Anpassungen. (Bei mir lief das Plugin nicht "out-of-the-box".)
    $sieveport = 4190;
    $preferred_mech = "PLAIN LOGIN DIGEST-MD5";
    Auf meinen Debian lief der sieve Daemon auf Port 4190 anstatt auf Port 2000 (default). (Ein "cat /etc/services | grep sieve" sollte u.a. 4190/tcp zurückliefern.)
  2. Nun aktivieren wir noch das Plugin über squirrelmail-configure.
    squirrelmail-configure
    -> Menu Auswahl Punkt 8
    -> Plugin avelsieve und javascript_libs hinzufügen
    -> Konfiguration speichern

Konfiguration imapd/cyrus

Mein cyrus verwendet saslauthd als Authentifizierungs Mechanismus. Wichtig bei meiner Konfiguration war das in der /etc/imapd.conf als sasl Mechanismus nicht nur PLAIN sondern auch LOGIN mit eingetragen ist. Ich habe noch CRAM-MD5 und DIGEST-MD5 aktiviert.
sasl_mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5
Mit dieser Änderung verschwand dann auch folgende Fehlermeldung "Could not log on to timsieved daemon on your IMAP server localhost."

Sonntag, 11. August 2013

Bereitstellung von Webserverdiensten (http, https) auf einer virtuellen Maschine mit nginx als Reverse Proxy (Teil 2)

Im 1.Teil hatten wir uns bereits mit der Einrichtung von nginx als http Reverse Proxy beschäftigt. In diesem Teil wollen wir nginx als SSL http Reverse Proxy einrichten. Als Zertifikat verwenden wir ein sogenanntes "self-signed certificate" für die Domain "ssl.mindfab.net".

Zertifikat erstellen

Zuerst erstellen wir das Verzeichnis wo unser Zertifikat für die Domain mail.mindfab.net liegen soll.
root@xx: cd /etc/nginx
root@xx: mkdir -p ssl/ssl.mindfab.net

root@xx: cd ssl/ssl.mindfab.net
Nun erstellen wir uns als erstes einen private Key.
root@xx:/etc/nginx/ssl/ssl.mindfab.net# openssl genrsa -des3 -out ssl.mindfab.net.key 2048
Generating RSA private key, 2048 bit long modulus
.................................+++
..........+++
e is 65537 (0x10001)
Enter pass phrase for ssl.mindfab.net.key:
Verifying - Enter pass phrase for ssl.mindfab.net.key:

Als nächstes erzeugen wir ein CSR (Certificate Signing Request). Hierbei ist es wichtig das der später zu verwendende Domain Name (https://ssl.mindfab.net) unter Common Name eingetragen wird.
root@xx# openssl req -new -key ssl.mindfab.net.key -out ssl.mindfab.net.csr

Enter pass phrase for ssl.mindfab.net.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:Saxony
Locality Name (eg, city) []:Dresden
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MINDFAB.NET
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:ssl.mindfab.net
Email Address []:admin@mindfab.net

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Als nächstes soll das Passwort von dem Private Key entfernt werden damit nginx ohne Passwort Eingabe automatisiert starten kann.
root@xx# cp ssl.mindfab.net.key ssl.mindfab.net.key.bak
root@xx# openssl rsa -in ssl.mindfab.net.key.bak -out ssl.mindfab.net.key

Enter pass phrase for ssl.mindfab.net.key.bak:
writing RSA key
Nun sollten wir folgende Dateien in unserem Verzeichnis haben.

root@xx# ls -l
-rw-r--r-- 1 root root 1062 Aug 10 17:05 ssl.mindfab.net.csr
-rw-r--r-- 1 root root 1679 Aug 10 17:11 ssl.mindfab.net.key
-rw-r--r-- 1 root root 1751 Aug 10 17:10 ssl.mindfab.net.key.bak
Schliesslich müssen wir das Zertifikat noch selbst signieren...
openssl x509 -req -days 365 -in ssl.mindfab.net.csr -signkey ssl.mindfab.net.key -out ssl.mindfab.net.crt
Signature ok
subject=/C=DE/ST=Saxony/L=Dresden/O=MINDFAB.NET/OU=IT/CN=ssl.mindfab.net/emailAddress=admin@mindfab.net
Getting Private key
Jetzt müssen wir noch nginx so konfigurieren das er unser Zertifikat verwendet. Wir passen daher unsere nginx Konfiguration entsprechend an.
vim /etc/nginx/conf.d/default.conf

## 192.168.100.12 -> ssl.mindfab.net ##
upstream sslmindfabnet  {
      server 192.168.100.12:80;
}

## Start https://ssl.mindfab.net ##
server {
    listen       443;
    ssl on;
    server_name  ssl.mindfab.net;

    # SSL cert files #
    ssl_certificate    /etc/nginx/ssl/ssl.mindfab.net/ssl.mindfab.net.crt;
    ssl_certificate_key    /etc/nginx/ssl/ssl.mindfab.net/ssl.mindfab.net.key;

    ssl_protocols    SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers        RC4:HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers    on;
    keepalive_timeout    60;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout    10m;   

    access_log  /var/log/nginx/ssl-ssl.mindfab.net.access.log;
    error_log  /var/log/nginx/ssl-ssl.mindfab.net.error.log;

    ## send request back to ssl.mindfab.net ##
    location / {
     proxy_pass  http://sslmindfabnet;    
     proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
     proxy_redirect off;
     proxy_buffering off;
     proxy_set_header         Accept-Encoding    "";   
     proxy_set_header        Host            $host;
     proxy_set_header        X-Real-IP       $remote_addr;
     proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header         X-Forwarded-Proto $scheme;
     add_header             Front-End-Https on;   
   }
}
## End ssl.mindfab.net ##
Nun starten wir den nginx Daemon neu und testen unsere Konfiguration.

service nginx reload

openssl s_client -connect ssl.mindfab.net:443

Referenzen

  • http://www.cyberciti.biz/faq/howto-linux-unix-setup-nginx-ssl-proxy/

Samstag, 10. August 2013

Installation von Squirrelmail mit Lighttpd

Problembeschreibung

E-Mails sollten immer und von überall aus erreichbar sein. Hierfür habe ich einen IMAP Server. Ein Web Interface soll den problemlosen Zugang zu den E-Mails auf diesen Server ermöglichen.

Lösungsansatz

Als E-Mail Server dient der cyrus IMAP Server. Als Web Interface habe ich mich für squirrelmail entschieden, da es funktional, einfach zu konfigurieren und durch Plugins erweiterbar ist (z.B. sieve). Ziel soll sein, das man über lynx http://localhost die Login Seite von squirrelmail angezeigt bekommt. Später soll dann nginx als SSL Reverse Proxy vor diesen Rechner geschalten werden (siehe anderer Post).

Installation von Lighttpd

Da Lighttpd unter Debian als Packet verfügbar ist können wir es direkt installieren.
apt-get install lighttpd
Anschliessend können wir z.B. mit lynx die Installation des Webservers testen.
lynx localhost
Wenn nun eine "Placeholder" Seite erscheint können wir mit der PHP Installation weitermachen.

Installation von PHP

Da Lighttpd CGI Unterstützung hat verwenden wir das Debian Packet php5-cgi um PHP in Lighttpd zu realisieren.
apt-get install php5-cgi

Konfiguration von Lighttpd und PHP

In der Datei /etc/php5/cgi/php.ini kommentieren wir folgende Zeile aus.
cgi.fix_pathinfo=1
Die Konfigurationsdatei von Lighttpd /etc/lighttpd/lighttpd.conf ändern wir wie folgt. Unter server.modules fügen wir "mod_fastcgi" ein...
server.modules = (
        "mod_access",
        "mod_alias",
        "mod_compress",
        "mod_redirect",
        "mod_fastcgi",
#       "mod_rewrite",
)
Am Ende der Datei fügen wir dann noch die Konfiguration des Handlers hinzu...
fastcgi.server = ( ".php" => (( "bin-path" => "/usr/bin/php5-cgi", "socket" => "/tmp/php.socket" )))
 Abschliessend wir der Server neu gestartet...
service lighttpd restart

Installation und Konfiguration von Squirrelmail

Eine sehr gute Anleitung um Squirrelmail zu installieren und initial zu konfigurieren findet sich hier.

Kurz zusammengefasst müssen wir folgende Schritte ausführen. Als erstes wird das Packet installiert.
apt-get install squirrelmail
Dannach rufen wir zur Konfiguration squirrelmail-configure auf.
squirrelmail-configure
Squirrelmail kommt mit vorkonfigurierten Optionen für die verschiedenen IMAP Server (Taste "D"). Ich habe hier cyrus IMAP ausgewählt, siehe auch andere Blog Einträge.

Nun müssen wir noch den lighttpd konfigurieren, damit dieser beim Zugriff auf den Server die Squirrelmail Quellen verwendet. Hierzu editieren wir die Datei /etc/lighttpd/lighttpd.conf und fügen am Ende folgende Zeilen hinzu.

# For squirrelmail alias.url += ("/" => "/usr/share/squirrelmail/" )
Nun starten wir lighttpd neu und können testen ob wir die Login Seite von squirrelmail bekommen.
service lighttpd restart
lynx http://localhost

Referenzen

  • http://www.howtoforge.de/anleitung/installation-von-lighttpd-mit-php5-und-mysql-unterstutzung-auf-debian-etch/
  • http://blog.coldtobi.de/1_coldtobis_blog/archive/285_squirrelmail_and_lighttpd_--_an_installation_guide_--.html

Installation eines Debian Servers mit Software Raid-1 im Degraded Mode

Problembeschreibung


Die Frage warum man einen Linux Server mit Software Raid-1im Vorfeld (während der Installation/Konfiguration) mit einen "degraded" Raid-1 installieren möchte ist mehr als berechtigt. Folgendes Szenario hat mich jedoch hierzu bewogen.
Ein Dell PowerEdge SC1435 sollte einen anderen Server ersetzen. Der alte Server wurde mit 2 Festplatten im Software Raid-1 Verbund betrieben. Der neue sollte ebenfalls 2 (jedoch grössere) Festplatten bekommen. Wer den Dell SC1435 kennt, der weiss das nur 2 SATA Anschlüsse vorhanden sind. Das Problem war nun: Wie bekomme ich die Daten von der alten Festplatte relativ problemlos auf die neue Festplatte?

Lösungsansatz

Während der Installation des Dell Systems wurden sowohl die neue als auch die alte Festplatte in den Server eingebaut. Somit waren alle SATA Kanäle belegt und beide Raids liegen zu dieser Zeit im degraded Modus (da jeweils eine Festplatte fehlte). Nun habe ich im Debian Installer (Wheezy) auf der neuen Festplatte (sda) ein Software Raid-1 konfiguriert. Hierzu habe ich die Festplatte nach Wunsch in 2 Partitionen (System/Daten sda1 -> "md0" und Swap sda2 -> "md1") partitioniert. Als Verwendung der Paritionen geben wir natürlich bei beiden Linux Software Raid an. Anschliessend konfigurieren wir das Software Raid mit 2 md Devices. Beim erzeugen der Devices geben wir an, das wir 2 Disks/Devices haben und keine Spar Disks. Nun soll man die Devices für den Verbund auswählen. Hier wählen wir für "md0" NUR "/dev/sda1" aus, keine weiteren! Dasselbe wiederholen wir dann für "md1" wo wir NUR "/dev/sda2" auswählen. Der Installer nimmt dies erstaunlicherweise ohne sich zu beschweren.
Anschliessend führen wir die Installation zu Ende und Konfigurieren unser System mit den benötigten Diensten. Irgendwann erreichen wir den Punkt wo wir die Daten von den alten System kopieren möchten. Hierzu mounten wir einfach die alte Festplatte (falls nicht bereits geschehen) und kopieren was wir benötigen. Nach dem erfolgreichen kopieren fahren wir das System herunter und tauschen die alte Festplatte gegen die 2. neue Festplatte. Nun müssen wir nur noch das Raid-1 neu bauen lassen.

Als erstes müssen wir hierzu auf der 2. Festplatte (/dev/sdb) eine völlig identische Partitionierung wie auf der ersten Festplatte (/dev/sda) herstellen. Hierfür kann man "sfdisk" verwenden.
sfdisk -d /dev/sda | sfdisk /dev/sdb
.
.
.
Successfully wrote the new partition table
Re-reading the partition table ...
.
.
Die Ausgabe ist ziemlich verbose und sollte irgendwo die Meldung beinhalten das die Partitionstabelle erfolgreich geschrieben wurde.

Als nächstes lassen wir uns sicherheitshalber mal den Raid Status einschliesslich der zugeordneten Partitionen anzeigen.
root@xx:~# cat /proc/mdstat
Personalities : [raid1]
md1 : active (auto-read-only) raid1 sda2[0]
      97980288 blocks super 1.2 [2/1] [U_]
     
md0 : active raid1 sda1[0]
      1855336256 blocks super 1.2 [2/1] [U_]
     
unused devices: <none>

Nun sehen wir das wir bei "md0" die Partition "sda1" enthalten ist, und wir als 2. Partition "sdb1" hinzufügen müssen (für "md1" ist dies "sdb2").


root@xx:~# mdadm --manage /dev/md0 --add /dev/sdb1
mdadm: added /dev/sdb1
root@xx:~# mdadm --manage /dev/md1 --add /dev/sdb2
mdadm: added /dev/sdb2
Das System resynchronisiert nun das Raid Array, den Status hierzu können wir z.B. mit mdstat überprüfen.
root@xx:~# watch -n5 cat /proc/mdstat
Personalities : [raid1]
md1 : active raid1 sdb2[2] sda2[0]
      97980288 blocks super 1.2 [2/1] [U_]
          resync=DELAYED
    
md0 : active raid1 sdb1[2] sda1[0]
      1855336256 blocks super 1.2 [2/1] [U_]
      [>....................]  recovery =  0.4% (8669312/1855336256) finish=276.1min speed=111432K/sec
    
unused devices: <none>

Referenzen:

  • http://www.cyberciti.biz/tips/linux-raid-increase-resync-rebuild-speed.html
  • http://www.howtoforge.com/replacing_hard_disks_in_a_raid1_array

Donnerstag, 25. Juli 2013

Bereitstellung von Webserverdiensten (http, https) auf einer virtuellen Maschine mit nginx als Reverse Proxy (Teil 1)

Problembeschreibung

Ein Serversystem mit öffentlicher IP (200.200.200.200) virtualisiert mehrere Gast(Server)systeme welche HTTP, MAIL, SVN(SSH) usw. bereitstellen. Als Virtualisierungslösung verwenden wir kvm mit libvirt. Als Netzwerkmodus wird "nat" verwendet, da die Gäste keine öffentlichen Adressen bekommen sollen. Die Standardkonfiguration lässt in diesen Modus die Kommunikation des Gasts nach aussen hin zu (MASQUERADING), schirmt diesen jedoch beim Zugriff auf ihn selbst ab.
Wir haben auf den Gastsystem (192.168.100.10, 192.168.100.11) bereits HTTP Server (lighttp, apache) eingerichtet. Diese warten auf Port 80 auf eingehende Anfragen. Die Webserver sollen über www.mindfab.net (192.168.100.10) und über cms.mindfab.net (192.168.100.11) erreichbar sein. Dieses Problem lässt sich mit einfachen Portforwarding nicht lösen, da wir in Abhängigkeit von dem Servernamen (www.mindfab.net oder cms.mindfab.net) auf unterschiedliche IP-Adressen (http Server) zugreifen. Als Lösung dieses Problems bietet sich nginx, ein open source web Server und reverse proxy Server an.

Lösungsansatz

Als erstes sollten wir sicherstellen das unsere HTTP Server auf den Gästen ordnungsgemäss funktionieren und über die entsprechenden IP Adressen erreichbar sind. Dies kann man bspw. mit lynx überprüfen. Von unseren Host System aus überprüfen wir also folgendes...
lynx http://192.168.100.10
lynx http://192.168.100.11
Bei beiden Kommandos sollte die "default" Webseite erscheinen.

Als nächstes installieren wir nginx auf unseren Host...

apt-get install nginx
und beginnen mit der entsprechenden Konfiguration.

vim /etc/nginx/conf.d/default.conf
## 192.168.100.10 -> www.mindfab.net ##
upstream wwwmindfabnet  {
      server 192.168.100.10:80;
}
 
## 192.168.100.11 -> cms.mindfab.net ##
upstream cmsmindfabnet  {
      server 192.168.100.11:80;
}
 
## Start www.mindfab.net ##
server {
    listen       200.200.200.200:80;
    server_name  www.mindfab.net;
 
    access_log  /var/log/nginx/www.mindfab.net.access.log;
    error_log  /var/log/nginx/www.mindfab.net.error.log;
    root   /usr/share/nginx/www;
    index  index.html index.htm;
 
    ## send request back to www.mindfab.net ##
    location / {
     proxy_pass  http://wwwmindfabnet;     
     proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
     proxy_redirect off;
     proxy_buffering off;
     proxy_set_header        Host            $host;
     proxy_set_header        X-Real-IP       $remote_addr;
     proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
   }
}
## End www.mindfab.net ##
 
## START cms.mindfab.net ##
server {
   listen      200.200.200.200:80;
   server_name cms.mindfab.net;
   access_log  /var/log/nginx/cms.mindfab.net.access.log;
   error_log   /var/log/nginx/cms.mindfab.net.error.log;
   root        /usr/local/nginx/www;
   index       index.html;
 
   location / {
        proxy_pass  http://cmsmindfabnet;        
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header        Host            cms.mindfab.net;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
## END cms.mindfab.net  ##
 Nun starten wir den Daemon neu und testen anschliessend den Reverse Proxy.
service nginx restart
lynx http://www.mindfab.net # sollte uns den Inhalt des Webservers auf 192.168.100.10 anzeigen
lynx http://cms.mindfab.net # sollte uns den Inhalt des Webservers auf 192.168.100.11 anzeigen
Anmerkung: Selbstverständlich müssen wir sicherstellen das www.mindfab.net und cms.mindfab.net beide auf die IP Adresse 200.200.200.200 auflösen. Dies können wir durch eine entsprechende Konfiguration unseres DNS Servers erreichen. Für Testzwecke kann man aber auch zwei Einträge in seine /etc/hosts Datei anlegen.

Im 2.Teil werden wir unseren Reverse Proxy um SSL erweitern.

Referenzen:

  • http://www.cyberciti.biz/tips/using-nginx-as-reverse-proxy.html

Freitag, 19. Juli 2013

Portforwarding an Guest VM unter KVM mit libvirt und iptables

Problembeschreibung

Ein Serversystem mit öffentlicher IP virtualisiert mehrere Gast(Server)systeme welche HTTP, MAIL, SVN(SSH) usw. bereitstellen. Als Virtualisierungslösung verwenden wir kvm mit libvirt. Als Netzwerkmodus wird "nat" verwendet, da die Gäste keine öffentlichen Adressen bekommen sollen. Die Standardkonfiguration lässt in diesen Modus die Kommunikation des Gasts nach aussen hin zu (MASQUERADING), schirmt diesen jedoch beim Zugriff auf ihn selbst ab. Dieses Problem können wir durch den gezielten Einsatz von Einträgen in den PREROUTING und FORWARDING Bereichen von iptables lösen.

Im folgenden heisst unser Gastsystem "ssh-server" und hat die Gast-IP im privaten Subnetz von "192.168.100.10". Auf den Gast läuft ein SSH Server auf Port 22 dieser soll von aussen über Port 10000 über die öffentliche IP erreichbar sein.

Lösungsansatz

Libvirt  unterstützt seit Version 0.8.0 hook Scripte. Diese werden bspw. beim starten/beenden/usw. des libvirt deamons oder auch beim starten/beenden/usw. von virtuellen Maschinen über qemu aufgerufen. Diese Funktionalität werden wir nutzen um die Gast spezifischen Firewall regeln zu realisieren.

Als erstes erstellen wir also ein qemu hook Script und machen dies ausführbar.

touch /etc/libvirt/hooks/qemu
chmod +rx /etc/libvirt/hooks/qemu
Libvirt  ruft dann später das Script u.a. beim Starten und Stoppen von Gästen mit folgenden Parametern auf
  1. Objekt (z.B. Name des Gasts)
  2. Operation (z.B. start, stopped, reconnect, etc.)
  3. Unter-Operation oder "-"
  4. Extra-Argument oder "-"
Eine umfangreiche Dokumentation über Hook Scripte und deren Argumente findet sich auf der libvirt Webseite http://www.libvirt.org/hooks.html.

Wir editieren nundas Script folgendermassen.

#!/bin/bash

Guest="ssh-server"
Guest_ip="192.168.100.10"

if [[ $1 = $Guest ]]
then
        if [[ $2 = "stopped" || $2 = "reconnect" ]]
        then
                iptables -t nat -D PREROUTING -p tcp --dport 10000 -j DNAT --to $Guest_ip:22
                iptables -D FORWARD -d $Guest_ip/32 -p tcp -m state --state NEW -m tcp \
                --dport 22 -j ACCEPT
        fi
        if [[ $2 = "start" || $2 = "reconnect" ]]
        then
                iptables -t nat -I PREROUTING -p tcp --dport 10000 -j DNAT --to $Guest_ip:22
                iptables -I FORWARD -d $Guest_ip/32 -p tcp -m state --state NEW -m tcp \
                --dport 22 -j ACCEPT
        fi
fi
Nun stoppen wir ggf. den Gast und machen das Script durch Neustarten des libvirt Deamons bekannt.
virsh destroy ssh-server
/etc/init.d/libvirt-bin restart
virsh start ssh-server
Wenn alles funktioniert hat sollten keinerlei Fehlermeldungen im Log auftauchen und die iptables die entsprechenden Regeln enthalten.
tail -f /var/log/libvirt/libvirtd.log

iptables -L
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination        
ACCEPT     tcp  --  anywhere             192.168.100.10       state NEW tcp dpt:ssh

iptables -L -t nat

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination        
DNAT       tcp  --  anywhere             anywhere             tcp dpt:10000 to:192.168.100.10:22

Referenzen

  • http://www.jimscode.ca/index.php/component/content/article/19-linux/142-linux-port-forwarding-to-guest-libvirt-vms
  • http://www.libvirt.org/hooks.html