Contact

admin

About Me · Send mail to the author(s) E-mail · Twitter

At GROSSWEBER we practice what we preach. We offer trainings for modern software technologies like Behavior Driven Development, Clean Code and Git. Our staff is fluent in a variety of languages, including English.

Feed Icon

Tags

Open Source Projects

Archives

Blogs of friends

Now playing [?]

Error retrieving information from external service.
Audioscrobbler/Last.fm

ClustrMap

Migrating SharePoint Content Databases To A New Farm While Keeping Security Settings

Posted in .NET | SharePoint | SQL Server at Saturday, 25 April 2009 16:40 W. Europe Daylight Time

The server this blog is hosted on was upgraded recently, i.e. is now run on a new dedicated server. Actually, next to this blog there’s a lot more going on. We, a bunch of geeks, are self-hosting mail, web sites, blogs and some collaboration tools like SharePoint (Windows SharePoint Services, in our case). Yeah, SharePoint, a true beast in and of itself. I can’t tell you how much I do not miss developing software for it and setting up customer sites.

I wanted to make the move to the new server as smooth as possible for our SharePoint users. Because we do not use Active Directory to authenticate our users, we obviously had to migrate the SharePoint user accounts manually. That is, re-create each user on the new server giving them a random password and communicate the change.

Moving a SharePoint site is surprisingly pretty well documented on TechNet, but won’t tell you about one important aspect: When you move the site to a new farm and the site does not use Active Directory, you will have to set up security anew.

Why? Because SharePoint matches user accounts by their SID, a value that is unique for each user account, even across machines: OLDMACHINE\foo’s SID is different from NEWMACHINE\foo’s SID. Burdening the four site collection administrator with this task is simply a no-go.

During my research how to work around that I found the Dustin Miller’s excellent post “Fix those SIDs”. It describes the process of massaging a SharePoint site collection database to replace old SIDs with the account SIDs of the current machine. I’ve extended it a bit, because I also decided to rename the SharePoint Search account while moving to the new server (note the extra REPLACE in line 11).

DECLARE @login nvarchar(255), @SystemId varbinary(512)

DECLARE curUsers CURSOR LOCAL FOR 
SELECT tp_Login, tp_SystemID FROM UserInfo WHERE tp_Deleted = 0

OPEN curUsers
FETCH NEXT FROM curUsers INTO @login, @systemid

WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @newLoginName AS nvarchar(255) = REPLACE(REPLACE(@login, 'OLDMACHINE', 'NEWMACHINE'), 'spsearch', 'sharepoint-search')
    DECLARE @newSID varbinary(85) = SUSER_SID(@newLoginName)
    
    IF @newSID IS NOT NULL
    BEGIN
        PRINT 'Resetting user ' + @login + ' to new login ' + @newLoginName  + ' with SID '
        PRINT SUSER_SID(@newLoginName)

        UPDATE UserInfo
        SET    tp_SystemID = SUSER_SID(@newLoginName),
               tp_Login = @newLoginName
        WHERE CURRENT OF curUsers
    END
    
    FETCH NEXT FROM curUsers INTO @login, @systemid
END

CLOSE curUsers
DEALLOCATE curUsers
GO

After the script ran, take a look at the UserInfo table and SELECT rows that still contain OLDMACHINE in the tp_Login column. This helps you get a quick overview of what accounts have been missed during account re-creation.

As an extra step, I found it appropriate to update the site’s user entry as well (the account name that shows up in the site’s user list when no full name is given) to reflect the new machine name.

UPDATE    [AllUserData]
SET       [nvarchar1] = REPLACE(REPLACE([nvarchar1], 'OLDMACHINE', 'NEWMACHINE'), 'spsearch', 'sharepoint-search'),
          [nvarchar2] = REPLACE(REPLACE([nvarchar2], 'OLDMACHINE', 'NEWMACHINE'), 'spsearch', 'sharepoint-search'),
          [nvarchar3] = REPLACE(REPLACE([nvarchar3], 'OLDMACHINE', 'NEWMACHINE'), 'spsearch', 'sharepoint-search')

ChkDskAll ‒ ChkDsk For All Drives

Posted in .NET | Tools and Software | Windows at Saturday, 14 February 2009 16:41 W. Europe Standard Time

The Windows file systems (NTFS and FAT) are able to automatically detect if they are broken. You can even specify when that automatic check should be performed. But sometimes you would want to force a file system check, for example when Windows suddenly behaves strangely for no obvious reason. (For example last year, the day before I went on a month-long vacation, Vista suddenly refused to boot.)

