Saturday, September 1, 2007

Samples: Domain Redirect

From the top of my head, I see at least two scenarios where it is required that all http requests are done to a specific domain (versus the NETBIOS machine name): third party SSO requirements, Kerberos over HTTP authentication (SPNEGO).

Most SSO solutions out there for HTTP rely on setting a domain cookie on clients computer. As an example suppose all your boxes (Windows or Linux, doesn't matter) in DNS are named xxx.company.com. Also assume you have a third party SSO solution like CA SiteMinder (formerly Netegrity) setup in your environment. This solution, after authenticating, will set a domain cookie for company.com in your computer. For every subsequent request you do to any of the machines in your environment (xxx.company.com) the cookie is sent as part of the request. That cookie, after examined and validated, will determine if you have already logged in to the environment and will determine who you are.

As for Kerberos over HTTP, its required that the SPN (target principal) be registered in AD and in DNS. So, to make sure that everyone sends a well-formed request to every box for Kerberos authentication (instead of NTLM) its useful to automate the process of checking if the hostname of the request is expected and if it is not, redirect the browser to the proper hostname.

Ok, done with the formalities. Lets get to the code to make this work:



using System;
using System.Collections.Generic;
using System.Text;
using KodeIT.Web;

namespace FilterDotNet.Samples.DomainRedirect
{
public class Filter : IHttpFilter
{
internal const string SSO_DOMAIN = ".company.com";

void IHttpFilter.Init(IFilterEvents events)
{
events.PreProcHeaders += new EventHandler(OnPreProcHeaders);
}

void IHttpFilter.Dispose()
{
}

void OnPreProcHeaders(object sender, PreProcHeadersEventArgs e)
{
//
// If the host was empty, or not sent at all,
// let the request continue its path.
//
// If the host contains a dot (.) it may indicate
// an IP address was used (troubleshooting for ex)
// or a valid host header was used.
//

string hostName = e.Context.Headers[HttpHeader.Host];
if (String.IsNullOrEmpty(hostName) hostName.Contains("."))
{
return;
}

//
// If the request is being done locally on the box,
// let the request continue its path.
//

hostName = hostName.ToLower();
if (hostName.Equals("localhost") hostName.StartsWith("localhost:"))
{
return;
}

//
// Getting here means we do have to redirect this request
//

int colonIndex = hostName.IndexOf(':');
string portNumber = "80";
if (-1 != colonIndex)
{
portNumber = hostName.Substring(colonIndex + 1);
hostName = hostName.Substring(0, colonIndex);
}

string redirectResponse = String.Format(
"HTTP/1.1 301 Moved Permanently\r\n" +
"Connection: close\r\n" +
"Location: {0}://{1}{2}:{3}{4}\r\n" +
"\r\n",
e.Context.IsSecureConnection ? "https" : "http",
hostName,
SSO_DOMAIN,
portNumber,
e.Context.Url);

e.Context.WriteClient(ASCIIEncoding.ASCII.GetBytes(redirectResponse));
e.Context.TerminateRequest(false);
}
}
}


Its easy to see that the redirection only takes place if it was not made from the localhost and also if no IP address was used to reach the web server. Obviously, more can be done here, like injecting request headers with important information like the machine's real name, any environment variables you see useful, and finally setting the target domain as a configured element.

No comments: