WCF service discovery with message security - wcf

I have a client-server application based on WCF where I'm using ServiceDiscovery to find the server from the client. During development with security turned off discovery was working fine but when we turned on message security based on certificates the ServiceDiscovery stopped working.
When I searched for a solution I found this MSDN article, http://msdn.microsoft.com/en-us/library/dd456791%28v=vs.110%29.aspx where it says;
When using message level security it is necessary to specify an EndpointIdentity on the service discovery endpoint and a matching EndpointIdentity on the client discovery endpoint. For more information about message level security, see Message Security in WCF.
I have been searching, reading and writing code but I can't seem to get this into working code. Any ideas?
Exctract of original server code:
private Binding CreateBinding()
{
WSDualHttpBinding binding = new WSDualHttpBinding(WSDualHttpSecurityMode.Message);
// Set other binding properties
return binding;
}
private static void EnableServiceDiscovery(ServiceHostBase host)
{
host.AddServiceEndpoint(new UdpDiscoveryEndpoint());
host.Description.Behaviors.Add(new ServiceDiscoveryBehavior());
}
Compact extract of original client code:
public IEnumerable<MyServiceEndpoint> FindServicesOnNetwork()
{
DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
var myServiceEndpoints = discoveryClient.Find(new FindCriteria(typeof (IMyService))).Endpoints;
discoveryClient.Close();
return myServiceEndpoints.Select(endpoint => new MyServiceEndpoint(endpoint.Address.Uri.ToString())).ToList();
}

Related

MQTTnet Connection Issue with HiveMQ Cloud

I am new to the MQTT world and I am trying to create a .Net 5.0 application that connects to a HiveMQ Cloud Broker.
I have created a free broker and I am able to connect to it with HiveMQ Websocket Client.
Here is a screenshot of my host.
I have created MQTT credentials for the host and I am able to connect over the sample client. Here is a screenshot of that client.
This works, I can publish and subscribe to the message queue.
However, now I am trying to translate this to c# and I am not able to connect. I am starting with this example project: https://github.com/rafiulgits/mqtt-client-dotnet-core
Then plugged the values from my cluster instance but I am a getting connection timeout on startup.
Here is what my service configuration looks like:
public static IServiceCollection AddMqttClientHostedService(this IServiceCollection services)
{
services.AddMqttClientServiceWithConfig(aspOptionBuilder =>
{
//var clientSettinigs = AppSettingsProvider.ClientSettings;
//var brokerHostSettings = AppSettingsProvider.BrokerHostSettings;
aspOptionBuilder
.WithCredentials("Test1", "xxxxx") //clientSettinigs.UserName, clientSettinigs.Password)
.WithClientId("clientId-jqE8uIw6Pp") //clientSettinigs.Id)
.WithTcpServer("xxxxxxxxxxxxxx.s2.eu.hivemq.cloud", 8884); //brokerHostSettings.Host, brokerHostSettings.Port);
});
return services;
}
private static IServiceCollection AddMqttClientServiceWithConfig(this IServiceCollection services, Action<AspCoreMqttClientOptionBuilder> configure)
{
services.AddSingleton<IMqttClientOptions>(serviceProvider =>
{
var optionBuilder = new AspCoreMqttClientOptionBuilder(serviceProvider);
configure(optionBuilder);
return optionBuilder.Build();
});
services.AddSingleton<MqttClientService>();
services.AddSingleton<IHostedService>(serviceProvider =>
{
return serviceProvider.GetService<MqttClientService>();
});
services.AddSingleton<MqttClientServiceProvider>(serviceProvider =>
{
var mqttClientService = serviceProvider.GetService<MqttClientService>();
var mqttClientServiceProvider = new MqttClientServiceProvider(mqttClientService);
return mqttClientServiceProvider;
});
return services;
}
I am not sure where I am going wrong, any help would be greatly appreciated.
You appear to be trying to connect to the WebSocket endpoint (port 8884) in your code, when I suspect you really should be using the normal TLS endpoint (port 8883)
Also you will need to use different clientid values if you want to have both clients connected at the same time as having matching will mean the clients will continuously kick each other off the broker.
(edit: on looking closer the client ids are actually different, but only in the last char)
I had this issue in two days ago and it seems coming form TLS confgurations/settings. By the way, my Startup.cs service injections and some configurations were same with yours. I have .NetCore app and I am trying to connect my own hivemq broker (cloud side).
In this case we need to add additional option to our mqtt client option build phase.
When I add this code, Auth problems gone.
.WithTls();
Here is part of the client option codes should like that
AddMqttClientServiceWithConfig(services,optionBuilder =>
{
var clientSettings = BrokerAppSettingsProvider.BrokerClientSettings;
var brokerHostSettings = BrokerAppSettingsProvider.BrokerHostSettings;
optionBuilder
.WithCredentials(clientSettings.UserName, clientSettings.Password)
.WithTls()
.WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port);
});
return services;
We can consider this as a different solution.

