From dca5d09126266ffabfeada6b9c90b7373e685869 Mon Sep 17 00:00:00 2001 From: odubajDT Date: Tue, 4 Apr 2023 07:19:02 +0200 Subject: [PATCH] feat: support TLS connection in flagD provider Signed-off-by: odubajDT --- .../FlagdProvider.cs | 33 +++++++++++++-- .../README.md | 3 +- .../FlagdProviderTest.cs | 42 ++++++++++++++++++- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs index cb37bae4..82f141ee 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs +++ b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs @@ -1,6 +1,9 @@ using System; +using System.IO; +using System.Text; using System.Linq; using System.Threading.Tasks; +using System.Security.Cryptography.X509Certificates; using Google.Protobuf.WellKnownTypes; using Grpc.Core; @@ -505,14 +508,36 @@ private static Service.ServiceClient buildClientForPlatform(Uri url) if (!useUnixSocket) { + var flagdCertPath = Environment.GetEnvironmentVariable("FLAGD_SERVER_CERT_PATH") ?? ""; #if NET462 - return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions + var handler = new WinHttpHandler(); +#else + var handler = new HttpClientHandler(); +#endif + if (flagdCertPath != "") { - HttpHandler = new WinHttpHandler(), - })); +#if NET5_0_OR_GREATER + if (File.Exists(flagdCertPath)) { + X509Certificate2 certificate = new X509Certificate2(flagdCertPath); + 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."); + } #else - return new Service.ServiceClient(GrpcChannel.ForAddress(url)); + // 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."); #endif + } + return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions + { + HttpHandler = handler + })); } #if NET5_0_OR_GREATER diff --git a/src/OpenFeature.Contrib.Providers.Flagd/README.md b/src/OpenFeature.Contrib.Providers.Flagd/README.md index 470af5e1..f8ceb8ab 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/README.md +++ b/src/OpenFeature.Contrib.Providers.Flagd/README.md @@ -81,12 +81,13 @@ The URI of the flagd server to which the `flagd Provider` connects to can either | host | FLAGD_HOST | string | localhost | | | port | FLAGD_PORT | number | 8013 | | | tls | FLAGD_TLS | boolean | false | | +| tls certPath | FLAGD_SERVER_CERT_PATH | string | | | | unix socket path | FLAGD_SOCKET_PATH | string | | | | Caching | FLAGD_CACHE | string | | LRU | | Maximum cache size | FLAGD_MAX_CACHE_SIZE | number | 10 | | | Maximum event stream retries | FLAGD_MAX_EVENT_STREAM_RETRIES | number | 3 | | -Note that if `FLAGD_SOCKET_PATH` is set, this value takes precedence, and the other variables (`FLAGD_HOST`, `FLAGD_PORT`, `FLAGD_TLS`) are disregarded. +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. If you rely on the environment variables listed above, you can use the empty constructor which then configures the provider accordingly: diff --git a/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs b/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs index c7681987..1655229c 100644 --- a/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs +++ b/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs @@ -6,14 +6,54 @@ using OpenFeature.Error; using ProtoValue = Google.Protobuf.WellKnownTypes.Value; using System.Collections.Generic; -using System.Linq; using OpenFeature.Model; using System.Threading; +using System; namespace OpenFeature.Contrib.Providers.Flagd.Test { public class UnitTestFlagdProvider { + [Fact] + public void BuildClientForPlatform_Should_Throw_Exception_When_FlagdCertPath_Not_Exists() + { + // Arrange + var url = new Uri("https://localhost:5001"); + System.Environment.SetEnvironmentVariable("FLAGD_SERVER_CERT_PATH", "non-existing-path"); + + // Act & Assert + Assert.Throws(() => new FlagdProvider(url)); + + // Cleanup + System.Environment.SetEnvironmentVariable("FLAGD_SERVER_CERT_PATH", ""); + } + + [Fact] + public void BuildClientForPlatform_Should_Return_Client_For_Non_Unix_Socket_Without_Certificate() + { + // Arrange + var url = new Uri("https://localhost:5001"); + + // Act + var flagdProvider = new FlagdProvider(url); + var client = flagdProvider.GetClient(); + + // Assert + Assert.NotNull(client); + Assert.IsType(client); + } + +#if NET462 + [Fact] + public void BuildClientForPlatform_Should_Throw_Exception_For_Unsupported_DotNet_Version() + { + // Arrange + var url = new Uri("unix:///var/run/flagd.sock"); + + // Act & Assert + Assert.Throws(() => new FlagdProvider(url)); + } +#endif [Fact] public void TestGetProviderName() {