JavaRush /Курсы /Spring Boot /Sensitive values и management port

Sensitive values и management port

Spring Boot
22 уровень , 4 лекция
Открыта

1. Риск лишней видимости в Actuator

Когда мы впервые включаем env и configprops, кажется, что это просто удобная отладка: посмотрел — и понял, что именно загрузилось из YAML, что перетёрлось переменной окружения, а что прилетело из аргументов запуска. Проблема в том, что Actuator — это не “лог для разработчика”, а HTTP‑поверхность. А HTTP, как известно, имеет вредную привычку становиться доступным не только нам.

Представьте, что у вас дома есть “щиток” с автоматами, и вы решили сделать к нему дверцу прозрачной, чтобы “быстрее понимать, что где”. Пока это внутри квартиры — окей. Но если кто-то вытащит этот щиток в подъезд, или вы сами случайно откроете туда доступ, прозрачность внезапно превратится в подарок “всем желающим”.

С Actuator ровно так же. Endpoint’ы env и configprops могут показывать:

  • какие property sources вообще есть в приложении, а значит — где можно искать секреты;
  • какие значения победили, а значит — что именно сейчас используется;
  • откуда они пришли, а значит — где это лежит: файл, env vars, системные свойства и т.д.

И даже если вы не открывали эти endpoint’ы “наружу”, остаются практические сценарии, где это становится проблемой: общий dev‑стенд, публичный тестовый сервер “на минутку”, проброшенный порт, случайный reverse‑proxy, “да я просто накинул include: "*", чтобы не мучаться”. У Actuator нет встроенного чувства стыда, он честно ответит. Поэтому мы и добавляем “ремень безопасности” в виде sanitization и аккуратного management port.

2. Что такое sensitive values в контексте backend‑сервиса

Новички часто думают, что “чувствительное” — это строго пароль и токен, а всё остальное можно показывать. На практике чувствительность — это шире: это любые данные, которые помогают атаковать систему, нарушают приватность или раскрывают внутреннюю кухню. И самое неприятное: иногда значение само по себе безобидное, но связка “ключ + источник + окружение” становится уже полезной информацией для злоумышленника (или просто для любопытного соседа по офисному Wi‑Fi).

Ниже небольшая “карта местности”, чтобы вы понимали, что именно может внезапно оказаться sensitive, даже в учебном catalog-service:

Категория Пример значения Почему это неприятно показывать Где часто всплывает
Секреты (очевидные) пароли, API keys, токены, client secrets прямой доступ к чужим сервисам/данным env, configprops, иногда info (если вы туда положили)
Внутренние адреса jdbc:..., internal hostnames, URLs внутренних API помогает “прощупывать” инфраструктуру env, configprops
Путь к файлам/директориям /opt/app/config/..., C:\Users\... помогает понимать окружение, иногда утечки пользовательских данных configprops (origin/inputs), env
Бизнес‑конфиденциальное цены, набор курсов, фичи, флаги “скоро запуск” это не “секрет”, но может быть нежелательно публичным configprops (ваши @ConfigurationProperties)
Персональные данные email’ы, телефоны, user ids приватность и комплаенс чаще в логах и доменных endpoint’ах, но иногда и в конфиге

Для нашего catalog-service особенно показателен “бизнес‑конфиденциальный” кусок: список курсов и цены мы держим в конфигурации. С технической точки зрения это не пароль. Но с продуктовой — это вполне может быть информация, которую вы не хотите отдавать любому, кто угадает URL /actuator/configprops. Поэтому главный принцип на сегодня будет такой: sanitization полезен, но не заменяет здравого смысла и ограничения exposure.

3. Sanitization: как Spring Boot маскирует значения

Когда вы впервые открываете env или configprops, вы можете заметить приятную штуку: некоторые значения уже не показываются “как есть”, вместо них приходит ******. Это и есть sanitization — маскирование потенциально чувствительных данных. Boot пытается быть вашим другом и не устраивать “угадай мой токен по логам” прямо из коробки.

Но важно понимать две разные ручки управления:

Первая ручка — показывать ли значения вообще. Для этого у многих endpoint’ов есть настройка в стиле show-values (в частности, для env и configprops). Вторая ручка — какие ключи всегда нужно маскировать, даже если значения в целом показывать разрешено. Это обычно настраивается через keys-to-sanitize.

Чтобы не держать это в голове как “абстракцию”, удобнее смотреть на show-values как на режим вывода:

show-values Что увидите в ответе Кому подходит
never значения всегда скрыты (маска вместо value) безопасный baseline, особенно без Security
when_authorized значения показываются только “авторизованному” полезно, когда есть механизм авторизации (в нашем курсе его нет)
always значения показываются, кроме тех, что попали под sanitization по ключам локальная отладка “на своём ноутбуке”, и то временно