Call Service Fabric service from console application using WCF HTTPS endpoint

I have a service hosted in a Service Fabric cluster in Azure (not locally) and I'm trying to call a method in it using a console application on my local machine. Using WCF for communication, I have a HTTPS endpoint set up in my application on a specific port, and have configured load balancing rules for the port in the Azure portal. The cluster has 6 nodes and the application is the only one deployed on the cluster.
Have followed the ServiceFabric.WcfCalc on GitHub (link), which works on a local cluster using HTTP endpoints, but can't call a method on the service using HTTPS endpoints once it has been deployed. What do I need to do to get it working? Have tried following the example here but don't know how to configure this for HTTPS with a service on multiple nodes for a console application to access.
Thanks in advance.
EDIT Here's my client code which I am using to call the service method. I pass the fabric:/ URI into the constructor here.
public class Client : ServicePartitionClient<WcfCommunicationClient<IServiceInterface>>, IServiceInterface
{
private static ICommunicationClientFactory<WcfCommunicationClient<IServiceInterface>> communicationClientFactory;
static Client()
{
communicationClientFactory = new WcfCommunicationClientFactory<IServiceInterface>(
clientBinding: new BasicHttpBinding(BasicHttpSecurityMode.Transport));
}
public Client(Uri serviceUri)
: this(serviceUri, ServicePartitionKey.Singleton)
{ }
public Client(
Uri serviceUri,
ServicePartitionKey partitionKey)
: base(
communicationClientFactory,
serviceUri,
partitionKey)
{ }
public Task<bool> ServiceMethod(DataClass data)
{
try
{
//It hangs here
return this.InvokeWithRetry((c) => c.Channel.ServiceMethod(data));
}
catch (Exception)
{
throw;
}
}
}
When debugging my console application on my local machine, the application hangs on the InvokeWithRetry call which calls the method in my service in Service Fabric. The application does not throw any exceptions and does not return to the debugger in Visual Studio.
Make sure you run every service instance /replica with a unique url.
Make sure you call the WebHttpBinding constructor using WebHttpSecurityMode.Transport.
Make sure you register the url using the same port number (443 likely) as in you service manifest endpoint declaration.
Make sure the endpoint is configured as HTTPS.
The warning you see in Service Fabric is telling you that there is already another service registered to listen on port 443 on your nodes. This means that Service Fabric fails to spin up your service (since it throws an exception internally when it is trying to register the URL with http.sys). You can change the port for your service to something else that will not conflict with the existing service, e.g.:
<Resources>
<Endpoint Name="CalculatorEndpoint" Protocol="https" Type="Input" Port="44330" />
</Endpoints>
If you log in to Service Fabric Explorer on https://{cluster_name}.{region}.cloudapp.azure.com:19080 you should be able to see what other applications and services are running there. If you expand services all the way down to node you should be able to see the registered endpoints, including ports, for existing services.
Bonus
You can query the cluster using FabricClient for all registered endpoints
var fabricClient = new FabricClient();
var applicationList = fabricClient.QueryManager.GetApplicationListAsync().GetAwaiter().GetResult();
foreach (var application in applicationList)
{
var serviceList = fabricClient.QueryManager.GetServiceListAsync(application.ApplicationName).GetAwaiter().GetResult();
foreach (var service in serviceList)
{
var partitionListAsync = fabricClient.QueryManager.GetPartitionListAsync(service.ServiceName).GetAwaiter().GetResult();
foreach (var partition in partitionListAsync)
{
var replicas = fabricClient.QueryManager.GetReplicaListAsync(partition.PartitionInformation.Id).GetAwaiter().GetResult();
foreach (var replica in replicas)
{
if (!string.IsNullOrWhiteSpace(replica.ReplicaAddress))
{
var replicaAddress = JObject.Parse(replica.ReplicaAddress);
foreach (var endpoint in replicaAddress["Endpoints"])
{
var endpointAddress = endpoint.First().Value<string>();
Console.WriteLine($"{service.ServiceName} {endpointAddress} {endpointAddress}");
}
}}}}}
Just run that with the proper FabricClient credentials (if it is a secured cluster) and you should see it listing all endpoints for all services there. That should help you find the one that has an endpoint for :443

WCF Discovery and DataService V3

