Blog

Home > Blog

Posts Tagged ‘IIS’

Securing your WSDL

Monday, October 4th, 2010

This is a short follow-up to my previous post on Securing Web Services with Sitecore Authentication Providers in 5 easy steps.

ASP.Net helpfully generates documentation pages for your web services automatically.  This is the HTML that is served when you browse to an asmx file.  It gives you a list of all the methods connected to the web service.  Clicking into any one of these methods provides information on the parameters and return values, as well as the request methods that are allowed, generally SOAP and HTTP POST, but sometimes also HTTP GET.  It also provides a link to the auto-generated WSDL file with a complete list of types, properties and method signatures provided by your web service.

That’s great if you’re creating a web service designed to be consumed by the public, but a bit too much information to publish on your corporate internet site.  At least that’s what I think.  It’s more than enough information for a potential attacker to craft a well though out attack on your site and blind-side you.  Frankly it’s the equivalent of hiding the key under the front door mat and leaving a note with directions to the key posted on the door.

The Broadsword Way

Using the web.config file it’s possible to disable all documentation functions for all web services simultaneously. Noice.

<configuration>
  <system.web>
    <webServices>
      <protocols>
        <remove name=”Documentation” />
      </protocols>
      <wsdlHelpGenerator href=”DefaultSdlHelpGenerator.aspx”/>
    </webServices>
  </system.web>
</configuration>

This way you just get a “Request format is unrecognised” error.

The Scalpel Way

It’s possible you don’t want to nuke everything.  That’s OK.

You can replace the standard auto-generated help pages without affecting the WSDL file by leaving the Documentation protocol alone and specifying a custom help page like so:

<configuration>
  <system.web>
    <webServices>
      <wsdlHelpGenerator href=”WSHelpPage.aspx”/>
    </webServices>
  </system.web>
</configuration>

Note that the href is a file path and not a URL.  Also you can use absolute or relative paths, but if you specify a relative path then it should be relative to the web.config file.

You can also replace the WSDL file but that’s a bit harder.

In your asmx class file, add one or more WebServiceBindingAttribute decorations to the class.  This allows you to arbitrarily define multiple sets of metadata for use with your web service.  So, for example, you could have a different WSDL file for each of several namespaces, or you could split the methods in the class across several namespaces.

You can set the Name arbitrarily as it’s just an internal reference.  Set the Location property to the URL of your WSDL file.  This can be relative or absolute and if relative is in relation to the asmx file.

On each method you can then specify a SoapDocumentationMethodAttribute passing in the name of a binding you set earlier. This tells the system which method should be emitted (and documented) against which binding.  Noice.

So by way of example straight from MSDN:

using System.Web.Services;
using System.Web.Services.Protocols;

// Binding is defined in this XML Web service and uses the default namespace.
[ WebServiceBinding(Name="LocalBinding")]

// Binding is defined in this XML Web service, but it is not a part of the default namespace.
[ WebServiceBinding(Name="RemoteBinding", Namespace="http://foo.com/MyBinding", Location=”MyWSDL.aspx” )]
public class MyWebServices()
{

    [WebMethod]
    [SoapDocumentationMethod(Binding=”LocalBinding”)]
    public string HelloWorldInternal() { return “Hello world, internally!”; }

    [WebMethod]
    [SoapDocumentationMethod(Binding=”RemoteBinding”)]
    public string HelloWorldExternal() { return “Hello world, externally!”; }
}

Incidentally you can also use the SoapDocumentationMethodAttribute to specify element names in the request/response too. Noice.


No Comments

Securing Web Services with Sitecore Authentication Providers in 5 easy steps

Saturday, October 2nd, 2010