И вот ключевой момент курса: пока у нас нет Security, режим when_authorized чаще всего превращается в “сюрприз: вы всё равно не авторизованы” или “сюрприз: вы всё равно всё показываете, потому что защита не настроена так, как вы думаете”. Поэтому в catalog-service на уровне курса разумнее опираться на never как безопасный default, и только точечно включать always, когда вы осознанно в локальной среде и понимаете последствия.

Для ощущения, как это выглядит, вот типичный фрагмент из ответа, где значение замаскировано:

{
  "property": {
    "source": "systemEnvironment",
    "value": "******"
  }
}

Важно: даже если value скрыто, сам факт наличия свойства, его имя и источник уже могут быть полезной информацией. Поэтому “маска” — это не пропуск в мир “можно открывать всем”.

4. Настраиваем show-values для env и configprops в catalog-service

Когда вы диагностируете env и configprops, есть соблазн держать их в режиме открытой лупы: так проще увидеть, какое значение победило и во что оно связалось. Но такой режим хорош только как временная local‑диагностика. Для постоянного режима catalog-service безопаснее, чтобы endpoint’ы могли быть доступны, а значения по умолчанию оставались скрыты.

Мы уже настроили exposure policy: в local/dev открываем больше диагностических endpoint’ов, а в prod оставляем минимум. Теперь добавим поверх этого ещё одно правило: даже если endpoint доступен в local, значения по умолчанию всё равно скрываем.

Начнем с application-local.yaml. Здесь мы предполагаем, что в local мы хотим видеть env и configprops, но без “вскрытия” значений:

# src/main/resources/application-local.yaml
management:
  endpoints:
    web:
      exposure:
        # В local можно включить расширенную диагностику...
        include: "health,info,env,configprops"

  endpoint:
    env:
      # ...но значения по умолчанию не показываем (только факт и источник).
      show-values: never
    configprops:
      # То же самое для @ConfigurationProperties.
      show-values: never

С точки зрения поведения это означает следующее. Endpoint’ы доступны по HTTP, потому что мы их включили в include, но значения будут замаскированы. Это хороший компромисс для новичка: вы можете проверить, что свойство существует и откуда оно пришло, но не превращаете Actuator в “витрину переменных окружения”.

Для prod мы держим “узкую” поверхность и не открываем env/configprops вообще. Тогда show-values в проде становится второстепенным, потому что endpoint’ов просто нет снаружи:

# src/main/resources/application-prod.yaml
management:
  endpoints:
    web:
      exposure:
        # В prod оставляем только базовые endpoint'ы.
        include: "health,info"

Теперь важная практическая мысль. Иногда вам в local правда нужно увидеть значение. Например, вы отлаживаете, почему app.catalog.title не тот, или почему max-featured-count вдруг стал 0. В этот момент самый безопасный путь — не менять файл, а временно переопределить настройку через аргумент запуска или переменную окружения.

Например, локально можно запустить с временными override’ами:

--management.endpoint.env.show-values=always
--management.endpoint.configprops.show-values=always

Если нужен только один endpoint, включайте только его.

Смысл в том, что вы не фиксируете “опасный режим” в репозитории, а используете его как одноразовую лупу. В мире реальных проектов это очень помогает не устроить себе “а почему на dev‑стенде всем показываются токены?” через две недели после “временной отладки”.

5. keys-to-sanitize: когда маскировать нужно даже “не совсем секреты”

Очень человеческая ошибка — считать, что достаточно поставить show-values: always в локальном профиле, а потом “ну мы же всё равно маскируем секреты”. Проблема в том, что секреты маскируются по шаблонам ключей. А люди, включая нас, иногда называют ключи так, будто специально хотят обмануть систему: app.catalog.magicString, app.catalog.superValue, app.catalog.pleaseDontLookHere. И угадайте, что сделает sanitization? Правильно: вежливо покажет всё как есть.

Поэтому keys-to-sanitize — это способ сказать Boot: “Вот эти паттерны ключей всегда скрывай, даже если я включил показ значений”. Для env и configprops это можно задать отдельно. В учебном проекте мы обычно не храним настоящие секреты, но окружение (особенно на рабочем ноутбуке) может быть богато на переменные типа *_TOKEN и *_KEY.

Пример базовой настройки в local, которая делает поведение более предсказуемым:

# src/main/resources/application-local.yaml
management:
  endpoint:
    env:
      # Маскируем типовые "опасные" ключи, даже если включили показ значений.
      keys-to-sanitize: "password,secret,token,key"
    configprops:
      # То же самое для configprops (ваших @ConfigurationProperties).
      keys-to-sanitize: "password,secret,token,key"

