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.