SSL errors using Service Fabric ServicePartitionClient - c#

We have a singleton service fabric service that needs to communicate to a partitioned service, both of which are running in a secure cluster with certificate-based authentication. We are using ServicePartitionClient to do the talking. What follows is a simplified version of our implementations of ICommunicationClient and ICommunicationClientFactory as required by the ServicePartitionClient.
The client:
public class HttpCommunicationClient : ICommunicationClient
{
// Lots of fields omitted for simplicity.
public HttpCommunicationClient(HttpClientWrapper client, Uri baseAddress)
{
this.HttpClient = client;
this.BaseAddress = baseAddress;
}
public Uri BaseAddress { get; set; }
// Wraps System.Net.Http.HttpClient to do stuff like add default headers
public HttpClientWrapper HttpClient { get; }
public ResolvedServicePartition ResolvedServicePartition { get; set; }
public string ListenerName { get; set; }
public ResolvedServiceEndpoint Endpoint { get; set; }
public Uri GetUri(string relativeUri)
{
return new Uri(this.BaseAddress, relativeUri);
}
}
The factory:
// Note that this base class is under the
// Microsoft.ServiceFabric.Services.Communication.Client namespace,
// it's not something we wrote
public class HttpCommunicationClientFactory :
CommunicationClientFactoryBase<HttpCommunicationClient>
{
// Lots of fields omitted for simplicity.
private readonly HttpClientWrapper client;
public HttpCommunicationClientFactory(
ServicePartitionResolver resolver,
IEnumerable<IExceptionHandler> exceptionHandlers)
: base(resolver, exceptionHandlers, null)
{
// There's a bunch of other args that are omitted for clarity.
this.client = new HttpClientWrapper();
}
protected override Task<HttpCommunicationClient> CreateClientAsync(
string endpoint,
CancellationToken cancellationToken)
{
HttpCommunicationClient client = new HttpCommunicationClient(
this.client,
new Uri(endpoint));
if (this.ValidateClient(endpoint, client))
{
return Task.FromResult(client);
}
else
{
throw new ArgumentException();
}
}
protected override bool ValidateClient(HttpCommunicationClient client)
{
// Not much to validate on httpclient
return true;
}
protected override bool ValidateClient(
string endpoint,
HttpCommunicationClient client)
{
return !string.IsNullOrWhiteSpace(endpoint);
}
protected override void AbortClient(HttpCommunicationClient client)
{
}
}
And here's an example of how everything is being constructed and used:
internal class FooFabricClient : IDisposable
{
// Lots of fields omitted for simplicity.
private readonly HttpCommunicationClientFactory clientFactory;
private readonly ServicePartitionClient<HttpCommunicationClient> partitionClient;
public FooFabricClient(Uri fabricUri, ServicePartitionKey partitionKey = null)
{
this.clientFactory = new HttpCommunicationClientFactory(
ServicePartitionResolver.GetDefault());
this.partitionClient = new ServicePartitionClient<HttpCommunicationClient>(
this.clientFactory,
fabricUri,
partitionKey,
retrySettings: new OperationRetrySettings());
}
public async Task<Foo> GetFooAsync()
{
return await this.partitionClient.InvokeWithRetryAsync(async (client) =>
{
// Note: See above for actual GetUri() implementation and context,
// but this will give us a Uri composed of the client's BaseAddress
// (passed in during HttpCommunicationClientFactory.CreateClientAsync())
// followed by "/foo"
Uri requestUri = client.GetUri("foo");
return await client.HttpClient.GetAsync<Foo>(requestUri);
});
}
Now, the issue is that when I call GetFooAsync(), it throws an exception saying this:
Could not establish trust relationship for the SSL/TLS secure channel. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
After some debugging, we found that this is most likely due to the fact that we get the internal service fabric IP address (e.g., 10.0.0.4) as HttpCommunicationClient.BaseAddress, so when we make our API call the server cert doesn't validate against the domain in the request. As a temporary fix we've done the following in the calling service:
ServicePointManager.ServerCertificateValidationCallback += (a, b, c, d) => true;
Of course, we'd rather not just blindly say "yup, looks good" when validating server certs, so how should we go about resolving this issue? Is there another way to set up the client(s) so that they can properly validate the server cert on the request without needing our own callback? Or do we just need to put acceptable thumbprints in our config and compare against those in the ServicePointManager callback or whatever?

Related

Custom IWebProxy implementation, when using with HttpClient doesn't detect Credentials change?

I'm trying to write a basic rotating proxy implementation for HttpClient via the IWebProxy interface, problem is when changing the IWebProxy.Credentials property inside GetProxy the request doesn't actually use the credentials set and therefore the proxy returns error 407:
namespace ProxyTestMin
{
public record WebProxyEntry(Uri Uri, ICredentials Credentials);
public class MyWebProxy : IWebProxy
{
private List<WebProxyEntry> _proxies;
private int _position = 0;
public MyWebProxy(List<WebProxyEntry> proxies) => _proxies = proxies;
public ICredentials? Credentials { get; set; }
public bool IsBypassed(Uri host) => false;
public Uri? GetProxy(Uri destination)
{
_position++;
_position %= _proxies.Count;
Credentials = _proxies[_position].Credentials;
return _proxies[_position].Uri;
}
}
public class Program
{
static List<WebProxyEntry> proxies = new()
{
new(new("http://XX.XX.XX.XX:3128"), new NetworkCredential("user1", "pass1")),
new(new("http://YY.YY.YY.YY:3128"), new NetworkCredential("user2", "pass2")),
};
static async Task Main(string[] args)
{
var httpClientHandler = new HttpClientHandler() { Proxy = new MyWebProxy(proxies) };
using var httpClient = new HttpClient(httpClientHandler, true);
for (int i = 0; i < proxies.Count; i++)
{
var response = await httpClient.GetAsync("https://api.ipify.org");
Console.WriteLine($"{i + 1}:" + await response.Content.ReadAsStringAsync());
}
}
}
}
If I disable authentication on the proxy it works OK, and if I set the user/pass to be the same on all proxies and just set Credentials in the MyWebProxy constructor it also works fine. Unfortunately though I need to be able to have different credentials for each proxy, and it seems that if Credentials is set anywhere other than the constructor the GetAsync call uses the wrong credentials for the proxy.
Does anyone know if there's a way to workaround this without creating multiple HttpClientHandler/HttpClient instances?

How to get the methods from a generic interface being passed into a generic class?

I have this code below (possibly incorrectly implemented) where I've been trying to create a generic way to use multiple web services (to reduce code redundancy). So far so good up until "ClaimGetAsync()" on the last line.
public static class Test {
public static void TestWebServiceA() {
var ws = new WebService<ARMDevelopment.WebServiceAWI>();
ws.GetClaim(new ARMDevelopment.WebServiceAWIClient(), new ARMDevelopment.User(),
"https://trustonline.delawarecpf.com/tows/webserviceawi.svc", "userName", "password");
}
public static void TestWebServiceB() {
var ws = new WebService<BWDevelopment.WebServiceBW>();
ws.GetClaim(new BWDevelopment.WebServiceBWClient(), new BWDevelopment.User(),
"https://trustonline.delawarecpf.com/tows/webservicebw.svc", "userName", "password");
}
}
public class WebService<T> {
public async void GetClaim<TOne, TTwo>(TOne entity1, TTwo entity2, string url, string userName, string password)
where TOne : IWebServiceClient, new() // class, new()
where TTwo : IUser, new() // limits the TTwo class to implement IUser -> IUser created by me
{
TOne webServiceClient = new TOne(); // entity1;
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
EndpointAddress endpoint = new EndpointAddress(url);
ChannelFactory<T> channelFactory = new ChannelFactory<T>(binding, endpoint);
TTwo user = new TTwo(); // entity2;
user.UserName = await entity1.EncryptValueAsync(userName);
user.Password = await entity1.EncryptValueAsync(password);
T wcfClient = channelFactory.CreateChannel();
var response = wcfClient.ClaimGetAsync(user, 12345);
}
}
IUser and IWebServiceClient I created and User and WebServiceClient in the web service files inherit them as they are partials.
public interface IUser {
string UserName { get; set; }
string Password { get; set; }
}
public interface IWebServiceClient
{
Task<string> EncryptValueAsync(string text);
}
But now the last line in the method has no way to discover "ClaimGetAsync" because wcfClient is a generic T
var response = wcfClient.ClaimGetAsync(user, 12345);
How can I get the generic to find/discover ClaimGetAsync() so that I can use it?
UPDATE - A member asked me to post some of the interface code that gets generated from the web service util svcutil.exe:
Here is one from the AWI web service and I have about 20_ more from all the other services we use from this provider
namespace WebServices_Development_TrustOnline_ARM
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.ServiceModel.ServiceContractAttribute(Namespace="urn:trustonline", ConfigurationName="WebServices_Development_TrustOnline_ARM.WebServiceAWI")]
public interface WebServiceAWI
{
[System.ServiceModel.OperationContractAttribute(Action="urn:trustonline/EncryptValue", ReplyAction="urn:trustonline/EncryptValue")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
System.Threading.Tasks.Task<string> EncryptValueAsync(string text);
}
[System.ServiceModel.OperationContractAttribute(Action="urn:trustonline/ClaimGet", ReplyAction="urn:trustonline/ClaimGet")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
[return: System.ServiceModel.MessageParameterAttribute(Name="webserviceresult")]
System.Threading.Tasks.Task<WebServices_Development_TrustOnline_ARM.WebServiceResult> ClaimGetAsync(WebServices_Development_TrustOnline_ARM.User user, int claimnumber);
}
public interface IClaimsService
{
Task<IEnumerable<Claim>> ClaimGetAsync<TUser>(TUser user, int claimsNumber);
}
public class WebService<T> where T : IClaimsService
{
}

Can't get a valid response from a REST web service using System.Net.Http.HttpClient

I am using this test method (and helper class) to verify the response from an external web service:
[TestMethod]
public void WebServiceReturnsSuccessResponse()
{
using (var provider = new Provider(new Info()))
using (var result = provider.GetHttpResponseMessage())
{
Assert.IsTrue(result.IsSuccessStatusCode);
}
}
private class Info : IInfo
{
public string URL { get; set; } =
"https://notreallythe.website.com:99/service/";
public string User { get; set; } = "somename";
public string Password { get; set; } = "password1";
}
I can't get this test to pass; I always get a 500 - Internal Server Error result. I have connected via an external utility (Postman) - so the web service is up and I can connect with the url & credentials that I have.
I think the problem is in my instantiation of the HttpClient class, but I can't determine where. I am using Basic authentication:
public class Provider : IProvider, IDisposable
{
private readonly HttpClient _httpClient;
public Provider(IInfo config){
if (config == null)
throw new ArgumentNullException(nameof(config));
var userInfo = new UTF8Encoding().GetBytes($"{config.User}:{config.Password}");
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.URL),
DefaultRequestHeaders =
{
Accept = { new MediaTypeWithQualityHeaderValue("application/xml")},
Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(userInfo)),
ExpectContinue = false,
},
};
}
public HttpResponseMessage GetHttpResponseMessage()
{
return _httpClient.GetAsync("1234").Result;
}
}
The response I get back appears to go to the correct endpoint; the RequestUri in the response looks exactly like I expect, https://notreallythe.website.com:99/service/1234.
You need to load up Fiddler and do a recording of the HTTP traffic when this operation succeeds (through the browser).
Then, load up your code, stand up another instance (or window) of Fiddler, and do the same thing with your code. Now, compare the two Fiddler windows to see what is different.
You only need to compare those things in Fiddler that are highlighted in blue. You can ignore the other communications.