Заметьте тонкость: keys-to-sanitize не решает проблему exposure. Он только помогает не выдать значение “как есть”. Сами ключи, их наличие и источники будут видны, если endpoint открыт. Поэтому этот механизм — про “минимизировать ущерб”, а не про “сделать безопасно вообще”.

И ещё один забавный, но полезный вывод: если вы всё-таки заводите секретные параметры в конфиге, то лучше называть их так, чтобы они попадали под sanitization. Токен должен быть токеном, а не “хрустальной строкой судьбы”.

6. Отдельный management port

Даже при аккуратном include/exclude и скрытых значениях остаётся практическая боль: Actuator endpoint’ы живут рядом с обычными URL вашего приложения. Для разработчика это не страшно, но для организации окружений, и для привычки “не смешивать продуктовые запросы и диагностику”, отдельный management port бывает очень удобным.

Идея простая: основной сервис отвечает на одном порту, например 8080, а служебные endpoint’ы Actuator — на другом, например 8081. Визуально это выглядит примерно так:

flowchart LR
  U["Браузер / curl / Postman"] -->|"http://localhost:8080"| A["Product API + landing page"]
  U -->|"http://localhost:8081"| M["Actuator endpoints"]

В Spring Boot это делается одной настройкой:

# src/main/resources/application-local.yaml
management:
  server:
    # Отдельный порт только для /actuator/** (удобство, не безопасность).
    port: 8081

Важно: это optional local/dev ветка, а не новый основной вариант проекта. Базовый сценарий всё ещё проще: приложение и Actuator живут на одном порту, а вы управляете тем, что видно, через exposure policy. Отдельный port имеет смысл, когда хочется физически развести продуктовые запросы и диагностику, но он не обязателен.