In order to schedule a file system check for the next reboot you will have to

  1. Open an elevated command prompt or log in as an administrator,
  2. Run chkdsk <Drive>: /f,
  3. Rinse and repeat for all installed drives.

This task isn’t easy for inexperienced users, especially given that they might not know about the chkdsk command line tool in the first place. They could use the UI, but would have to repeat the process for each and every drive nonetheless.

Chkdsk UI

To make this task easier, I wrote a little .NET application that automates scheduling file system check for all drives at the next boot. Just double-click ChkDskAll.exe, enter administrative credentials and wait for the goodness to happen.

ChkDskAll In Action

If a drive has already been scheduled for scanning, it won’t be scheduled a second time. To exclude drives from being included in the scan, have a look at ChkdskAll.exe.config. For example, TrueCrypt drives should be excluded if you do not mount them as fixed drives.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <!-- The list of excluded drives, e. g. "CDE". -->
        <add key="ExcludeDrives" value="YZ"/>
    </appSettings>
</configuration>

Creating Remote Desktop Connection Files On The Fly With PowerShell

Posted in .NET | PowerShell at Friday, 13 February 2009 18:28 W. Europe Standard Time

About a month ago I got myself an IBM Lenovo X301 laptop. It’s the third machine I regularly use: I have a desktop at work, one workstation at home and now there also is the sweet X301. Even with only the second machine at work in place I found it crucial to keep my work environments automatically in sync. Live Mesh has been of great help with that. Despite being a CPU hog at times, it’s a great tool to synchronize your files across machines.

Now with Remote Desktop Connection files (*.rdp) there is actually a bit more work involved than just syncing files. My workstations both have two monitors and I like Remote Desktop windows to be placed on the second monitor. The laptop, on the other hand just has a single display (surprise!) and less screen real estate. Because of the varying screen resolutions a RDP window on my home workstation will not fit the display area at work without obscuring other UI  elements like the taskbar. On the laptop, the situation gets worse as the display is simply not large enough to fit my default window size of 1500x1024 pixels.

Desktops

The dimensions and location of the Remote Desktop window is stored in the plain-text RDP file itself. A conceivable solution might be to create three RDP files per server, one for each of my machines. Actually this would involve touching three files every time I need to change some value for the connection. Horrible.

Fortunately there is PowerShell to the rescue. There’s even a script that you can use to create RDP files on the fly. You’ll have to patch line 178 to make it work (see below). Also make the Set-RDPSetting function global by uncommenting lines 87 and 216.

# Old
$content += @("${tempname}:$($datatypes[$tempname]):$tempvalue")

# New
$content += @("${tempname}:$($datatypes[$tempname]):$tempvalue" + [System.Environment]::NewLine)

Now that we're set to use the Set-RDPSetting function, let us create a script to create a RDP file in the system’s temporary folder. See the highlighted lines below, there are three hashtables:

  • $workstations for your workstations (mine are AXL, FIRIEL and EOWYN),
  • $servers for the RDP servers you want to connect to and
  • $defaults for default connection properties.
. c:\Path\To\Set-RDPSetting.ps1

$workstations = @{
	'AXL' = @{
		'desktopwidth' = 1500
		'desktopheight' = 1024
		}
	'FIRIEL' = @{
		'desktopwidth' = 1300
		'desktopheight' = 800
		}
	'EOWYN' = @{
		'desktopwidth' = 1300
		'desktopheight' = 800
		}
	}
	
$servers = @{
	'192.168.6.161' = @{
		'session bpp' = 24
		'domain' = 'DEVELOPMENT'
		}
	'host.with.ssl.certificate' = @{
		'session bpp' = 24
		'authentication level' = 2
		'disable wallpaper' = $true
		'desktopwidth' = 1280
		'desktopheight' = 1024
		}
	}

