.net-corehttps.net-5testcontainerstestserver

Calling thirdparty container with .net core TestHost/TestServer via SSL: Bypass SSL Validation using Testservers CreateClient() method


I am trying to add keycloak as a testcontainer to my .net core (5) integration tests using the dotnet-testcontainers library .

My Problem is, I am struggling with HTTPS-Support having a container using self-signed certificates and TestServer-Class for my integration tests.

To be precise, I am using Microsofts TestServer class to create real API requests with an in-memory config for using a keycloak-testcontainer with exposed port 8443 and its self-signed certificate.

The Problem is: I can’t add a HttpClientHandler to TestServers HttpClient(created via serverCreateClient()) to allow non-trusted certs within this handler. I have created a concrete example here on branch apitests-https. The failing test can be found here, its in the SucceedsWhenGetRequestWithTokenReturnsListOfArticlestest method. I added some Comments to the class and the Startup.cs of DemoApi - Project that shows what i've tried.

As a result, the TestServers internal Jwt Middleware uses the default HttpClient and throws the following AuthenticationException:

 ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'System.String'.
 ---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors
   at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---

I already tried multiple things to make it work which are commented in the code.

using it via UseEnvironment("Testing") when creating the TestServer-Instance in the Api Test. Debugging shows the code is called, but still the exception occurs.

So, honestly I am a bit out of ideas on how to make this work with TestServer or otherwise. Essentially, I need the handler I use in BaseFixture.GetTestToken()/KeycloakTest.cs to be also used within my TestServer instance, but can't apply it in the CreateClient() which does not accept parameters. Any help is highly appreciated, may it be a solution or a hint to another way to solve this. TestServer is not necessarily fixed when there's another way to work this out.


Solution

  • Ok, I worked it out. Imho it is not the best solution, but as long as I don't have another one, this should work. All I had to do was:

    1. Add IWebHostEnvironment as a field to Startup.cs and inject in in Startup() constructor.

    Startup then looks like this:

            private IWebHostEnvironment CurrentEnv { get; }
    
            public Startup(IWebHostEnvironment env, IConfiguration configuration)
            {
                Configuration = configuration;
                CurrentEnv = env;
            }
    

    From here on, the rest was pretty easy. In my ConfigureServices() method, I can now check via CurrentEnv.IsEnvironment("Testing") if I am in an integration test, and for these I could bypass ssl validation by setting the jwt BackChannelHandler manually.

    Code:

    services.ConfigureJwtAuthentication(options =>
                {
                    options.Audience = Configuration["Jwt:Audience"];
                    options.Authority = Configuration["Jwt:Issuer"];
                    options.TokenValidationParameters.ValidIssuer = options.Authority;
    
                    if (CurrentEnv.IsEnvironment("Testing"))
                    {
                        options.BackchannelHttpHandler = new HttpClientHandler()
                        {
                            ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
                        };
                    }
                });
    

    I have to admit, this feels hacky and at best I would love to not need to touch any application related code to make this test work. Best solution would be to get the certificate from my keycloak testcontainer and add it automatically to the trusted certs of the TestServers Application instance. If anyone could give another answer / a hint on how to get along with this, I'd be happy to see it. But for now this is what works best.