Notes about open source software, computers, other stuff.

Tag: network (Page 2 of 2)

Installing and configuring Puppet

Puppet is a configuration management system. In short this means that by setting up a server (the Puppet master) you can manage many other machines (nodes) with this server by specifying which packages should be installed, files that need to be present, their permissions, etc. The nodes poll the server every 30 minutes (by default) to see if they should apply any changes to their configuration. Other packages that implement a similar idea are CfEnfine and Chef.

Note that all these instructions were performed as root.

The puppet master

Gaffel will be puppet master. I’ve added a DNS entry for puppet.karssen.org that points to gaffel. This installs the client and the Puppet master:

$ aptitude install puppet puppetmaster

The main configuration of server and client can be found in /etc/puppet/puppet.conf. We’ll leave it at the default for now. The file /etc/puppet/manifests/site.pp contains options that apply to the whole site. Let’s make it and add the following contents:

import "nodes"
 
# The filebucket is for backups. Originals of files that Puppet modifies
# get stored here.
filebucket { main: server => 'puppet.karssen.org' }
File  { backup => main}
 
# Set the default $PATH$ for executing commands on node systems.
Exec { path => "/usr/bin:/usr/sbin:/bin:/sbin:" }

The file /etc/puppet/manifests/nodes.pp defines the nodes/clients that will be managed by puppet as well as what configuration will be applied to them, so-called roles. For now, let’s make a quick example:

node common {
	include packages
}
 
node lambik inherits common {
	include ntp::client
}

Both the ‘packages’ and the ‘ntp’ modules still need to be defined. Let’s do that now.

Modules are collections of puppet code (known as manifests) and related files that are used for client configuration. Modules are stored in /etc/puppet/modules/.
Let’s start with the ntp example. First make the necessary directory structure:

$ mkdir -p /etc/puppet/modules/ntp/{manifests,files,templates}

Every modules needs a file init.pp that declares the class. It can also include other files. The files and templates directories are used to store files that need to be copied to the node or templates to make such files, respectively. We’ll come across some examples of both. This is the init.pp file for the ntp role (/etc/puppet/modules/ntp/manifests/init.pp):

class ntp::client {
	 package { "ntp":
    		 ensure => installed,
	 }
 
	 service { "ntp_client":
		 name       => "ntp"
    		 ensure     => running,
#		 hasstatus  => true,
		 hasrestart => true,
		 require    => Package["ntp"],
	 }
}

Here we indicate that the NTP service must be running and that it’s init script (in /etc/init.d) accepts the status and restart options. Lastly in the require line we note that before this manifest can be applied we must make sure that the package ntp has been installed. This is necessary, because the order in which the two directives are executed is not necessarily the order in
which they appear in the manifest.

The # in from of the hasstatus attribute is because of a bug inthe puppet version (2.6.4) shipped with Ubuntu 11.04. See http://projects.puppetlabs.com/issues/5610 for the bug report. In version 2.6.7 it is supposedly fixed.

In our nodes.pp file we also mentioned a packages class. In this class we list all the packages that we want to have installed on the node. Let’s make the packages module. First create the necessary directories:

$ mkdir -p /etc/puppet/modules/packages/{manifests,files,templates}

Add the file /etc/puppet/modules/packages/manifests/init.pp:

class packages {
	 $base_packages = [
	 "openssh-server",
	 "nfs-common",
	 "etckeeper",
	 "htop",
	 "iotop",
	 "iftop",
	 ]
 
	 $editor_packages = [
	 "emacs",
	 "emacs-goodies-el",
	 "elscreen",
	 ]
 
	 $all_packages = [
	 $base_packages,
	 $editor_packages,
	 ]
 
	 package { $all_packages:
	      ensure => installed,
	 }
}

Here I’ve defined three variables (beginning with a $ sign), one for base packages, one for editor-related packages and one called $all_packages that incorporates them both. Finally, I tell puppet to ensure they are all installed.

Setting up a client

As a test client I’m using lambik, one of my MythTV frontends.

