Descrição do problema
Para as necessidades de controle remoto do Docker, o Docker pode fornecer uma API da web.
Essa API pode não exigir autenticação (o que é altamente desencorajado) ou usar autenticação de certificado.
O problema é que a autenticação de certificado nativo não fornece a verificação de revogação de certificado. E isso pode ter sérias conseqüências.
Eu quero dizer como eu resolvi esse problema.
Resolução de problemas
Antes de tudo, direi o que falarei sobre o Docker for Windows. O Linux pode não ser tão ruim assim, mas não sobre isso agora.
O que nós temos? Temos um Docker, com este tipo de configuração:
{ "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" }
Os clientes podem se conectar com seus certificados, mas esses certificados não são verificados quanto à revogação.
A idéia de resolver o problema é escrever seu próprio serviço de proxy, que atuaria como intermediário. Nosso serviço será instalado no mesmo servidor que o Docker, ele pegará a porta 2376, se comunicará com o Docker via //./pipe/docker_engine.
Sem pensar duas vezes, criei um projeto ASP.NET Core e fiz o proxy mais simples:
Código de proxy mais simples 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); } } } });
Isso foi suficiente para solicitações simples de GET e POST da API do Docker. Mas isso não é suficiente, porque para operações mais complexas (que requerem entrada do usuário), o Docker usa algo semelhante ao WebSocket. A emboscada foi que o Kestrel recusou-se a aceitar solicitações vindas do Docker Client, citando o fato de que não havia nenhum corpo na solicitação com o cabeçalho Connection: Upgrade. Mas foi sim.
Eu tive que abandonar o Kestrel e escrever um pouco mais de código. De fato - o seu próprio servidor web. Abra independentemente uma porta, crie uma conexão TLS, analise cabeçalhos HTTP, estabeleça uma conexão interna com o Docker e troque fluxos de entrada / saída. E funcionou.
As fontes podem ser encontradas aqui .
Portanto, o aplicativo está escrito e seria necessário executá-lo de alguma forma. A idéia é criar um contêiner com nosso aplicativo, jogar npine: // dentro e publicar a porta 2376
Criar uma imagem do Docker
Para criar a imagem, precisamos de um certificado público de uma autoridade de certificação (ca.cer), que emitirá certificados para os usuários.
Este certificado será instalado nas autoridades de certificação raiz confiáveis do contêiner no qual nosso proxy será iniciado.
A instalação é necessária para o procedimento de verificação de certificado.
Eu não me incomodei em escrever um arquivo do Docker que eu próprio criaria o aplicativo.
Portanto, ele deve ser montado de forma independente. Na pasta com dockerfile, execute:
dotnet publish -c Release -o ..\publish .\DockerTLS\DockerTLS.csproj
Agora devemos ter: Dockerfile
, publish
, ca.cer
. Coletamos a imagem:
docker build -t vitaliyorg.azurecr.io/docker/proxy:1809 . docker push vitaliyorg.azurecr.io/docker/proxy:1809
Obviamente, o nome da imagem pode ser qualquer.
Lançamento
Para iniciar o contêiner, precisamos do certificado do servidor certificate.pfx
e um arquivo com a senha password.txt
. Todo o conteúdo do arquivo é considerado uma senha. Portanto, não deve haver feeds de linha extras.
Deixe tudo isso na pasta: c:\data
no servidor em que o Docker está instalado.
No mesmo servidor, execute:
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
Registo
Com os docker logs
você pode ver quem fez o que. Você também pode ver as tentativas de conexão que falharam.