I would like to expose discovery endpoints (both TCP and UDP) for my Data Services v3 and enable services to be discoverable from the client and discover them in another application. The main point in the discovery is to get the service endpoint address at the client.
I have tried to adapt the samples that Microsoft have provided for WCF Discovery, but so far I failed to achieve my goal.
I have created a custom Data Service Host Factory on server side:
public class CustomDataServiceHostFactory : System.Data.Services.DataServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var serviceHost = base.CreateServiceHost(serviceType, baseAddresses);
EndpointDiscoveryBehavior endpointDiscoveryBehavior = new EndpointDiscoveryBehavior();
// Create XML metadata to add to the service endpoint
XElement endpointMetadata = new XElement(
"Root",
new XElement("Information", "This endpoint is Data Service v3!"),
new XElement("Time", System.DateTime.Now.ToString("MM/dd/yyyy HH:mm")));
// Add the XML metadata to the endpoint discovery behavior.
endpointDiscoveryBehavior.Extensions.Add(endpointMetadata);
//may be this is not the safest way to set the behaviour
foreach (var endpoint in serviceHost.Description.Endpoints)
{
endpoint.Behaviors.Add(endpointDiscoveryBehavior);
}
// Make the service discoverable over UDP multicast
serviceHost.Description.Behaviors.Add(new ServiceDiscoveryBehavior());
serviceHost.AddServiceEndpoint(new UdpDiscoveryEndpoint());
return serviceHost;
}
}
On the client side I have tried the following code:
DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
// Find service endpoints
// ServiceReference.DataModel is the generated class for the Data Service client proxy
FindCriteria findCriteria = new FindCriteria(typeof(ServiceReference.DataModel));
findCriteria.Duration = TimeSpan.FromSeconds(30);
FindResponse findResponse = discoveryClient.Find(findCriteria);
// Check to see if endpoints were found & print the XML metadata in them.
if (findResponse.Endpoints.Count > 0)
{
foreach (XElement xElement in findResponse.Endpoints[0].Extensions)
{
Console.WriteLine("Printing Metadata from ServiceEndpoint:");
Console.WriteLine("Endpoint Information: " + xElement.Element("Information").Value);
Console.WriteLine("Endpoint Started at Time: " + xElement.Element("Time").Value);
Console.WriteLine();
}
}
Unfortunately this does not work. I get InvalidOperationException:
Attempted to get contract type for DataModel, but that type is
not a ServiceContract, nor does it inherit a ServiceContract.
If I am heading in the right direction I need a way to express the type for the service contract for the discovery. Too bad I am not sure that it will even work like the normal WCF Discovery...
Please share your ideas or even better - working solutions.
I think exception message is clear enough.
For service discovery You try to use type of your data model, while You must use type of your WCF service implementation - this is different things.
Basically DataServicesV3 service adapter uses your data model and exposes it as a WCF service with it's own service contract.
Look at DataServiceV3 type declaration see that it is implementing some interface, i just don't remember name, in this interface declaration you will find [ServiceContract] and [ServiceOperation] attributes. This is Your SERVICE CONTRACT for all ancestors of DataServiceV3. They use THE SAME contract. Here stands another problem I haven't managed to solve yet - how to make WS-Discovery work with DataServices if they share same contract. You'd better dig in this way.

Can I self-host an HTTPS service in WCF without the certificate store and without using netsh http add sslcert?

I am attempting to host a service that serves up basic web content (HTML, javascript, json) using a WebHttpBinding with minimal administrator involvement.
Thus far I have been successful, the only admin priviledges necessary are at install time (register the http reservation for the service account and to create the service itself). However, now I am running into issues with SSL. Ideally I would like to support a certificate outside the windows certificate store. I found this article - http://www.codeproject.com/KB/WCF/wcfcertificates.aspx - which seems to indicate you can specify the certificate on the service host, however at runtime navigating a browser to https://localhost/Dev/MyService results in a 404.
[ServiceContract]
public interface IWhoAmIService
{
[OperationContract]
[WebInvoke(
Method = "GET",
UriTemplate = "/")]
Stream WhoAmI();
}
public class WhoAmIService : IWhoAmIService
{
public Stream WhoAmI()
{
string html = "<html><head><title>Hello, world!</title></head><body><p>Hello from {0}</p></body></html>";
html = string.Format(html, WindowsIdentity.GetCurrent().Name);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
return new MemoryStream(Encoding.UTF8.GetBytes(html));
}
}
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(WhoAmIService), new Uri("https://localhost:443/Dev/WhoAmI"));
host.Credentials.ServiceCertificate.Certificate = new X509Certificate2(#"D:\dev\Server.pfx", "private");
WebHttpBehavior behvior = new WebHttpBehavior();
behvior.DefaultBodyStyle = WebMessageBodyStyle.Bare;
behvior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
behvior.AutomaticFormatSelectionEnabled = false;
WebHttpBinding secureBinding = new WebHttpBinding();
secureBinding.Security.Mode = WebHttpSecurityMode.Transport;
secureBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
ServiceEndpoint secureEndpoint = host.AddServiceEndpoint(typeof(IWhoAmIService), secureBinding, "");
secureEndpoint.Behaviors.Add(behvior);
host.Open();
Console.WriteLine("Press enter to exit...");
Console.ReadLine();
host.Close();
}
If I change my binding security to none and the base uri to start with http, it serves up okay. This post seems to indicate that an additional command needs to be executed to register a certificate with a port with netsh (http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/6907d765-7d4c-48e8-9e29-3ac5b4b9c405/). When I try this, it fails with some obscure error (1312).
C:\Windows\system32>netsh http add sslcert ipport=0.0.0.0:443 certhash=0b740a29f
29f2cc795bf4f8730b83f303f26a6d5 appid={00112233-4455-6677-8899-AABBCCDDEEFF}
SSL Certificate add failed, Error: 1312
A specified logon session does not exist. It may already have been terminated.
How can I host this service using HTTPS without the Windows Certificate Store?
It is not possible. HTTPS is provided on OS level (http.sys kernel driver) - it is the same as providing HTTP reservation and OS level demands certificate in certificate store. You must use netsh to assign the certificate to selected port and allow accessing the private key.
The article uses certificates from files because it doesn't use HTTPS. It uses message security and message security is not possible (unless you develop your own non-interoperable) with REST services and webHttpBinding.
The only way to make this work with HTTPS is not using built-in HTTP processing dependent on http.sys = you will either have to implement whole HTTP yourselves and prepare new HTTP channel for WCF or you will have to find such implementation.

