Узнайте, как повысить безопасность ваших приложений Next.js, размещенных на вашем сервере. В этом руководстве описаны лучшие практики работы с образами контейнеров Docker, управление секретными данными, устранение уязвимостей и многое другое, чтобы ваши проекты Next.js были лучше защищены от угроз.
Самостоятельное размещение приложения Next. js обеспечивает гибкость и контроль, но также сопряжено с проблемами безопасности. Если вы решили отказаться от таких платформ, как Vercel или Netlify, теперь вы несете ответственность за все — от безопасности образа контейнера до управления секретами.
Next. js приобрел репутацию сложного для самостоятельного размещения, но недавние обновления упростили контейнеризацию. Это отличная новость для развертывания, но она не решает ваши проблемы с безопасностью.
Какой образ контейнера вам следует выбрать и как вы будете отслеживать обновления безопасности? Что насчёт базовой платформы? Уязвимости ОС хоста? Как вы обрабатываете переменные среды и секреты? Каковы ваши правила брандмауэра?
В этой статье мы рассмотрим наиболее важные аспекты безопасности при самостоятельном размещении приложений Next.js.
Создание безопасного образа контейнера Next. js
Выбор правильного образа контейнера важен для обеспечения безопасности вашего приложения Next.js. Хотя официальный пример Next.js Docker является отправной точкой, необходимо учитывать некоторые оптимизации.
Выбор базового образа
В официальном примере используется Node 18 в Alpine Linux, который популярен благодаря своему небольшому размеру. Однако musl libc в Alpine может привести к проблемам совместимости с некоторыми библиотеками, особенно если вы не разрабатываете и не тестируете musl локально.
Более безопасным вариантом является использование стандартного образа Node на базе Debian, который более совместим и с меньшей вероятностью вызовет непредвиденные проблемы. См. это обсуждение плюсов и минусов.
Версия узла и варианты изображения
Node 18 по-прежнему находится на длительной поддержке (LTS), но работает только в режиме поддержки. Рассмотрите возможность перехода на Node 20, текущую активную версию LTS, для активного развития и поддержки. Согласно документации:
Основные версии Node. js переходят в статус «Текущий выпуск» на шесть месяцев, что даёт авторам библиотек время на добавление поддержки. Через шесть месяцев выпуски с нечётными номерами (9, 11 и т. д.) перестают поддерживаться, а выпуски с чётными номерами (10, 12 и т. д.) переходят в статус «Активный LTS» и становятся готовыми к общему использованию. Статус «Активный LTS» означает «долгосрочную поддержку», которая обычно гарантирует исправление критических ошибок в течение 30 месяцев. Для производственных приложений следует использовать только выпуски Active LTS или Maintenance LTS.
Пример Dockerfile для Next. js использует многоэтапную сборку для оптимизации кэширования и уменьшения размера итогового образа.
Полную версию со всеми инструментами сборки можно использовать на ранних этапах, но для финального этапа «бегунка» я бы рекомендовал использовать «тонкий» вариант образа из списка тегов. Тонкая версия «не содержит общих пакетов, содержащихся в теге по умолчанию, и содержит только минимальные пакеты, необходимые для запуска Node». Идеально для производства.
Рекомендуемые изображения
- Этапы сборки (базовый и установочный):
node:20-bookworm
(или привязка к конкретной версии Node, напримерnode:20.15-bookworm
) - Этап выполнения:
node:20-bookworm-slim
(или привязка к конкретной версии Node, напримерnode:20.15-bookworm-slim
)
(Полный файл Dockerfile приведен в конце этой статьи)
Базовой операционной системой является Debian Bookworm, которая является последней версией. Если вы используете node:lts
, то вы также получите Debian Bookworm. Однако со временем это может измениться, поэтому рекомендуется закреплять конкретные версии. Таким образом, вы не столкнётесь с внезапными изменениями при выходе новых версий.
Эта комбинация обеспечивает баланс между совместимостью, активной поддержкой и уменьшением поверхности атаки. Не забывайте регулярно обновлять номера версий в вашем Dockerfile, чтобы оставаться актуальными.
Запуск от имени пользователя, не имеющего права доступа root
Еще одна важная мера безопасности — не запускать приложение от имени пользователя root, даже внутри контейнера.
Если вы запускаете Next. js от имени пользователя root, то любой выполняемый им код будет иметь права суперпользователя. Это позволяет устанавливать другое программное обеспечение, запускать другие процессы и получать доступ ко всем файлам в контейнере. Это упрощает использование уязвимости для выхода из контейнера, считывание переменных среды или доступ к другим инструментам в контейнере.
Если злоумышленник воспользуется уязвимостью, работа в качестве пользователя без прав суперпользователя ограничит потенциальный ущерб.
В официальном примере показано создание выделенного пользователя nextjs
и группы nodejs
. Следуйте этой практике в своём Dockerfile, чтобы файлы принадлежали этому пользователю, а основной процесс выполнялся от его имени. nextjs
RUN groupadd -r nodejs && useradd -r -g nodejs -d /app -s /sbin/nologin nextjs \
&& chown -R nextjs:nodejs /app
Строки в Dockerfile для добавления нового пользователя и группы.
Скопируйте выходные данные при сборке Next.js в автономном режиме и chown
отправьте их nextjs:nodejs
пользователю и группе:
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
Строки в Dockerfile для копирования файлов с правильным правом собственности.
Наконец, вам нужно установить директиву USER
перед CMD
USER nextjs
Строка в Dockerfile для указания пользователя, от имени которого CMD
будет выполняться команда.
Полный файл Dockerfile приведен ниже.
Управление эксплуатацией секреты в Next.js контейнеры
В Next. js изначально не предусмотрено надежное управление секретными данными, но есть несколько стратегий, которые помогут обеспечить защиту вашей конфиденциальной информации в среде с контейнерами.
Как рекомендовано в нашем контрольном списке по безопасности Next. js, не храните секреты напрямую в переменных среды. Вместо этого используйте специальный инструмент для управления секретами. Это обеспечивает централизованный контроль, ведение журналов и упрощение смены учётных данных.
Один из вариантов — использовать dotenvx, который позволяет шифровать секреты внутри файла .env
. Другой вариант — использовать ссылки, хранящиеся в таком инструменте, как 1Password. Ссылаясь на секреты в файле .env
, а затем используя интерфейс командной строки для их получения при запуске, вы избегаете раскрытия секретов в среде вашего контейнера.
В качестве альтернативы можно использовать Hashicorp Vault Secrets, AWS Secrets Manager, Doppler и Infisical. Мы используем 1Password, потому что он также подходит для хранения всех наших корпоративных секретов.
Здесь я создал файл .env.production
с секретными именами, указывающими на ссылку на 1Password:
ARCJET_KEY="op://app.arcjet.com/ARCJET_KEY/credential"
Пример,.env.production
содержащий ссылку на 1Password для хранения секрета.
Интерфейс командной строки 1Password требует, чтобы токен учётной записи службы был установлен как OP_SERVICE_ACCOUNT_TOKEN
с ограниченным доступом только для чтения значений из конкретного хранилища 1Password.
Мы переносим интерфейс командной строки в контейнер с помощью этой директивы COPY:
COPY --chown=nextjs:nodejs --from=1password/op:2 /usr/local/bin/op /usr/local/bin/op
Затем выполняется команда CMD
для запуска сервера Next.js с помощью 1Password CLI run
(docs):
CMD [
"/usr/local/bin/op", "run", "--env-file=/app/.env.production",
"--", "node", "/app/server.js"
]
Это удаляет все секреты из переменных среды, кроме токена учётной записи службы. Хотя этот токен можно использовать для доступа к секретам в 1Password, он привязан к конкретному хранилищу, и вы получаете такие преимущества, как:
- Никаких раскрытых секретов: в вашей среде секреты никогда не существуют в виде обычного текста.
- Аудит: менеджеры секретов предлагают подробные журналы аудита для отслеживания доступа.
- Ротация: легко ротировать секреты без перестроения образа контейнера.
Это значительное улучшение по сравнению с обычными переменными среды, которые не регистрируются, не могут быть легко изменены и нигде не отслеживаются.
Next.js Пример файла Dockerfile
Этот Dockerfile использует Node 20 в Debian Bookworm для установки зависимостей проекта и сборки приложения Next.js. Предполагается, что вы настроили output: "standalone"
в next.config.js
и затем скопировали автономный результат в минимальную среду выполнения, запустив её от имени пользователя без прав суперпользователя для повышения безопасности. Управление секретами осуществляется извне с помощью 1Password, на который есть ссылка в файле .env.production
.
FROM node:20.15-bookworm-slim AS base
ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn global add turbo@2
# Installer grabs the files we need, then installs the dependencies
FROM base AS installer
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app
COPY . .
RUN npm ci
RUN turbo run build
# Runner copies everything from the installer, then runs the app as a locked
# down nextjs user
FROM node:20.15-bookworm-slim AS runner
WORKDIR /app
# ca-certificates is needed for 1Password CLI
RUN apt-get update && apt-get install -y ca-certificates --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN groupadd -r nodejs && useradd -r -g nodejs -d /app -s /sbin/nologin nextjs \
&& chown -R nextjs:nodejs /app
# Copy in the built files. Automatically leverage output traces to reduce image
# size: https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --chown=nextjs:nodejs --from=installer /app/.env.production .
COPY --chown=nextjs:nodejs --from=installer /app/next.config.mjs .
COPY --chown=nextjs:nodejs --from=installer /app/package.json .
COPY --chown=nextjs:nodejs --from=installer /app/.next/standalone ./
COPY --chown=nextjs:nodejs --from=installer /app/.next/static ./apps/app/.next/static
COPY --chown=nextjs:nodejs --from=installer /app/public ./apps/app/public
# Include the 1Password CLI tool, which we use to fetch secrets at runtime
COPY --chown=nextjs:nodejs --from=1password/op:2 /usr/local/bin/op /usr/local/bin/op
USER nextjs
ENV NODE_ENV="production"
ENV NEXT_TELEMETRY_DISABLED=1
EXPOSE 3000
CMD [ "/usr/local/bin/op", "run", "--env-file=/app/.env.production", "--", "node", "/app/server.js" ]
Пример Dockerfile для Next.js с использованием минимального образа Debian Bookworm Node 20 и 1Password CLI для загрузки секретов из .env.production
файла.
Вы можете создать и запустить приложение с помощью следующих команд:
docker build -t nextjstest1:latest .
docker run -it --rm --name nextjstest1 -p 3000:3000 -e OP_SERVICE_ACCOUNT_TOKEN=... nextjstest1:latest
Если вы получили такую ошибку, проверьте правильность токена службы 1Password:
[ERROR] error initializing client: Validation: (failed to
session.DecodeSACredentials), Server: (failed to
DecodeSACredentials), failed to parseToken, format is invalid
Тебе вообще нужна оболочка?
Хотя оболочка удобна для отладки, ее включение в производственный контейнер Next. js создает ненужный риск. Оболочка представляет собой дополнительный вектор атаки, которым потенциально могут воспользоваться злоумышленники.
Зачем Снимать Оболочку?
- Сокращение поверхности атаки: устранение оболочки устраняет потенциальную точку входа для злоумышленников.
- Принцип наименьших привилегий: соблюдение этого принципа означает предоставление только минимально необходимого доступа. Если вашему приложению не требуется оболочка для выполнения основных функций, ее удаление повышает уровень безопасности.
Альтернативные подходы
- Изображения:Google Distroless Эти образы разработаны с учетом минимальной поверхности атаки и содержат только основные компоненты, необходимые для запуска вашего приложения. Они также предлагают версии без прав суперпользователя, что еще больше повышает безопасность.
- Специальные контейнеры для отладки: используйте отдельные образы контейнеров для отладки, оснащенные оболочкой и дополнительными инструментами. Это позволяет изолированно устранять неполадки без ущерба для производственной среды.
Компромиссы
Удаление оболочки усложняет отладку в производственном контейнере. Кроме того, использование образа контейнера без дистрибутива несовместимо с 1Password CLI для загрузки секретов во время выполнения. Образ без дистрибутива удаляет некоторые зависимости, необходимые для работы CLI, что препятствует его запуску.
Это означает, что вам нужно будет либо загрузить секреты в переменные среды, либо интегрировать SDK менеджера секретов в свой код. Если вы работаете на AWS и используете Kubernetes, то можете использовать AWS Secrets Manager с EKS.
Бесклассовый Dockerfile для Next.js
Образы Distroless по умолчанию не содержат инструментов для сборки, поэтому мы по-прежнему используем образ Node 20 Bookworm в качестве основы, но используем Distroless для бегуна:
FROM node:20.15-bookworm-slim AS base
ENV NEXT_TELEMETRY_DISABLED=1
# Installer grabs the files we need, then installs the dependencies
FROM base AS installer
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
# Runner copies everything from the installer, then runs the app as a locked
# down nonroot user
FROM gcr.io/distroless/nodejs20-debian12:nonroot AS runner
WORKDIR /app
# Copy in the built files. Automatically leverage output traces to reduce image
# size: https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --chown=nonroot:nonroot --from=installer /app/.env.production .
COPY --chown=nonroot:nonroot --from=installer /app/next.config.mjs .
COPY --chown=nonroot:nonroot --from=installer /app/package.json .
COPY --chown=nonroot:nonroot --from=installer /app/.next/standalone ./
COPY --chown=nonroot:nonroot --from=installer /app/.next/static ./apps/app/.next/static
COPY --chown=nonroot:nonroot --from=installer /app/public ./apps/app/public
USER nonroot
ENV NODE_ENV="production"
ENV NEXT_TELEMETRY_DISABLED=1
EXPOSE 3000
CMD [ "server.js" ]
Пример Dockerfile для Next.js с использованием Distroless nodejs20-debian12:nonroot
в качестве образа-исполнителя.
Заключение
Самостоятельное размещение Next. js дает вам больше гибкости и контроля, но требует проактивного подхода к безопасности. Тщательно выбирая образы контейнеров, эффективно управляя секретными данными и сводя к минимуму поверхность атаки, вы можете обеспечить высокую производительность и устойчивость ваших приложений Next. js к потенциальным угрозам.
Помните, что обеспечение безопасности — это непрерывный процесс. Будьте в курсе последних уязвимостей, регулярно обновляйте свои зависимости и применяйте многоуровневую стратегию защиты для своих проектов на Next.js.