$ aptitude install puppet

To make sure that puppet starts by default on system startup edit the file /etc/default/puppet and set START to yes:

# Defaults for puppet - sourced by /etc/init.d/puppet
 
# Start puppet on boot?
START=yes
 
# Startup options
DAEMON_OPTS=""

Now edit /etc/puppet/puppet.conf (on the client) and add the FQDN of the puppet master server to the [main] section:

[main]
logdir=/var/log/puppet
vardir=/var/lib/puppet
ssldir=/var/lib/puppet/ssl
rundir=/var/run/puppet
factpath=$vardir/lib/facter
templatedir=$confdir/templates
prerun_command=/etc/puppet/etckeeper-commit-pre
postrun_command=/etc/puppet/etckeeper-commit-post
server = puppet.karssen.org
 
[master]
# These are needed when the puppetmaster is run by passenger
# and can safely be removed if webrick is used.
ssl_client_header = SSL_CLIENT_S_DN
ssl_client_verify_header = SSL_CLIENT_VERIFY

Setting up secure communication between master and nodes and first test run

Puppet uses SSL certificates to set up a secure connection between master and nodes. Before you can apply any changes to the client, certificates need to be exchanged and signed. First, tell the client to connect to the puppet master:

$ puppetd --test
info: Creating a new SSL key for lambik.karssen.org
warning: peer certificate won't be verified in this SSL session
info: Caching certificate for ca
warning: peer certificate won't be verified in this SSL session
warning: peer certificate won't be verified in this SSL session
info: Creating a new SSL certificate request for lambik.karssen.org
info: Certificate Request fingerprint (md5): 1D:A3:3A:4A:A6:DA:D6:C8:96:F4:D4:7E:52:F4:12:1D
warning: peer certificate won't be verified in this SSL session
warning: peer certificate won't be verified in this SSL session
warning: peer certificate won't be verified in this SSL session
Exiting; no certificate found and waitforcert is disabled

On the puppet master we can now sign the certificate:

$ puppetca -l
lambik.karssen.org
$ puppetca -s lambik.karssen.org
notice: Signed certificate request for lambik.karssen.org
notice: Removing file Puppet::SSL::CertificateRequest lambik.karssen.org at '/var/lib/puppet/ssl/ca/requests/lambik.karssen.org.pem'

On the client we can now rerun puppetd:

root@lambik:~# puppetd --test
info: Caching catalog for lambik.karssen.org
info: Applying configuration version '1311930908'
notice: /Stage[main]/Packages/Package[iotop]/ensure: ensure changed 'purged' to 'present'
notice: /Stage[main]/Packages/Package[iftop]/ensure: ensure changed 'purged' to 'present'
notice: /Stage[main]/Ntp/Package[ntp]/ensure: ensure changed 'purged' to 'present'
notice: /Stage[main]/Packages/Package[emacs-goodies-el]/ensure: ensure changed 'purged' to 'present'
notice: /Stage[main]/Packages/Package[htop]/ensure: ensure changed 'purged' to 'present'
info: Creating state file /var/lib/puppet/state/state.yaml
notice: Finished catalog run in 78.43 seconds

If all went well, we can now start the puppet client daemon to keep our system under puppet control:

$ service puppet start

Adding (configuration) files to the roles

Since I run my own NTP server (ntp.karssen.org, only accessible from inside my LAN), the NTP configuration file (/etc/ntp.conf) must be changed. Of course, we want Puppet to take care of this. The ntp.conf file I want to distribute to all nodes has the following contents (note that the only change is the name of the server and commenting the restrict lines):

# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
 
driftfile /var/lib/ntp/ntp.drift
 
 
# Enable this if you want statistics to be logged.
#statsdir /var/log/ntpstats/
 
statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable
 
# Specify one or more NTP servers.
 
# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
# more information.
server ntp.karssen.org
 
# Use Ubuntu's ntp server as a fallback.
server ntp.ubuntu.com
 
# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
# details.  The web page <http://support.ntp.org/bin/view/Support/AccessRestrict
ions>
# might also be helpful.
#
# Note that "restrict" applies to both servers and clients, so a configuration
# that might be intended to block requests from certain clients could also end
# up blocking replies from your own upstream servers.
 
# By default, exchange time with everybody, but don't allow configuration.
#restrict -4 default kod notrap nomodify nopeer noquery
#restrict -6 default kod notrap nomodify nopeer noquery
 
# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1
restrict ::1
 
# Clients from this (example!) subnet have unlimited access, but only if
# cryptographically authenticated.
#restrict 192.168.123.0 mask 255.255.255.0 notrust
 
 
# If you want to provide time to your local subnet, change the next line.
# (Again, the address is an example only.)
#broadcast 192.168.123.255
 
# If you want to listen to time broadcasts on your local subnet, de-comment the
# next lines.  Please do this only if you trust everybody on the network!
#disable auth
#broadcastclient

Save this file in /etc/puppet/modules/ntp/files (on the puppet master). Now edit the manifest for the ntp role (/etc/puppet/modules/ntp/manifest/init.pp) to add the file section and a subscribe command:

class ntp::client {
	 package { "ntp":
		      ensure => installed,
	 }
 
	 service { "ntp_client":
	      name       => "ntp",
	      ensure     => running,
#	      hasstatus  => true,
	      hasrestart => true,
	      require    => Package["ntp"],
	      subscribe  => File["ntp_client_config"],
	 }
 
	 file { "ntp_client_config":
		   path => "/etc/ntp.conf",
	   owner   => root,
	   group   => root,
	   mode    => 644,
	   source  => "puppet:///ntp/ntp.conf",
	   require => Package["ntp"],
	 }
}

The URL specified in the source line automatically looks in the right place (as mentioned just above) for the file. Because we don’t want to wait for puppet to automatically pass on this configuration, let’s run it by hand:

root@lambik:~# puppetd --test
info: Caching catalog for lambik.karssen.org
info: Applying configuration version '1311936811'
--- /etc/ntp.conf	2011-06-17 07:59:54.000000000 +0200
+++ /tmp/puppet-file20110729-12128-1h3fupz-0	2011-07-29 12:53:33.279622938 +0200
@@ -16,16 +16,14 @@
 # Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
 # on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
 # more information.
-server 0.ubuntu.pool.ntp.org
-server 1.ubuntu.pool.ntp.org
-server 2.ubuntu.pool.ntp.org
-server 3.ubuntu.pool.ntp.org
+server ntp.karssen.org
 
 # Use Ubuntu's ntp server as a fallback.
 server ntp.ubuntu.com
 
@@ -33,8 +31,8 @@
 # up blocking replies from your own upstream servers.
 
 # By default, exchange time with everybody, but don't allow configuration.
-restrict -4 default kod notrap nomodify nopeer noquery
-restrict -6 default kod notrap nomodify nopeer noquery
+#restrict -4 default kod notrap nomodify nopeer noquery
+#restrict -6 default kod notrap nomodify nopeer noquery
 
 # Local users may interrogate the ntp server more closely.
 restrict 127.0.0.1
info: FileBucket adding /etc/ntp.conf as {md5}32280703a4ba7aa1148c48895097ed07
info: /Stage[main]/Ntp::Client/File[ntp_client_config]: Filebucketed /etc/ntp.conf to main with sum 32280703a4ba7aa1148c48895097ed07
notice: /Stage[main]/Ntp::Client/File[ntp_client_config]/content: content changed '{md5}32280703a4ba7aa1148c48895097ed07' to '{md5}0d1b81c95bab1f6b08eb27dfaeb18bb5'
info: /Stage[main]/Ntp::Client/File[ntp_client_config]: Scheduling refresh of Service[ntp_client]
notice: /Stage[main]/Ntp::Client/Service[ntp_client]: Triggered 'refresh' from 1 events
notice: Finished catalog run in 3.06 seconds

Setting NFS mounts in /etc/fstab

