← Все статьи

Звёздочка в середине пути Vault — не wildcard

Протухший токен уронил оператор секретов. Его перевыпуск вскрыл более старый баг: 27 политик Vault со * в середине пути молча запрещали то, что объявляли, и прятали избыточные права.

В Gambchamp общий HashiCorp Vault обслуживает несколько клиентских окружений, а оператор секретов в одном из кластеров синхронизирует из него значения в Kubernetes. Однажды оператор уже шесть часов был в crash-loop — 68 рестартов, и каждый reconcile падал с:

PUT /v1/auth/token/renew-self → 403
* permission denied
* invalid token

Две проблемы, а не одна

Легко прочитать только первую строку и пойти искать проблему в политике. Решающая — вторая: на невалидный токен Vault отвечает обоими сообщениями. Сама авария была просто в том, что статический токен оператора истёк или был отозван.

Перевыпуск должен был быть механическим. Не получилось. Свежий токен с тремя задокументированными read-only политиками окружения читал три из четырёх нужных оператору путей. Четвёртый отвечал permission denied, хотя политика прямо его называла.

Glob, который никогда не был glob’ом

В ACL Vault * — это wildcard только последним символом пути. + совпадает ровно с одним сегментом. Во всех остальных местах * — это просто сегмент с буквальным именем *:

# выглядит как wildcard, совпадает только с сегментом по имени "*"
path "…/frontend/*/config" { capabilities = ["read"] }

# как должно было быть
path "…/frontend/+/config" { capabilities = ["read"] }

Прогон по всем политикам нашёл 27 со * в середине пути. Vault принимает такую политику как валидную — никакого линтинга «этот шаблон никогда не совпадёт» нет.

Признак того, что это ошибка шаблона, а не замысел: соответствующая широкая политика на всех клиентов была написана правильно, с +. Значит, старый токен всё это время носил именно её — доступ на чтение секретов фронтенда всех клиентов, а не только своего. Истечение токена вскрыло избыточные права, которые были невидимы, пока всё работало.

Фикс

Править 27 общих политик посреди аварии я не стал. Вместо этого создал одну отдельную политику с правильным синтаксисом +, ровно на четыре пути, которые читает оператор, и выпустил новый токен с тремя важными для оператора свойствами:

  • оставить default в списке политик — там живут renew-self и lookup-self, без них та же авария повторится по таймеру;
  • -period вместо -ttl, чтобы токен продлевался, пока жив потребитель;
  • -orphan, чтобы отзыв создавшего его токена не каскадировал.

Перед подключением я выполнил ровно тот вызов, что падал (token renew), прочитал четыре нужных пути и проверил, что путь другого окружения запрещён. Исправление общих шаблонов стало отдельной, неспешной задачей.

Выводы

  • 403 от Vault с двумя строками ошибки — читайте вторую. invalid token — не проблема политики.
  • * в середине пути — литерал. Для одного сегмента — +, и помните, что Vault не предупредит.
  • vault token capabilities <path> — единственное, что говорит правду о политике.
  • Никогда не «чините» доступ, навешивая более широкую политику. Это работает мгновенно и молча даёт чтение чужих окружений.
  • Настоящая хрупкость — статические токены. Kubernetes auth через ServiceAccount убирает этот класс отказов целиком.