API remota do Docker de autenticação de certificado com verificação de revogação

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.

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


All Articles