How do I set up a secure WCF service behind a firewall? - c#

I have a WCF service that is behind an enterprise-class firewall, which is doing both hostname and port translation, e.g.:
https://ws.address.com/Service.svc --> https://serv.internal.com:44000/Service.svc
The service is secured with SSL-128 and requires a client certificate.
Because the internal server name is not accessible from outside the firewall, we had to implement a ServiceHostFactory to translate the WSDL and XSD import references that WCF generates:
public class MyCustomFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(
Type serviceType, Uri[] baseAddresses)
{
MyCustomHost customServiceHost =
new MyCustomHost(serviceType, baseAddresses);
return customServiceHost;
}
class MyCustomHost : ServiceHost
{
public MyCustomHost(Type serviceType,
params Uri[] baseAddresses)
: base(serviceType,
GetBaseAddresses(serviceType, baseAddresses))
{
}
protected override void ApplyConfiguration()
{
base.ApplyConfiguration();
}
private static Uri[] GetBaseAddresses(
Type serviceType, params Uri[] baseAddresses)
{
UriBuilder newBaseAddress = new UriBuilder();
newBaseAddress.Path = "/" + serviceType.ToString() +
".svc";
// from config
newBaseAddress.Host =
MyCustomSettings.ServiceBaseAddress;
if (baseAddresses.Length > 0)
{
newBaseAddress.Scheme = baseAddresses[0].Scheme;
}
return new Uri[] { newBaseAddress.Uri };
}
}
}
Here's the problem with this: unless the service is hosted on the internal machine on the default SSL port of 443, we get the error:
No protocol binding matches the given address 'https://ws.address.com/Service.svc'. Protocol bindings are configured at the Site level in IIS or WAS configuration.
It appears, from tinkering, that if we change the internal server to host the service on 443, or configure the firewall to forward from 44000 to 44000, everything works. Those aren't options in our production environment, though.
Edit: Forgot to mention, we tried to use an IWsdlExportExtension to flatten the WSDL, but that caused severe problems with the proxy code generation in svcutil or VS2008, so we scrapped the idea.
Does anyone know any way around this? I'm pulling my hair out!
Thanks in advance!

You may need to explicitly create your own Binding (i.e., ServiceModel.WSHttpBinding) and add a Service Endpoint (.AddServiceEndpoint(..) ) with that binding.
http://msdn.microsoft.com/en-us/library/system.servicemodel.servicehost.addserviceendpoint(VS.85).aspx

Have you tried putting the ip port in the address (from the question it did not look like it was used everytime):
https://ws.address.com:44000/Service.svc
Another thing that it may be is, is WCF listening for https traffic on that port, see:
http://msdn.microsoft.com/en-us/library/ms733768.aspx

You shouldn't change the base addresses in the factory. Write an extension instead to modify the WSDL. This would be a "IWsdlExportExtension" and you wanna overwrite ExportEndpoint to modify the endpoint addresses. This will leave your service listening to the correct base address.
OR
If you don't wan to get started with a WSDL extension...move your existing code into the "CreateServiceHost" method and scratch your custom host! That is not very nice but should work.

You didn't mention your host. IIS/WAS? If so, you may need to add the external host name to IIS config to the secure bindings list.
here is some information on changing the host-name in IIS hosted service
here is the command-line:
cscript //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs
set W3SVC/1/SecureBindings "10.(internal addr).1:443:ws.address.com"
"127.0.0.1:443:Internal Host name"
If that doesn't take care of it I'd go with "routeNpingme" and suggest you just need to get the endpoints specified correctly, using a binding that specifies your https ports and names. I think you can do this with existing binding options... but you may need to create a custom.

My guess is that the site bindings of the site that hosts your service in IIS are configured to only listen on hostname serv.internal.com instead of the external name ws.address.com.
If the IIS site bindings are configured with a hostname, that hostname is checked against all incoming URLs. Only URLs with matching hostname can be processed by that binding.
The firewall will redirect the request to the inside server, but will not alter the incoming URL string...

Related

Set Custom Host Name as part of changing the Endpoint Address of a Soap Client