WCF, REST, SSL, Client, custom certificate validation

I have a specific problem that I can't solve. Let me explain in detail. I'm new to this technology so I might be using some wrong terms. Please correct and explain or ask for explanation if you don't understand.
I am creating a self hosted WCF REST server, hosted in WPF application. It uses https, SLL with WebHttpSecurityMode.Transport. I am using my own generated certificate.
I would like to create a WinForms client that would use this service. The format of the response form the server is JSON.
I would like to validate the certificate on the client with my custom validator inherited from X509CertificateValidator.
This is my server side code. I'm using a custom username validator that works fine. I have configured the certificate in the IIS Manager on my machine for the Default Website > Bindings, where I have generated the certificate (Windows 7).
WebServiceHost sh = new WebServiceHost(typeof(ReachService));
string uri = "https://localhost:9000/Service";
WebHttpBinding wb = new WebHttpBinding();
wb.Security.Mode = WebHttpSecurityMode.Transport;
wb.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
sh.AddServiceEndpoint(typeof(IReachService), wb, uri);
sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
sh.Open();
and this is my client code
Uri uri = new Uri("https://localhost:9000/Service");
WebChannelFactory<ReachService> cf = new WebChannelFactory<IReachService>(uri);
WebHttpBinding wb = cf.Endpoint.Binding as WebHttpBinding;
wb.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
wb.Security.Mode = WebHttpSecurityMode.Transport;
cf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
cf.Credentials.ServiceCertificate.Authentication.CustomCertificateValidator = new CustomCertificateValidator("PL2"); // this is the name that issued the certificate
cf.Credentials.UserName.UserName = "user1";
cf.Credentials.UserName.Password = "user1";
IReachService service = cf.CreateChannel();
try
{
CustomersList auth = service.GetCustomers();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
on calling service.GetCustomers() I get:
Could not establish trust relationship for the SSL/TLS secure channel with authority
'localhost:9000'.
InnerException Message:
The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
InnerException Message:
The remote certificate is invalid according to the validation procedure.
The server is working fine when I test in the browser.
But the client code is wrong cause it doesn't go to the custom cert validator class. And this class is the same as in the MSDN example on http://msdn.microsoft.com/en-us/library/system.identitymodel.selectors.x509certificatevalidator.aspx.
Can anyone please tell me where am I going wrong with this approach?
If you need more info please ask.
Thank you
It looks like the issue occurs because certificate was issued for some other hostname. You can check this (and customize if necessary) by providing custom ServicePointManager.ServerCertificateValidationCallback.
//don't use HttpWebRequest --you lose all of the strongly-typed method and data contracts!
//the code to create the channel and call a method:
SetCertPolicy();
var cf1 = new WebChannelFactory<TService>(new Uri(remoteServiceAddressSecure));
var service = cf1.CreateChannel();
sevice.DoMethod();
protected static void SetCertPolicy()
{
ServicePointManager.ServerCertificateValidationCallback += RemoteCertValidate;
}
private static bool RemoteCertValidate(object sender, X509Certificate cert, X509Chain chain,
SslPolicyErrors error)
{
// trust any cert!!!
return true;
}
If you want to use WCF on the client, then don't use WebHttpBinding, stick with the SOAP stuff it will work much better.
However, if you want to use a standard HTTP client like, WebClient or HttpWebRequest or HttpClient V.prototype or HttpClient V.Next then stick with the webHttpBinding.
Sorry for not addressing your direct question but you are likely to run into more problems because you are using a binding that was intended to make WCF services accessible to non-WCF platforms but then using WCF to try and access it.