WAP Exchange and Lync Applications

PowerShell Deployment of Web Application Proxy and ADFS in Under 10 Minutes

===========================================================================
Updated 10 September 2013: tested with Windows 2012 R2 RTM and the script functions as in R2 Preview. Outlook Anywhere bug in the Preview code has been fixed and Outlook now works with RTM. Updated the script to correct Autodiscover ExternalURL
===========================================================================

In this post I will be discussing deploying a highly available Windows 2012 R2 Preview ADFS and Web Application Proxy solution using only PowerShell. This was done as a proof of concept to compare the time taken as well as complexity to build and configure a Reverse Proxy solution to replace a UAG 2010 array. Those of you who have worked with UAG will know that just installing and applying UAG updates and Service Packs takes over an hour – before you even get to the application configuration which is totally manual. For a client recently with 6 trunks publishing Exchange, Lync, Office Web Apps, FIM and SharePoint with the same applications published on multiple domain names it would take over 8 hours of mind numbing, RSI inducing and error prone manual input. The configuration I have built here is for Exchange 2013, Lync 2013 and Office Web Apps Server only, but adding SharePoint and FIM would add about 30 seconds to the installation time.

The other purpose of these scripts is to get something that is repeatable and easily configurable which gives certainty of the configuration and avoids human error. Note that this is a proof of concept so I have not put in much error checking – all code should be tested in a lab environment.

The end result on the ADFS side is an ADFS farm with two servers and a Non-Claims-Aware Relying Party Trust for Exchange.

ADFS Relying Party Trusts 2

And on the Web Application Proxy side we have a cluster of servers with two ADFS preauthenticated applications and 10 Pass-through applications.

WAP Exchange and Lync Applications

Background Reading

These scripts are based on information, functions and script extracts from some of my previous blog posts so if you want more information and background then I recommend you review these:

Windows 2012 R2 Preview Web Application Proxy – Exchange 2013 Publishing Tests for the Web Application Proxy overview

Publish Lync 2013 with 2012 R2 Preview Web Application Proxy for the Lync configuration and SNI requirements

SSL SAN Certificate Request and Import from PowerShell for the PowerShell based certificate requests

Outlook Anywhere NTLM SSO with UAG 2010 KCD for the KCD configuration

Overview of the Scripts

For ease of installation I have split this into four PowerShell scripts which do the following:

Setup-ADFSserver1.ps1 which is run on the first internal ADFS server
  1. User input of a password for exporting the ADFS certificate to .PFX with private key
  2. User input of the credentials for the ADFS service account
  3. Either import the ADFS certificate from a PFX file, or if used for testing – generate a certificate request .ini file for ADFS, requests a certificate from an online CA and exports the certificate as a PFX file to a file share
  4. Install an ADFS farm using WID
  5. Add a non claims aware relying party trust for Exchange using a permit all issuance rule
Setup-ADFSserver2.ps1 which is run on the secondary ADFS server
  1. User input of the password for importing the ADFS certificate
  2. User input of the credentials for the ADFS service account
  3. Import the ADFS certificate
  4. Add the server as an additional node to the ADFS farm
Setup-WAPserver1.ps1 which is run on the first Web Application Proxy server
  1. User input of the password for importing the ADFS certificate
  2. User input of a password for exporting the Web Application Proxy certificate to .PFX with private key
  3. User input of the credentials for an administrator of the ADFS server (not the ADFS service account)
  4. Import the ADFS certificate
  5. Either import the Web Application Proxy certificate from a PFX file, or if used for testing – generate a certificate request .ini file for the Web Application Proxy, requests a certificate from an online CA and exports the certificate as a PFX file to a file share
  6. Install the Web Application Proxy and add it as an ADFS proxy
  7. Add application publishing rules for Lync and Office Web Apps Server
  8. Add application publishing rules for Exchange
  9. Add SPN for KCD (Kerberos Constrained Delegation) to allow the WAP server to delegate credentials for the Exchange server
  10. Set the default certificate binding for clients that do not support SNI (Server Name Indication)
Setup-WAPserver2.ps1 which is run on the secondary WAP server
  1. User input of the password for importing the ADFS certificate
  2. User input of the password for importing the Web Application Proxy certificate to .PFX with private key
  3. User input of the credentials for an administrator of the ADFS server (not the ADFS service account)
  4. Import the ADFS certificate
  5. Import the WAP certificate
  6. Install the Web Application Proxy and add it as an ADFS proxy
  7. Add SPN for KCD (Kerberos Constrained Delegation) to allow the WAP server to delegate credentials for the Exchange server
  8. Set the default certificate binding for clients that do not support SNI (Server Name Indication)

In my testing the first ADFS server took on average 2 minutes 15 seconds, the second ADFS server 2 minutes 15 seconds, the first WAP server 2 minutes 45 and the second WAP server 2 minutes 30. That is a total of 9 minutes and 45 seconds for a highly available ADFS and Reverse Proxy solution which is a whole lot better than configuring UAG.

Note that I am giving the option of using an internal Certificate Authority for the deployment by specifying ‘-OnlineRequest’ in the scripts. This was done to help me with testing, however in the real world you would use a public certificate on the Web Application Proxy for the publishing rules as well as ADFS. I normally use the same public ADFS certificate on the internal ADFS server to keep the configuration consistent and allow non-domain joined clients and mobile devices connecting via internal Wifi to connect without having to import an internal root certificate.

Prerequisites

  • Four Windows 2012 R2 Preview servers which are joined to the domain and use internal DNS
  • In order to publish Exchange OWA and ECP applications with ADFS authentication, the Web Application Proxy servers must be domain joined in order to perform KCD. This makes the scripts easier too as they can place and retrieve certificates from a share and request online certificates from the enterprise Certificate Authority
  • Certificate PFX files for ADFS and Web Application Proxy exported as PFX files with a private key (unless OnlineRequest is specified in which case the script will request certificates from an Enterprise online Certificate Authority)
  • The ADFS farm FQDN (adfs2.showcase.kloud.com.au in this case) DNS A record must exist in DNS before configuration. As an aside – this should be an A record not a CNAME to allow domain joined clients with the ADFS FQDN in the IE trusted sites to authenticate automatically
  • Exchange and Lync already configured
  • One or more load balancers to spread the traffic load across the Web Application Proxy ‘cluster’ and the ADFS farm servers and perform health checks to provide high availability

Scripts

First, a note about the credential prompts. I could have entered the credentials into the script as plain text and converted them to a secure string, but I think that is bad practice as you are dealing with certificates and service accounts where the passwords will not change. Leaving text files with passwords on disk in plain text is a bad thing. Another option is to input the credentials once and save them to the registry in an encrypted form but that is not really relevant here as you will typically run the script only once per server. A third option would be to use remote PowerShell and input credentials once before running the configuration on all servers.

Edit the ‘### Variables’ section of each script and run from an elevated PowerShell prompt.

Setup-ADFSserver1.ps1

Usage:

  • .\Setup-ADFSserver1.ps1
  • .\Setup-ADFSserver1.ps1 –OnlineRequest
param (
	[switch]$OnlineRequest
)

$Start = get-date

