diff --git a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs index 8ee94cbd..51a5e226 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs +++ b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using System.Security.Cryptography.X509Certificates; +using System.Net.Security; using Google.Protobuf.WellKnownTypes; using Grpc.Core; @@ -539,30 +540,49 @@ private Service.ServiceClient BuildClientForPlatform(Uri url) if (!useUnixSocket) { -#if NET462 +#if NET462_OR_GREATER var handler = new WinHttpHandler(); #else var handler = new HttpClientHandler(); #endif if (_config.UseCertificate) { -#if NET5_0_OR_GREATER - if (File.Exists(_config.CertificatePath)) { + if (File.Exists(_config.CertificatePath)) + { X509Certificate2 certificate = new X509Certificate2(_config.CertificatePath); +#if NET5_0_OR_GREATER handler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) => { // the the custom cert to the chain, Build returns a bool if valid. chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; chain.ChainPolicy.CustomTrustStore.Add(certificate); return chain.Build(cert); }; - } else { - throw new ArgumentException("Specified certificate cannot be found."); - } +#elif NET462_OR_GREATER + handler.ServerCertificateValidationCallback = (message, cert, chain, errors) => { + if (errors == SslPolicyErrors.None) { return true; } + + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + chain.ChainPolicy.ExtraStore.Add(certificate); + + var isChainValid = chain.Build(cert); + + if (!isChainValid) { return false; } + + var isValid = chain.ChainElements + .Cast() + .Any(x => x.Certificate.RawData.SequenceEqual(certificate.GetRawCertData())); + + return isValid; + }; #else - // Pre-NET5.0 APIs for custom CA validation are cumbersome. - // Looking for additional contributions here. - throw new ArgumentException("Custom certificate authorities not supported on this platform."); + throw new ArgumentException("Custom Certificates are not supported on your platform"); #endif + } + else + { + throw new ArgumentException("Specified certificate cannot be found."); + } } return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions { diff --git a/src/OpenFeature.Contrib.Providers.Flagd/README.md b/src/OpenFeature.Contrib.Providers.Flagd/README.md index f8ceb8ab..19c2146a 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/README.md +++ b/src/OpenFeature.Contrib.Providers.Flagd/README.md @@ -89,6 +89,7 @@ The URI of the flagd server to which the `flagd Provider` connects to can either Note that if `FLAGD_SOCKET_PATH` is set, this value takes precedence, and the other variables (`FLAGD_HOST`, `FLAGD_PORT`, `FLAGD_TLS`, `FLAGD_SERVER_CERT_PATH`) are disregarded. +Note that if you are on `NET462` through `NET48` as the target framework for your project, you are required to enable TLS and supply a certificate path as part of your configuration. This is a limitation Microsoft has [documented](https://learn.microsoft.com/en-us/aspnet/core/grpc/netstandard?view=aspnetcore-7.0#net-framework). If you rely on the environment variables listed above, you can use the empty constructor which then configures the provider accordingly: