Docker Remote-API für Zertifikatauthentifizierung mit Sperrüberprüfung

Problembeschreibung


Für die Anforderungen der Fernsteuerung von Docker kann Docker eine Web-API bereitstellen.
Diese API erfordert möglicherweise überhaupt keine Authentifizierung (von der dringend abgeraten wird) oder verwendet die Zertifikatauthentifizierung.


Das Problem ist, dass die native Zertifikatauthentifizierung keine Überprüfung der Zertifikatsperrung ermöglicht. Und das kann schwerwiegende Folgen haben.


Ich möchte erzählen, wie ich dieses Problem gelöst habe.


Lösung


Zunächst werde ich sagen, worüber ich über Docker für Windows sprechen werde. Linux ist vielleicht gar nicht so schlecht, aber jetzt nicht mehr.


Was haben wir Wir haben einen Docker mit dieser Art von Konfiguration:


{ "hosts": ["tcp://0.0.0.0:2376", "npipe://"], "tlsverify": true, "tlscacert": "C:\\ssl\\ca.cer", "tlscert": "C:\\ssl\\server.cer", "tlskey": "C:\\ssl\\server.key" } 

Clients können eine Verbindung mit ihren Zertifikaten herstellen, diese Zertifikate werden jedoch nicht auf Widerruf geprüft.


Die Idee zur Lösung des Problems besteht darin, einen eigenen Proxy-Service zu schreiben, der als Vermittler fungiert. Unser Service wird auf demselben Server wie Docker installiert, nimmt Port 2376 auf und kommuniziert mit Docker über //./pipe/docker_engine.


Ohne nachzudenken, habe ich ein ASP.NET Core-Projekt erstellt und das einfachste Proxy durchgeführt:


Einfachster Proxy-Code
 app.Run(async (context) => { var certificate = context.Connection.ClientCertificate; if (certificate != null) { logger.LogInformation($"Certificate subject: {certificate.Subject}, serial: {certificate.SerialNumber}"); } var handler = new ManagedHandler(async (host, port, cancellationToken) => { var stream = new NamedPipeClientStream(".", "docker_engine", PipeDirection.InOut, PipeOptions.Asynchronous); var dockerStream = new DockerPipeStream(stream); await stream.ConnectAsync(NamedPipeConnectTimeout.Milliseconds, cancellationToken); return dockerStream; }); using (var client = new HttpClient(handler, true)) { var method = new HttpMethod(context.Request.Method); var builder = new UriBuilder("http://dockerengine") { Path = context.Request.Path, Query = context.Request.QueryString.ToUriComponent() }; using (var request = new HttpRequestMessage(method, builder.Uri)) { request.Version = new Version(1, 11); request.Headers.Add("User-Agent", "proxy"); if (method != HttpMethod.Get) { request.Content = new StreamContent(context.Request.Body); request.Content.Headers.ContentType = new MediaTypeHeaderValue(context.Request.ContentType); } using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted)) { context.Response.ContentType = response.Content.Headers.ContentType.ToString(); var output = await response.Content.ReadAsStreamAsync(); await output.CopyToAsync(context.Response.Body, 4096, context.RequestAborted); } } } }); 

Dies war ausreichend für einfache GET- und POST-Anforderungen von der Docker-API. Das reicht aber nicht, weil Für komplexere Vorgänge (die Benutzereingaben erfordern) verwendet Docker etwas Ähnliches wie WebSocket. Der Hinterhalt bestand darin, dass Kestrel es rundweg ablehnte, Anfragen des Docker-Clients anzunehmen, und die Tatsache anführte, dass die Anfrage mit dem Header "Verbindung: Upgrade" keinen Text enthalten konnte. Aber es war.


Ich musste Kestrel verlassen und etwas mehr Code schreiben. In der Tat - Ihr eigener Webserver. Öffnen Sie unabhängig einen Port, erstellen Sie eine TLS-Verbindung, analysieren Sie HTTP-Header, stellen Sie eine interne Verbindung mit Docker her und tauschen Sie Eingabe- / Ausgabestreams aus. Und es hat funktioniert.


Quellen finden Sie hier .


Die Anwendung ist also geschrieben und es wäre notwendig, sie irgendwie auszuführen. Die Idee ist, mit unserer Anwendung einen Container zu erstellen, npine: // hineinzuwerfen und Port 2376 zu veröffentlichen


Erstellen Sie ein Docker-Image


Um das Image zu erstellen, benötigen wir ein öffentliches Zertifikat einer Zertifizierungsstelle (ca.cer), das den Benutzern Zertifikate ausstellt.


Dieses Zertifikat wird in den vertrauenswürdigen Stammzertifizierungsstellen des Containers installiert, in dem unser Proxy gestartet wird.


Die Installation ist für die Zertifikatsüberprüfung erforderlich.


Ich habe mir nicht die Mühe gemacht, eine solche Docker-Datei zu schreiben, dass ich die Anwendung selbst erstellen würde.
Daher muss es unabhängig zusammengebaut werden. Führen Sie im Ordner mit der Docker-Datei Folgendes aus:


 dotnet publish -c Release -o ..\publish .\DockerTLS\DockerTLS.csproj 

Wir sollten jetzt haben: Dockerfile , publish , ca.cer . Wir sammeln das Bild:


 docker build -t vitaliyorg.azurecr.io/docker/proxy:1809 . docker push vitaliyorg.azurecr.io/docker/proxy:1809 

Natürlich kann der Name des Bildes beliebig sein.


Starten


Um den Container zu starten, benötigen wir das Zertifikat des Servers certificate.pfx und eine Datei mit dem Passwort password.txt . Alle Dateiinhalte gelten als Passwort. Daher sollten keine zusätzlichen Zeilenvorschübe vorhanden sein.


Lassen Sie all diese Dinge in dem Ordner sein: c:\data auf dem Server, auf dem Docker installiert ist.


Führen Sie auf demselben Server Folgendes aus:


 docker run --name docker-proxy -d -v "c:/data:c:/data" -v \\.\pipe\docker_engine:\\.\pipe\docker_engine --restart always -p 2376:2376 vitaliyorg.azurecr.io/docker/proxy:1809 

Protokollierung


Mit docker logs Sie sehen, wer was getan hat. Sie können auch fehlgeschlagene Verbindungsversuche anzeigen.

Source: https://habr.com/ru/post/de433176/


All Articles