I am presently in a scenario where I need to have multiple servers that live behind a Load Balancer talk to each other directly, and I need to communicate with specific servers for PUSH notifications. This is for a chat tool that requires users that have been moved to different servers by a load balancer to still be able to talk to one another live.
The actual pushes are being handled with Signal-R, and I have all of that working. So here is the actual complication:
Normally, this would be simple enough to do by targeting them via IP Address to bypass the load balancer. However, this is complicated because the servers expects a specific Host name or it will reject the request.
I know it is possible to do this with a WebRequest, but would like to avoid having to build a proxy if I can help it.
Here is the piece I have where I'm trying to send a global push to tell everyone across all connected servers to update their buddy lists because someone logged in or out.
private void NotifyUsersChangedGlobal()
{
List<string> addressesToNotify = ChatUsers.Select(x => x.ServerIP).Distinct().ToList();
foreach (string address in addressesToNotify)
{
ChatUplinkSoapClient client = BuildClient(address);
client.NotifyUsersChanged();
}
}
And this is the Client Builder where (I assume) I need to handle assigning the custom Host name to ride on top of the IP Address
private ChatUplinkSoapClient BuildClient(string endpointIP)
{
string relativeUrl = "/WebServices/ChatUplink.asmx";
//Turn on HTTPS
HttpBindingBase binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
//Link together the IP Address and the asmx route
EndpointAddress endpoint = new EndpointAddress(endpointIP + relativeUrl);
//Make the Client
ChatUplinkSoapClient client = new ChatUplinkSoapClient(binding, endpoint);
//Need to set Host header to "HostHeaderName", or the server will reject the request.
return client;
}
Ideas?

Accessing WCF application through Service Fabric reverse proxy

We've made a a WCF application that we're hosting inside an On-Premise Service fabric cluster. Accessing it through the Service Fabric reverse proxy is giving us some difficulties.
Our cluster has 3 nodes(eg. 10.0.0.1-3) and the application should be accessible through the reverse proxy (listening on port 19081) on every node. Unfortunatly it only works through the SF reverse proxy on the node hosting the WCF application(also listening on port 19081). Accessing it through the other nodes results in a 400 bad request.
If we run the WCF service on a different port, we can access it directly / locally, but not through the Service Fabric Reverse Proxy.
We're running multiple ASP.NET Core/REST services on the cluster and these work fine.
Example
If the service is running on the 10.0.0.1 node we can access it through:
http://10.0.0.1:19081/myserviceType/soaphost/myservice.svc
However these URL's result in a 400 bad request status code:
http://10.0.0.2:19081/myserviceType/soaphost/myservice.svc
http://10.0.0.3:19081/myserviceType/soaphost/myservice.svc
Code example
We're using the following code to create the WCF Service Instance Listener:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[] {
CreateWcfListener("ServiceEndpoint", serviceProvider.GetService<ServiceType>())
};
}
private ServiceInstanceListener CreateWcfListener<T>(string endpoint, T serviceImplementation)
{
return new ServiceInstanceListener((context) =>
{
var endpointConfig = context.CodePackageActivationContext.GetEndpoint(endpoint);
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
string host = context.NodeContext.IPAddressOrFQDN;
string uriStem = endpointConfig.PathSuffix;
string uri = $"{scheme}://{host}:19081{context.ServiceName.AbsolutePath}/{uriStem}";
CustomBinding listenerBinding = CreateListenerBinding();
WcfCommunicationListener<T> listener = new WcfCommunicationListener<T>(
wcfServiceObject: serviceImplementation,
serviceContext: context,
address: new EndpointAddress(uri),
listenerBinding: listenerBinding);
return listener;
}, endpoint);
}
We would like to know why it doesn't work, but more importantly how to fix it.

URL of Stateful Service using OWIN communication listener

