Skip to content

Latest commit

 

History

History
373 lines (276 loc) · 25.8 KB

File metadata and controls

373 lines (276 loc) · 25.8 KB

Салют 👋,
Данная лабораторная работа посвящена изучению Docker и как с ним работать. Эта лабораторная работа послужит подпоркой для старта в выявлении и определении уязвимостей на уровне сканирования контейнеров при сборке приложений.

Для сдачи данной работы также будет требоваться ответить на дополнительные вопросы по описанным темам.


Структура репозитория лабораторной работы

lab05
├── client
│   ├── client.py
│   ├── Dockerfile
│   └── requirements.txt
├── docker-compose.yml
├── README.md
├── server
│   ├── app.py
│   ├── Dockerfile
│   └── requirements.txt
└── source
    ├── Dockerfile
    ├── hello.py
    ├── image.tar
    └── requirements.txt

Материал

Контейнеризация

Сборка приложения включает создание контейнерного образа, в котором упаковано приложение с конфигурациями, чтобы приложение функционировало. Docker основан на использовании общих функций ядра ОС Linux (cgroups, namespace) для изоляции и управления ресурсами.

Образ — это статический, неизменяемый шаблон, на базе которого создаются контейнера с ОС, приложением, зависимостями, библиотекакм и конфигурационными файлами. Нужен для создания воспроизводимой, неизменяемой среды выполнения приложений в контейнерах.

Для сборки образов используется Dockerfile, где прописаны версии зависимостей и инструкции, минимизирующие разрешения и атаки. Это инструкции, где описывается, как собрать образ. Впоследствии собирается контейнер.

Контейнер — это изолированная среда выполнения приложения с необходимыми зависимостями, кодом, системными утилитами, библиотеками и настройками. Использует не собственную гостевую ОС, а ядро хостовой ОС и имеет своё собственное файловое пространство, процессы и сеть.

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

Контейнеризация — это технология, позволяющая упаковать приложение вместе со всеми его зависимостями, библиотеками, настройками и средой выполнения в единый изолированный виртуальный контейнер.

Namespaces

Необходимы для организации изолированных рабочих пространств — контейнеров. Когда мы запускаем контейнер, Docker создает набор пространств имен для данного контейнера, что создает изолированный уровень в своем пространстве имен и не имеет доступа к внешней системе.

pidИзоляция процессов — контейнер видит только свои процессы
netУправление сетевыми интерфейсами — собственный сетевой стек
ipcИзоляция IPC (InterProcess Communication) ресурсов
mntУправление точками монтирования — собственная файловая система
utsИзоляция hostname и domain — контейнер имеет собственное имя хоста

Cgroups

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

$ docker container run -d \
        -e NGINX_HOST xxx.xxx \
        -p 8080:80 \
        -v "$PWD/html" usr/share/nginx/html \
        --memory=50m \
        --cpus="2.5" \
        nginx

Основные проблемы

  • образ может содержать устаревшие или уязвимые версии библиотек CVE (Common Vulnerabilities and Exposures)
  • поддельные и злонамеренные образы
  • отсутствие подписей и проверки целостности
  • ошибка конфигурации и избыток прав — образы с избыточными правами доступа, запуском от root или с небезопасными настройками
  • присутствие секретов и конфиденциальных данных в образах
  • отсутствие регулярного обновления из-за неподдерживаемых образов

Контекст безопасности

Не запускать от root
USERDockerfile
Явно прописывать учётную запись с минимальными правами. Root внутри контейнера = root на хосте при побеге.
Без --privileged
capabilities
Отключает все средства изоляции, даёт доступ к ФС и устройствам хоста. Явно прописывать только нужные capabilities.
Профили безопасности
AppArmorseccompSELinux
Не отключать профили Linux security. Ограничивают syscalls, сеть, обращения к ФС хоста.
Не использовать host network
bridgenone
В режиме host контейнер делит сеть с хостом, включая доступ к Docker API. Использовать bridge или none.
Не монтировать docker.sock
docker.socket
Доступ к сокету = полный контроль над Docker daemon. Не подключать без крайней необходимости.
Секреты вне образа
secretsenv
Не хранить секреты в Dockerfile/ENV. Использовать Docker secrets или внешние менеджеры.
Лимиты ресурсов
--memory--cpus
Ограничивать CPU/RAM на уровне контейнера. Без лимитов один контейнер может забрать все ресурсы хоста.
Минимальные образы
alpineslimdistroless
Официальные образы с минимальным набором инструментов. Сканировать на CVE (Trivy, Docker Scout).