### Variables
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$ADFSdisplayName = "Kloud Showcase ADFS 2"
$ADFSuser = "kloud\ADFSfarm2012R2"
$CertificateLocalPath = "C:\Certificates\"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"
$RelyingPartyTrustExchangeName = "Exchange"
$RelyingPartyTrustExchangeURI = "https://ex2010-01.kloud.internal/owa"
$RelyingPartyTrustExchangeIssuanceRule = "@RuleTemplate = `"AllowAllAuthzRule`""    # Escape quotes
$RelyingPartyTrustExchangeIssuanceRule += "`n => issue(Type = `"http://schemas.microsoft.com/authorization/claims/permit`", Value = `"true`");"    # New line and escape quotes
# Only for Online Request
$CertificateAuthority = "dc01.kloud.internal\kloud-Showcase-CA"

### Preparation
$PfxPasswordADFS = Get-Credential "PFX password" -message "Enter the password for the ADFS PFX certificate"
if ($PfxPasswordADFS -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$ADFSuserCredential = Get-Credential $ADFSuser -message "Enter the password for the ADFS service account"
if ($ADFSuserCredential -eq $null) {
	write-host "No password entered, exiting"
	exit
}

if ((Test-Path $CertificateLocalPath) -eq $false) {
	mkdir $CertificateLocalPath
}

$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS

$CertificateADFSsubjectCN = "CN=" + $CertificateADFSsubject
cd $CertificateLocalPath

function New-CertificateRequest {
	param (
		[Parameter(Mandatory=$true, HelpMessage = "Please enter the subject beginning with CN=")]
		[ValidatePattern("CN=")]
		[string]$subject,
		[Parameter(Mandatory=$false, HelpMessage = "Please enter the SAN domains as a comma separated list")]
		[array]$SANs,
		[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
		[string]$OnlineCA,
		[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
		[string]$CATemplate = "WebServer"
	)

	### Preparation
	$subjectDomain = $subject.split(',')[0].split('=')[1]
	if ($subjectDomain -match "\*.") {
		$subjectDomain = $subjectDomain -replace "\*", "star"
	}
	$CertificateINI = "$subjectDomain.ini"
	$CertificateREQ = "$subjectDomain.req"
	$CertificateRSP = "$subjectDomain.rsp"
	$CertificateCER = "$subjectDomain.cer"

	### INI file generation
	new-item -type file $CertificateINI -force
	add-content $CertificateINI '[Version]'
	add-content $CertificateINI 'Signature="$Windows NT$"'
	add-content $CertificateINI ''
	add-content $CertificateINI '[NewRequest]'
	$temp = 'Subject="' + $subject + '"'
	add-content $CertificateINI $temp
	add-content $CertificateINI 'Exportable=TRUE'
	add-content $CertificateINI 'KeyLength=2048'
	add-content $CertificateINI 'KeySpec=1'
	add-content $CertificateINI 'KeyUsage=0xA0'
	add-content $CertificateINI 'MachineKeySet=True'
	add-content $CertificateINI 'ProviderName="Microsoft RSA SChannel Cryptographic Provider"'
	add-content $CertificateINI 'ProviderType=12'
	add-content $CertificateINI 'SMIME=FALSE'
	add-content $CertificateINI 'RequestType=PKCS10'
	add-content $CertificateINI '[Strings]'
	add-content $CertificateINI 'szOID_ENHANCED_KEY_USAGE = "2.5.29.37"'
	add-content $CertificateINI 'szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"'
	add-content $CertificateINI 'szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"'
	if ($SANs) {
		add-content $CertificateINI 'szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"'
		add-content $CertificateINI '[Extensions]'
		add-content $CertificateINI '2.5.29.17 = "{text}"'

		foreach ($SAN in $SANs) {
			$temp = '_continue_ = "dns=' + $SAN + '&"'
			add-content $CertificateINI $temp
		}
	}

	### Certificate request generation
	if (test-path $CertificateREQ) {del $CertificateREQ}
	certreq -new $CertificateINI $CertificateREQ

	### Online certificate request and import
	if ($OnlineCA) {
		if (test-path $CertificateCER) {del $CertificateCER}
		if (test-path $CertificateRSP) {del $CertificateRSP}
		certreq -submit -attrib "CertificateTemplate:$CATemplate" -config $OnlineCA $CertificateREQ $CertificateCER

		certreq -accept $CertificateCER
	}
}

### Script

if ($OnlineRequest) {

    # Certificate creation
    New-CertificateRequest -subject $CertificateADFSsubjectCN -OnlineCA $CertificateAuthority

    # Certificate export
    $ADFScertificate = dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubject}
    if (($ADFScertificate.count) -gt 1) {
	    write-host -foregroundcolor Yellow "`n`nDuplicate certifcates! Delete all certificates with the subject $CertificateADFSsubject"
	    exit
    }
    Export-PfxCertificate -Cert $ADFScertificate -FilePath $CertificateADFSremotePath -Password $PfxPasswordADFS.Password
}
else {
    Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password
}

# ADFS Install
Add-WindowsFeature ADFS-Federation -IncludeManagementTools
Import-Module ADFS
$CertificateThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubjectCN}).thumbprint
Install-AdfsFarm -CertificateThumbprint $CertificateThumbprint -FederationServiceDisplayName $ADFSdisplayName -FederationServiceName $CertificateADFSsubject -ServiceAccountCredential $ADFSuserCredential

# ADFS Non Claims Aware Relying Party Trust
Add-AdfsNonClaimsAwareRelyingPartyTrust -Name $RelyingPartyTrustExchangeName -Identifier $RelyingPartyTrustExchangeURI -IssuanceAuthorizationRules $RelyingPartyTrustExchangeIssuanceRule

$Finish = get-date
$Elapsed = $finish - $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed

Setup-ADFSserver2.ps1

Usage: .\Setup-ADFSserver2.ps1

$Start = get-date

### Variables
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$ADFSprimaryServer = "2012R2ADFS1.kloud.internal"
$ADFSuser = "kloud\ADFSfarm2012R2"
$CertificateLocalPath = "C:\Certificates\"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"

### Preparation
$PfxPasswordADFS = Get-Credential "PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordADFS -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$ADFSuserCredential = Get-Credential $ADFSuser -message "Enter the password for the ADFS service account"
if ($ADFSuserCredential -eq $null) {
	write-host "No password entered, exiting"
	exit
}

$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS

### Script
# Import ADFS Certificate
if ((Test-Path $CertificateLocalPath) -eq $false) {
	mkdir $CertificateLocalPath
}
cd $CertificateLocalPath
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password
$CertificateThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $ADFSfqdn}).thumbprint

# ADFS Install
Add-WindowsFeature ADFS-Federation -IncludeManagementTools
Import-Module ADFS
Add-AdfsFarmNode -CertificateThumbprint $CertificateThumbprint -ServiceAccountCredential $ADFSuserCredential -PrimaryComputerName $ADFSprimaryServer -PrimaryComputerPort 80

$Finish = get-date
$Elapsed = $finish - $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed

Setup-WAPserver1.ps1

Usage:

  • .\Setup-WAPserver1.ps1
  • .\Setup-WAPserver1.ps1 –OnlineRequest
param (
	[switch]$OnlineRequest
)

$Start = get-date

### Variables
# WAP and ADFS
$CertificateWAPsubject = "mail.showcase.kloud.com.au"
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$CertificateWAPsans = "mail.showcase.kloud.com.au","autodiscover.showcase.kloud.com.au","lyncws.showcase.kloud.com.au","lyncdiscover.showcase.kloud.com.au","meet.showcase.kloud.com.au","dialin.showcase.kloud.com.au","owas.showcase.kloud.com.au","teams.showcase.kloud.com.au","my.showcase.kloud.com.au"
$ADFSadministrator = "kloud\administrator"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"
$CertificateLocalPath = "C:\Certificates\"
# Lync
$LyncDomain = "showcase.kloud.com.au"
$LyncWebServicesRoot = "lyncws."
$OfficeWebAppsRoot = "owas."
# Exchange
$AdfsExchangeRelyingParty = "Exchange"
$ExchangeExternalURL = "https://mail.showcase.kloud.com.au/"
$ExchangeAutodiscoverURL = "https://autodiscover.showcase.kloud.com.au/"
$ExchangeInternalURL = "https://ex2010-01.kloud.internal/"
$ExchangeSPN = "http/ex2010-01.kloud.internal"
#SNI
$SniIPport = "0.0.0.0:443" # IP and port to bind to. 0.0.0.0:443 matches all
# Only for Online Request
$CertificateAuthority = "dc01.kloud.internal\kloud-Showcase-CA"

### Preparation
$PfxPasswordADFS = Get-Credential "ADFS PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordADFS -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$PfxPasswordWAP = Get-Credential "WAP PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordWAP -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$ADFScredentials = Get-Credential $ADFSadministrator -message "Enter the password for the ADFS service account"
if ($ADFScredentials -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateWAP = "$CertificateWAPsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS
$CertificateWAPremotePath = $CertificateRemotePath + $CertificateWAP
$CertificateWAPsubjectCN = "CN=" + $CertificateWAPsubject

function New-CertificateRequest {
	param (
		[Parameter(Mandatory=$true, HelpMessage = "Please enter the subject beginning with CN=")]
		[ValidatePattern("CN=")]
		[string]$subject,
		[Parameter(Mandatory=$false, HelpMessage = "Please enter the SAN domains as a comma separated list")]
		[array]$SANs,
		[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
		[string]$OnlineCA,
		[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
		[string]$CATemplate = "WebServer"
	)

	### Preparation
	$subjectDomain = $subject.split(',')[0].split('=')[1]
	$CertificateINI = "$subjectDomain.ini"
	$CertificateREQ = "$subjectDomain.req"
	$CertificateRSP = "$subjectDomain.rsp"
	$CertificateCER = "$subjectDomain.cer"

	### INI file generation
	new-item -type file $CertificateINI -force
	add-content $CertificateINI '[Version]'
	add-content $CertificateINI 'Signature="$Windows NT$"'
	add-content $CertificateINI ''
	add-content $CertificateINI '[NewRequest]'
	$temp = 'Subject="' + $subject + '"'
	add-content $CertificateINI $temp
	add-content $CertificateINI 'Exportable=TRUE'
	add-content $CertificateINI 'KeyLength=2048'
	add-content $CertificateINI 'KeySpec=1'
	add-content $CertificateINI 'KeyUsage=0xA0'
	add-content $CertificateINI 'MachineKeySet=True'
	add-content $CertificateINI 'ProviderName="Microsoft RSA SChannel Cryptographic Provider"'
	add-content $CertificateINI 'ProviderType=12'
	add-content $CertificateINI 'SMIME=FALSE'
	add-content $CertificateINI 'RequestType=PKCS10'
	add-content $CertificateINI '[Strings]'
	add-content $CertificateINI 'szOID_ENHANCED_KEY_USAGE = "2.5.29.37"'
	add-content $CertificateINI 'szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"'
	add-content $CertificateINI 'szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"'
	if ($SANs) {
		add-content $CertificateINI 'szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"'
		add-content $CertificateINI '[Extensions]'
		add-content $CertificateINI '2.5.29.17 = "{text}"'

		foreach ($SAN in $SANs) {
			$temp = '_continue_ = "dns=' + $SAN + '&"'
			add-content $CertificateINI $temp
		}
	}

	### Certificate request generation
	if (test-path $CertificateREQ) {del $CertificateREQ}
	certreq -new $CertificateINI $CertificateREQ

	### Online certificate request and import
	if ($OnlineCA) {
		if (test-path $CertificateCER) {del $CertificateCER}
		if (test-path $CertificateRSP) {del $CertificateRSP}
		certreq -submit -attrib "CertificateTemplate:$CATemplate" -config $OnlineCA $CertificateREQ $CertificateCER

		certreq -accept $CertificateCER
	}
}

### Script

# Import ADFS Certificate
if ((Test-Path $CertificateLocalPath) -eq $false) {
	mkdir $CertificateLocalPath
}
cd $CertificateLocalPath
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password

if ($OnlineRequest) {
	# Request Web Application Proxy certificate
	New-CertificateRequest -subject $CertificateWAPsubjectCN -SANs $CertificateWAPsans -OnlineCA $CertificateAuthority

	# Export Web Application Proxy certificate
	$WAPcertificate = dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateWAPsubject}
	if (($WAPcertificate.count) -gt 1) {
		write-host -foregroundcolor Yellow "`n`nDuplicate certifcates! Delete all certificates with the subject $CertificateWAPsubject"
		exit
	}
	Export-PfxCertificate -Cert $WAPcertificate -FilePath $CertificateWAPremotePath -Password $PfxPasswordWAP.Password
}
else {
Import-PfxCertificate –FilePath $CertificateWAPremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordWAP.Password
}

# Add Web Application Proxy Role
Install-WindowsFeature Telnet-Client, RSAT-AD-PowerShell, Web-Application-Proxy -IncludeManagementTools

# Web Application Proxy Configuration Wizard
$CertificateADFSThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubject}).thumbprint
Install-WebApplicationProxy -CertificateThumbprint $CertificateADFSThumbprint -FederationServiceName $CertificateADFSsubject -FederationServiceTrustCredential $ADFScredentials

## Web Application Proxy Applications
$CertificateWAPThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateWAPsubject}).thumbprint

# Lync
Add-WebApplicationProxyApplication -Name 'Lync Web Services' -ExternalPreAuthentication PassThrough -ExternalUrl "https://$LyncWebServicesRoot$LyncDomain/" -BackendServerUrl ("https://"+$LyncWebServicesRoot+$Lyncdomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Lync Lyncdiscover' -ExternalPreAuthentication PassThrough -ExternalUrl "https://lyncdiscover.$LyncDomain/" -BackendServerUrl ("https://lyncdiscover."+$LyncDomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Lync Dialin' -ExternalPreAuthentication PassThrough -ExternalUrl "https://dialin.$LyncDomain/" -BackendServerUrl ("https://dialin."+$LyncDomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Lync Meet' -ExternalPreAuthentication PassThrough -ExternalUrl "https://meet.$LyncDomain/" -BackendServerUrl ("https://meet."+$LyncDomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Office Web Apps Server' -ExternalPreAuthentication PassThrough -ExternalUrl "https://$OfficeWebAppsRoot$LyncDomain/" -BackendServerUrl ("https://"+$OfficeWebAppsRoot+$LyncDomain+"/") -ExternalCertificateThumbprint $CertificateWAPThumbprint

# Exchange

Add-WebApplicationProxyApplication -Name 'Exchange OWA' -ExternalPreAuthentication ADFS -ADFSRelyingPartyName $AdfsExchangeRelyingParty -ExternalUrl $ExchangeExternalURL"owa/" -BackendServerUrl $ExchangeInternalURL"owa/" -BackendServerAuthenticationSpn $ExchangeSPN -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Exchange ECP' -ExternalPreAuthentication ADFS -ADFSRelyingPartyName $AdfsExchangeRelyingParty -ExternalUrl $ExchangeExternalURL"ecp/" -BackendServerUrl $ExchangeInternalURL"ecp/" -BackendServerAuthenticationSpn $ExchangeSPN -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Exchange Autodiscover' -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeAutodiscoverURL"autodiscover/" -BackendServerUrl $ExchangeInternalURL"autodiscover/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Exchange Outlook Anywhere' -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"rpc/" -BackendServerUrl $ExchangeInternalURL"rpc/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Exchange OAB' -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"OAB/" -BackendServerUrl $ExchangeInternalURL"OAB/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Exchange EWS' -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"EWS/" -BackendServerUrl $ExchangeInternalURL"EWS/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name 'Exchange ActiveSync' -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"Microsoft-Server-ActiveSync/" -BackendServerUrl $ExchangeInternalURL"Microsoft-Server-ActiveSync/" -ExternalCertificateThumbprint $CertificateWAPThumbprint

## Exchange SPN for KCD

Import-Module -Name ActiveDirectory
$WAPserver = Get-ADComputer (hostname)
Set-ADObject $WAPserver.DistinguishedName -Add @{"msDS-AllowedToDelegateTo" = $ExchangeSPN}
Set-ADObject $WAPserver.DistinguishedName -Replace @{"userAccountControl" = 16781312}

# Set default SNI binding
sleep 15 # sleep for 15 seconds to allow WAP bindings to be set
$certsList = @()
$certs = netsh http show sslcert | where {$_ -match ":port" -or $_ -match "Certificate Hash" -or $_ -match "Application ID"}
$certs = $certs.replace(" : ","#").replace(" ","")
$certs | foreach {
	if ($_ -match ":port") {
		$tempObject = New-Object -Type PSObject
		$tempObject | Add-Member -Type noteproperty -Name Binding -value $_.split("#")[1]
	}
	elseif ($_ -match "CertificateHash") {
		$tempObject | Add-Member -Type noteproperty -Name CertificateHash -value ($_.split("#")[1]-replace " ")
	}
	else {
		$tempObject | Add-Member -Type noteproperty -Name ApplicationID -value ($_.split("#")[1]-replace " ")
		$certsList += $tempObject
	}
}

$match = $certsList | where {$_.Binding -match $CertificateWAPsubject}
$CertificateHash = $match.CertificateHash
$ApplicationID = $match.ApplicationID

write-host -ForegroundColor Green "`n`nDefault bindings before changes"
netsh http show sslcert | where {$_ -match "ip:port"}

$command = "http add sslcert ipport=$SniIPport certhash=$CertificateHash appid=$ApplicationID"
write-host -ForegroundColor Yellow "`n`nAdding Certificate"
$command | netsh

write-host -ForegroundColor Yellow "`n`nDefault bindings after changes. Check that $SniIPport exists"
netsh http show sslcert | where {$_ -match "ip:port"}

$Finish = get-date
$Elapsed = $finish - $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed

Setup-WAPserver2.ps1

Usage: .\Setup-WAPserver2.ps1

$Start = get-date

### Variables
$CertificateWAPsubject = "mail.showcase.kloud.com.au"
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$ADFSadministrator = "kloud\administrator"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"
$CertificateLocalPath = "C:\Certificates\"
$ExchangeSPN = "http/ex2010-01.kloud.internal"
$SniIPport = "0.0.0.0:443" # IP and port to bind to. 0.0.0.0:443 matches all

### Preparation
$PfxPasswordADFS = Get-Credential "ADFS PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordADFS -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$PfxPasswordWAP = Get-Credential "WAP PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordWAP -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$ADFScredentials = Get-Credential $ADFSadministrator -message "Enter the password for the ADFS service account"
if ($ADFScredentials -eq $null) {
	write-host "No password entered, exiting"
	exit
}
$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateWAP = "$CertificateWAPsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS
$CertificateWAPremotePath = $CertificateRemotePath + $CertificateWAP
$CertificateWAPsubjectCN = "CN=" + $CertificateWAPsubject

### Script

# Import ADFS Certificate
if ((Test-Path $CertificateLocalPath) -eq $false) {
	mkdir $CertificateLocalPath
}
cd $CertificateLocalPath
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password

# Import WAP Certificate
Import-PfxCertificate –FilePath $CertificateWAPremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordWAP.Password

# Add Web Application Proxy Role
Install-WindowsFeature Telnet-Client, RSAT-AD-PowerShell, Web-Application-Proxy -IncludeManagementTools

# Web Application Proxy Configuration Wizard
$CertificateADFSThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubject}).thumbprint
Install-WebApplicationProxy -CertificateThumbprint $CertificateADFSThumbprint -FederationServiceName $CertificateADFSsubject -FederationServiceTrustCredential $ADFScredentials

## Exchange SPN for KCD
Import-Module -Name ActiveDirectory
$WAPserver = Get-ADComputer (hostname)
Set-ADObject $WAPserver.DistinguishedName -Add @{"msDS-AllowedToDelegateTo" = $ExchangeSPN}
Set-ADObject $WAPserver.DistinguishedName -Replace @{"userAccountControl" = 16781312}

# Set default SNI binding
sleep 15 # sleep for 15 seconds to allow WAP bindings to be set
$certsList = @()
$certs = netsh http show sslcert | where {$_ -match ":port" -or $_ -match "Certificate Hash" -or $_ -match "Application ID"}
$certs = $certs.replace(" : ","#").replace(" ","")
$certs | foreach {
	if ($_ -match ":port") {
		$tempObject = New-Object -Type PSObject
		$tempObject | Add-Member -Type noteproperty -Name Binding -value $_.split("#")[1]
	}
	elseif ($_ -match "CertificateHash") {
		$tempObject | Add-Member -Type noteproperty -Name CertificateHash -value ($_.split("#")[1]-replace " ")
	}
	else {
		$tempObject | Add-Member -Type noteproperty -Name ApplicationID -value ($_.split("#")[1]-replace " ")
		$certsList += $tempObject
	}
}

$match = $certsList | where {$_.Binding -match $CertificateWAPsubject}
$CertificateHash = $match.CertificateHash
$ApplicationID = $match.ApplicationID

write-host -ForegroundColor Green "`n`nDefault bindings before changes"
netsh http show sslcert | where {$_ -match "ip:port"}

$command = "http add sslcert ipport=$SniIPport certhash=$CertificateHash appid=$ApplicationID"
write-host -ForegroundColor Yellow "`n`nAdding Certificate"
$command | netsh

write-host -ForegroundColor Yellow "`n`nDefault bindings after changes. Check that $SniIPport exists"
netsh http show sslcert | where {$_ -match "ip:port"}

$Finish = get-date
$Elapsed = $finish - $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed

Add the servers to the load balancer, external DNS entries and you should be able to login to OWA and join a Lync meeting.

WAP applications

Publish Lync 2013 with 2012 R2 Preview Web Application Proxy

I discussed the new Windows 2012 R2 Preview Web Application Proxy (WAP) remote access role in a previous post Windows 2012 R2 Preview Web Application Proxy – Exchange 2013 Publishing Tests. I showed how to publish Exchange 2013 (except for Outlook Anywhere which isn’t working) and a claims based application.

In this post I am going to cover:

Publishing Lync Applications

Lync has a few different namespaces that need to be published:

  • Lync External Web Services (which includes the Lync Web App and Lync Scheduler)
  • Lync meeting join
  • Lync dialin page
  • Lyncdiscover for client autodiscover
  • Office Web Apps Server for PowerPoint sharing

Lync applications cannot use Preauthentication and have to use Pass-through which lets the backend server provide authentication. Setup the Web Application Proxy by installing the Remote Access Web Application Proxy role service as mentioned in the previous post. Install the certificate and run the configuration wizard to setup the server as an ADFS proxy.

Note that there is a dependency on a Windows 2012 R2 Preview ADFS farm. Even though we are not using Preauthentication, WAP cannot be setup without being configured as an ADFS Proxy.

The Lync applications can be published manually like this – note the port 4443 on the Backend server URL:

Or using a script:

$domain = "marc.kloud.com.au"
$webServices = "lyncws."
$certificate = "F7F6B300FE4D569FD598E1C9722571CF9AD780DD"

Add-WebApplicationProxyApplication -Name 'Lync Web Services' -ExternalPreAuthentication PassThrough -ExternalUrl "https://$webServices$domain/" -BackendServerUrl ("https://"+$webServices+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Lync Lyncdiscover' -ExternalPreAuthentication PassThrough -ExternalUrl "https://lyncdiscover.$domain/" -BackendServerUrl ("https://lyncdiscover."+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Lync Dialin' -ExternalPreAuthentication PassThrough -ExternalUrl "https://dialin.$domain/" -BackendServerUrl ("https://dialin."+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Lync Meet' -ExternalPreAuthentication PassThrough -ExternalUrl "https://meet.$domain/" -BackendServerUrl ("https://meet."+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Office Web Apps Server' -ExternalPreAuthentication PassThrough -ExternalUrl 'https://owas.marc.kloud.com.au/' -BackendServerUrl 'https://owas.marc.kloud.com.au/' -ExternalCertificateThumbprint $certificate

Important Configuration

There are three main things to be aware of when publishing Lync and Office Web Apps Server:

  • The Office Web Apps Server published application must have the same host name for ‘ExternalURL’ and ‘BackendServerURL’
  • The Lync 2013 mobile clients do not support Server Name Indication (SNI)
  • As we are publishing a full domain, not a sub path, you cannot use the same backend host name. So make sure that the backend servers have certificates for all the public names too

Office Web Apps Server URLs

Web Application Proxy supports header rewriting/translation in order to publish different internal names to the external names. It does not yet support body rewriting which is what I think the warning at the top of the diagram above is all about. It links to http://technet.microsoft.com/library/cc732148(WS.10).aspx but that content does not exist yet. I initially thought the problem may be the Office Web Apps Farm InternalURL configuration which had the internal server name shown below:

But that was not an issue. If I set the OWAS publishing rule on the WAP server with different external and internal URLs:

The client gets a referral to the Office Web Apps Server external URL https://owas.marc.kloud.com.au/m/ but the response body contains some X-Headers and javascript URLs for the backend URL of https://marc-owas1.marc.kloud.com.au/m/ URLs as you can see on the right hand side in the diagram below (click for a larger version). The client then tries to open a connection to the internal hostname which does not exist in DNS.

WAP OWAS Fiddler 2

In order to make this work, the Web Application Proxy publishing rule for Office Web Apps Server must have the same ‘ExternalURL’ and ‘BackendServerURL’.

Lync mobile clients do not support SNI

Server Name Indication (SNI) is a feature that is supported by just about every client browser (but not at all on Windows XP), but has only been supported by Microsoft web servers since IIS 8 in Windows 2012. SNI allows multiple certificates to be used on a single IP and port combination similar to the way host headers work with HTTP. With HTTPS traffic, the client needs to perform the certificate handshake before it sends the host header, so the server does not know which site is being requested and therefore which certificate to present. Lync 2013 and the Lync Windows Store App support SNI (I think because they use Internet Explorer underneath) however the Lync mobile clients do not.

Update 19 July 2013: After additional testing it was confirmed that Windows Phone 8 Lync Mobile 2013 and ActiveSync clients do support SNI, so the following problem will not be seen if you only use Windows Phone 8. iOS has supported SNI since version 4 and Android since version 3 (Honeycomb) and you can test this in a browser by going to an SNI test site like https://alice.sni.velox.ch/ – however the Lync and ActiveSync clients do not provide the SNI extension when connecting on either mobile OS (iOS does send the SNI name for autodiscover but not when it connects to the Microsoft-Server-Activesync URI).

The Problem

As I was testing this on a lab I did not have public DNS or a public certificate. I used the Lync 2013 client on my Android phone, modified the hosts file and imported the root certificate. The Lync mobile client would not connect and I did not see any traffic hitting the WAP application in the WAP performance counters. When viewing the client side logs I saw this:

HttpConnection: javax.net.ssl.SSLException: SSL handshake aborted: ssl=0x5886b8e0: I/O error during system call, Connection reset by peer

I installed Wireshark to see what was happening on the WAP server and what I saw pointed me at the problem:

Working: A web browser sends the ‘server_name’ extension containing the host name as can be seen below.

Not working: The Lync mobile client does not send the ‘server_name’ extension (neither does the Exchange ActiveSync client) as can be seen below:

The problem is that the client does not specify a server name and WAP does not specify a default certificate. I can’t change the client function (although I hope Microsoft will add SNI support soon), so the only option is to change the server.

The Solution

Finding the solution took a lot of searching. This TechNet blog Server Name Indication (SNI) with IIS 8 (Windows Server 2012) pointed me in the right direction when talking about how IIS 8 has a way to add a legacy SSL binding to support non-SNI compliant clients. Web Application Proxy however is not based on IIS. That blog mentioned using netsh to view the HTTP SSL bindings:


netsh http show sslcert

This lists all bindings including Remote PowerShell and ADFS

As well as the Web Application Proxy published applications

The MSDN article How to: Configure a Port with an SSL Certificate showed how to add a new binding. The trick is to add an IP:port binding in addition to the Hostname:port which acts as a legacy non-SNI binding. As all of the WAP applications have the same certificate and Application ID, I reused those and created the new binding:


netsh http add sslcert ipport=0.0.0.0:443 certhash=f7f6b300fe4d569fd598e1c9722571cf9ad780dd appid={f955c070-e044-456c-ac00-e9e4275b3f04}

This assumes that all names are on the single certificate as it sets the default binding on all IP addresses. If you had multiple certificates you would need multiple IP addresses and set a binding for each as you would have in TMG or UAG. This really reduces the effectiveness of SNI, so in order to not complicate the configuration or waste IPv4 addresses the clients need to be updated.

Bonus Feature

As mentioned by my colleague in An Overview of Server Name Indication (SNI) and Creating an IIS SNI Web SSL Binding Using PowerShell in Windows Server 2012, it should be possible to script and automate all of our configuration. I thought it would be a quick process to automatically create the legacy binding, but had to use good old PowerShell text parsing to get it working. This is not the most elegant script, but I am a self-taught scripter and this works for me. It does the following:

  • takes the netsh output and pulls out the IP:port or Hostname:port as well as the Certificate Hash and Application ID
  • removes the split and all spaces
  • creates an object for each existing binding
  • matches the existing WAP binding that has the correct certificate
  • creates the netsh command for creating the binding
  • runs the netsh command (netsh creation cannot be run directly in PowerShell which is why the command is piped to netsh)
  • shows the new binding information
$hostName = "mail.marc.kloud.com.au" # Name of existing hostname to match
$IPport = "0.0.0.0:443" # IP and port to bind to. 0.0.0.0:443 matches all

$certsList = @()
$certs = netsh http show sslcert | where {$_ -match ":port" -or $_ -match "Certificate Hash" -or $_ -match "Application ID"}
$certs = $certs.replace(" : ","#").replace(" ","")
$certs | foreach {
	if ($_ -match ":port") {
		$tempObject = New-Object -Type PSObject
		$tempObject | Add-Member -Type noteproperty -Name Binding -value $_.split("#")[1]
	}
	elseif ($_ -match "CertificateHash") {
		$tempObject | Add-Member -Type noteproperty -Name CertificateHash -value ($_.split("#")[1]-replace " ")
	}
	else {
		$tempObject | Add-Member -Type noteproperty -Name ApplicationID -value ($_.split("#")[1]-replace " ")
		$certsList += $tempObject
	}
}

$match = $certsList | where {$_.Binding -match $hostName}
$CertificateHash = $match.CertificateHash
$ApplicationID = $match.ApplicationID

cls
write-host -ForegroundColor Gree "`n`nDefault bindings before changes"
netsh http show sslcert | where {$_ -match "ip:port"}

$command = "http add sslcert ipport=$IPport certhash=$CertificateHash appid=$ApplicationID"
write-host -ForegroundColor Yellow "`n`nAdding Certificate"
$command | netsh

write-host -ForegroundColor Yellow "`n`nDefault bindings after changes. Check that $IPport exists"
netsh http show sslcert | where {$_ -match "ip:port"}

I save this as ‘Set-DefaultCertificate.ps1’ and the result of running the script is shown below.

Lync on Azure

I originally setup my lab on Azure as it was the fastest way for me to get up and running with Server 2012 R2 Preview. I then wanted to test publishing Lync, so installed a Lync Standard Edition server. Note that Lync is not listed as supported software on Azure Microsoft server software support for Windows Azure Virtual Machines. Lync Front End servers can run on Azure but Lync Edge servers cannot because they need two distinct network interfaces – one for internal communication and one for external. Azure does not support more than one network interface and also does not support adding additional IP addresses to a network interface, so there is no way to install a Lync Edge server.

I had setup Lync 2013 before in Azure, but this time I hit an error installing the SQL databases as part of step 2 of the ‘Install or Update Lync Server System’ in the Lync Deployment Wizard where it tries to create and attach the rtcxds database:

InstallDatabaseInternalFailure: An internal error has occurred while trying to create or update the database

Error: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding

InstallDatabaseInternalFailure: An error occurred while applying SQL script for the feature BackendStore

I could see the rtcxds.mdf and rtcxds.ldf files being created, but after 10 minutes it would fail with a timeout error and the databases would be removed. I initially thought it was an environmental issue as I had tried to use Pat Richard’s Set-Cs2013Features script from http://www.ehloworld.com/1697 which preinstalls the SQL Express instances and patches. So I installed another Windows 2012 server and just let Lync do the SQL install – same problem. I went back to a Windows 2008 R2 server and still had the same problem. Sorry to doubt your script Pat J

I tried a whole lot of things to do with the NT AUTHORITY\NETWORK SERVICE as there were some errors regarding that, but eventually I discovered the reason based on the last comment on this TechNet support forum post http://social.technet.microsoft.com/Forums/lync/en-US/f8d960d3-eaea-442a-8a03-da8b38ca7e1a/backendstore-database-creation-fails-lync-2013

Azure Operating System disks have read/write host cache enabled – primarily aimed at helping read performance. This means that a write is cached and not written to disk immediately. When using Active Directory Domain Controllers on Azure Placement of the Windows Server AD DS database and SYSVOL states:

Unlike Operating System disk drives, data disk drives do not cache writes by default. Data disk drives that are attached to a VM use write-through caching. Write-through caching makes sure the write is committed to durable Windows Azure Storage before the transaction is complete from the perspective of the VM’s operating system. It provides durability, at the expense of slightly slower writes.

SQL has a similar requirement for write-through caching. The rtcxds database and log files are each created as 4 GB and it seems that creating the 8 GB of rtcxds files puts too much pressure on the write cached network storage.

So the solution is to attach a new empty disk with at least 10 GB (this is suitable for a test) to the Azure VM with no read or write cache

Initialise the disk, format it and give it a drive letter. Then install the databases from PowerShell, specifying the data disk:


Install-CsDatabase -LocalDatabases -DatabasePaths F:\CsData

Run step 2 from ‘Install or Update Lync Server System’ in the Lync Deployment Wizard again and it succeeds.

Summary

2012 R2 Preview Web Application Proxy fully supports publishing Lync 2013 and Office Web Apps Server 2013 as long as the default binding is set and the OWAS publishing rule internal and external URLs are the same. The following was tested successfully:

  • Meeting join
  • Dialin page
  • Lync Web App
  • Lync Web Scheduler
  • Lyncdiscover
  • Lync 2013 mobile client
  • Lync Windows Store App (Lync MX)
  • Office Web Apps upload and presentation
  • Lync 2013 client Address book web search (tested with an Edge server outside of Azure)
  • Lync 2013 client Address book group expansion (tested with an Edge server outside of Azure)

The Web Application Proxy looks very promising as a Reverse Proxy for the primary Microsoft applications I work with. SharePoint has not been tested yet, but that should work in either ADFS Preauthentication or Pass-through mode as long as the URLs match all the way through. I am looking forward to hopefully not having to use UAG again.

Windows 2012 R2 Preview Web Application Proxy – Exchange 2013 Publishing Tests

==================
Updated: 10 September 2013

==================
Updated: 15 July 2013

  • I have heard from a member of the Web Application Proxy product group who said there is a bug in the Preview version that prevents Outlook Anywhere from working. They say it will be fixed in the RTM version
  • Lync 2013 and  Office Web Apps 2013 have been tested and work with some configuration changes. See https://blog.kloud.com.au/2013/07/15/publish-lync-2013-with-2012-r2-preview-web-application-proxy/ 
  • ActiveSync does not support SNI so a default binding needs to be set on the Web Application Proxy as per the post above to make it work
  • Clarification about ADFS being a hard requirement for the Web Application Proxy, even if only doing pass-through
  • Clarification about modifying published applications
  • Clarification about case sensitivity

==================

The announcement from Microsoft TechEd North America early in June that got me really interested was the Windows 2012 R2 Web Application Proxy which was described as:

Web Application Proxy – The Web Application Proxy is a new role service in the Windows Server Remote Access role. It provides the ability to publish access to corporate resources, and enforce multi-factor authentication as well as apply conditional access policies to verify both the user’s identity and the device they are using resources, and enforce multi-factor authentication as well as verify the device being used before access is granted.

The Web Application Proxy is a reverse proxy and ADFS (Active Directory Federation Services) Proxy that also provides functionality like Workplace Join for Windows 8.1 using the Device Registration Service (DRS). I have a particular interest in the reverse proxy side having done a lot of work with UAG lately which makes me miss TMG! As discussed in my previous blog Publish Lync 2013 Including Mobility and Office Web Apps with UAG 2010, with TMG disappearing the only Microsoft product (other than IIS with ARR) with reverse proxy functionality is Microsoft Forefront UAG (Unified Access Gateway).

UAG development seemed to have slowed down and the latest Service Pack added support for Exchange 2013 and SharePoint 2013 but not even Lync 2013 that came out at the same time. No word has been heard about any future development and with people like Erez Ben Ari (Ben Ari on TechNet) moving to the IIS team it seems like the writing is on the wall for Microsoft’s Forefront TMG and UAG products. UAG is a very powerful product but is hamstrung by its legacy roots in e-Gap and IAG and reliance on TMG underneath. TMG is not supported on Windows 2012 so if it was to move forward a total rewrite would have been needed.

In this blog I cover a bit about the Web Application Proxy and show how to configure ADFS, the Web Application Proxy and publish a claims-aware test application. I also did some testing with publishing Exchange 2013 to see how it worked.

TechEd Sessions and Documentation about Web Application Proxy

A session at TechEd North America titled Enable Work from Anywhere without Losing Sleep: Remote Access with the Web Application Proxy and VPN Solutions showed off some of the functionality (video and slides available at the link) and the same session at TechEd Europe last week WCA-B333 came out with a lot more information that I only saw after I started testing unfortunately. There is now a single TechNet page dedicated to the Web Application Proxy http://technet.microsoft.com/en-us/library/dn280944.aspx

Web Application Proxy Functionality

The Web Application Proxy (WAP) is a Role Service under the Remote Access role of Windows 2012 which also includes DirectAccess, VPN and routing services. It can provide simple reverse proxy functionality using ‘Pass-through’ where no preauthentication is performed, or provide Active Directory Federation Services (AD FS or ADFS) authentication by performing the ADFS proxy function. Note that even in Pass-through mode, WAP needs a Windows Server 2012 R2 Preview ADFS farm and must be setup as an ADFS Proxy. Without ADFS you can’t even complete the configuration wizard. Pass-through and ADFS federation to claims aware applications can be performed like previous AD FS proxies as a workgroup machine in the DMZ.

The reverse proxy functionality that seems like it could be a TMG/UAG replacement is the ability for the WAP to provide preauthentication for non-claims aware backend applications. It takes ADFS authentication and initiates a new session to the backend server providing Single Sign On (SSO) across multiple backend applications. This was possible with UAG by modifying applications to use the Claims to Windows Token Service (C2WTS) as described in Access OWA with ADFS, but the promise of this new functionality is that no modification is required on the backend service.

The catch in this scenario is that the WAP can only provide preauthentication and backend authentication to non-claims aware applications published with Integrated Windows Authentication (IWA) using Kerberos. No IWA with NTLM or basic authentication support. NTLM and basic are supported in Pass-through mode only. So this is not a full UAG/TMG replacement for applications such as Exchange that typically would have preauthentication performed by the reverse proxy. Slide 9 from the Europe WCA-B333 session mentioned above, shows the preauthentication support.

In my testing though, it wasn’t as simple as this as NTLM and Basic did not work for rich clients – even in pass-through mode (Update: for ActiveSync this is fixed by adding a default SSL binding as ActiveSync does not support SNI) . Slide 40 on the same presentation states that UAG will still be supported until 2015. Additionally it says that the Web Application Proxy is ‘part of a broader BYO Access platform alongside AD, AD FS, Intune’ and ‘We are not building all of UAG functionality into Windows Server’, so it looks like Web Application Proxy may not be a total replacement for UAG.

Exchange 2013 Testing

There is very little documentation right now for the Web Application Proxy so the following are my observations and assumptions based on testing Windows Server 2012 R2 Preview in Windows Azure. That meant I could only have a single network card (a restriction of Azure) and I don’t know if that would change any functionality (as it would for TMG and UAG).

ADFS and KCD Requirements

The first important thing to note is that any preauthentication or ADFS proxy functionality requires Windows Server 2012 R2 ADFS on the backend. So for anything other than Pass-through, ADFS is a hard dependency. It was mentioned that the recommendation now is to run ADFS on a domain controller and ADFS seems to be taking over as the primary authentication mechanism for all Windows applications. Additionally a schema upgrade will be required to support all of the device registration functionality.

The second important thing to know is that in order to do preauthentication to backend applications, the WAP must be domain joined as Kerberos Constrained Delegation (KCD) is required. In the past ADFS proxies were not domain joined and the WAP can act the same way as long as KCD is not needed.

Setup ADFS

Setup a Domain Controller and add the ADFS role. Add a ‘Non-Claims-Aware Relying Party Trust’ for the Exchange CAS or CAS Array as can be seen below.

For the Issuance Authorization Rules, either ‘Permit All Users’ or you can limit who can authenticate through ADFS (and the Web Application Proxy) using Active Directory attributes. In this example I have created an AD group that allows access through the WAP. In the Issuance Authorization Rules, specify ‘Permit or Deny Users Based on an Incoming Claim’, specify ‘Group SID’ and select the relevant group. This allow the full flexibility of ADFS claims rules which can include specifying IP subnets, whether people are coming through the ADFS Proxy and many more.

Setup Web Application Proxy

Join the computer to the domain and install the Remote Access Role, and the Web Application Proxy Role Service.

Import the ADFS public certificate and any other certificates required for publishing. As with Server 2012, 2012 R2 supports Server Name Indication (SNI) which was covered previously on the Kloud blog An Overview of Server Name Indication (SNI) and Creating an IIS SNI Web SSL Binding Using PowerShell in Windows Server 2012. This allows multiple certificates to be used on a single IP and port combination, which is a great improvement over TMG and UAG.

Start the Web Application Proxy configuration wizard and setup the server as an ADFS proxy.

Publish an ‘Active Directory Federation Services (AD FS)’ application

Specify the Relying Party from the list of enabled Relying Parties configured on the on-premise domain joined ADFS farm server.

Setup the external and internal URLs and SPN for the internal web server. The publishing can either be a specific path or the whole server, however unlike TMG there is no ordering of applications so if you publish a whole server you cannot add a sub path as a higher rule. In this case I have published Exchange OWA and ECP as separate applications.

Note the warning at the top about the external and internal URLs not matching. That help topic is not live so I do not know what problems can arise, but this is probably for applications like SharePoint 2013 with Host Name Site Collections that require the external and internal URLs to match. And this is where you see the benefit of a modern reverse proxy – it shows you the PowerShell commands to create the application. Also make sure to add the trailing ‘/’ on both the external and internal URLs or it will complain.

For the ECP application I can create it with PowerShell:

Add-WebApplicationProxyApplication -BackendServerAuthenticationSpn 'http/marc-exchange1.marc.kloud.com.au' -BackendServerUrl 'https://marc-exchange1.marc.kloud.com.au/ecp/' -ExternalCertificateThumbprint 'F7F6B300FE4D569FD598E1C9722571CF9AD780DD' -ExternalUrl 'https://mail.marc.kloud.com.au/ecp/' -Name 'Exchange ECP' -ExternalPreAuthentication ADFS -ADFSRelyingPartyName 'Exchange'

The final task is to setup KCD to allow the WAP to impersonate the user and obtain a Kerberos ticket to access the Exchange CAS. This is done by editing the WAP computer object and allowing delegation to the Exchange CAS SPN, or CAS Array Alternate Service Account user or computer object. A PowerShell function for this is provided in my previous blog post Outlook Anywhere NTLM SSO with UAG 2010 KCD

Testing Web Clients

Browse to the OWA URL e.g. https://mail.marc.kloud.com.au/owa/ which redirects to

https://adfs.marc.kloud.com.au/adfs/ls?version=1.0&action=signin&realm=urn:AppProxy:com&appRealm=d2b07cee-c3e0-e211-af1e-00155d53e448&returnUrl=https://mail.marc.kloud.com.au/owa/?bO=1

Logging in with a domain user who is not a member of the ‘WAP Allowed’ group gives a friendly error:

While a user who is a member of the group gets OWA

Testing Rich Clients

In order to test Outlook Anywhere and ActiveSync rich clients, instead of creating separate applications for RPC, OAB, Autodiscover and Microsoft-Server-ActiveSync applications I published the whole server https://mail.marc.kloud.com.au/ as a test. This meant that Outlook couldn’t even get autodiscover as it doesn’t understand ADFS authentication. So back to separate applications and added autodiscover and RPC for Outlook Anywhere as Pass-through applications

This allowed a new Outlook profile autodiscover to work but Outlook just hangs on the ‘Logging on to the mail server’ for a few minutes and fails to connect.

I tried to change the Exchange internal and external URLs to be same to make sure the lack of body rewriting in WAP wasn’t causing the problem. I published the root of the server https://mail.marc.kloud.com.au/ and autodiscover https://autodiscover.marc.kloud.com.au/ in Pass-through mode but still could not get Outlook Anywhere or ActiveSync to work initially (Update: ActiveSync does not support SNI, so in order to make it work a default SSL binding must be added as mentioned in my post about Lync Publish Lync 2013 with 2012 R2 Preview Web Application Proxy). Possibly this is something in my configuration as according to the slide above, NTLM and Basic authentication should just work in Pass-through mode. If somebody else gets Outlook Anywhere to work, please let me know. (Update: according to a member of the Web Application Proxy product group there is a bug in the Preview version that prevents Outlook Anywhere from working. They say it will be fixed in the RTM version).

Testing Claims Aware Applications

Just to round out the testing, I used the WIF SDK 3.5 sample claims app ‘PassiveRedirectBasedClaimsAwareWebApp’ and configured it on a Windows 2012 R2 server using the process described in the ADFS and SSO lab setup guide. Create the Relying Party Trust as described in the documentation.

On the Web Application Proxy, add an ADFS preauthenticated application based on the ‘Claims App’ RP and configure the external and internal URLs

The end result is the published application:

WAP applications

Logging on from an external client shows the claims that the client has in the ADFS security token.

Opening other ADFS preauthenticated applications provides SSO as the client has a valid ADFS cookie and a valid Web Application Proxy cookie.

My Thoughts

This is a preview release that is probably 6 months out from final release, so many things may change before RTM. With very little documentation I am guessing my way through some of the scenarios and configuration, so don’t take this a final analysis of the product.

The Good

  • Single server for ADFS Proxy, Reverse Proxy, DirectAccess and VPN
  • Totally PowerShell enabled and integrated into Windows
  • Server Name Indication (SNI) support to provide a single ‘listener’ with multiple certificates
  • Not based on IIS
  • Stateless server with centralised ADFS configuration
  • Provides new solutions like Workplace Join to enable work from anywhere and long term persistent SSO for authenticated and authorised devices
  • Multi Factor Authentication (MFA) looks easy to define, but I couldn’t test it now

The Different

  • Windows 2012 R2 Preview ADFS farm is required to install Web Application Proxy
  • Cannot be an ADFS proxy for a previous ADFS version
  • Non-SNI compatible clients like Lync mobile and Exchange ActiveSync require additional configuration on the Web Application Proxy to support them
  • Case sensitive and requires trailing / for path based application publishing. https://mail.marc.kloud.com.au/owa/ does not equal https://mail.marc.kloud.com.au/OWA/ This isn’t an issue if you publish the root of the server. This is consistent with the web standards and Unix/Linux web servers that are case sensitive. It is a departure from IIS and TMG which do not care about case

The Bad aka ‘Hopefully will be added or fixed’

  • No Basic or NTLM preauthentication
  • Hard requirement on ADFS, even if only doing Pass-through
  • Pass-through not allowing Outlook Anywhere to work – may just be me!
  • No SSO for non-ADFS published applications. Pass-through applications each do their own authentication
  • No ability to edit published application on the WAP server in the GUI. This can be done from PowerShell only. See http://technet.microsoft.com/en-us/library/dn283404
  • No external HTTP to HTTPS redirect, or HTTP publishing
  • Descoped the full URL translation like IIS ARR can do in this release. Header translation only, not body translation
  • Exchange Outlook Anywhere is not supported with preauthentication. Planned to support in a future version
  • No built in outbound load balancing to the backend servers so it requires a load balancer. TMG and UAG can provide this currently even if it isn’t used much. This support would remove a barrier to entry for some smaller companies

I would also like to see the ability in the console and WAP PowerShell cmdlets to set a default SSL binding certificate for non-SNI supported browsers so it doesn’t have to be done via netsh.

Outlook Anywhere NTLM SSO with UAG 2010 KCD

Outlook Anywhere can be configured with two authentication methods – Basic and NTLM. Outlook Anywhere NTLM authentication has always been a bit of a tricky beast when using a pre-authenticating reverse proxy like TMG or UAG. The benefit it can bring is that a user signed on to a domain joined computer with a domain account can get seamless SSO (Single Sign On) without entering a password. This can happen if the user is on the corporate network or if they are remote using Outlook Anywhere (RPC over HTTP) and cached credentials. This sounds like a great solution but the reality is that it often causes more problems than it is worth when using TMG or UAG as they have to use Kerberos Constrained Delegation (KCD) to connect to Exchange.

This post will cover a bit of background around NTLM Outlook Anywhere and KCD and a seemingly hidden configuration change you need to make to successfully use Outlook Anywhere NTLM authentication with UAG and a hardware load balancer. I could not find this referenced anywhere else, so am hopeful that it will save somebody a lot of troubleshooting time. Bear with me as this post gets a little detailed. If you are impatient (and we have all been in that situation where we only care about the solution) then jump to the fix.

Symptoms

On a recent customer project, we had a UAG array that needed to connect to a Hardware Load Balancer (HLB) Virtual IP address (VIP) which load balanced an Exchange Client Access Server (CAS) array. The experience for remote Outlook clients using Outlook Anywhere is they keep getting authentication prompts when trying to connect or create a new Outlook profile. Putting in the correct user name and password will just prompt again and again.

Publishing Outlook Anywhere with NTLM

A white paper by Greg Taylor from the Exchange Product Group was released by Microsoft in January 2011 Publishing Outlook Anywhere Using NTLM Authentication With Forefront TMG or Forefront UAG which covered the steps for TMG and UAG to enable NTLM Outlook Anywhere authentication. This configuration guide covered a lot of the issues people encountered and worked fine if you used a single Exchange Client Access Server (CAS) or wanted UAG to perform the load balancing with a server farm.

Adding a Hardware Load Balancer to the Solution

The problem comes when trying to use a Hardware Load Balancer (HLB) between UAG and the CAS Array. As UAG will only connect to a single published name that is configured on the ‘Addresses’ section of the ‘Web Servers’ tab and will be using KCD, Exchange needs to have a Service Principal Name (SPN) for the published name and UAG must be allowed to delegate authentication. However an SPN should not exist twice in an Active Directory forest, so the CAS array members cannot all have the SPN added. If UAG tries to connect to the server using the SPN, authentication will fail. This was covered in detail in an Exchange Team Blog Combining Web Farm publishing with Software or Hardware Based Load Balanced CAS arrays.

Alternate Service Account

A solution for CAS Array Kerberos authentication was introduced with Exchange 2010 Service Pack 1 in the form of the Alternate Service Account (ASA) which is covered in Configuring Kerberos Authentication for Load-Balanced Client Access Servers and Using Kerberos with a Client Access Server Array or a Load-Balancing Solution. All Client Access Servers use a shared computer account (or use account if required) via the Microsoft Exchange Service Host service and SPNs are added to this account to allow clients to authenticate to any CAS using the CAS Array FQDN. The steps to configure the ASA are:

  1. Create a computer account for the ASA
  2. Add the SPNs to the ASA
  3. Convert the Offline Address Book to an application using ConvertOABDir.ps1
  4. Deploy the ASA account to the Exchange CAS array members using RollAlternateserviceAccountPassword.ps1

The first link above states:

“External or Internet-based clients that use Outlook Anywhere won’t use Kerberos authentication. Therefore, the fully qualified domain names that are used by these clients don’t have to be added as SPNs to the ASA credential.”

This seems to be the root of the problem with Outlook Anywhere and UAG, as UAG needs to use Kerberos authentication to perform Kerberos Constrained Delegation (KCD).

KCD, S4U2Self and S4U2Proxy

In order for the client to authenticate to the Client Access Server via UAG, the high level flows are:

  1. UAG takes the user credentials in non-Kerberos form, for example Basic or NTLM
  2. UAG uses the Kerberos protocol transition extension S4U2Self (Service for User to Self) to obtain a Kerberos ticket on behalf of the user, as if the user had obtained it directly
  3. UAG uses the Kerberos Constrained Delegation (KCD) extension S4U2Proxy (Service for User to Proxy) to impersonate the user and obtain a Kerberos ticket to access the Exchange CAS
    1. The UAG server needs to be allowed to delegate authentication to the SPN Exchange uses
  4. The Exchange CAS responds to UAG as if the user had authenticated directly with Kerberos
  5. UAG sends the response back to the user device

More information on Kerberos Constrained Delegation and the S4U2Self and S4U2Proxy Kerberos extensions is available from:

http://msdn.microsoft.com/library/cc246071(PROT.13).aspx

http://technet.microsoft.com/en-US/library/cc738207(v=ws.10)

Usual Exchange and UAG Configuration

Get-OutlookAnywhere -server serverone | Set-OutlookAnywhere -ClientAuthenticationMethod NTLM
  • Add SPNs for additional UAG published names if you are using custom ports as mentioned in my previous blog about custom port redirect problems.
setspn -s http/exchange.domain.com CAS-ASA$
  • Configure UAG Outlook Anywhere application for KCD as per the White Paper above
  • Configure Active Directory to allow UAG to use KCD to the ASA computer account SPNs that are published by UAG. Rather than exporting the LDIF file from UAG, you can run the following script to setup delegation where ‘exchange.domain.com’ is what is entered on the UAG application ‘Addresses’ tab
$SPNs = "http/exchange.domain.com","http/exchange"
$UAGservers = "UAG01","UAG02","UAG03","UAG04"
Import-Module -Name ActiveDirectory
foreach ($UAGserver in $UAGservers) {
    $TempComputer = $null
    $Tempcomputer = Get-ADComputer $UAGserver
    Set-ADObject $Tempcomputer.DistinguishedName -Add @{"msDS-AllowedToDelegateTo" = $SPNs}
    Set-ADObject $Tempcomputer.DistinguishedName -Replace @{"userAccountControl" = 16781312}
}

At this point you will have done everything mentioned for Exchange and UAG in the Microsoft documentation but it will not work and Outlook users will keep getting prompted for credentials.

Cause

Outlook Anywhere uses the RPC application in IIS which uses the DefaultAppPool Application Pool.

The DefaultAppPool is different to all of the Exchange specific application pools as it uses the ApplicationPoolIdentity identity, not LocalSystem as can be seen below.

As it is not using LocalSystem this application pool cannot use the Exchange Alternate Service Account as it does not have access to the Exchange Service Host SPNs. That means UAG cannot delegate authentication and Kerberos Constrained Delegation will not work. KCD will not fall back to a lower authentication mechanism on authentication failure.

How to Make It Work

The fix is pretty simple – modify DefaultAppPool to LocalSystem or create a new application pool that uses LocalSystem. The first option is not recommended as there are other applications using the DefaultAppPool and doing so may give more rights than are required. Creating a new application pool is possible in the IIS console, but the DefaultAppPool has quite a few non-standard settings. So the easiest way is to use the IIS PowerShell module to copy the application pool. This was my first attempt at using the IIS PowerShell module and I could not get it consistently copy the DefaultAppPool across multiple Exchange versions and Windows patch levels. In the script below there is a dirty hack that creates the application pool, deletes it and then copy. Without the precreation I would get the error ‘Copy-Item : Object reference not set to an instance of an object’ when trying to copy. Note the iisreset at the end – this will disconnect users so plan ahead.

Import-Module webadministration
### Need to stop DefaultAppPool and precreate the new app pool to get it to work consistently
Stop-WebAppPool DefaultAppPool
New-WebAppPool MSExchangeRpcAppPool
Remove-WebAppPool MSExchangeRpcAppPool
copy IIS:\AppPools\DefaultAppPool IIS:\AppPools\MSExchangeRpcAppPool
Start-WebAppPool DefaultAppPool
### Modify new app pool to LocalSystem
$RpcAppPool = dir IIS:\AppPools | where {$_.name -eq "MSExchangeRpcAppPool"}
$RpcAppPool.processModel.identityType = "LocalSystem"
$RpcAppPool | set-item
### Change RPC applications to use the new app pool
set-itemproperty "IIS:\Sites\Default Web Site\Rpc" ApplicationPool MSExchangeRpcAppPool
set-itemproperty "IIS:\Sites\Default Web Site\RpcWithCert" ApplicationPool MSExchangeRpcAppPool
iisreset

You end up with a new application pool using LocalSystem that only contains the RPC applications.

This process has been tested and works with Exchange 2010 Service Pack 2 and Service Pack 3 on Windows 2008 R2. Exchange 2013 and Windows 2012 have not been validated.

Bonus Information – KCD and Trusts

Attempting to work out if Outlook Anywhere NTLM with UAG and KCD was supported with a trust sent us down many conflicting documents and references. This UAG page in particular made me think it would not work Configuring single sign-on with Kerberos constrained delegation when it says:

“The following are the requirements for Kerberos constrained delegation:

  • The Forefront UAG server must be part of a domain.
  • You must define only one authentication server for the trunk to which the application belongs.
  • All domain controllers in the internal network must be running Windows Server 2003.
  • Users must be part of the same Active Directory forest as the Forefront UAG server and the application servers.
  • Forefront UAG servers and application servers must be part of the same domain”

However Summary (Kerberos Protocol Transition and Constrained Delegation) says:

“The accounts of users accessing the services do not have to be in the same domain as the services”

It wasn’t clear if that meant it could be in a different domain in the same forest, or if it could cross forest boundaries. Many other documents and articles confused the issue and I could not find a nice concise link stating it would work. This is probably the closest but it is for ISA and IAG (the predecessors of TMG and UAG) KCD with Cross-Forest Accounts.

The bottom line is that it does work – users from trusted forest A can access their linked mailboxes from Outlook Anywhere via UAG in forest B and get seamless SSO.

For KCD to work, UAG and the resource (Exchange in this case) have to be in the same domain. For S4U2Self protocol transition to work, there must be a two way forest trust (which means Windows 2003 Forest functional level or later) and the UAG servers need to be able to resolve and connect directly to the trusted forest Active Directory Domain Contollers for authenticating end users. In our situation we had to add an additional domain suffix to the UAG server internal network interfaces and open up firewall ports.

UAG 2010 – Problems with Custom Trunk Ports and Failing Redirects

UAG 2010 prior to Service Pack 1 Update 1 did not support publishing trunks on custom ports – only 80 and 443 were supported. That meant each UAG trunk required a separate IP address per trunk. With SP 1 Update we could publish UAG trunks on custom ports on a single IP address, although it doesn’t seem many people actually did this. For a customer recently where UAG 2010 was required with 5 trunks, there was an existing network architecture restriction that required the UAG servers to use public IP addresses. We needed high availability and using the existing Hardware Load Balancing (HLB) solution meant 15 public IP addresses in a default 1 trunk per IP configuration – 5 per UAG server and 5 HLB virtual IP addresses.

5 trunks were required to meet a combination of different domain names, certificates), different authentication mechanisms, different access methods and authorisation requirements. We could not give the UAG servers private IP addresses for the External interfaces, so the compromise was to publish each trunk on the same public IP address using a custom port. The HLB would publish the trunks on port 443 and connect to UAG on the relevant custom port. This meant 7 public IP addresses used (5 x HLB and 2 x UAG servers) which was the best we could do.

Environment Overview

UAG custom ports 3

The diagram above (click for a larger version) shows a high level overview with dummy trunk names and IP addresses showing that each HLB VIP is a different IP address and each UAG trunk is on the same address using a custom port.

Trunk 3 is a non-authenticated trunk for Lync 2013 referenced in my previous blog post Publish Lync 2013 Including Mobility and Office Web Apps with UAG 2010

Trunks 1, 2, 4 and 5 all publish the same applications for Exchange 2010, SharePoint 2013 and FIM 2010 on different domain names and from different locations.

The table below has more detail with dummy trunk and published names.

Trunk Name HLB Port UAG Port Trunk FQDN Public FQDN Internal host name (addresses) Network Access Pre-authentication
Trunk1 1.1.1.1:443 2.1.1.1:443 trunk1.showcase.kloud.com.au mail.showcase.kloud.com.au mail.showcase.kloud.com.au Internet Yes
Trunk2 1.1.1.2:443 2.1.1.1:2443 trunk2.showcase.kloud.com.au mail.showcase.kloud.com.au mail.showcase.kloud.com.au MPLS Cloud Yes
Trunk3 1.1.1.3:443 2.1.1.1:3443 trunk3.showcase.kloud.com.au lyncws.showcase.kloud.com.au lyncws.showcase.kloud.com.au Internet and MPLS Cloud No
Trunk4 1.1.1.4:443 2.1.1.1:4443 trunk4.showcase.kloud.com.au mail.contoso.com mail.showcase.kloud.com.au Internet Yes
Trunk5 1.1.1.5:443 2.1.1.1:5443 trunk5.showcase.kloud.com.au mail.contoso.com mail.showcase.kloud.com.au MPLS Cloud Yes

Issues Encountered

With UAG 2010 Service Pack 3, all applications on trunk 1, 4 and 5 worked correctly (trunk 3 worked in all scenarios due to not doing authentication). The problem was that all applications on trunk 2 (except for SharePoint) would authenticate but not be able to connect to the application. Using Outlook Web App as an example, the client would:

  1. Browse to https://mail.showcase.kloud.com.au/owa
  2. be taken to the following for authentication https://mail.showcase.kloud.com.au/uniquesig0c4449dcaa9c2c7cb80a00574fc3ea78/uniquesig0/InternalSite/OWA/Login.asp?resource_id=5C0BCBBECE4E49BDA6859D1CD5DDD09C&login_type=2&site_name=trunk1&secure=1&URLHASH=7fc49ee2-2e27-4518-8fb8-b54b0ff77b87&orig_url=https%3a%2f%2fmail.showcase.kloud.com.au%2fowa
  3. After authentication the client browser would appear to be stuck on a validation page https://mail.showcase.kloud.com.au/uniquesig0c4449dcaa9c2c7cb80a00574fc3ea78/uniquesig0/InternalSite/Validate.asp
  4. After about 40 seconds the connection would timeout with the following in the address bar https://mail.showcase.kloud.com.au/uniquesig0c4449dcaa9c2c7cb80a00574fc3ea78/uniquesig0/InternalSite/RedirectToOrigURL.asp?site_name=trunk1&secure=1

If I went to the original URL again https://mail.showcase.kloud.com.au/owa I could get to the Outlook Web App page without having to authenticate. Sometimes I needed to go to the original URL 2 or 3 times for it to work.

Troubleshooting

UAG 2010 Service Pack 3 tracing symbols had not been released at the time of troubleshooting (they were released on 30 April) so I rolled back to Service Pack 2. With Service Pack 2, only trunk 1 worked correctly so that was making the situation worse. The UAG trace logs were massive and did not get me any closer to a solution.

Fiddler and HttpWatch showed the same thing, that after the validate.asp page it was trying to:

  1. Redirect to https://mail.showcase.kloud.com.au/uniquesig0c4449dcaa9c2c7cb80a00574fc3ea78/uniquesig0/InternalSite/RedirectToOrigURL.asp?site_name=trunk1&secure=1
  2. and then to one of the following addresses depending on which Service Pack I had
    1. https://trunk2.showcase.kloud.com.au:2443/uniquesigacde95fbf87ff4ca921a1aca1d5b6d510f79207ec5f596b4b80c06329b09f2591329bdfadfac0bdd33fa95f57bf2ef36/uniquesig1/owa/
    2. https://mail.showcase.kloud.com.au:2443/owa

which contained the trunk name and custom port or the published name with a custom port. A sample of the logon experience from HttpWatch is shown below (click for a larger version).

UAG_Custom_Port_5

Cause

After a lot of testing and troubleshooting I eventually raised a Microsoft PSS call. After a couple of days of testing, my very knowledgeable PSS engineer eventually worked out the cause of the issue:

  • UAG has a problem resolving the names internally when custom ports are in use and the ‘Public host name’ matches the ‘Addresses’ entry. In this example both are set to ‘mail.showcase.kloud.com.au’.

Trunk 1 worked as it was using the default port 443. Trunk 4 and 5 worked as the ‘Public host name’ ‘mail.contoso.com’ was different to the ‘Addresses’ published name ‘mail.showcase.kloud.com.au’. It turns out UAG 2010 Service Pack 2 has another bug with the custom port publishing which is why only trunk 1 worked when using SP 2.

Resolution

Using SP3, the solution for trunk 2 was to change the ‘Addresses’ name UAG connected to the internal servers on to another name e.g. ‘mail-internal.showcase.kloud.com’. This caused another issue – a new name was required on the certificate used by the internal web servers. The internal certificate had to be regenerated with the additional FQDN added to the Subject Alternate Names and was applied to all Exchange Client Access Servers. For FIM we thought we could get away with changing ‘Replace the host header with the following’ as you see below (which is not an option on the OWA application template) but encountered what seems like another bug where UAG does not pass the host header, but rather uses the original ‘Addresses’ host name. So FIM needed a SAN added to the certificate too.

SharePoint worked in all scenarios so I can only assume it is because of the AAM (Alternate Access Mapping) already catering for the different names being published and the host header worked as expected.

Summary

The publishing scenario used here is probably an edge case. Most environments would use private IP addresses and not need custom ports so would not come across this issue. Others using custom ports may have different internal and external DNS namespaces, like mail.showcase.kloud.com.au externally and mail.kloud.local internally which would not hit this issue. I seem to have hit the perfect set of requirements for causing problems. Maybe some karmic payback – although just having to work with UAG is punishment for something. I miss the relative simplicity and flexibility of TMG like being able to publish multiple domain names on a single publishing rule!

Even though others will probably not see this problem, it is good to be aware that there are some issues with custom ports, using the same name internally and externally and that host headers do not always work as expected.

The next blog will cover how to configure and publish Outlook Anywhere NTLM authentication with UAG to provide seamless Single Sign On from domain joined computers.

Publish Lync 2013 Including Mobility and Office Web Apps with UAG 2010

Microsoft Forefront TMG (Threat Management Gateway) has been the primary way Lync Web Services have been published in the past. With the untimely demise of TMG, the only Microsoft product (other than IIS) with reverse proxy functionality is Microsoft Forefront UAG (Unified Access Gateway). TMG will continue to be supported until 2015 for mainstream support and 2020 for extended support. If TMG is not already installed however, technically it cannot be used for a new installation.

In this blog I will explain how to setup UAG 2010 Service Pack 3 to publish all Lync 2013 reverse proxy features including mobility (for Lync mobile clients) and Office Web Apps. But first a little background unless you want to jump straight to UAG 2010 Configuration.

IIS ARR (Application Request Routing)

According to this NextHop blog Using IIS ARR as a Reverse Proxy for Lync Server 2013, IIS ARR is the only supported Microsoft solution for customers who do not already own TMG. A couple of things to note is that IIS ARR cannot do pre-authentication (not supported for Lync anyway) and there does not seem to be any sizing or scaling documentation out yet.

So IIS ARR looks like a solution if you just publish Lync, or if pre-authentication is not important to you – and this seems to be the way most Microsoft products like Exchange and SharePoint are going.

A recent customer engagement had a requirement for pre-authentication and authorisation (allowing only members of a specific group access to Outlook Web App for example) for Exchange 2010, SharePoint 2013 and FIM 2010. Additionally Lync 2013 required no pre-authentication or authorisation. TMG had been planned for and following the end of life statement above, UAG 2010 was chosen. IIS ARR did not meet all of the requirements.

UAG 2010 – No Official Support For Lync

It is important to note up front that Lync 2013 is not officially supported on UAG 2010. UAG 2010 Service Pack 3 provided support for publishing Exchange 2013 and SharePoint 2013, but Lync was noticeably absent. Additionally, Office Web Apps Server 2013 is not specified as supported.

What’s new in Forefront UAG Service Pack 1 Update 1 added support for publishing Lync 2010 Lync Web App (and SharePoint 2010 Office Web Apps) and lists some known issues including ‘Forefront UAG does not support Lync Mobility scenarios’. More information at Publishing Lync web services

Ben Ari from the Microsoft UAG CSS team (who publicly seems to know more about UAG than anybody else) wrote a clarification blog UAG Lync Mobility and other Lync clients stating:

UAG does not support accessing Lync using Lync Mobility on any platform, nor the use of the Lync software client. The only thing that IS supported is using the Web-based version of the Lync client. For customers who need to publish Lync for Mobility and the software client, Microsoft recommends publishing the Lync Edge server using TMG, or a comparable transparent-publishing firewall solution (note, though, that using the TMG server that’s on your UAG server is not supported for this purpose)”

At that time, Lync 2013 had reached RTM but not General Availability so I assume this references Lync 2010. Even the official TechNet article on Lync 2013 Reverse Proxy configuration only lists TMG and ISA as the Microsoft options.

Investigating using UAG 2010 to Publish Lync 2013

Bearing in mind the lack of support from Microsoft, our project decided to try using UAG to publish all Lync 2013 web services including mobility and Office Web Apps and see how far we got. Looking into publishing Lync on UAG 2010 I found three main articles which pointed me in the right direction:

Lync publishing on UAG

Publish Lync 2010 with ForeFront Unified Access Gateway 2010 (UAG)

UAG as a Lync reverse proxy

The blogs above cover a lot of the steps required for Lync Web App and mobility, but the full list of reverse proxy functions we required were:

  • Meeting join
  • Dialin page
  • Lync Web App
  • Lync 2013 and Lync 2010 mobile clients
  • Lync Windows Store App (Lync MX)
  • Lync autodiscover
  • Persistent Chat administration
  • Lync client Address book web search
  • Lync client Address book group expansion
  • Office Web Apps upload and presentation from Lync client and Lync Web App

UAG 2010 Configuration

I won’t cover UAG initial configuration and certificates here, except to say that you need a UC Certificate from a public certificate authority with SAN names containing the FQDNs of:

  • Lync trunk public host name
  • Lync External Web Services
  • Lync Meet Simple URL
  • Lync Dialin Simple URL
  • Lyncdiscover for each SIP domain
  • Office Web Apps farm external URL

Create Trunk

The first step to publish Lync with UAG 2010 is to create a new trunk. This has to be a separate trunk to the one used for Exchange or other applications, as the Lync trunk cannot do any authentication on UAG – all authentication will be handled by the Lync Front End servers. Create a new HTTPS portal trunk and run through the wizard. Note that the trunk ‘Public host name’ must be a different name to any of the applications you are intending on publishing as we will be publishing whole domain names ‘/*’ and not just specific paths. Add authentication servers now and they will be disabled after the wizard completes.

Edit Trunk Authentication

Configure the trunk and on the ‘Authentication’ tab, clear ‘Require users to authenticate at session logon’

On the ‘Session’ tab, enable ‘Disable component installation and activation’ and ‘Disable scripting for portal applications’ to allow non supported browsers to connect. Increase the ‘Inactive session timeout (seconds)’ from the default 5 minutes to something closer to the 3 day (259200 seconds) ‘SessionExpirationInterval’ default for Lync 2013 as documented in Set-CsMcxConfiguration

Enable authentication pass through. UAG 2010 SP 3 defaults to having ‘FullAuthPassthru’ set to 1 in ‘HKEY_LOCAL_MACHINE\SOFTWARE\WhaleCom\e-Gap\von\UrlFilter’. For Lync to function we need another 32-bit DWORD key ‘KeepClientAuthHeader’ with a value of ‘1’ in the same location to retain the authentication headers.

Activate the trunk and go get a coffee while you wait for the activation process to complete and TMG storage to synchronise across all UAG array members.

Create Applications

Web Services

Create an application called ‘Lync 2013 Web Services’ using the ‘Microsoft Lync Web App 2010’ template. Follow the wizard and ensure you leave the path as ‘/’ and destination HTTPS port as ‘4443’. Uncheck ‘Use SSO’ and ‘Add portal and toolbar link’ and leave ‘Authorize all users’ enabled.

Three applications will be created as you can see below.

With the default configuration of Web Services, errors are posted in the UAG Web Monitor:

“A request for application Lync Web Services of type Lync2010 on trunk lync; Secure=1 failed because a POST action without a content-type header is not allowed. The URL is /WebTicket/WebTicketService.svc”

“A request for application Lync Web Services of type Lync2010 on trunk lync; Secure=1 failed because a POST action without a content-type header is not allowed. The URL is /ucwa/v1/applications/211401975557/me/reportMyActivity”

To allow Lync Mobile clients to authenticate to the Webticket service and update presence, edit the ‘Lync 2013 Web Services’ application and on the ‘Web Settings’ tab enable ‘Allow POST requests without a content-type header’.

Edit the meet and dialin applications and ensure ‘Addresses’ and ‘Public Host Name’ on the ‘Web Services’ tab are correct if you use different internal and external domain names. For readability I rename them to ‘Lync 2013 Meet’ and ‘Lync 2013 Dialin’.

Lyncdiscover

Create an application called ‘Lync 2013 Lyncdiscover’ using the ‘Microsoft Lync Web App 2010’ template with ‘Public Host Name’ ‘lyncdiscover.<sipdomain>’ and the same settings as the Lync Web Services application. Once the wizard is complete, remove the ‘Lync 2013 Lyncdiscover – Meet’ and ‘Lync 2013 Lyncdiscover – Dialin’ applications created by the rule.

Office Web Apps Server

Create an application called ‘Office Web Apps 2013’ using the ‘Microsoft Office SharePoint Server 2013’ template. Public host name should be the ExternalURL of the Office Web Apps Server Farm. Change the published port to ‘HTTPS’ port ‘443’ as it defaults to HTTP. As with the others, uncheck ‘Use SSO’ and ‘Add portal and toolbar link’ and leave ‘Authorize all users’ enabled.

Final Configuration

You should end up with an application list similar to this:

The final step is to edit the trunk to allow Lync Web App to upload and present presentations using Office Web App Server. The default configuration denies PUT requests for UCWA (Unified Communications Web API) and you receive ‘Invalid Method’ warning messages stating:

“A request from source IP address x.x.x.x, user on trunk lync; Secure=1 for application Lync Web Services of type Lync2010 failed because the method used PUT is not valid for requested URL /ucwa/v1/applications/111597367048/communication”

Configure the trunk settings and on the ‘URL Set’ tab, click ‘Add Primary’. Enter the following details:

Name: Lync2010_UCWA

Action: Accept

URL: /ucwa/.*

Parameters: Ignore

Methods: PUT, POST, GET

Close the trunk settings and activate the UAG configuration.

Lync_UAG_8_ucwa_v2

Testing

Considering the stated lack of support for Lync 2013 and specifically Lync Mobility, surprisingly all of the functions mentioned above worked without a hitch after the ‘POST without a content-type header’ and UCWA PUT issues were resolved. Lync 2013 and Lync 2010 mobile clients on Windows Phone, iOS and Android could sign in. Lync Windows Store App connects. Lync Web App worked beautifully including audio, video and content sharing. The Lync client could search the address book, join meetings and users could get dialin information and create Persistent Chat rooms. From our testing, everything is working as expected. I keep waiting for something to fail, but so far all is good.

I have been doing a lot of work with UAG lately and run into a lot of bugs and problems, so I think this may be the first of a few UAG blogs.

Edit 15 May 2013 – updated UCWA path.