I used the following example to configure a communication listener for my Stateful Service:
https://github.com/Microsoft/azure-docs/blob/master/articles/service-fabric/service-fabric-reliable-services-communication-webapi.md
Relevant snippet:
public Task<string> OpenAsync(CancellationToken cancellationToken)
{
var serviceEndpoint = this.serviceContext.CodePackageActivationContext.GetEndpoint(this.endpointName);
var protocol = serviceEndpoint.Protocol;
int port = serviceEndpoint.Port;
if (this.serviceContext is StatefulServiceContext)
{
StatefulServiceContext statefulServiceContext = this.serviceContext as StatefulServiceContext;
this.listeningAddress = string.Format(
CultureInfo.InvariantCulture,
"{0}://+:{1}/{2}{3}/{4}/{5}",
protocol,
port,
string.IsNullOrWhiteSpace(this.appRoot)
? string.Empty
: this.appRoot.TrimEnd('/') + '/',
statefulServiceContext.PartitionId,
statefulServiceContext.ReplicaId,
Guid.NewGuid());
}
...
Service manifest snippet:
<Endpoints>
<Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8090" />
<Endpoint Name="ReplicatorEndpoint" />
</Endpoints>
Now when deploy my application, I get my service on URL with all kind of guids:
http://localhost:8090/ba794109-bba3-4cdf-8434-d718be264087/131407811483781446/614de30b-14a7-4172-a03d-4e28d23cf28d
If I try to access http://localhost:8090/ on itself, I'm getting error 503
Any way to map the general URL to the Primary partition and replica? Or is it impossible in Stateful Services? In Stateless you will get this out of the box.
The "out of the box" solution you are referring to is dependent on the partitioning type. A singleton partition can be accessed by its service URL:
http://localhost:8090/ApplicationName/ServiceName
This doesn't work for a service with Named or Int64Range partitioning because the URL doesn't refer to a specific partition of the service. That's why you have to call the service URL that contains the GUIDs. The GUIDs refer to the partition.
In order to solve this problem you could use a reverse proxy. A reverse proxy allows you to provide the partition information through the URL. You can call your partitioned service through the URL:
http(s)://<Cluster FQDN | internal IP>:Port/<ServiceInstanceName>/<Suffix path>?PartitionKey=<key>&PartitionKind=<partitionkind>&ListenerName=<listenerName>&TargetReplicaSelector=<targetReplicaSelector>&Timeout=<timeout_in_seconds>
In your cluster it would probably look like:
http://clusterIP:19081/ApplicationName/ServiceName/PartitionKey=1&PartitionKind=Int64Range
Note that the reverse proxy (currently) is not available on the local development cluster. For development purposes I would recommend to either use the URL with GUIDs or temporarely change your partitioning to a singleton scheme.

Programmatically implement WCF with Certificate

I am quite new to WCF and trying to get my head around the security. I am still reading and learning, but I came to a point where I got a working version of WCF with Certificate authentication. I know that the code has some weaknesses; however, my initial goal was to create communication using certificate authentication. Also, I wanted to create everything programmatically (no Web.config configurations for the services or clients). The reason for this is that the client should be able to link an Assembly (Class Library) and get access to the server. Also, I am loading the certificates from the file system (again, I know this is not secure). I would like to get a little bit feedback.
The following client snippet is creating an object that I can use to connect to the server. The anonymous type T is my service interface e.g. IService.
Here is my client implementation:
var url = "URL TO WS";
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message = {ClientCredentialType = MessageCredentialType.Certificate}
}
};
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
if (channelFactory.Credentials != null)
{
channelFactory.Credentials.ClientCertificate.Certificate =
new X509Certificate2(#"PATH\TO\Client.pfx"); // Client Certificate PRIVATE & PUBLIC Key
channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; // I know this is not good, but I dont have a valid certificate from a trusted entity
}
wcfClient = channelFactory.CreateChannel();
return wcfClient;
The service is a bit more complex. I use .svc files with their code-behind. If I understand the use of .svc files correctly, then I believe this is the entry point where the .NET framework creates a ServiceHost and automatically opens it? In my implementation I do not open the ServiceHost, I only implemented a ServiceHostFactoryBase and referenced it in the .svc Markup language. Look at the Factory section - this is the part where I implement my custom Host Factory.
<%# ServiceHost Language="C#" Debug="true"
Service="Service.Services.LevelService" CodeBehind="LevelService.svc.cs"
Factory="Service.Security.ServiceHostFactory.HostFactory" %>
And my custom Host Factory looks like this:
public class HostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var serviceType = Type.GetType(constructorString);
if (serviceType.GetInterfaces().Count() != 1)
throw new NotImplementedException("The service can only have one implemented interface");
var interfaceType = serviceType.GetInterfaces()[0];
var myServiceHost = new ServiceHost(serviceType, baseAddresses);
var httpBinding = new WSHttpBinding();
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Mode = SecurityMode.Message;
myServiceHost.Credentials.ServiceCertificate.Certificate = new X509Certificate2(#"PATH\TO\Server.pfx");
myServiceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
myServiceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator = new MyX509CertificateValidator();
myServiceHost.Credentials.ClientCertificate.Certificate = new X509Certificate2(#"PATH\TO\Client.cer");
myServiceHost.AddServiceEndpoint(interfaceType, httpBinding, String.Empty);
return myServiceHost;
}
}
The custom validator doess't do much yet, but here it is as well:
public class MyX509CertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
// Check that there is a certificate.
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
// Check that the certificate issuer matches the configured issuer.
//throw new SecurityTokenValidationException("Certificate was not issued by a trusted issuer");
}
}
If I understand correctly, the Server has ONLY the PUBLIC key of the client registered since I only reference the .cer file.
My big question is now, if I would like to get anything like this on a production server - and lets assume nobody will actually get the executables (including the certificates), would this be a possible solution to keep unwanted people out of my webservice? Basically, I don't want anybody else consuming my webservice - only if you have the proper certificate. Also, how much of an issue is the part where I set on the client:
CertificateValidationMode = X509CertificateValidationMode.None
I know there are many questions - but overall, I would like to know if I made some fundamental mistakes in this implementation.
Ok,
after going through a lot of tutorials and demo applications, I figured out that the best way to go ahead is actually using the Certificate Store on Windows. However, I still might consider a hybrid solution where the Server has the certificates in the Certificate store and the client has it embedded in a resource. If you are struggling with WCF and Certificates, have a look at those links:
IIS7 Permissions Overview - ApplicationPoolIdentity
I was able to create Transport as well as Message secured WCF web services. I would suggest to READ the linked articles because there is so much information that will make you understand certificates and their usage. Especially when dealing with self-singed certificates!
I ended up implementing wsHttpBinding using Message Security Mode + Client Certificate with ChainTrust.
Hope this will help someone else!

