tags:

views:

1089

answers:

4

We have a WCF service hosted on ServerA which is a server with no-direct Internet access and has a non-Internet routable IP address.

The service is fronted by BIGIP which handles SSL encryption and decryption and forwards the unencrypted request to ServerA (at the moment it does NOT actually do any load balancing, but that is likely to be added in the future) on a specific port.

What that means is that our clients would be calling the service through https://www.OurDomain.com/ServiceUrl and would get to our service on http://SeverA:85/ServiceUrl through the BIGIP device;

When we browse to the WSDL published on https://www.OurDomain.com/ServiceUrl all the addresses contained in the WSDL are based on the http://SeverA:85/ServiceUrl base address

We figured out that we could use the host headers setting to set the domain, but our problem is that while this would sort out the domain, we would still be using the wrong scheme – it would use http://www.OurDomain.com/ServiceUrl while we need it to be Https.

Also – as we have other services (asmx based) hosted on that server we had some issues setting the host headers, and so we thought we could get away with creating another site on the server (using, say, port 82) and set the host header on that; now, on top of the http/https problem we have an issue as the WSDL contains the port number in all the urls, where BigIP works on port 443 (for the SSL)

Is there a more flexible solution than implementing Host Headers? Ideally we need to retain flexibility and ease of supportability.

Thanks for any help…

A: 

If you are adding SSL on top of existing service, it probably would also affect the binding's security mode for WCF client, which you can easily override.

For WSDL, why don't you just download the files, change the URL to whatever you want, and publish them manually as files?

eed3si9n
A: 

I got a great tip that setting the address attribute on the endpoint to the url you wish to display in the WSDL and then add a listenUri attribute to the endpoint with the actual Uri to listen on would do the trick.

The Url in the test page does not get affected (i.e it will still show the address specified in the ListenUri) but within the WSDL the correct Uri will be set (the one specified in the address.

Most annoyingly, though - when I tried this shortly after I posted the question, I could not get it to work within IIS, only when self hosting in a console app; checking myself today I found that it does indeed work; so now I'm not sure why it didn't work for me before;

What we have done in the mean time is buid a simple custom behaviour that changed the service description putting the address required in the WSDL from configurtion; obviously if there's built in support for that it's much better, so I will hopfully get some time next week to look into this further.

Yossi Dahan
+1  A: 

This is essentially a multi-part problem that involves a number of discrete solutions to provide the full answer. Essentially there are 3 problems with sitting behind the F5.

  1. The advertised service endpoint hostname.
  2. Hostname of links to xsd:import'ed schemas that describe the data contract
  3. the http/https problem you describe.

Changing the host headers, as you have found solves 1 and 2 (you can approach this in ways other than host headers, but no need to go into that here). Number 3 is a bit more tricky and requires more code (too much to dump out here).

The short answer is that you need to write a ContractBehavior that implements both IContractBehavior and IWsdlExportExtension.

The important bit you need to implement is the IWsdlExportExtension.ExportEndpoint. Within this method you need to iterate over all the WsdlPort Extensions, and when you find an extension that is of type SoapAddressBinding you need to replace the SoapAddressBinding.Location property with a new Uri that contains the https protocol specifier. You also need to do similar bits for the xsd import addresses and schema links.

If your service is also using WS-Addressing You then need to do something similar to handle the additional addresses it writes out to the wsdl.

I based the code I ended up writing on the WsdlExtras project available on CodePlex (http://www.codeplex.com/WCFExtras/). The method used in the WsdlExtras provides a great base for any extra bits you may need to add to it (From memory I don't think it dealt with the WS-Addressing bits). The bit you want to look at is the "Override SOAP Address Location URL".

Mark Allanson
Thanks Mark, this is pretty much what we've ended up doing, but I'm not sure if my solution is complete enough (think it is), will be really interested in comparing it to yours, hopefully sometime this week :-)
Yossi Dahan
A: 

To your service class add the attribute:

<ServiceBehavior(AddressFilterMode:=AddressFilterMode.Any)>

This allows the service to be addressed by the client as https://... but the service to be hosted on http://.....

In the web.config of the service host, the endpoint element must have an absolute URL in the address attribute that is the public URL that will be used by the client. In the same endpoint element, set the listenUri attribute to the absolute URL on which the service host is listening. The way I determine what the default absolute URI the host is listening on is is to add a service reference in a client application which points the the physical server where the service is hosted. The web.config of the client will have an address for the service. I then copy that into the listenUri attribute in the hosts web.config.

In your service behavior configuration add the element serviceMetaData with attribute httpGetEnabled=true

So you'll have something like:

<serviceBehaviors>
  <behavior name="myBehavior">
    <serviceMetadata httpGetEnabled="true" />
  </behavior
</serviceBehaviors>
...
<services>
  <service name="NamespaceQualifiedServiceClass" behavior="myBehavior" >
    <endpoint address="https://www.sslloadbalancer.com" binding="someBinding" contract="IMyServiceInterface" listenUri="http://www.servicehost.com" ...  />
  </service>
</services>

I am not sure if this works with message security or transport security. For this particular application, the credentials were passed as part of the DataContract so we had basicHttpBinding security mode = none. Since the transport is secure (to the ssl load balancer) there were no security issues.

It is also possible in to leave the listenUri attribute blank, however it must be present.

Unfortunately, there is a bug in WCF where the the base address of imported schemas in the WSDL have the listenUri base address rather than the public base address (the one configured using the address attribute of the endpoint). To work around that issue, you need to create an IWsdlExportExtension implementation which brings the imported schemas into the WSDL document directly and removes the imports. An example of this is provided here http://winterdom.com/2006/10/inlinexsdinwsdlwithwcf. Additionally you can have the example class inherit from BehaviorExtensionElement and complete the two new methods with:

Public Overrides ReadOnly Property BehaviorType() As System.Type
    Get
        Return GetType(InlineXsdInWsdlBehavior)
    End Get
End Property

Protected Overrides Function CreateBehavior() As Object
    Return New InlineXsdInWsdlBehavior()
End Function

This will allow you to add an extension behavior in the .config file and add the behavior using configuration rather than having to create a service factory.

under the system.servicemodel configuration element add:

  <endpointBehaviors>
    <behavior name="SSLLoadBalancerBehavior">          
      <flattenXsdImports/>
    </behavior>
  </endpointBehaviors>
     </behaviors>
<extensions>
  <behaviorExtensions>
    <!--The full assembly name must be specified in the type attribute as of WCF 3.5sp1-->
    <add name="flattenXsdImports" type="Org.ServiceModel.Description.FlattenXsdImportsEndpointBehavior, Org.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>        
  </behaviorExtensions>
</extensions>

And then reference the new endpoint behavior in your endpoint configuration using the behaviorConfiguration attribute

<endpoint address="" binding="basicHttpBinding" contract="WCFWsdlFlatten.IService1" behaviorConfiguration="SSLLoadBalancerBehavior">
Richard Collette