Если смотреть грубо и без погружения в кишки, Boot поднимает отдельный web‑сервер для management endpoints. Ваши обычные controller’ы продолжают жить на основном порту, а /actuator/** переезжает на management port.

Что это меняет. Разделяется трафик: вы можете проверять health, info и конфигурацию, не смешивая это с обычными запросами каталога. Для некоторых окружений это упрощает жизнь: можно “по умолчанию” работать с приложением на 8080, а диагностику держать на 8081.

Что это не меняет. Это не “встроенная безопасность”. Если management port доступен кому-то извне, то endpoint’ы доступны так же, как если бы они были на основном порту. Это как вынести запасные ключи под коврик у соседней двери: формально дверь другая, но идея защиты… спорная.

Поэтому в рамках курса мы воспринимаем отдельный port как организационную опцию для local/dev, а не как обязательный и не как “замену продуманной exposure policy”.

Ссылки и привычки с отдельным портом

Как только вы выносите Actuator на отдельный порт, всплывает бытовая деталь, которую легко не заметить: ссылки в landing page вида <a href="/actuator/health"> ведут на тот же порт, что и сама страница. То есть на основном порту они начнут давать 404, потому что /actuator/** теперь слушает 8081.

Это не ошибка Spring Boot — это мы поменяли физическую “адресацию” management endpoints. Поэтому нужно заранее договориться, как вы это оформляете для команды, или для себя будущего, который через месяц откроет проект и скажет: “почему ссылка умерла?”.

Здесь важно не держать сломанное состояние как норму. Пока Actuator живёт на том же порту, ссылки вида <a href="/actuator/health"> и <a href="/actuator/info"> на landing page удобны и действительно ускоряют проверку сервиса. Если вы временно включили отдельный management port, эти относительные ссылки на основной странице перестают быть полезными: они всё равно поведут на основной порт, где Actuator уже не слушает.

Поэтому separate port лучше воспринимать как отдельную ветку local/dev‑настройки. В этой ветке обычные ссылки на landing page лучше заменить нейтральной подсказкой про путь /actuator и отдельно открыть правильный порт, чем оставлять на странице красивый, но мёртвый 404.

Если вы включили такую ветку и всё равно хотите оставить подсказку на странице, можно добавить нейтральную заметку без привязки к конкретному порту, чтобы не сломать prod:

<!-- src/main/resources/static/index.html -->
<p>
  <!-- Подсказка без привязки к порту: порт может отличаться в разных средах. -->
  Actuator endpoints доступны по пути <code>/actuator</code>.
  Если в среде настроен отдельный management port, откройте Actuator на этом порту.
</p>

Эта маленькая фраза экономит кучу времени новичкам: они перестают подозревать, что “Actuator не работает”, и начинают проверять, куда именно он был вынесен.

Ещё одна привычка, которая помогает: когда вы включаете management.server.port, сразу после старта приложения смотрите в startup logs. Там обычно видно, на каком порту поднялся основной сервер и где стартовал management server. Если вы этого не делаете, вы будете тратить минуты, а иногда и часы, на “почему у меня 404”, хотя ответ уже был в логах.

7. Sanitization не спасает от утечек в логах

Очень коварный момент: sanitization относится к ответам Actuator endpoint’ов, но не имеет никакой власти над тем, что вы логируете сами. Если вы однажды сделаете log.info("CatalogProperties={}", catalogProperties), то в лог улетит всё: список курсов, цены, флаги и так далее. А логи, в отличие от local‑Actuator, обычно живут долго и иногда оказываются в местах, где вы их не планировали видеть.

Чтобы связать это с нашим catalog-service, покажу два коротких фрагмента. Первый — “плохая, но очень соблазнительная идея”:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// Предполагается, что у вас есть logger (например, через Lombok @Slf4j или вручную).
log.info("Catalog properties: {}", catalogProperties); // Печатает весь объект конфигурации целиком.

Так вы печатаете весь объект, а значит — всё содержимое конфигурации. Для учебного проекта это может быть просто шум, а для реального — утечка.

Второй — более зрелый стиль: логировать только агрегаты и безопасные флаги, которые действительно помогают понять состояние сервиса, не превращая лог в дамп:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// Вместо полного дампа — логируем только то, что реально помогает диагностике.
log.info("Catalog loaded: {} courses, featured limit={}",
        catalogProperties.courses().size(),     // Сколько курсов загрузилось (агрегат)
        catalogProperties.maxFeaturedCount());  // Ограничение, влияющее на поведение сервиса

Разница кажется “мелкой”, но именно такие мелочи обычно отделяют сервис, который удобно диагностировать, от сервиса, который удобно “случайно сдать на экзамене по утечкам”.

Если объединить это с темой сегодняшней лекции, правило получается простым: Actuator помогает увидеть, а sanitization помогает не увидеть лишнее в ответе. Но за то, что вы пишете в логах, отвечаете вы сами, без подсказок от Boot. Именно в таком виде Actuator полезен: не как открытая витрина настроек, а как аккуратный инструмент точечной диагностики — от условий auto-configuration до карты маршрутов, старта приложения и собственных health‑проверок.

8. Типичные ошибки при настройке Actuator

Ошибка №1: открыть env и configprops “временно”, а потом забыть про это.
Это самая частая история. Сервис удобно диагностируется, всё классно… пока вы не выкатываете тот же конфиг на общий dev‑сервер или не оставляете его в репозитории. Избежать этого помогает профильная дисциплина: расширенные endpoint’ы только в local/dev, а в prod — минимальный набор. И ещё лучше — временные overrides через аргументы запуска вместо правки YAML.

Ошибка №2: воспринимать sanitization как “раз можно открыть endpoint, значит всё безопасно”.
Маска ****** скрывает значение, но не скрывает сам ключ, его наличие и источник. Иногда уже этого достаточно, чтобы понять, что у вас есть, например, переменные с токенами, откуда они берутся и как называются. Sanitization — страховка на случай ошибки, а не лицензия на бездумное раскрытие диагностики.

Ошибка №3: перепутать “exposure” и show-values.
include/exclude отвечает на вопрос “endpoint вообще доступен по HTTP?”, а show-values — “что он показывает внутри ответа?”. Если endpoint открыт, но значения скрыты, это всё равно endpoint. И наоборот: если вы не открыли endpoint в prod, то show-values там почти не играет роли, потому что никто его не увидит по HTTP.

Ошибка №4: надеяться, что отдельный management.server.port сам по себе решит вопрос безопасности.
Отдельный порт — это удобное разделение, но не защита. Если порт доступен, endpoint доступен. В учебном проекте мы не строим сетевые правила и не подключаем Security, поэтому воспринимайте management port как “удобнее организовать” и “меньше путаться”, а не как “теперь всё защищено”.

Ошибка №5: логировать целиком @ConfigurationProperties или весь Environment ради отладки.
Это родная привычка после System.out.println: “давайте выведем всё”. В Boot‑сервисе это быстро превращается в риск утечки и в тонну шума. Гораздо полезнее логировать только то, что реально нужно для диагностики: количества, активные профили, режимы, ключевые флаги. Всё остальное для просмотра “как есть” у вас уже есть через Actuator — причём с возможностью маскировать значения.

1
Задача
Spring Boot, 22 уровень, 4 лекция
Недоступна
Маскирование чувствительного значения
Маскирование чувствительного значения
1
Задача
Spring Boot, 22 уровень, 4 лекция
Недоступна
Отдельный management port
Отдельный management port
1
Опрос
Actuator Мониторинг, 22 уровень, 4 лекция
Недоступен
Actuator Мониторинг
Диагностика Spring Boot приложений
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