Хабрахабр

Docker Remote API с аутентификацией по сертификату с проверкой отзыва

Для нужд удаленного управления Docker'ом, Docker умеет предоставлять веб-API.
Это API может как вовсе не требовать аутентификации (что крайне не рекомендуется), так и использовать аутентификация по сертификату.

И это может иметь серьезные последствия. Проблема заключается в том, что родная аутентификация по сертификату не предусматривает проверку отзыва сертификата.

Я хочу рассказать как я решил эту проблему.

Возможно в Linux все не так плохо, но сейчас не об этом. Для начала следует сказать что говорить я буду про Docker для Windows.

У нас есть Docker, с вот таким конфигом: Что мы имеем?

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

Клиенты могут подключаться со своими сертификатами, но эти сертификаты не проверяются на предмет отзыва.

Наш сервис будет установлен на том же сервере что и Docker, заберет себе порт 2376, будет общаться с Docker по //./pipe/docker_engine. Идея решения проблемы заключается в том, чтобы написать свой прокси-сервис, который выступал бы в качестве посредника.

NET Core проект и сделал простейшее проксирование: Недолго думая я создал ASP.

Код простейшего прокси

app.Run(async (context) =>
, 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); } } }
});

Но этого мало, т.к. Этого оказалось достаточно для простых запросов GET и POST из Docker API. Засада была в том, что Kestrel наотрез отказывался принимать запросы, которые приходили от Docker Client, мотивируя это тем, что в запросе с заголовком Connection: Upgrade не может быть тела. для более сложных операций (требующий пользовательской ввод) Docker использует что-то похожее на WebSocket. А оно было.

По сути — свой web сервер. Пришлось отказаться от Kestrel и написать чуть больше кода. И это сработало. Самостоятельно открывать порт, создавать TLS соединение, парсить HTTP заголовки, устанавливать внутреннее соединение с Docker и обмениваться потоками ввода-вывода.

Исходники можно посмотреть здесь.

Идея заключается в том, чтобы создать контейнер с нашим приложением, прокинуть внутрь npine:// и опубликовать порт 2376 Итак, приложение написано и надо бы его как-то запускать.

Для сборки образа нам потребуется публичный сертификат центра сертификации (ca.cer), который будет выдавать сертификаты пользователям.

Этот сертификат будет установлен в доверенные корневые центры сертификации контейнера, в котором будет запущен наш прокси.

Установка его необходима для процедуры проверки сертификата.

Из папки с dockerfile запускаем: Я не заморачивался написанием такого Docker-файла, который сам бы собирал приложение.
Поэтому его надо собрать самостоятельно.

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

Собираем образ: Сейчас у нас должны быть: Dockerfile, publish, ca.cer.

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

Разумеется, имя образа может быть любое.

Все содержимое файла считается паролем. Для запуска контейнера нам понадобятся сертификат сервера certificate.pfx и файл с паролем password.txt. Поэтому лишних переводов строк быть не должно.

Пусть все это добро находится в папке: c:\data на сервере, где установлен Docker.

На этом же сервере запускаем:

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

Там же можно видеть попытки подключения, которые завершились неудачно. С помощью docker logs можно видеть кто что делал.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть