diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..b56e1950e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /build +COPY app/go.mod ./ +RUN go mod download +COPY app/ . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-s -w" -trimpath \ + -o /build/quicknotes . + +# Создаём папку /data и даём права в builder +RUN mkdir -p /data && chown 65532:65532 /data + +FROM gcr.io/distroless/static:nonroot +WORKDIR /app +COPY --from=builder /build/quicknotes . +COPY --from=builder /data /data +COPY app/seed.json . +EXPOSE 8080 +ENTRYPOINT ["/app/quicknotes"] \ No newline at end of file diff --git a/[builder b/[builder new file mode 100644 index 000000000..e69de29bb diff --git a/[stage-1 b/[stage-1 new file mode 100644 index 000000000..e69de29bb diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..a59a76b3a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,35 @@ +services: + quicknotes: + image: quicknotes:lab6 + container_name: quicknotes-lab6 + restart: unless-stopped + user: "0:0" + ports: + - "8080:8080" + volumes: + - quicknotes-data:/data + environment: + - ADDR=:8080 + - DATA_PATH=/data/notes.json + - SEED_PATH=/app/seed.json + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 5s + + # Drop all capabilities + cap_drop: + - ALL + # добавлять ничего не надо, так как приложению не нужны capabilities + + # Read-only root filesystem + read_only: true + + # no-new-privileges + security_opt: + - no-new-privileges:true + +volumes: + quicknotes-data: \ No newline at end of file diff --git "a/default\357\200\272" "b/default\357\200\272" new file mode 100644 index 000000000..e69de29bb diff --git a/submissions/image-1.png b/submissions/image-1.png new file mode 100644 index 000000000..f2b5f10a6 Binary files /dev/null and b/submissions/image-1.png differ diff --git a/submissions/image-10.png b/submissions/image-10.png new file mode 100644 index 000000000..f29e2d6fe Binary files /dev/null and b/submissions/image-10.png differ diff --git a/submissions/image-11.png b/submissions/image-11.png new file mode 100644 index 000000000..98a413027 Binary files /dev/null and b/submissions/image-11.png differ diff --git a/submissions/image-12.png b/submissions/image-12.png new file mode 100644 index 000000000..0f6cca419 Binary files /dev/null and b/submissions/image-12.png differ diff --git a/submissions/image-13.png b/submissions/image-13.png new file mode 100644 index 000000000..9c2adc250 Binary files /dev/null and b/submissions/image-13.png differ diff --git a/submissions/image-14.png b/submissions/image-14.png new file mode 100644 index 000000000..53969aaea Binary files /dev/null and b/submissions/image-14.png differ diff --git a/submissions/image-15.png b/submissions/image-15.png new file mode 100644 index 000000000..bfbb7e9e6 Binary files /dev/null and b/submissions/image-15.png differ diff --git a/submissions/image-16.png b/submissions/image-16.png new file mode 100644 index 000000000..df8fc8a1e Binary files /dev/null and b/submissions/image-16.png differ diff --git a/submissions/image-17.png b/submissions/image-17.png new file mode 100644 index 000000000..20c35cd65 Binary files /dev/null and b/submissions/image-17.png differ diff --git a/submissions/image-18.png b/submissions/image-18.png new file mode 100644 index 000000000..d8c97cc65 Binary files /dev/null and b/submissions/image-18.png differ diff --git a/submissions/image-19.png b/submissions/image-19.png new file mode 100644 index 000000000..20f089390 Binary files /dev/null and b/submissions/image-19.png differ diff --git a/submissions/image-2.png b/submissions/image-2.png new file mode 100644 index 000000000..c8945d649 Binary files /dev/null and b/submissions/image-2.png differ diff --git a/submissions/image-20.png b/submissions/image-20.png new file mode 100644 index 000000000..c7746b23b Binary files /dev/null and b/submissions/image-20.png differ diff --git a/submissions/image-21.png b/submissions/image-21.png new file mode 100644 index 000000000..343bbbafa Binary files /dev/null and b/submissions/image-21.png differ diff --git a/submissions/image-3.png b/submissions/image-3.png new file mode 100644 index 000000000..d109c2c43 Binary files /dev/null and b/submissions/image-3.png differ diff --git a/submissions/image-4.png b/submissions/image-4.png new file mode 100644 index 000000000..7c297e3d7 Binary files /dev/null and b/submissions/image-4.png differ diff --git a/submissions/image-5.png b/submissions/image-5.png new file mode 100644 index 000000000..d5fa1011b Binary files /dev/null and b/submissions/image-5.png differ diff --git a/submissions/image-6.png b/submissions/image-6.png new file mode 100644 index 000000000..4c5f1bcf5 Binary files /dev/null and b/submissions/image-6.png differ diff --git a/submissions/image-7.png b/submissions/image-7.png new file mode 100644 index 000000000..999afc637 Binary files /dev/null and b/submissions/image-7.png differ diff --git a/submissions/image-8.png b/submissions/image-8.png new file mode 100644 index 000000000..c57ed4312 Binary files /dev/null and b/submissions/image-8.png differ diff --git a/submissions/image-9.png b/submissions/image-9.png new file mode 100644 index 000000000..530b5f7d8 Binary files /dev/null and b/submissions/image-9.png differ diff --git a/submissions/image.png b/submissions/image.png new file mode 100644 index 000000000..69c4aa48c Binary files /dev/null and b/submissions/image.png differ diff --git a/submissions/lab6.md b/submissions/lab6.md new file mode 100644 index 000000000..4e8e0747a --- /dev/null +++ b/submissions/lab6.md @@ -0,0 +1,214 @@ +# Lab 6 + +Frolova AI, M25RO-01 + +a.frolova@innopolis.university + +Ссылка на PR: https://github.com/inno-devops-labs/DevOps-Intro/pull/1215 + +## Task1 - Dockerfile + +Ссылка на Dockerfile: (https://github.com/kicchhi/DevOps-Intro/blob/feature/lab6/Dockerfile) + +Размер Docker контейнера (размер < 25 MB): + +![alt text](image.png) + +Запуск контейнера: + +![alt text](image-1.png) + +Информация об образе (`docker inspect quicknotes:lab6 | jq '.[0].Config'`): + +![alt text](image-2.png) + +Размер образа `golang:1.24-alpine` - **83.5 MB**. В сравнении с ним, мой `quicknotes:lab6` весит 14.8 MB. + +Это наглядно демонстрирует эффективность многостадийной сборки: мы исключаем из финального образа компилятор, пакетный менеджер и все инструменты сборки, оставляя только статический бинарник. + +![alt text](image-3.png) + +### Ответы на вопросы + +> Q: Почему важен порядок слоев? Покажите время сборки до и после для двух стратегий: +>> 1) COPY . . && go mod download && go build - копируются все файлы проекта, включая исходники, слой .COPY инвалидируется, и go mod download перезапускается, даже если зависимости не менялись. +>> 2) COPY go.mod go.sum ./ && go mod download && COPY . . && go build - копируются только те файлы, в котоорых есть зависимости (go.mod и go.sum). Они меняются редко, поэтому слой с go mod download кещируется. Затем копируются исходники, и сборка идет быстро, даже если код часто меняется. + +> A: Разница между двумя стратегиями составила 0.27 секунд. Для маленького проекта с маленьким количеством заависимостей эта раззнице почти незаметна, но на больших проектам может достигать минут. + +1) Время сборки с плохой стратегией: + +![alt text](image-4.png) + +2) Время сборки с хорошей стратегией: + +![alt text](image-5.png) + +--- + +> Q: Почему CGO_ENABLED=0? Что произойдет в distroless-static, если вы забудете об этом? + +> A: `CGO_ENABLED=0` отключает CGO и заставляет Go компилировать статически скомпонованный бинарник, который не требует внешних библиотек. Образ `gcr.io/distroless/static:nonroot` не содержит динамического компоновщика и системных библиотек. Если оставить `CGO_ENABLED=1`, Go соберёт динамически скомпонованный бинарник, который при запуске в distroless выдаст ошибку. Контейнер не запустится. + +--- + +> Q: Что такое gcr.io/distroless/static:nonroot? Что в нем есть, чего нет и почему это важно для CVE? + +> A: `gcr.io/distroless/static:nonroot` — это минимальный образ от Google, предназначенный для запуска статически скомпилированных приложений. + +Что в нем есть: +- Минимальная файловая система; +- `ca-certificates` для http запросов; +- базовые файлы. + +Чего нет: +- Оболочки (bash. sh); +- Пакетного менеджера; +- Утилит curl, grep, sed; +- Компилятора и инструментов разработки. + +Почему это важно для CVE - отсутствие оболочки пакетного менеджеоа делает distroless-образы гораздо безопаснее. Там нет apt чтобы установить вредоносный пакет, bash для выполнения атаки и тд. + +--- + +> Q: -ldflags='-s -w' и -trimpath: что делает каждый флаг и какова его стоимость? + +> A: Эти флаги используются при сборке бинарного файла для уменьшения его размера и повышения производимости. + +| Флаг | Что делает | Эффект на образ | Стоимость | +|------|------------|-----------------|-------------------| +| `-s` | Удаляет таблицу символов (имена функций, переменных) | Уменьшает размер бинарника | Усложняет отладку (нет имён функций в стеке ошибок) | +| `-w` | Удаляет DWARF-отладочную информацию | Уменьшает размер бинарника | Нельзя использовать отладчик (например, `dlv`) | +| `-trimpath` | Убирает абсолютные пути к исходным файлам | Делает бинарник воспроизводимым (не зависит от папки сборки) | Усложняет чтение трейсов (пути становятся относительными) | + +--- + +## Task 2 - Compose + Healthcheck + Persistent Volume + +### 2.1 compose.yaml + +Ссылка на файл compose.yaml: https://github.com/kicchhi/DevOps-Intro/blob/feature/lab6/compose.yaml + +### 2.2 Ответы на вопросы + +> Q: В Distroless нет оболочки. Как вы проверяете его работоспособность? Выберите стратегию и объясните. (Варианты: HTTP через отдельный sidecar; wget-только отладочный образ; полагайтесь на стандартное поведение Docker, которое просто проверяет, жив ли процесс; используйте бинарный файл, который уже есть в образе.) + +> A: Я оставила healthcheck с wget, но он не работает (unhealthy), потому что в distroless нет wget. В реальном продакшене я бы использовала debug-образ или sidecar, чтобы иметь корректный healthcheck. + +--- + +> Q: Почему volumes: [quicknotes-data:/data] выживает docker compose down? И что все-таки его уничтожает? + +> A: A: `docker compose down` останавливает и удаляет контейнеры, но не удаляет именованные тома. Том сохраняется на диске, пока его не удалить командой `docker compose down -v` или `docker volume rm quicknotes-data`. + +--- + +> Q: depends_on без condition: service_healthy — чего он на самом деле ждет? К каким ошибкам это может привести? + +> A: `depends_on` по умолчанию ждёт старта контейнера, а не его готовности к работе. Это может привести к ошибкам, если зависимый сервис ещё не готов (например, база данных ещё не поднялась). Для корректной работы нужно использовать `condition: service_healthy`. + +--- + +### 2.3 Persistence test + +> Последовательность: note present → down → up → present → down -v → up → absent + +**Проблема:** +При запуске контейнера от `nonroot` возникает ошибка `permission denied` при записи в монтированный том `/data`. + +**Решение:** +В `compose.yaml` добавлена строка `user: "0:0"`, которая запускает контейнер от root. Это позволяет записывать данные в том и корректно выполнять тест на устойчивость. + + +1. Запускаю контейнер: + +![alt text](image-6.png) + +2. Создаю заметку: + +![alt text](image-7.png) + +3. Проверяю, что заметка создалась: + +![alt text](image-8.png) + +4. Останавливаю compose без удаления тома (`docker compose down`): + +![alt text](image-9.png) + +5. Запускаю compose снова: + +![alt text](image-10.png) + +6. Проверяю, что заметка созранилась: + +![alt text](image-11.png) + +7. Останавливаю compose с удалением тома (`docker compose down -v`): + +![alt text](image-12.png) + +8. Запускаю: + +![alt text](image-13.png) + +9. После остановки с удалением тома заметка ожидаемо не сохранилась: + +![alt text](image-14.png) + +--- + +## Bonus + +- На этом этапе дополнила compose.yaml (добавила security_opt и read_only) + +1. Проверка USER nonroot - 65532 - UID пользователя nonroot. + +`docker inspect quicknotes:lab6 --format='{{.Config.User}}'` + +![alt text](image-15.png) + +2. Проверка Distroless - означает, что в образе нетт оболочки + +`docker compose exec quicknotes sh` + +![alt text](image-16.png) + +3. Проверка cap_drop: ALL - все избыточные привилегии отключены. Контейнер имеет только минимально необходимые права. + +`docker inspect quicknotes-lab6 --format='{{.HostConfig.CapDrop}}'` + +![alt text](image-17.png) + +4. Проверка read_only true - файловая система корня доступна только для чтения, а утилита touch отсутствует, что предотвращает запись в системные директории. + +`docker compose exec quicknotes touch /test.txt` + +![alt text](image-18.png) + +5. no-new-privilegies - процесс внутри контейнера не может получить новые привилегии, что предотвращает эскалацию прав. + +`docker inspect quicknotes-lab6 --format='{{.HostConfig.SecurityOpt}}'` + +![alt text](image-19.png) + +6. Trivy scan + +- Базовый образ (`debian 13.5`): **0 HIGH/CRITICAL** уязвимостей +- Go-бинарник (`gobinary`): **13 HIGH** уязвимостей (в стандартной библиотеке Go) + +Уязвимости связаны с версией Go 1.24.13, а не с кодом QuickNotes. Для устранения достаточно обновить базовый образ до `golang:1.24-alpine` с актуальными патчами. Это демонстрирует важность регулярного обновления базовых образов. + +```bash +docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy:0.59.1 image --severity HIGH,CRITICAL --no-progress \ + quicknotes:lab6 +``` + +![alt text](image-20.png) + +![alt text](image-21.png) + +### Какой из 6 defaults даёт наибольшую безопасность на строку YAML? + +`cap_drop: ALL` — одной строкой отключает все избыточные привилегии Linux, которые есть у контейнера по умолчанию. Это самая эффективная строчка для безопасности, потому что она предотвращает потенциальную эскалацию прав в случае взлома приложения, не требуя изменения кода или образа. В сочетании с `read_only: true` и `no-new-privileges` она образует минимальный набор hardening-правил для production-контейнеров.