How to Enable/Disable Azure Function programmatically

Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.

Application Insights with invalid instrumentation key - how to handle as no error is thrown

Is there a way to capture an error when when an invalid instrumentation key is used when tracing messages to Application Insights?
I'm programmatically specifying an instrumentation key like below but no exception is thrown. I'm trying to build a Logging WebApi that will return a success or failure dependent on whether the message was successfully logged to Application Insights?
TelemetryConfiguration config = TelemetryConfiguration.CreateDefault();
config.InstrumentationKey = "ABC";
client.TrackTrace("Test"),SeverityLevel.Information);
You should implement your own channel that implements ITelemetryChannel, and handle exceptions as you want.
Here's a naive example:
public class SynchronousTelemetryChannel : ITelemetryChannel
{
private const string ContentType = "application/x-json-stream";
private readonly List<ITelemetry> _items;
private object _lock = new object();
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public SynchronousTelemetryChannel()
{
_items = new List<ITelemetry>();
EndpointAddress = "https://dc.services.visualstudio.com/v2/track";
}
public void Send(ITelemetry item)
{
lock (_lock)
{
_items.Add(item);
}
}
public void Flush()
{
lock (_lock)
{
try
{
byte[] data = JsonSerializer.Serialize(_items);
new Transmission(new Uri(EndpointAddress), data, ContentType, JsonSerializer.CompressionType).SendAsync().Wait();
_items.Clear();
}
catch (Exception e)
{
// Do whatever you want.
}
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
Then, initialize the configuration with your channel via code or configuration file:
TelemetryConfiguration.Active.TelemetryChannel = new SynchronousTelemetryChannel();

Categories