Certificado de Docker Remote API de autenticación con verificación de revocación

Descripción del problema


Para las necesidades de control remoto de Docker, Docker puede proporcionar una API web.
Es posible que esta API no requiera autenticación (lo cual es altamente desaconsejado), o use autenticación de certificado.


El problema es que la autenticación de certificados nativos no proporciona la verificación de revocación de certificados. Y esto puede tener serias consecuencias.


Quiero decir cómo resolví este problema.


Resolución de problemas


En primer lugar, diré lo que hablaré sobre Docker para Windows. Linux puede no ser tan malo, pero no se trata de eso ahora.


Que tenemos Tenemos un Docker, con este tipo de configuración:


{ "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" } 

Los clientes pueden conectarse con sus certificados, pero no se verifica la revocación de estos certificados.


La idea de resolver el problema es escribir su propio servicio proxy, que actuaría como intermediario. Nuestro servicio se instalará en el mismo servidor que Docker, recogerá el puerto 2376 y se comunicará con Docker a través de //./pipe/docker_engine.


Sin pensarlo dos veces, creé un proyecto ASP.NET Core e hice el proxy más simple:


Código de proxy más simple
 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); } } } }); 

Esto fue suficiente para solicitudes GET y POST simples de la API de Docker. Pero esto no es suficiente, porque Para operaciones más complejas (que requieren la entrada del usuario), Docker usa algo similar a WebSocket. La emboscada fue que Kestrel se negó rotundamente a aceptar solicitudes que provenían del Cliente Docker, citando el hecho de que no podía haber ningún cuerpo en la solicitud con la conexión: encabezado de actualización. Pero lo fue.


Tuve que abandonar Kestrel y escribir un poco más de código. De hecho, su propio servidor web. Abra un puerto de forma independiente, cree una conexión TLS, analice los encabezados HTTP, establezca una conexión interna con Docker e intercambie flujos de entrada / salida. Y funcionó.


Las fuentes se pueden encontrar aquí .


Entonces, la aplicación está escrita y sería necesario ejecutarla de alguna manera. La idea es crear un contenedor con nuestra aplicación, lanzar npine: // dentro y publicar el puerto 2376


Construir una imagen de Docker


Para construir la imagen, necesitamos un certificado público de una autoridad de certificación (ca.cer), que emitirá certificados a los usuarios.


Este certificado se instalará en las autoridades de certificados raíz de confianza del contenedor en el que se lanzará nuestro proxy.


Instalarlo es necesario para el procedimiento de verificación del certificado.


No me molesté en escribir un archivo Docker que construyera la aplicación yo mismo.
Por lo tanto, debe ensamblarse de forma independiente. Desde la carpeta con dockerfile, ejecute:


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

Ahora deberíamos tener: Dockerfile , publish , ca.cer . Recopilamos la imagen:


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

Por supuesto, el nombre de la imagen puede ser cualquiera.


Lanzamiento


Para iniciar el contenedor, necesitamos el certificado del servidor certificate.pfx y un archivo con la contraseña password.txt . Todo el contenido del archivo se considera una contraseña. Por lo tanto, no debe haber saltos de línea adicionales.


Deje que todo esto esté en la carpeta: c:\data en el servidor donde está instalado Docker.


En el mismo servidor, ejecute:


 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 

Registro


Con los docker logs puede ver quién hizo qué. También puede ver los intentos de conexión que fallaron.

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


All Articles