From dea30dc633b8b77f9b6080e082081af7c8d1009b Mon Sep 17 00:00:00 2001 From: odubajDT Date: Wed, 29 Mar 2023 14:03:28 +0200 Subject: [PATCH 1/4] feat: support TLS connection in flagD provider Signed-off-by: odubajDT --- .../FlagdProvider.cs | 33 ++++++++++++-- .../FlagdProviderTest.cs | 44 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs index bcab5830..5fd82201 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; @@ -387,16 +390,40 @@ private static Value ConvertToPrimitiveValue(ProtoValue value) private static Service.ServiceClient buildClientForPlatform(Uri url) { var useUnixSocket = url.ToString().StartsWith("unix://"); + X509Certificate2 certificate = null; if (!useUnixSocket) { + var flagdCertPath = Environment.GetEnvironmentVariable("FLAGD_SERVER_CERT_PATH") ?? ""; + + if (flagdCertPath != "") + { + if (!File.Exists(flagdCertPath)) + { + return null; + } + certificate = new X509Certificate2(flagdCertPath); + + } #if NET462 - return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions + var handler = new WinHttpHandler(); + if (flagdCertPath != "") { + handler.ClientCertificates.Add(certificate); + } + return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions { - HttpHandler = new WinHttpHandler() + HttpHandler = handler })); #else - return new Service.ServiceClient(GrpcChannel.ForAddress(url)); + var handler = new HttpClientHandler(); + if (flagdCertPath != "") + { + handler.ClientCertificates.Add(certificate); + } + return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions + { + HttpHandler = handler + })); #endif } diff --git a/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs b/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs index 7a62ee46..b9093166 100644 --- a/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs +++ b/test/OpenFeature.Contrib.Providers.Flagd.Test/FlagdProviderTest.cs @@ -5,11 +5,55 @@ using Google.Protobuf.WellKnownTypes; using OpenFeature.Error; using ProtoValue = Google.Protobuf.WellKnownTypes.Value; +using System; namespace OpenFeature.Contrib.Providers.Flagd.Test { public class UnitTestFlagdProvider { + [Fact] + public void BuildClientForPlatform_Should_Return_Null_When_FlagdCertPath_Not_Exists() + { + // Arrange + var url = new Uri("https://localhost:5001"); + System.Environment.SetEnvironmentVariable("FLAGD_SERVER_CERT_PATH", "non-existing-path"); + + // Act + var flagdProvider = new FlagdProvider(url); + + // Assert + Assert.Null(flagdProvider.GetClient()); + + // 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() { From be093aaba933f38fa62004235516ac6c92915e96 Mon Sep 17 00:00:00 2001 From: odubajDT Date: Thu, 30 Mar 2023 08:17:09 +0200 Subject: [PATCH 2/4] adapt README Signed-off-by: odubajDT --- src/OpenFeature.Contrib.Providers.Flagd/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenFeature.Contrib.Providers.Flagd/README.md b/src/OpenFeature.Contrib.Providers.Flagd/README.md index 8351a0af..d922adbc 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/README.md +++ b/src/OpenFeature.Contrib.Providers.Flagd/README.md @@ -81,9 +81,10 @@ 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 | | | -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: From 210c5b2fb4c1f75c0e20126c57ad9968288cce09 Mon Sep 17 00:00:00 2001 From: odubajDT Date: Fri, 31 Mar 2023 08:40:43 +0200 Subject: [PATCH 3/4] fixes according to PR review Signed-off-by: odubajDT --- .../FlagdProvider.cs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs index 5fd82201..5ad32cd4 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs +++ b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs @@ -395,7 +395,11 @@ private static Service.ServiceClient buildClientForPlatform(Uri url) if (!useUnixSocket) { var flagdCertPath = Environment.GetEnvironmentVariable("FLAGD_SERVER_CERT_PATH") ?? ""; - +#if NET462 + var handler = new WinHttpHandler(); +#else + var handler = new HttpClientHandler(); +#endif if (flagdCertPath != "") { if (!File.Exists(flagdCertPath)) @@ -403,28 +407,12 @@ private static Service.ServiceClient buildClientForPlatform(Uri url) return null; } certificate = new X509Certificate2(flagdCertPath); - - } -#if NET462 - var handler = new WinHttpHandler(); - if (flagdCertPath != "") { handler.ClientCertificates.Add(certificate); } return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpHandler = handler })); -#else - var handler = new HttpClientHandler(); - if (flagdCertPath != "") - { - handler.ClientCertificates.Add(certificate); - } - return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions - { - HttpHandler = handler - })); -#endif } #if NET5_0_OR_GREATER From e29bbe38dd717b9faeea86aa625ad9a22043dfd0 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Fri, 31 Mar 2023 12:55:42 -0400 Subject: [PATCH 4/4] fixup: add custom TLS handler Signed-off-by: Todd Baert --- .../FlagdProvider.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs index 5ad32cd4..f83b5b33 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs +++ b/src/OpenFeature.Contrib.Providers.Flagd/FlagdProvider.cs @@ -400,14 +400,24 @@ private static Service.ServiceClient buildClientForPlatform(Uri url) #else var handler = new HttpClientHandler(); #endif - if (flagdCertPath != "") - { - if (!File.Exists(flagdCertPath)) - { - return null; + if (flagdCertPath != "") { +#if NET5_0_OR_GREATER + if (File.Exists(flagdCertPath)) { + 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."); } - certificate = new X509Certificate2(flagdCertPath); - handler.ClientCertificates.Add(certificate); +#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."); +#endif } return new Service.ServiceClient(GrpcChannel.ForAddress(url, new GrpcChannelOptions {