Привет!

В очередной раз собирая образ Docker своего бота для Telegram и используя в качестве базы официальный образ python:3.12.2-alpine3.19, обратил внимание на то, что docker scout показывает наличие свежей уязвимости в pip . Я бы не сказал, что она как‑то влияет на мое приложение, но сам факт наличия потенциальной уязвимости «на борту» контейнера с приложением, которое работает под рутом и с проброшенным сокетом Docker (НЕ лучшая практика!) натолкнул меня на мысль, как можно минимизировать этот риск?

Прежде чем мы начнем, небольшое соглашение:

Я не профессиональный программист, курсов Яндекс Практикума не заканчивал, пишу для таких же новичков в Docker и Python как и я)

Путей существует на самом деле немало, для конкретно моего приложения, я решил изучить вопрос сборки образа на базе самого минимального базового образа Linux - Alpine Linux. Для уменьшения поверхности атаки. И вот тут я столкнулся с парочкой моментов:

Alpine не содержит в себе Python

Ну хорошо, можно же установить, подумал я. Можно то оно можно, вот только размер такого контейнера будет довольно большим, достаточно посмотреть на образ, который собран с использованием в качестве базы python:3.12.2-alpine3.19. В ветке Main репозитория Alpine Linux доступен Python версии 3.11.8, что мне не подходит. Эти моменты можно безболезненно обойти используя для сборки контейнера многоэтапную сборку, где на первом этапе скомпилировать и установить все зависимости:

# First stage
FROM python:3.12.2-alpine3.19 AS builder
COPY requirements.txt .
...
# Install dependencies to the venv path
RUN python3 -m venv --without-pip venv
RUN pip install --no-cache --target="/venv/lib/python3.12/site-packages" -r requirements.txt

Здесь мы за основу берем все тот же официальный образ python:3.12.2-alpine3.19 , копируем файл с зависимостями приложения и выполнив инициализацию виртуального окружения, устанавливаем все зависимости приложения.

Что именно в Python копировать из первого этапа сборки?

Для меня это был самый сложный момент - поиск ничего внятного не подсказывал. Пришлось изучать структуру каталогов Python и методом проб и ошибок выяснил что для корректной работы Python 3.12.2 в образе Alpine Linux достаточно вот такой структуры:

/usr/local/bin/python3
/usr/local/bin/python3.12
/usr/local/lib/python3.12
/usr/local/lib/libpython3.12.so.1.0
/usr/local/lib/libpython3.so

Теперь мы можем скопировать из первого этапа сборки во второй этап нужную нам структуру Python:

# Second unnamed stage
FROM alpine:3.19.1
...
# Сopy only the necessary python files and directories from first stage
COPY --from=builder /usr/local/bin/python3 /usr/local/bin/python3
COPY --from=builder /usr/local/bin/python3.12 /usr/local/bin/python3.12
COPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12
COPY --from=builder /usr/local/lib/libpython3.12.so.1.0 /usr/local/lib/libpython3.12.so.1.0
COPY --from=builder /usr/local/lib/libpython3.so /usr/local/lib/libpython3.so
...

На выходе получаем в моем случае работающее приложение на Python, собранное на основе минимального образа Alpine Linux, у которого по мнению docker scout нет известных уязвимостей.

Полный Dockerfile бота доступен на Github.

А какие практики используете Вы?

Комментарии (11)


  1. MountainGoat
    14.04.2024 14:25
    +4

    Хмм, последний раз когда я этим интересовался, года 3 назад, писали что ставить Питон на образ Alpine - серьёзная ошибка. потому что он потянет с собой столько зависимостей, что в итоге получится надутый мешок, который весит больше, чем если поставить Питон на образ из Убунты, и ещё использует какие-то не те библиотеки. В общем, жирным шрифтом писали так не делать.

    Что-то с тех пор изменилось?


    1. Orenlab Автор
      14.04.2024 14:25

      Сейчас есть официальный образ Python. Мой бот собранный на основе него весил около 91Мб (сборка в 2 этапа), образ же собранный по методу, который я описал в статье весит 89,36Мб. Для более сложных приложений придется доустанавливать кучу библиотек, тогда да - овчинка может не стоить выделки, и время будет потрачено (в том числе на сборку) и размер образа будет раздут.


    1. rs_al
      14.04.2024 14:25

      У multibuild если собирать? Python+poetry+build-essential на Debian например, сборка venv с компиляцией и потом перенос на Alpine + Python?


  1. 9982th
    14.04.2024 14:25
    +4

    CVE-2018-20225

    An issue was discovered in pip (all versions) because it installs the version with the highest version number, even if the user had intended to obtain a private package from a private index. This only affects use of the --extra-index-url option, and exploitation requires that the package does not already exist in the public index (and thus the attacker can put the package there with an arbitrary version number).

    NOTE: it has been reported that this is intended functionality and the user is responsible for using --extra-index-url securely

    Правильно ли я понимаю, что docker scout считает уязвимыми все существующие образы с python в которых pip устанавливается отдельно за последние пять лет и продолжит считать все будущие?


  1. bungu
    14.04.2024 14:25

    А что мешало взять образ 3.12.2-alpine3.19 и обновить в нем pip?


    1. Orenlab Автор
      14.04.2024 14:25

      На pypi последняя версия pip - 24.0. Эта же версия используется в 3.12.2-alpine3.19, если не ошибаюсь


      1. bungu
        14.04.2024 14:25

        Ну в таком случае его вообще можно удалить из поставки


  1. TomSome
    14.04.2024 14:25

    Python 3.12.3 Release Date: April 9, 2024


    1. Orenlab Автор
      14.04.2024 14:25

      Ничего не изменилось: hub.docker.com


  1. ckpunT
    14.04.2024 14:25

    Можете попробовать pyinstaller. Он соберет только необходимые файлы для вашего приложения в один бинарь. Решение спорное, но вроде все ваши потребности закрывает и более безопасным способом


    1. Orenlab Автор
      14.04.2024 14:25

      Спасибо, попробую!