# http://dev.remotenetworktechnology.com/ts/rdpfile.htm
$defaults = @{
	'allow desktop composition' = $true
	'allow font smoothing' = $true
	'alternate shell' = $null
	'audiomode' = 2
	'authentication level' = $false
	'auto connect' = $true
	'autoreconnection enabled' = $true
	'bitmapcachepersistenable' = $true
	'compression' = $true
	'connect to console' = $false
	'desktopheight' = $null
	'desktopwidth' = $null
	'disable cursor setting' = $false
	'disable full window drag' = $false
	'disable menu anims' = $true
	'disable themes' = $false
	'disable wallpaper' = $false
	'displayconnectionbar' = $true
	'domain' = $null
	'drivestoredirect' = '*'
	'full address' = $args[0]
	'keyboardhook' = 1
	'maximizeshell' = $false
	'negotiate security layer' = $true
	'prompt for credentials' = $false
	'promptcredentialonce' = $true
	'redirectclipboard' = $true
	'redirectcomports' = $false
	'redirectdrives' = $false
	'redirectposdevices' = $false
	'redirectprinters' = $false
	'redirectsmartcards' = $false
	'remoteapplicationmode' = $false
	'screen mode id' = 1
	'server port' = 3389
	'session bpp' = 32
	'shell working directory' = $null
	'smart sizing' = $true
	'username' = 'agross' # Does not really matter what's in here.
	# http://blogs.msdn.com/ts/archive/2008/09/02/specifying-the-ts-client-start-location-on-the-virtual-desktop.aspx
	'winposstr' = '0,3,2046,129,3086,933'	
	}

Next we check if the local machine has a configuration section in the $workstations hashtable and the script has been called with parameters.

if ($workstations.Keys -inotcontains $Env:ComputerName)
{
	"The local computer is not configured."
	exit
}

if ($args -eq $null -or $args.Length -eq 0)
{
	"No arguments. Supply the RDP server name as the first argument."
	exit
}

Note the Patch-Defaults function and how we use it to add and replace keys in the $defaults hashtable. The replacement values come from $workstations and $servers, with the server settings having precedence. This way, you can configure the connection profile according to the current machine and the server to which the connection will be made. Flexibility!

function Patch-Defaults
{
	param(
		[Hashtable] $defaults = $(Throw "Defaults hashtable is missing"),
		[Hashtable] $patch = $(Throw "Patch hashtable is missing")
		)

	end
	{
		if ($patch -ne $null)
		{
			$patch.GetEnumerator() | ForEach-Object { $defaults[$_.Name] = $_.Value }
		}
	}
}	

Patch-Defaults -Defaults $defaults -Patch $workstations[$Env:ComputerName.ToUpper()]
Patch-Defaults -Defaults $defaults -Patch $servers[$args[0].ToLower()]
$defaults.GetEnumerator() | Sort-Object -Property Name

Now that we have all connection properties in place, we create a temporary connection file from the hashtable key/value pairs and start mstsc.exe with that file.

$tempFile = [System.IO.Path]::GetTempFileName()
$defaults.GetEnumerator() | Sort-Object -Property Name | Set-RDPSetting -Path $tempFile -Name $_ -Value $_

# For debugging purposes.
#"Temporary file: " + $tempFile
#Get-Content $tempFile
#Read-Host

$MstscCommand = $Env:SystemRoot + "\system32\mstsc.exe"
&$MstscCommand $tempFile

How do we use the script we just created?

You can either create batch files calling the script or use a tool like SlickRun to execute PowerShell with the script.

@powershell.exe -noprofile -command .\Open-CustomRDP.ps1 your.server.example

Another tedious task has been automated!

.NET Bootcamp: Unit Tests

Posted in .NET | BDD | DNUG Leipzig at Saturday, 07 February 2009 16:19 W. Europe Standard Time

In ungefähr einer Woche, am 16.02.2009, findet das zweite .NET Bootcamp der .NET User Group Leipzig statt. Das Thema lautet diesmal Unit Tests und wird neben einer Einführung in das Testen auch Test-Driven Development und Behavior-Driven Development aufgreifen.

Die Organisation des Bootcamps findet wieder nach dem Konzept “Lernen durch Lehren” (LdL) statt. Das heißt, dass jeder Teilnehmer im Vorfeld zwei Fragen als “Hausaufgabe” bekommt und seine Ergebnisse während der Veranstaltung vorstellt. Ich werde die Rolle des Moderators übernehmen, Fragen klären, weitere Aspekte aufzeigen und sicher auch etwas von den Teilnehmern lernen.

Wie unser erster Testballon auf dem Gebiet LdL zum Thema .NET Framework 3.5 ist dieses Bootcamp mit 19 Teilnehmern überbucht. Offenbar kommt LdL gut bei unseren User Group-Mitgliedern an, aus Sicht der Organisation kann ich dies jedenfalls bestätigen!

How To Set Up Secure LDAP Authentication with TeamCity

Posted in Build | Networking at Monday, 02 February 2009 17:35 W. Europe Standard Time

Last week we got a TeamCity Enterprise license at work. After using this great product for about a year we found ourselves running out of available build configurations. (There are 20 in the fully-functional free Professional edition which should be enough to evaluate the product. I recommend giving it a try.) There are a couple of advanced features in the TeamCity Enterprise edition we were looking forward to, for example authentication against a LDAP directory, an Active Directory in our case (AD = LDAP + DNS + a bunch of other stuff).