Дополнительно

В случае, если возникает проблема с вызовом docker buildx для macos silicon, следует использовать вот это описание из gistup для фикса samelink.


Задание

  • 1. Поставьте Docker и buildkit
# Ubuntu / Debian
$ sudo apt update && sudo apt install -y docker.io docker-compose
$ sudo systemctl enable --now docker
$ sudo usermod -aG docker $USER && newgrp docker

# Fedora
$ sudo dnf install -y docker docker-compose
$ sudo systemctl enable --now docker
$ sudo usermod -aG docker $USER

# macOS
$ brew install docker buildkit

# Проверка
$ docker --version
$ docker run hello-world
  • 2. Перейдите в source и выведите на терминале, далее проанализируйте следующие команды консоли
$ docker buildx build -t hello-appsec-world .
$ docker run hello-appsec-world
$ docker run --rm -it hello-appsec-world

$ docker save -o hello.tar hello-appsec-world
$ docker load -i hello.tar
$ docker load -i image.tar
  • 3. Откройте Dockerfile и проведите аудит безопасности по чеклисту:

    • Какой базовый образ используется? Он официальный? Есть ли пиннинг версии (тег, не latest)?
    • Используется ли multi-stage build? Зачем?
    • Есть ли инструкция USER? От какого пользователя запускается приложение?
    • Используется COPY . . или копируются только нужные файлы?
    • Есть ли .dockerignore? Что он исключает?
    • Есть ли секреты или пароли в ENV, ARG или COPY?
    • Пиннингованы ли версии зависимостей в requirements.txt?

    Зафиксируйте результат аудита в отчёте. Сделайте commit.

  • 4. Создайте файл .dockerignore в директории source/ со следующим содержимым:

.git
.gitignore
__pycache__
*.pyc
venv
.env
*.tar
*.md

Соберите образ до и после добавления .dockerignore, сравните размеры:

$ docker images | grep hello-appsec-world

Опишите в отчёте: какие файлы попадали в образ без .dockerignore и почему это опасно.

  • 5. Замените в Dockerfile скрипт на свой python-файл из прошлых лабораторных работ. Вложите свой файл в директорию source/. Проанализируйте и доработайте Dockerfile под ваш скрипт. Сделайте commit.

Пример анализа по текущему Dockerfile в репозитории

# Этап 1: сборка зависимостей
FROM python:3.11-slim AS builder
WORKDIR /hello
# Копируем файл с зависимостями
COPY requirements.txt . 
# Устанавливаем зависимости в отдельную директорию wheelhouse для кеширования
RUN pip install --upgrade pip && pip wheel --wheel-dir=/wheels -r requirements.txt

# Этап 2: запускаемый образ
FROM python:3.11-slim
WORKDIR /hello
# Копируем файл с зависимостями
COPY --from=builder /wheels /wheels # Копируем собранные wheel-пакеты
COPY requirements.txt . 
# Устанавливаем зависимости из wheel-пакетов
RUN pip install --no-index --find-links=/wheels -r requirements.txt
# Копируем исходный код приложения
COPY hello.py .

# Переменные окружения для улучшенной работы Python
ENV PYTHONUNBUFFERED=1
# Запускаем приложение
CMD ["python", "hello.py"] 
  • 6. Выведите на терминале и проанализируйте следующие команды консоли. Сравните хеш сумму вашего архива с image.tar из репозитория, выведите на терминал.
$ docker buildx build -t hello-appsec-world .
$ docker run hello-appsec-world
$ docker save -o hello_your_project.tar hello-appsec-world

$ docker load -i hello_your_project.tar
$ docker run hello-appsec-world

