gRPC Net6 Client load balancing & SRV record DNS config - c#

My service is hosted under IIS (https) and JWT is used for authentication/authorization.
The IT person configured an SRV record DNS but I'm not sure he did it correctly.
I'm getting the following error:
Status(StatusCode="Unavailable", Detail="Error getting DNS hosts for address '_http._tcp.grpcpocclientsideloadbalancing.400dev.rsvzinasti.be'. SocketException: The requested name is valid, but no data of the requested type was found.
The DNS config: here
Using nslookup returns the correct addresses: here
My code looks like this:
var credentials = CallCredentials.FromInterceptor((c, m) =>
{
m.Add("Authorization", "Bearer " + GetStsClaim());
return Task.CompletedTask;
});
var clientHandler = new SocketsHttpHandler();
clientHandler.UseProxy = false;
var handler = new SubdirectoryHandler(clientHandler, "/TestGrpc");
var channel = GrpcChannel.ForAddress("dns:///_http._tcp.grpcpocclientsideloadbalancing.400dev.rsvzinasti.be",
new GrpcChannelOptions
{
HttpHandler = handler,
Credentials = ChannelCredentials.Create(ChannelCredentials.SecureSsl, credentials),
ServiceConfig = new ServiceConfig
{
LoadBalancingConfigs = { new RoundRobinConfig() }
}
});
var client = new Greeter.GreeterClient(channel);
var request = new HelloRequest();
request.Name = "test";
var reply = await client.SayHelloAsync(request);
Calling Dns.GetHostAddresses("dns://_http._tcp.grpcpocclientsideloadbalancing.400dev.rsvzinasti.be") throws
'No such host is known.'
Could you help me find what is wrong with my config ?
Thanks in advance

Related

Salesforce Pub/Sub client in .NET

I would like to subscript to a Salesforce platform event. I am trying to create a client in C#/.NET for a Salesforce Pub/Sub API. There are examples in other languages but not in .NET : https://github.com/developerforce/pub-sub-api
I am using the Grpc.Net.Client nuget packages.
var topicName = "/event/SomeEvent__e";
var pubSubEndpoint = "https://api.pubsub.salesforce.com:7443";
var accessToken = "xxxx";
var organisationId = "xxxx";
var instanceUrl = "https://xxxxx.sandbox.my.salesforce.com";
var credentials = CallCredentials.FromInterceptor((c, m) =>
{
m.Add("accesstoken", accessToken);
m.Add("instanceurl", instanceUrl);
m.Add("tenantid", organisationId);
return Task.CompletedTask;
});
var options = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
};
var channel = GrpcChannel.ForAddress(pubSubEndpoint, options);
var client = new PubSub.PubSubClient(channel);
var topicRequest = new TopicRequest()
{
TopicName = topicName
};
var topic = client.GetTopic(topicRequest);
I know my credentials are correct because I can use postman to hit the oauth2 endpoint and get a valid access token. But when I try and call a client method like client.GetTopic, then I get the following error.
Status(StatusCode="PermissionDenied", Detail="An error occurred while getting the metadata for org CORE/prod/00DN0000000c8Hk and topic /event/SomeEvent__e. Ensure the credentials and topic name are correct. rpcId: 21d854fb-17dc-4778-9524-6264bd1a920d")
Am I setting up the credentials object wrong? I cannot find any example of subscribing to a Salesforce Pub/Sub in .NET.

SignalR HubConnectionBuilder header problem

