dotnet / Docker.DotNet

:whale: .NET (C#) Client Library for Docker API
https://www.nuget.org/packages/Docker.DotNet/
MIT License
2.23k stars 381 forks source link

TLS Authentication fails using Docker.DotNet. Works perfectly using curl or the docker daemon #606

Closed carlos-sarmiento closed 1 year ago

carlos-sarmiento commented 1 year ago

Output of dotnet --info:

.NET SDK:
 Version:   7.0.102
 Commit:    4bbdd14480

Runtime Environment:
 OS Name:     ubuntu
 OS Version:  20.04
 OS Platform: Linux
 RID:         ubuntu.20.04-x64
 Base Path:   /usr/share/dotnet/sdk/7.0.102/

Host:
  Version:      7.0.2
  Architecture: x64
  Commit:       d037e070eb

.NET SDKs installed:
  5.0.408 [/usr/share/dotnet/sdk]
  6.0.405 [/usr/share/dotnet/sdk]
  7.0.102 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 5.0.17 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.13 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.17 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.13 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  Not set

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

What version of Docker.DotNet?:

3.125.12

Steps to reproduce the issue:

  1. I'm trying to use Docker.DotNet to connect to a remote docker instance that is protected using TLS and Client Auth
  2. I've tested the setup using both curl and the docker cli and the mutual auth component works correctly.
    • docker context update --docker "host=tcp://somedomain.com:2376,ca=ca.pem,cert=server.pem,key=server-key.pem" local-https
    • curl https://somedomain.com:2376/images/json --cert server.pem --key server-key.pem --cacert ca.pem
  3. I packaged my cert into a pfx with this command:
    • openssl pkcs12 -export -inkey server-key.pem -in server.pem -out key.pfx -certfile ca.pem

What actually happened?: When running using Docker.Net it fails with SSL Authentication errors: On the dotnet app:

      System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
       ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
       ---> Interop+Crypto+OpenSslCryptographicException: error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate
         --- End of inner exception stack trace ---
         at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount)
         at System.Net.Security.SslStreamPal.HandshakeInternal(SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions, SelectClientCertificate clientCertificateSelectionCallback)
         --- End of inner exception stack trace ---
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean receiveFirst, Byte[] reAuthenticationData, CancellationToken cancellationToken)
         at Microsoft.Net.Http.Client.ManagedHandler.ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
         at Microsoft.Net.Http.Client.ManagedHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
         at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
         at Docker.DotNet.DockerClient.PrivateMakeRequestAsync(TimeSpan timeout, HttpCompletionOption completionOption, HttpMethod method, String path, IQueryString queryString, IDictionary`2 headers, IRequestContent data, CancellationToken cancellationToken)
         at Docker.DotNet.DockerClient.MakeRequestAsync(IEnumerable`1 errorHandlers, HttpMethod method, String path, IQueryString queryString, IRequestContent body, IDictionary`2 headers, TimeSpan timeout, CancellationToken token)
         at Docker.DotNet.ContainerOperations.ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken)
...

On the docker host:

15:52:44 http: TLS handshake error from 10.1.0.93:49520: tls: failed to verify client certificate: x509: certificate signed by unknown authority

What did you expect to happen?: Normal, successful connection

Additional information:

carlos-sarmiento commented 1 year ago

Ok, so after more debugging I figured out the issue. Leaving the information here in case others are facing the same problem.

My certificates are signed by a proper CA authority, this means that the client auth cert is signed by an intermediate authority which is itself signed by the root CA. In these kind of circumstances, the client is expected to send to the server the full certificate chain right until the root CA. So in my case, it would be sending both the ClientCertificate and the Intermediate Certificate. With those two, the server can use the root CA to validate the intermediate and then the client and everything would work correctly.

Dotnet does not do that, instead it sends only the Client certificate to the server, signed by the intermediate. Since the server does not have the intermediate cert, it cannot validate the client and therefore rejects the request. This is a known bug on dotnet and it seems to be getting fixed in 8.0

In the meantime, a workaround is to include the intermediate cert on the ca.pem file installed on the Docker server. This way everything can be validated correctly.