TeamCity uses LDAP to determine if a user should be able to access the TeamCity web interface. It does that through the LDAP bind operation, asking LDAP to validate the username and password combination entered at the login page.

TeamCity Login Dialog

After hitting the login button TeamCity will connect to the LDAP server, basically taking the text entered in the dialog above passing it to the LDAP bind operation. If the server accepts the username/password combination this means that access is granted. Some things to take into consideration when using LDAP authentication are:

  • TeamCity does not authenticate against an organizational unit in Active Directory (X.500 address). It just determines if the user (authenticated by username and password) exists anywhere in the directory. You can vote on this ticket to get that fixed.
  • Because TeamCity does not try to get additional information on the user’s groups memberships it is currently (as of TeamCity 4.0) not possible to automatically assign TeamCity roles to an LDAP user.
  • If you use the default LDAP configuration settings as shown in the TeamCity documentation, the LDAP connection will be unsecured, making the username and password vulnerable to eavesdropping by anyone who knows how to use packet sniffer.
  • Specific to Windows: You do not need an Active Directory infrastructure with a Domain Controller in place. Windows also supports Active Directory Application Mode (ADAM) on Windows Server 2003, renamed to Active Directory Lightweight Directory Services (AD LDS) in Windows Server 2008.

Given the things above, what are your options to secure the LDAP connection? You could change the authentication scheme to not use "simple” LDAP authentication, but choose from a variety of SASL options. I didn’t go down that road, because when I started to configure LDAP for TeamCity I basically knew nothing about neither LDAP nor SASL.

Using LDAPS (LDAP over SSL), which is also supported by Windows servers running some AD mode, appeared to be a viable option to enforce secure communication between TeamCity and the LDAP server.

Installing The LDAP Server

Setting Up LDAPS with Active Directory (Domain Controller mode)

There’s not much set up needed with this configuration. When you install Active Directory in Domain Controller mode you should also get an instance of Certificate Services that will create a self-signed certificate for your domain controller. This certificate will be used for LDAPS connections to the directory server, which is typically the domain controller.

As an aside, I’m not expert in setting up AD, please refer to your network administrator.

Setting Up LDAPS with Active Directory Application Mode (ADAM) or Active Directory Lightweight Directory Services (AD LDS)

As noted above, this setup is supported on any Windows Server and does not require the full-blown “Domain Controller” version of Active Directory. ADAM/LDS supports user authentication either against the ADAM/LDS instance (users created in the directory) or a against local Windows accounts (through a user proxy, see below)

Installing ADAM or AD LDS

Installing ADAM/LDS differs depending on which Windows Server version you have. I did it with Windows Server 2003:

  1. Navigate to the Control Panel and open up the Software control panel applet, appwiz.cpl
  2. Click “Add or remove Windows features”
  3. Select Active Directory Services, click on the Details… button and select Active Directory Application Mode. Close the window.
  4. Scroll down to Certificate Services entry and check it. (IIS will also be installed as part of Certificate Services to support web registrations.)
  5. Click Next.
  6. On the next dialog, you will be asked what type of Root Certificate Authority (CA) to install. Select “stand-alone“ CA and also check the “Create key pair” option.
  7. The next dialogs allows to select different options for the Root CA keys and the CA itself. I went with the defaults.
  8. Certificate Services and ADAM will be installed.
  9. Under Programs in the Start Menu there will be a new folder named “ADAM”. Click on the “Create ADAM instance” link.
  10. The ADAM wizard pops up, click Next.
  11. Choose “Create new unique instance” and click Next.
  12. Enter the name of the ADAM instance. I chose “TeamCity”, because we’re using ADAM to authenticate TeamCity users. Click Next.
  13. Write down the ports that are presented in the next step. The default LDAP port is 389, and the port for LDAPS is 636. Click Next.
  14. In the next step, choose to create a directory partition. Mine is called CN=TeamCity, DC=test, DC=local. Click Next until you reach the “Import LDIF files” dialog.
  15. Import at least the MS-User.ldf and MS-UserProxy.ldf schemas to enable the creation of local directory users and user proxies for Windows accounts.
  16. Click Next and wait for ADAM to be configured.
Setting Up ADAM or AD LDS to Accept SSL Connections

There are two good tutorials that I used to enable SSL on ADAM, so I won’t reiterate them here. I suppose the process of enabling SSL on LDS is similar.

User Management