WP7 Mango - How to get an IP address for a given hostname

I need to get an IP address for a given hostname from a DnsEndPoint, and convert it to an IPEndPoint. How would I go about doing this? WP7 lacks a Dns.GetHostEntry function, so is there any way to do this without creating a Socket, sending data to the host, then receiving a ping from the host and reading the RemoteEndPoint property to get the IP address of the host?
Try using DeviceNetworkInformation.ResolveHostNameAsync in the Microsoft.Phone.Net.NetworkInformation namespace, like this:
public void DnsLookup(string hostname)
{
var endpoint = new DnsEndPoint(hostname, 0);
DeviceNetworkInformation.ResolveHostNameAsync(endpoint, OnNameResolved, null);
}
private void OnNameResolved(NameResolutionResult result)
{
IPEndPoint[] endpoints = result.IPEndPoints;
// Do something with your endpoints
}
There is no way to do this built into the framework. You could use a socket assumming that the host supports ping. It will depend on the network you are running in (I'd assume you can't control this) and the exact requirements of the application.
It may be easier to get your app to work with IP addresses and not require a hostname if all you have is an IP address.
I think Im dealing with the same problem. I also have a dynamic IP updating the dns with No-ip.
For what I know the System.Net.Dns is not available in this version of Windows Phone.
Maybe in next releases.
http://msdn.microsoft.com/en-us/library/system.net.dns.aspx
At the start of my app Im going to create a web service call to the host (to the webserver in it) asking for the IPAddress. I think I'll solve the problem in the meantime.
This could be the WCF service
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetIpAddress(string value);
}
public class Service1 : IService1
{
public string GetIpAddress()
{
// Add the proper error handling and collection matching of course
IPAddress s = Dns.GetHostAddresses("www.mysite.com")[0];
return s.ToString();
}
}
If you guys find a direct approach please let me know

Categories