$ docker load -i image.tar
$ docker run hello-appsec-world
  • 7. Доработайте свой python скрипт подключаемыми библиотеками, далее их необходимо разместить в requirements.txt. Размещение библиотек в следующем формате:
flask==2.2.3
requests==2.28.1
  • 8. Сделайте commit. Повторите сборку приложения по вашему Dockerfile для доработанного скрипта python. Сохраните image в виде .tar архива. Сделайте commit.
  • 9. Проанализируйте слои и размер образа. Сравните single-stage и multi-stage build:
# Посмотреть слои образа
$ docker history hello-appsec-world

# Размер образа
$ docker images hello-appsec-world

Опишите в отчёте: сколько слоёв, какой размер, какие слои самые тяжёлые. Если Dockerfile не использует multi-stage — переделайте на multi-stage и сравните размер до/после.

  • 10. Проверьте, от какого пользователя работает контейнер:
$ docker run --rm hello-appsec-world whoami
$ docker run --rm hello-appsec-world id

Если выводит root — добавьте в Dockerfile инструкцию USER с непривилегированным пользователем. Пересоберите и проверьте повторно. Сделайте commit.

  • 11. Выведите на терминале и проанализируйте следующие команды консоли
$ docker login
$ docker tag hello-appsec-world yourusername/hello-appsec-world
$ docker push yourusername/hello-appsec-world
$ docker inspect yourusername/hello-appsec-world
$ docker container create --name first hello-appsec-world # выпишите id контейнера

$ docker image pull geminishkv/hello-appsec-world
$ docker inspect geminishkvdev/hello-appsec-world
$ docker container create --name second hello-appsec-world
  • 12. Запустите контейнер Ubuntu и изучите изоляцию изнутри (связь с namespaces из материала):
$ docker container run -it --rm ubuntu /bin/bash

# Внутри контейнера:
$ whoami                    # от какого пользователя?
$ id                        # uid, gid
$ ps aux                    # какие процессы видны? (только свои — pid namespace)
$ cat /etc/os-release       # какая ОС внутри?
$ hostname                  # имя хоста (uts namespace)
$ ls /proc/1/ns/            # namespace-файлы процесса PID 1
$ exit

Опишите в отчёте: что показывает каждая команда и как это связано с namespaces (pid, uts, mnt).

  • 13. Проверьте ресурсные лимиты контейнера — запустите с ограничением памяти:
# Запустить с лимитом 64 MB RAM
$ docker run -d --name stress-test --memory=64m --cpus=0.5 ubuntu sleep 300

# Проверить лимиты
$ docker stats stress-test --no-stream

# Почистить
$ docker rm -f stress-test

Опишите в отчёте: что произойдёт, если приложение попробует выделить больше памяти, чем лимит? Зачем ограничивать ресурсы?

  • 14. Выведите оба контейнера first и second на терминал
  • 15. Создайте Docker-сеть и проверьте связность между контейнерами:
$ docker network create lab05-net
$ docker network ls

# Запустите два контейнера в одной сети
$ docker run -d --name net-a --network lab05-net nginx
$ docker run -it --rm --network lab05-net ubuntu bash -c "apt update && apt install -y iputils-ping && ping -c 3 net-a"

# Почистить
$ docker rm -f net-a
$ docker network rm lab05-net

Опишите в отчёте: как контейнеры находят друг друга по имени? Какой DNS использует Docker?

  • 16. Перейдите в основной корень lab05 и запустите docker compose:
$ docker compose up --build
  • 17. Откройте соседнее окно терминала и проверьте работу приложения
$ curl -i http://localhost:8000
# или откройте в браузере: http://localhost:8000
  • 18. Остановите docker compose и почистите ресурсы
$ docker ps -a
$ docker ps -q
$ docker images

$ docker ps -q | xargs docker stop
$ docker compose down
  • 19. Доработайте docker-compose.yml и скрипт из предыдущих шагов, чтобы воспроизвести шаги п.15–п.17 с демонстрацией. Сделайте commit.
  • 20. Залейте изменения в свой удалённый репозиторий, проверьте историю commit.
  • 21. Подготовьте отчет gist.

Смотри также


Troubleshooting

Если столкнулись с проблемами — смотрите Troubleshooting.

Links