I had SignalR working with a HubConnection prior to .NET Core 6. Now the same code is generating an error when you try to start the connection that the builder is creating:
New-line characters are not allowed in header values.
I also think my other version was working with HTTP and this new project is using HTTPS. The Hub is up and working fine within the website itself. But my external app is having trouble establishing a connection using the following code:
var notifyConnection = new HubConnectionBuilder()
.WithUrl(baseUrl + "/notify", options =>
{
options.AccessTokenProvider = async () =>
{
var stringData = JsonConvert.SerializeObject(new { username = user, password = pass });
var content = new StringContent(stringData);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(baseUrl + "/api/token", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
};
})
.WithAutomaticReconnect()
.Build();
notifyConnection.Closed += async (error) =>
{
if (Debug) _logger.LogInformation("Hub connection closed: " + error.Message);
await Task.Delay(new Random().Next(0, 5) * 1000);
await notifyConnection.StartAsync();
};
try
{
var task = notifyConnection.StartAsync();
task.Wait();
}
catch (Exception ex)
{
if (Debug) _logger.LogInformation("Hub connection start error: " + ex.Message);
}
The exception happens when the connection is attempted to be started asynchronously. Anyone run into a similar issue? I have verified that stringData does not have new-line characters. SignalR client is 6.0.6. I am stumped. Any help is greatly appreciated.
UPDATE:
I had trouble with the token generator. I was able to get past this error. Now it is throwing the following error:
The SSL connection could not be established, see inner exception.
The inner exception says that the certificate is invalid. That is because the certificate is for an external connection rather than the server's IP address. The HttpClient is already ignoring certificate errors with the following code:
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
httpClient = new HttpClient(handler);
UPDATE 2:
It is now working. I had to also tell the web sockets to ignore certificate errors. Found the solution in another question:
https://stackoverflow.com/a/63973431/2022236

c# webclient NetworkCredentials in Nodejs

I have a c# .net Client with this Code:
using(WebClient client = new WebClient())
{
string serialisedData = "";
serialisedData = JsonConvert.SerializeObject(myData);
client.Credentials = new NetworkCredential(config.UserData.Username, config.UserData.Password);
byte[] responsebyte = client.UploadData(config.ServerAddress, System.Text.Encoding.UTF8.GetBytes(serialisedData));
}
That Client sends data to my nodejs Server.
Nodejs Code:
var http = require('http');
var _server = http.createServer(_listener);
_server.listen(1234);
console.log( 'started' );
function _listener(req, res) {
let data = []
req.on('data', chunk => {
data.push(chunk)
})
req.on('end', () => {
data = Buffer.concat(data);
var dataString = new Buffer.from(data).toString("utf-8");
const data = JSON.parse(dataString);
// data has all the data from the c# object "myData"
res.write('response')
res.end()
})
}
But how can I access the credentials of this connection?
This is how I can Access the credentials in c#:
HttpListener listener = new HttpListener();
listener.Prefixes.Add($"https://+:{Config.Port}/");
listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
listener.Start();
for (; ; )
{
Console.WriteLine("Listening...");
IAsyncResult result = listener.BeginGetContext(new AsyncCallback(DoWork), listener);
result.AsyncWaitHandle.WaitOne();
result = null;
}
private void DoWork(IAsyncResult asyncResult)
{
HttpListener listener = (HttpListener)asyncResult.AsyncState;
HttpListenerContext context = listener.EndGetContext(asyncResult);
HttpListenerBasicIdentity identity = (HttpListenerBasicIdentity)context.User.Identity;
// identity has the credentials
}
Edit: I cant change the c# Code anymore. So only nodejs solutions are needed
Edit2: The headers also have no Auth or Authentification property…
Edit3: I cant even find if other location exists except the header for credentials/authentification. But this must be possible right? I mean c# can somehow read this stuff from somewhere…
Any Idea what I can try to find the credentials?
To make your C# client to send its networkCredentials as HTTP Basic Authentication to your Nodejs server; the server should return a response whose header contains a HTTP 401 Unauthorized status and a WWW-Authenticate field if the request does not contain the Authorization header. This will cause your C# client retry the POST with Authorization header.
This process it is called Authentication challenge in case you want to search for more info.
There are serveral packages that does that for you; like http-auth or you can code it by hand (it is not very hard as it is just a matter of checking the existence of the Authorization header in the request and, if there is none or incorrect credentials, make a 401 response with a WWW-Authenticate field)
i.e. from the top of my head:
var http = require('http');
var _server = http.createServer(listener);
_server.listen(1234);
console.log('started');
function listener(req, res) {
if (!req.headers.authorization) {
res.statusCode = 401;
res.statusMessage = 'Unauthorized';
res.setHeader('WWW-Authenticate', 'Basic');
res.end();
}
}

How do I send basic authentication credentials for a prem wcf https service over azure relay C#?

I have a WebAapp on Azure that sends a request to Azure Relay. It should transfer to a listener on premises WCF HTTPS service hosted on IIS that requires basic authentication. How do I send authorization basic header for the onprem WCF service over the Azure Relay . How do I send ? example,
"Authorization": "Basic 239837987XYC"
I have used channel factory,
var ChannelFactory<Overview.MyChannel> cf;
var relayNamespace ="myrelaynamespace";
var relayListener = "myrelaylistener";
var endPointAddress = new EndpointAddress(ServiceBusEnvironment.CreateServiceUri("https", relayNamespace, relayListener));
cf = new ChannelFactory<Overview.ItServiceManagementAOChannel>(binding, endPointAddress);
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = "onpremWCFusername";
loginCredentials.UserName.Password = "onpremWCFpassword";
cf.Endpoint.Behaviors.Add(new TransportClientEndpointBehavior
{
TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(ConfigurationManager.AppSettings.Get("WcfRelayKeyName"), ConfigurationManager.AppSettings.Get("WcfRelayKey"))
});
cf.Endpoint.Behaviors.Add(loginCredentials);
I get the Error: The value could not be added to the collection, as the collection already contains an item of the same type: 'System.ServiceModel.Description.ClientCredentials'. This collection only supports one instance of each type.
Parameter name: item
using (var ch = cf.CreateChannel())
{
try
{
var resp = ch.CreateTaskAsync(req).Result;
}
}
Try to specify the windows credential as client credential.
factory.Credentials.Windows.ClientCredential.UserName = "administrator";
factory.Credentials.Windows.ClientCredential.Password = "123456";
IService sv = factory.CreateChannel();
Feel free to let me know if the problem still exists.

IdentityServer3 connect/token endpoint always return 401: unauthorized

I am trying to setup IdentityServer3 for my project.
When I run IdentityServer3 on local development machine it works all fine, but when I host it on the shared server I get a 401 error. I am trying to access token using endpoint connect\token. Here is the configuration for identityserver3
IdentityServerOptions identityServerOptions = new IdentityServerOptions
{
SiteName = "Ripple IdentityServer",
SigningCertificate = LoadCertificate(),
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions
{
EnablePostSignOutAutoRedirect = true,
},
LoggingOptions = new LoggingOptions
{
EnableWebApiDiagnostics = true,
WebApiDiagnosticsIsVerbose = true,
EnableHttpLogging = true,
EnableKatanaLogging = true
},
Factory = factory,
};
The strange thing is I am not getting any logs. I know the logs are working because when I access the connect/authorize endpoint, I can see log information. Here is my client registration
client = new Client
{
ClientId = app.Id,
ClientName = app.Name,
Flow = Flows.ResourceOwner,
AllowedScopes = app.AllowedScopes.Split(';').ToList(),
AllowedCorsOrigins = new List<string> { "*" }
};
if (app.Secret != null && app.Secret != "")
{
client.ClientSecrets = new System.Collections.Generic.List<Secret>();
app.Secret = app.Secret.Replace("{", "").Replace("}", "");
string[] secrets = app.Secret.Split(',');
foreach (var s in secrets)
{
client.ClientSecrets.Add(new Secret(s.Sha256()));
}
}
Here is the client code to get access token
var data = new StringContent(string.Format("grant_type=password&username={0}&password={1}&Domain={2}&scope={3}",
HttpUtility.UrlEncode(username),
HttpUtility.UrlEncode(password),
HttpUtility.UrlEncode(domainId),
HttpUtility.UrlEncode(requiredScope)), Encoding.UTF8, "application/x-www-form-urlencoded");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", applicationId, appSecretKey))));
HttpResponseMessage response = client.PostAsync("connect/token", data).Result;
Without logs, I am totally lost. Where should I look for more information to debug?
Found solution. Shared hosting like godaddy did not support Basic authentication. So request to access token was getting rejected on server level. That was the reason why no log file was not are getting generated.
To work around this problem, I have to implement my own version on ISecretParser. In this implementation i parsed of my own authentication header
e.g. Authentication MyAuth ClientID:ClientSecret
Then register this parser with IdentityServerServiceFactory and it worked like charm.
I hope this solution will help others who are trying to host IdentiyServer3 on shared servers.

Categories

Resources