On my clients I want to mount several NFS shares. Let’s create the directories for the nfs_mounts module (on the puppet master of course):

$ mkdir -p /etc/puppet/modules/nfs_mounts/{manifests,files,templates}

Next, let’s edit the manifest (/etc/puppet/modules/nfs_mounts/manifests/init.pp):

class nfs_mounts {
	 # Create the shared folder unless it already exists
	 exec { "/bin/mkdir -p /var/sharedtmp/":
		   unless => "/usr/bin/test -d /var/sharedtmp/",
	 }
 
	 mount { "/var/sharedtmp/":
	    atboot  => true,
	    ensure  => mounted,
	    device  => "nfs.karssen.org:/var/sharedtmp",
	    fstype  => "nfs",
	    options => "vers=3",
	    require => Package["nfs-common"],
	 }
}

This should make the /var/sharedtmp directory and mount it. Note that I mention the nfs_common package in a require line. This package was defined in the packages module (in the $base_packages variable. Now let’s add this module to the nodes.pp file:

node common {
  include packages
}
 
node lambik inherits common {
	include ntp::client
	include nfs_mounts
}

Since I’ve got more than a single NFS mount, let’s extend the previous example and use a defined resource. Change the file /etc/puppet/modules/nfs_mounts/manifests/init.pp as follows:

define nfs_mount(
	  $location,
	  $server  = "nfs.karssen.org",
	  $options = "vers=3",
	  $fstype  = "nfs"
) {
  file {"$location":
	  ensure => directory,
  }
 
  mount { "$location":
  	atboot  => true,
	ensure  => mounted,
	device  => "${server}:${location}",
	fstype  => "$fstype",
	options => "$options",
	require => [ Package["nfs-common"], File["$location"] ],
  }
}
 
class nfs_mounts {
 
			 nfs_mount { "/home":
		 	   location => "/home",
		 }
 
			 nfs_mount { "/var/sharedtmp":
		 	    location => "/var/sharedtmp",
 		 }
 
			 nfs_mount { "/var/video":
	 	    	    location => "/var/video",
 		 }
 
			 nfs_mount { "/var/music":
	 	    	    location => "/var/music",
 		 }
}

Here we first define a resource called nfs_mount, which can accept various parameters, all of which have a default value, except $location. Secondly we ensure that this location is a directory and then we define how it should be mounted. In the subsequent class definition we use this nfs_mount resource several times to mount the various NFS shares.
Note that it would have been easier if the definition of nfs_mount would have started with

define nfs_mount (
	  $location = $name,

because then the invocations of nfs_mount in the class would not
need the location => line. Unfortunately this doesn’t work. It’s
a known bug that has been fixed in version 2.6.5
(http://projects.puppetlabs.com/issues/5061).

Links

Using rsync to backup to a remote Synology Diskstation

An updated version of the script can be found here.

I recently bought a NAS, a Synology DiskStation DS211j and stuffed two 1TB disks in it. I configured the disks to be in RAID 1 (mirrored) in case one of them decides to die. I then brought the NAS to a family member’s house and installed it there. Now she uses it to back up her important files (and as a storage tank for music and videos).

The good thing for me is that I can now make off-site backups of my home directories. I configured the DS211j to accept SSH connections so that I can log into it (as user admin or root). I used the web interface to create a directory for my backups (which appeared to be /volume1/BackupLennart after logging in with SSH).

After making a hole in her firewall that allowed me to connect to the DS211j, I created a backup script in /etc/cron.daily with the following contents:

#!/bin/bash
#
# This script makes a backup of my home dirs to a Synology DiskStation at
# another location. I use LVM for my /home, so I make a snapshot first and
# backup from there.
#
# Time-stamp: <2011-02-06 21:30:14 (lennart)>
 
###############################
# Some settings
###############################
 
# LVM options
VG=raidvg01
LV=home
MNTDIR=/mnt/home_rsync_snapshot/
 
# rsync options
DEST=root@remote-machine.example.com:/volume1/BackupLennart/
SRC=${MNTDIR}/*
OPTIONS="-e ssh --delete --progress -azvhHS --numeric-ids --delete-excluded "
EXCLUSIONS="--exclude lost+found --exclude .thumbnails --exclude .gvfs --exclude .cache --exclude Cache"
 
 
 
###############################
# The real work
###############################
 
# Create the LVM snapshot
if [ -d $MNTDIR ]; then
    # If the snapshot directory exists, another backup process may be
    # running
    echo "$MNTDIR already exists! Another backup still running?"
    exit -1
else
    # Let's make snapshots
    mkdir -p $MNTDIR
    lvcreate -L5G -s -n snap$LV /dev/$VG/$LV
    mount /dev/$VG/snap$LV $MNTDIR
fi
 
 
# Do the actual backup
rsync $OPTIONS $EXCLUSIONS $SRC $DEST
 
# Remove the LVM snapshot
if [ -d $MNTDIR ]; then
    umount /dev/$VG/snap$LV
    lvremove -f /dev/$VG/snap$LV
    rmdir $MNTDIR
else
    echo "$MNTDIR does not exist!"
    exit -1
fi

Let’s walk through it: in the first section I configure several variables. Since I use LVM on my server, I can use it to make a snapshot of my /home partition. The LVM volume group I use is called ‘raidvg01’. Withing that VG my /home partition resides in a logical volume called ‘home’. The variable MNTDIR is the place where I mount the LVM snapshot of ‘home’.

The rsync options are quite straight forward. Check the rsync man page to find out what they mean. Note that I used the --numeric-ids option because the DS211j doesn’t have the same users as my server and this way all ownerships will still be correct if I ever need to restore from this backup.

In the section called “The real work” I first create the MNTDIR directory. Subsequently I create the LVM snapshot and mount it. After this the rsync backup can be run and finally I unmount the snapshot and remove it, followed by the removal of the MNTDIR.

Since the script is placed in /etc/cron.daily it will be executed every day. Since we use SSH to connect to the remote DS211j I set up SSH key access without a password. This Debian howto will tell you how to set that up.

The only thing missing in this setup is that the backups are not stored in an encrypted form on the remote NAS, but for now this is good enough. I can’t wait until the network bandwidth on both sides of this backup connection get so fast (and affordable) that I can easily sync my music as well. Right now uploads are so slow that I hardly dare to include those. I know that I shouldn’t complain since the Netherlands has one of the highest broadband penetrations in the world, but, hey, don’t you just always want a little more, just like Oliver Twist?

Using Windows AD for Apache authentication

Recently I was setting up a Subversion repository (on a Linux server) that needs to be accessed via HTTP. Users should be able connect to the repositories without authentication, but authentication is needed to perform write actions. Of course Apache’s htpasswd/htaccess combination would provide just that, but since we have a Windows 2008 Active Domain controller that provides authentication to our Windows machines I thought it would be a good idea to use it.

Configuration of the autentication and authorization is done by Apache’s mod_authnz_ldap and (on Red Hat EL) configured in /etc/httpd/conf.d/subversion.conf (which exists after installing the subversion package with yum.

Basic configuration with htaccess
For simple authentication with Apache’s htaccess mechanism the config looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so
 
<Location /repos>
   DAV svn
   SVNParentPath /var/www/svn
   SVNReposName "My company's repository"
 
   # Limit write permission to list of valid users.
   <LimitExcept GET PROPFIND OPTIONS REPORT>
      AuthType Basic
      AuthName "Authorization Realm for SVN"
      AuthUserFile /etc/httpd/conf.d/svn_htpasswd
      Require valid-user
 
   </LimitExcept>
</Location>

After using htpasswd to create a file with usernames and passwords on the server users could commit to the repository.

Configuration for AD Global Catalog
The first LDAP-like construction I got working was when using the AD Global Catalog. Normal LDAP traffic uses port 389, but the AD’s Global Catalog uses port 3268. The username needed to commit with SVN is windows_logon_name@your_AD.suffix, the so-called userPrincipalName. Here, your_AD and suffix are the DC’s of the LDAP/AD tree. By using this userPrincipalName users from different DC trees can be authenticated. The configuration file looks this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so
 
<Location /repos>
    DAV svn
    SVNParentPath /var/www/svn
    SVNReposName "My company's repository"
 
    # Limit write permission to list of valid users.
    <LimitExcept GET PROPFIND OPTIONS REPORT>
      AuthType Basic
      AuthName "Authorization using your LDAP account"
      AuthBasicProvider ldap
      AuthzLDAPAuthoritative off
      # Active Directory requires an authenticating DN to access records
      AuthLDAPBindDN "svntest@your_AD.suffix"
 
      # This is the password for the AuthLDAPBindDN user in Active Directory
      AuthLDAPBindPassword "some_good_password"
 
      # The LDAP query URL
      AuthLDAPURL "ldap://ldap.your_company.com:3268/?userPrincipalName?sub"
      AuthUserFile /dev/null
 
      # Require a valid user
      Require valid-user
    </LimitExcept>
</Location>

With this configuration I could commit with this command: svn commit -m "First AD test" --username your_windows_username@your_AD.suffix.

Configuration for AD + Windows logon Name
As mentioned earlier, the previous method allows people from different parts of the AD tree to log in. In order to restrict access to for example a specific OU, the AuthLDAPURL has to be changed. In our case the LDAP tree is not a simple OU=Users,DC=our_company,DC=com, but consists of several nested OU structures. I used the adsiedit.msc snapin (ADSI editor) on the AD controller to find out the exact structure, since I needed to find out which parts were CNs and which where OUs.
In order to authenticate against a the windows login names in a certain sub-OU the AuthLDAPURL is

AuthLDAPURL "ldap://ldap.your_company.com:389/OU=Group 1, OU=Location 1, DC=your_AD, DC=suffix?sAMAccountName?sub?(objectClass=*)"

Configuration for AD + Windows Display Name
If you want the users to use their common name (the Display Name in the AD) use:

AuthLDAPURL "ldap://ldap.your_company.com:389/OU=Group 1, OU=Location 1 DC=your_AD, DC=suffix?cn"

Users can now commit with: svn commit -m "Another AD test" --username "Firstname Lastname".

Configuration for AD + another field
In our case login authentication on the Linux/UNIX machines is not done through the AD. Furthermore, the user names are not synchronised between Linux and Windows. This poses a small inconvenience, since by default an svn commit uses the Linux username. As the AD doesn’t know about this name, the first authentication fails. subsequently Apache asks for the user name, and then the user can enter his Windows AD credentials (principle name, display name or windows login name, depending on which of the above configurations was chosen). So as a quick workaround (and just to see if I could get it to work) I entered my Linux user name into the Office field in the AD. In the ADSI Editor I found the real name of the field: physicalDeliveryOfficeName With the following AuthLDAPURL I could use the Office field to authenticate me:

AuthLDAPURL "ldap://ldap.your_company.com:389/OU=Group 1, OU=Location 1 DC=your_AD, DC=suffix?physicalDeliveryOfficeName"

Now a simple svn commit works.

Some useful links:

Script to tunnel RDP connections through stepping stone server using SSH

In order to connect to the servers at work we need to connect through a stepping stone host (via SSH). Since some of the servers are MS Windows machines which can be managed via the Remote Desktop Protocol (RDP), this traffic needs to be tunneled over SSH as well.
I wrote the following bash script to automate setting up the tunnel. It sets some default variables and then looks for an available port between 1234 and 1254 (chosen completely arbitrarily) and uses it for the tunnel. Then it uses the rdesktop program to start the RDP connection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/bin/bash
#
# This script makes an ssh tunnel to a stepping stone server
# and uses it to start an rdesktop connection to the machine 
# given as the first argument of the script. 
#
# (C) L.C. Karssen
# $Id: winremote.sh,v 1.14 2010/02/10 13:03:08 lennart Exp $
#
 
# User-configurable variables
ssh_username=your_steppingstone_username_here
steppingstone=steppingstone.your_company.com
rdesktop_username=your_windows_username_here
rdesktop_domain=your_windows_domain_here
rdesktop_opts="-z -g 1024x768 -a 16"
rdesktop_port=3389 # This is the standard MS rdesktop port
 
 
# For ordinary users it should not be necessary to change anything below this line. 
# Some functions:
usage()
{
    cat <<EOF
Usage:
    $program [options] rdesktop_hostname 
 
Make a remote desktop connection through an SSH tunnel.
 
Options: 
    -h, --help                                   print this help message
    -s, --steppingstone steppingstone_hostname   set other stepping stone host
                                                   (default: $steppingstone)
    -t, --timeout sec                            set timeout (default: 1)
    -v, --verbose                                verbose output
     --version                                   print version
 
Note that some customisations need to be made in the first few lines of this 
script (e.g. user names and other defaults)
EOF
}
 
program=`basename $0`
 
# Command line option parsing. Shift all options 
verbose=
timeout=1
 
while [ $# -gt 0 ]
do 
    case $1 in
	-v | --verbose | -d | --debug ) 
	    verbose=true
	    ;;
	--version )
	    echo '$Revision: 1.14 $'
	    exit 0
	    ;;
	-t | --timeout ) 
	    shift
	    timeout="$1"
	   if [ $timeout -lt 1 ]; then
	       timeout=1
	   fi
	   if [ $verbose ]; then
	       echo "Timeout set to $timeout"
	   fi
	   ;;
	-s | --steppingstone ) 
	   shift
	   steppingstone="$1"
	   if [ $verbose ]; then
	       echo "Steppingstone server is $steppingstone"
	   fi
	   ;;
	-h | --help ) 
	   usage
	   exit 0
	   ;;
	-*) 
	   echo "$0: invalid option $1" >&2
 	   usage
	   exit 1
	   ;;
	*) 
	   break
	   ;;
    esac
    shift
done
 
# Server name (as seen on the steppingstone) that we want to connect to:
rdesktop_server=$1 
 
################### Config done, let's get to work ########################
 
# Simple usage description
if [ "$rdesktop_server" == "" ]; then
    echo "Error: No rdesktop host given" >&2
    usage
    exit 1
fi
 
# Find a free port on the local machine that we can use to connect through
min_port=1234
max_port=1254
used_ports=(`netstat -tan | awk '{print $4}' | grep 127.0.0.1 | awk -F: '{print $2}' | sort -g`)
if [ $verbose ]; then
    echo "Used ports are: ${used_ports[@]}"
fi
 
# In the next line we first print the $used_ports as an array, but with 
# each port on a single line. This is then piped to an awk script that 
# puts all the values in an array and subsequently walks through all ports 
# from $min_port to $max_port in order to find the first port that is not 
# in the array. This port is printed.
local_port=`printf "%i\n" ${used_ports[@]} | \
    awk -v minp=$min_port -v maxp=$max_port \
    '{ array[$1]=1 } END { for (i=minp; i<=maxp; i++) { if (i in array) continue; else { print i; break } } }'`
if [ "$local_port" == "" ]; then
    echo "Error: No ports free! Exiting..." >&2
    exit 2
fi
if [ $verbose ]; then
    echo "Selected port was: $local_port"
fi
 
# Create tunnel:
if [ $verbose ]; then
    echo "Creating SSH tunnel..."
fi
ssh_opts="-f -N -L"
ssh $ssh_opts $local_port:$rdesktop_server:$rdesktop_port \
    $ssh_username@$steppingstone 
 
# Allow the ssh tunnel to be established
sleep $timeout
 
# Abort if tunnel has not been established
pidof_ssh=`pgrep -f "ssh $ssh_opts $local_port"`
if [ "$pidof_ssh" == "" ]; then
    echo "Error: Timeout while establishing tunnel" >&2
    echo "Exiting..."
    exit 3
fi
 
# Make rdesktop connection
if [ $verbose ]; then
    echo "Opening Remote desktop connection to $rdesktop_server..."
fi
rdesktop $rdesktop_opts -u $rdesktop_username -p - \
    -d $rdesktop_domain localhost:$local_port
 
# Clean up tunnel
if [ $verbose ]; then
    echo "Cleaning up SSH tunnel with pid $pidof_ssh and local port $local_port"
fi
kill $pidof_ssh

Cloning Ubuntu virtual machines: some problems (and solutions)

Yesterday I set up a KVM virtual machine on my new Ubuntu 9.10 server. The VM also ran Ubuntu 9.10 server. In order to do some performance tests (what would be the speed up of having the VM’s disks on an LVM LV on the host, compared to having them in a file on the host) I used virt-clone to clone the machine:

virt-clone --connect=qemu:///system -o testldap -n testldap-lvm -f testldap-lvm/ubuntu-kvm/disk0.img

This clones the VM named testldap to testldap-lvm and put its disk file in the subdirectory testldap-lvm/ubuntu-kvm/. After that I still had to convert this image file to it’s location in an LV, but that’s not what this post is about.

As the machine is cloned, the MAC address of its virtual NIC is also changed. The ‘source’ VM had 52:54:00:f2:cc:40, the new VM was given 00:16:36:46:34:42. As I booted the new VM I noticed it wouldn’t come up as expected. I couldn’t reach it via the fixed IP that I had given the source VM (even though the source VM was shut down, of course). Closer inspection revealed that the interface name for the NIC in the new VM had changed. I vaguely remembered that Debian-derived distro’s do that: because they don’t want NIC name assignments (eth0, eth1, etc.) to change if a new network adapter is added, they tie a name to a MAC address. And, as noted, the MAC address had indeed changed in the cloning process.

The assignments between MAC and eth? name are recorded in the file /etc/udev/rules.d/70-persistent-net.rules. They are set by the script /lib/udev/write_net_rules, so I removed the execute permissions on that file. However, this was not a clean solution, since it resulted in an error on start up. I found that editing /lib/udev/rules.d/75-persistent-net-generator.rules is a far better solution. Adding the lines

# ignore KVM virtual interfaces
ENV{MATCHADDR}=="52:54:00:*", GOTO="persistent_net_generator_end"
# This seems to be the range used by Xen, but also by virt-clone
ENV{MATCHADDR}=="00:16:36:*", GOTO="persistent_net_generator_end"

seems to do the trick (don’t forget to remove the rules already added in /etc/udev/rules.d/70-persistent-net.rules). Make sure to add them after the lines

# read MAC address
ENV{MATCHADDR}="$attr{address}"

so that the variable MATCHADDR has a value. I documented this solution in the Ubuntu bug report that seemed the most appropriate as well.

This solved one problem. Then the next problem reared its ugly head: Both the source VM and the clone refused to finish their boot process, they kept hanging on the NFS mounts defined in /etc/fstab. The only option mountall gave was to enter the root password (after pressing ESC) or type Crtl-D to continue. Doing the latter resulted in nothing but an infinite wait. In an Ubuntu bug report I found that using DHCP for the network interface would solve the problem. And, indeed it did. However, since I want static IP addresses for my servers this was not a solution that I liked. Much to my surprise the NFS mounts worked perfectly after changing the interface (in /etc/network/interfaces) back to static. I don’t know why, but on both VMs I set the configuration for eth0 from static to dhcp, rebooted, changed it back to static and rebooted again to find the problem solved… Strange!

Update 2009-12-18:
As it turns out, the solution to the mount problem doesn’t always work. I tried it again, but now it failed to work after switchting back from DHCP to a static IP. I guess it has something to do with the lease time of the IP, because in the case I described above there was a night between using the DHCP IP and turning static back on. So somewhere, something needs to time out before switching back from DHCP to static IPs works again.

Newer posts »

© 2021 Lennart's weblog

Theme by Anders NorenUp ↑