You now have a LDAP server running that will serve requests for the LDAP and LDAPS protocols. Next, you would have to add users to the directory, which could either be

  • Local directory users: user and password stored in the directory; used with “simple” bindings, or
  • Windows users: users password stored by the local Windows account manager or in a full-blown AD domain; used with “proxied” bindings (from the outside, these also appear as “simple” bindings).

Windows users require a user proxy in the directory, linking the proxy to a Windows account SID. The link between the proxy and the Windows account is established though the Windows account’s Security Identifier (SID) which must be supplied when the proxy is created. Setting up user proxies is a bit complicated and well worth another post.

Please note that by default authenticating users through their respective proxies (proxied binding) requires a secure connection, unless you explicitly disable it. Unfortunately the attribute to change is not given in the linked article: it is msDS-Other-Settings. You can either require security for simple or proxied bindings by setting RequireSecureProxyBind (defaults to 1) and RequireSecureSimpleBind (defaults to 0) to either 0 or 1.

The net result of the default ADAM configuration (RequireSecureProxyBind=1) together with the default TeamCity configuration (ldap://some-server, which is unsecured) is that authentication requests for user proxies will always fail.

Setting Up TeamCity

Setting Up TeamCity to Use The LDAP Server

The easiest way is to start with the default TeamCity configuration in <TeamCity data directory>/config/ldap-config.properties:

java.naming.referral=follow
java.naming.provider.url=ldap://ldap.test.local:389
java.naming.security.authentication=simple

Unless you want to require your users to enter their login in the DOMAIN\username format I recommend adding the loginFilter property:

java.naming.referral=follow
java.naming.provider.url=ldap://ldap.test.local:389
java.naming.security.authentication=simple
loginFilter=.+

Now we need to set up the correct "user name" string to present it to the LDAP server. This string is created from the text entered in the "Username" text box on the login screen ($login$) and differs depending on whether you use LDAP with AD or ADAM/LDS:

java.naming.referral=follow
java.naming.provider.url=ldap://ldap.test.local:389
java.naming.security.authentication=simple
loginFilter=.+

# AD - authenticate against the TEST domain
formatDN=TEST\\$login$

# ADAM and presumably AD LDS - users will have to reside in the CN=Users,CN=TeamCity,DC=test,DC=local container
formatDN=CN=$login$,CN=Users,CN=TeamCity,DC=test,DC=local

Setting Up LDAPS Security

Enabling LDAPS is pretty easy from a TeamCity perspective. You just have to change line 2 of the configuration above to use the secure LDAP protocol:

java.naming.referral=follow
java.naming.provider.url=ldaps://ldap.test.local:636
java.naming.security.authentication=simple
loginFilter=.+
formatDN=<some value>

Changing the protocol to use ldaps:// will not instantly work and users would not be authenticated. Why?

Trusting The Certificate

What does LDAPS mean from a Java perspective? If you work on a domain (AD) or use ADAM/LDS with SSL you are very likely to work with self-signed SSL certificates. Such certificates are inherently untrusted as they are not issued by some trusted party (and this trusted party will charge money). Nevertheless they are perfectly okay for your environment.

When TeamCity establishes the SSL connection to your LDAP server, it is first presented with that untrusted certificate – and bails. Here’s a snippet from the TeamCity log files:

[2009-01-27 16:14:39,864]  ERROR - Side.impl.auth.LDAPLoginModule - 
 
javax.naming.CommunicationException: simple bind failed: ldap.test.local:636
[Root exception is javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]

To establish LDAPS connections successfully, you have to tell Java to trust your LDAP server’s certificate. Andreas Sterbenz has created a little Java utility called InstallCert that helps in that regard. Unfortunately you will have to compile it yourself against the Java 1.5 runtime, so here’s a compiled version that works with TeamCity.

Place the files from the ZIP in your <TeamCity root>\jre\bin directory. Open a command prompt and enter

java InstallCert ldap.test.local:636

Following the procedure described in Andreas' post, the utility will create a file called jssecacerts in the same directory. Overwrite <TeamCity root>\jre\lib\security\cacerts with that file.

After re-starting the TeamCity web server, it is now able to establish secured connections to the LDAP server. The user names and passwords transmitted over these connections will not be visible to outsiders.

Wrapping It Up

In this article I’ve shown you how to enable and secure TeamCity’s LDAP authentication in any Windows environment, be it an Active Directory domain or a couple of stand-alone Windows Servers. For both scenarios user management is centralized, either though the AD console or LDAP console in combination with the Windows user management console.

Figuring out all that has taken a considerable amount of time for me and hopefully saves you a couple of minutes that you can spend outside in the sun.