[Credits to http://keithelder.net/blog/]

During a complicated project involving several 3rd party system systems I discovered that I had a need to expose some Sitecore functionality to external applications.  The obvious model to go with was web services, since I could readily make use of the Sitecore context system to access and alter the environment.

In the many articles that I read on the subject none seemed to address the matter of security.  Not so much securing the web service from access by the outside world, but providing a properly authenticated Sitecore user for the Context object, rather than simply elevating privileges through the Sitecore.SecurityModel.SecurityDisabler class.

In may particular case I had a Windows application that would call into the Sitecore web service (a simple asmx file within the website) using the .Net 3.5 service reference model.  This required a number of fiddly configuration steps and I thought I would share these with the community.

This is my first Sitecore article so please bear with me if I have made some beginner errors or assumptions – feedback via comments is always welcome!

[
Edit
I was recently asked why anyone might bother securing their web services.  Good question.  The answer really depends on what your web service does.  For many people, web services will be used to perform complicated actions that will be referred to by several pages on a site.  They need to be public facing as they are often called by JavaScript directly from a client, so IPSec is no use, and they will often perform privileged functions such as creating a new user.

Let’s examine a plausible scenario.  You have a page that allows users to sign up to your site.  Posting the form calls a web service by AJAX to do the work of creating the user in Sitecore.  If I look at your AJAX code I will be able to see the URL of the web service.  From there I can query the web service’s WSDL file and discover all the methods you provide.  Some of them will do interesting things.  If I’m of malintent I could create 100,000,000,000 users in your system and attempt a denial-of-service.  Or much worse depending on what other methods your web service exposes.

You simply can’t rely on people not finding your file.  They will.  Google will.  A colleague once described it as “security through obscurity” and it’s a fool’s defence.

Incidentally you can prevent your web service from displaying the auto-generated WSDL (as you only need that for consumer web services once your site is live.  See here for more information on protecting your WSDL.
]

Step 1: Configure HTTPS

Sitecore authentication is performed with usernames and passwords being transmitted in the clear, a side-effect of Forms-based authentication.  It is therefore imperative that you configure https to ensure the credentials cannot be trapped in transit.

There are a million articles on configuring this for a production environment, but for a dev environment I’ll save you the trouble and describe them here.

  1. Create a self-signed certificate in IIS7 for the host-header that you are developing against
    • In IIS Manager navigate to the server root node
    • Select the Server Certificates feature
    • Click the action to Create Self-Signed Certificate
    • Name the certificate with the host header you are using, in my case that was localhost
  2. Enable the HTTPS binding on your IIS site
    • Navigate to the Site node for your site, in my case it was Default Web Site
    • Click the action to edit Bindings…
    • Add… a binding for HTTPS on port 443 and set the SSL certificate to the one you created before

You should now be able to access Sitecore using https, though you will likely get certificate errors from your browser because the certificate isn’t signed by a trusted authority.  Don’t worry about that for now, in production you will use a properly signed certificate, won’t you!

Step 2: Configure your Service Reference

I’m going to assume you already have a Visual Studio project to house the code that will call your web service (the caller).  I’m also going to assume you’ve already created your web service (the callee).  Mine is called HelloWorld().

  1. In your caller project, right-click the References node and select to Add Service Reference…
  2. Enter the url to your service in the Address box and hit Go
  3. From the tree on the left of the dialog you should now see your service and a SOAP interface that you can import – select it and give it a namespace and click OK

When you’ve done that you should see an app.config file appear in your project, or if you had one it now has some new content.  If you are in a web project you will likely have guessed to look in the web.config instead.  There is a <system.serviceModel> section in there.  This specifies binding and endpoint information for your auto-generated service classes.  There are a few things you will need to change – the important bits are below:

<bindings>
  <basicHttpBinding>
   <binding…>
     <security mode=”Transport”>
        <transport clientCredentialType=”None” proxyCredentialType=”None” realm=”" />
        <message clientCredentialType=”UserName” algorithmSuite=”Default” />
     </security>
   </binding>
  </basicHttpBinding>
</bindings>
<client>
  <endpoint address=”https://…” … />
</client>

Step 3: Creating the Web Service extensions

Ideally the integration of the authentication doesn’t interfere with the business logic of our methods.  We can use a series of attributes and supporting service extensions to help here.

SitecoreAuthenticationHeader – a custom header that will transport our credentials

using System.Web.Services.Protocols;
using SC = Sitecore;

public class SitecoreAuthenticationHeader : SoapHeader
{
        public string Domain;
        public string Username;
        public string Password;

        internal SitecoreAuthenticationHeader() { }
        public SitecoreAuthenticationHeader( string domain, string username, string password )
        {
            Domain = domain;
            Username = username;
            Password = password;
        }

        public static bool Validate( SitecoreAuthenticationHeader soapHeader )
        {
            try
            {
                return SC.Security.Authentication.AuthenticationManager.Login(
                        soapHeader.Domain + “\\” + soapHeader.Username,
                        soapHeader.Password,
                        false
                );
            }
            catch (Exception)
            {
                return false;
            }
        }
}

SitecoreAuthentication – a SoapExtension class that will detect the header and pass it on for validation

Note that this raises an exception when validation fails since there is no elegant way to return values with a declarative syntax.

using System.Web.Services.Protocols;
using System.Security.Authentication;

public class SitecoreAuthentication : SoapExtension
{
    public override object GetInitializer( Type serviceType ) { return null; }
    public override object GetInitializer( LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute ) { return null; }
    public override void Initialize( object initializer ) { }

    public override void ProcessMessage( SoapMessage message )
    {
        if ( message.Stage == SoapMessageStage.AfterDeserialize )
        {
            bool validated = false;
            foreach ( SoapHeader hdr in message.Headers )
            {
                if ( hdr is SitecoreAuthenticationHeader )
                {
                    try
                    {
                        if ( !( validated = SitecoreAuthenticationHeader.Validate( (SitecoreAuthenticationHeader)hdr ) ) )
                            throw new Exception();
                    }
                    catch { throw new AuthenticationException( “SOAP authentication failed. Invalid credentials specified.” ); }
                    break;
                }
            }

            if ( !validated ) throw new AuthenticationException( “SOAP authentication failed. Credentials not specified.” );
        }
    }
}

SitecoreAuthenticationAttribute – a SoapExtensionAttribute that tells the infrastructure to include the SitecoreAuthentication extension into the processing stack

using System.Web.Services.Protocols;

public class SitecoreAuthenticationAttribute : SoapExtensionAttribute
{
    private int _priority;

    public override int Priority
    {
        get { return _priority; }
        set { _priority = value; }
    }
    public override Type ExtensionType
    {
        get { return typeof( SitecoreAuthentication); }
    }
}

Step 4: Integrating Sitecore Authentication Declaratively

We need to add a field to the WebService class.  This will receive the header but importantly it will tell the framework that there is a header of a particular type that it needs to watch out for, which triggers the rest of the wiring.

using System.ServiceModel;

public class WebServiceAPI : System.Web.Services.WebService
{
    public SitecoreAuthenticationHeader AuthenticationHeader;

    [WebMethod]
    [SoapHeader("AuthenticationHeader"), SitecoreAuthentication]
    public string HelloWorld()
    {
        try
        {
            if ( !Sitecore.Context.User.IsAuthenticated )
                return “Goodbye cruel world!”;
            else
                return “Hello world”;
        } catch ( FaultException fe )
        {
            return fe.Message;
        }
    }
}

If you were paying attention you would also have noticed the two additional attributes on the WebMethod itself.

The SoapHeaderAttribute connects up the field and the header from the soap message, while the SitecoreAuthenticationAttribute provides the connection to the SitecoreAuthentication SoapExtension that will process the header and ultimately perform the validation.

The FaultException is raised by the framework and contains the message from our AuthenticationException as a subset of some other messages.

Step 5: Calling the web service

Here is some sample code for actually making a call to the HelloWorld web service method.  Note that I have already configured all the other stuff as described above.

public class TestClass
{
  private void _testAuthentication()
  {
    //disable certificate checking on the certificate for now
    System.Net.ServicePointManager.ServerCertificateValidationCallback = ( s, cert, chain, pError ) => true;

    //create an instance of the service class
    SitecoreService.WebServiceAPISoapClient c = new SitecoreService.WebServiceAPISoapClient();

    //specify Sitecore credentials for executing the method
    SitecoreService.SitecoreAuthenticationHeader hdr = new SitecoreService.SitecoreAuthenticationHeader()
                                                                                      { Domain = “sitecore”,
                                                                                        Username = “admin”,
                                                                                        Password = “b” };

    //execute the method passing in the credentials
    string ret = c.HelloWorld( hdr );
  }
}

Of note is the first line, that overrides the systems native server certificate validation with a lambda expression that simply always evaluates to true.  This gets us around the fact our certificate is self-signed, but obviously don’t put that into production code (I use an app setting to disable it usually).

Also of note here, the code generator has added the header as the first parameter to my method – it will do that.  It ensures you don’t forget to supply the credentials.  You can re-use the header object you’ve created by storing it somewhere convenient.  I’d also be inclined not to hard-wire the credentials into the code, but that is up to your particular scenario.

1, 2, 3, 4, 5…Done

Once you’ve got all that you will notice that you have a properly validated Sitecore.Context.User and you can use code, or add more extensions, to decide whether the user that is logged in has sufficient rights to perform the action.


No Comments

Microsoft Exchange 2003 Direct Push and the Apple iPhone 3G

Wednesday, July 16th, 2008

Like any good technophile I picked up a new iPhone 3G on Friday the 11th and the very first thing I did when getting back to the office was to try to get my Exchange Server to do Direct Push. I have had some small frustrations from the wide distribution of documentation on the subject so hopefully this post will save someone some time.

For the sake of simplicity this article will deal with the simple case of:

  • Stand alone Exchange, i.e. not a front-end/back-end setup
  • No proxy server, e.g. ISA
  • Single firewall

Frankly if your setup is more complicated than that you probably already know how to do this and aren’t reading this anyway. Moving right along…

This is what you’ll need before you get started:

  • An iPhone 3G (it doesn’t work on the v1 phone)
  • Exchange Server 2003 SP2 or later
  • Access to your firewall/router
  • A fixed IP address on the internet
  • Access to your domain settings
  • A valid SSL certificate on your Exchange server – get one, they’re not that expensive

Background

Direct Push works because the internet is slow. That’s the headline.

Basically the iPhone will make an HTTPS connection to your Exchange Server’s “Microsoft-Server-ActiveSync” virtual folder (most likely on the default web site). It will hold each connection open as long as possible, or until some pre-configured timeouts occur. Should you receive an email during this open connection, Exchange will send notification down to the iPhone which will tell you that you have new mail. Simple as that. The reason it works is because the internet protocols were designed to not receive an instant response from the server when making a request (see “slow” above). Direct Push takes advantage of this extended open connection.

To prevent your battery from draining in 25 minutes flat the chatter on the connection is kept to a minimum. It’s very clever.

Before You Start

If you have a Wi-Fi connection active on the phone it won’t work. Direct Push only works over the air (the 3G connection). This is because the Wi-Fi radio will kill your battery. With Wi-Fi enabled I believe the phone reverts to a pull model, based on observation, but I can’t confirm that.

Setup

Is your iPhone’s Wi-Fi off?

Step 1. Router/Firewall Setup

  • Go to the “Port forwarding” or “Services” setup
  • Open port 443 on TCP to enable the HTTPS communication – do not be tempted to do this using HTTP. It’s possible, but don’t do it. You have been warned.
  • Make sure the endpoint is your Exchange server’s internal IP address
  • Restart the router/firewall

Step 2. Domain Name Setup

  • Add a new host to your internet domain called “exchange” and point it to your router’s fixed internet IP address – not absolutely necessary but it makes everything a bit clearer if you ask me.

Step 3. Exchange 2003 SP2 Setup

  • Open Exchange System Manager
  • Expand “Global Settings”
  • Right-click “Mobile Services” and select “Properties”
  • There are several options required to support older technologies but the ones you want right at the moment are as follows:
    • Enable user initiated synchronisation – get the whole thing started
    • Enable Direct Push over HTTP(S) – the bit we want
  • Optionally configure Device Security – I recommend it cause then if you lose the thing you can do a “remote wipe”. These are the settings I like:
    • Enforce password on device – makes you enter a PIN to get into the iPhone which is a bit of a pain but worth it for the security. Do you want anyone who finds your phone to have access to all your email and contacts? Cause that’s what will happen.
    • Wipe device after failed attempts – this means if you get the password wrong enough times the phone will wipe itself. Set this number as low as you dare.
    • Refresh settings on the device – set this to 24 to ensure the security policy is checked for updates daily

Step 4. Configure Your Users

  • Open Active Directory Users and Computers on the Exchange server
  • Right-click the user to configure and select “Exchange Tasks”
  • Select “Configure Exchange Features” from the task list
  • Under “Mobile Services” ensure that “User Initiated Synchronisation” and “Up-to-date Notifications” are set to Enabled – the Enable and Disable buttons are cleverly hidden at the bottom of the Features grid

Step 5. Configure IIS

  • On the Exchange server open up Internet Information Services Manager
  • Locate the web site containing the virtual folder named “Microsoft-Server-ActiveSync”
  • Right-click the web site in the left pane tree and select “Properties”
  • On the “Web Site” tab enter 443 in the “SSL port” – note this may cause a problem if you already have an SSL site on the server
  • On the “Directory Security” tab setup your SSL certificate – setting this up is beyond the scope of this article but very straight forward. Google it. Remember: if you have been following along the server will be named exchange.mydomain.com and not www.mydomain.com. Make sure your SSL certificate has the correct name.

Step 6. Test Your Server Setup

  • Open a web browser and point it to https://exchange/OMA where “exchange” is the name of your Exchange server (mine is called exchange)
  • You might get a certificate error, that will be because the server name on the certificate does not match the server name – that’s OK when connecting to the server from the inside – just continue
  • Enter your network credentials (i.e. login) in the form DOMAIN\username for the “User name” field
  • You will probably get a warning page saying the device type is not supported, just click OK
  • If you’ve got it right you will see a text version of your mailbox – if not see Troubleshooting below

Step 7. Setup your iPhone

  • Turn Off Wi-Fi
  • Tap “Settings”, “Mail, Contacts, Calendars”
  • Under “Accounts” tap “Add Account…”
  • Tap “Exchange”
  • Enter your email address, username (in the form DOMAIN\username) and password
  • Ensure SSL is on
  • Set the “Server” field to exchange.mydomain.com (substitute mydomain for whatever your domain name is, obviously)

That’s it – should be up and running now. Send yourself an email and see.

Troubleshooting

In my brief time setting this up here are the places where you might come unstuck:

  • Router/firewall – make sure the you have 443 pointed at your exchange server
  • Exchange test failed? It did for me! – I got a bunch of errors the first time I ran the Exchange test. To resolve them check the following:
    • The ASP.Net version on the OMA virtual folder is set to 1.1.4322 (the Microsoft-Server-ActiveSync can stay at 2.0.50727)
    • The App Pool account (normally Network Service) has read/execute privilege on the appropriate Exchange folders (e.g. “C:\Program Files\Exchsrvr\OMA\Browse”)
    • The App Pool account has read/write privilege on BOTH ASP.Net framework versions temp folders (i.e. “C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files” and “C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files” )
  • DNS name – make sure you have allowed sufficient time for the new name “exchange” to have fully delegated. This can take 24 hrs.
  • Firewall problems – Some firewalls have an idle connection timeout that will need to be increased to at least 15 minutes (by Microsoft’s recommendations). This means that your firewall is disconnecting you – check your documentation or, as always, Google

Helpful Links

Some of the pages that helped me:

Microsoft – Enterprise firewall configuration for Exchange ActiveSync Direct Push Technology

Exchange Team Blog – Direct Push is just a heartbeat away

Brian M Posey (Exchange MVP) – Microsoft Exchange Direct Push Technology (seems to be broken)

Apple’s less that complete instructions (don’t worry, it’s Apple, it just works! Right?)


View Comments (46)

Public proxied access to SharePoint (WSS or MOSS)

Wednesday, April 30th, 2008

We use Microsoft Office SharePoint 2007 internally to manage all kinds of work and share documents. It’s a very effective tool. We also use it to manage our software registration database.

To facilitate customer software registrations we have a public-facing web service that uses SharePoint APIs to communicate with our registration site. The configuration of SharePoint required some fiddling to make this work. The problem was that we had SharePoint configured with no anonymous access, and obviously the users of our software that would be registering would not have credentials for our network – we didn’t really want to enable anonymous access so a creative solution was required.

In order to achieve this we used the web.config <identity /> element in our public-facing website to set the identity of the thread to a fixed user account created just for the task. The web service application pool is configured in IIS to run as “Network Service”. The SharePoint API seemed to pick up the user from the HttpContext.Current.User object, rather than from the thread which means that all our communications were failing with 401.5 errors. I was a little surprised to discover that despite the <identity userName=userpassword=passimpersonate=true/> in the web.config file, the HttpContext.Current.User was still anonymous.

The two tricks that made it work were:

  • In Global.asax use the Application_BeginRequest() method as follows:
    HttpContext.Current.User = new WindowsPrincipal( WindowsIdentity.GetCurrent() );
  • In our code ensure that each time we made a connection to the SPWeb we called:
    spWeb.AllowUnsafeUpdates = true;

The Global.asax trick ensures that the context carries the same user account as the impersonated thread.

The AllowUnsafeUpdates is required when impersonating.


No Comments

Flash FLV over IIS

Wednesday, April 4th, 2007

Recently I was deploying a web site with Flash video on. The Flash SWF files and associated FLV files (these are the ones containing the video that is streamed back to the user) were all in the correct directories however the videos still would not play. Instead I simply saw a blank screen.

Windows IIS only serves files for which a MIME type is configured. There are several levels you can configure these including for the whole server (right-click server > Properties > MIME types) for a particular site (right-click site > Properties > HTTP Headers > MIME Types) or even for a folder beneath the site. You will notice that the settings are inherited, so specifying the value for the server negates the need to specify it for each site, and so on.

Set the MIME type as follows:

Extension: .flv

MIME Type: video/x-flv (other MIME type settings may also work)

If you altered the MIME setting on the server you will need to restart with “iis_reset” but if you only altered the setting for a given site then simply stopping and starting that site is enough.


No Comments

Level 4, 263 Kent Street, Sydney, Australia  +61 2 9321 1555  info@5limes.com.au
Copyright ©2009 5 Limes Pty Ltd. ABN 87 119 340 680  All Rights Reserved