Documentation
¶
Overview ¶
Package jwksproxyhttp — cluster-ВНУТРЕННИЙ публикатор набора проверочных ключей.
Запись ОДНА, и она наша ¶
Публикуется ПРОЕКЦИЯ ключницы (keyset.go): только наши ключи, только публичный материал, собственного кэша нет by construction. Путь записи объявляется настройкой своей чеканки, а не выводится из издателя (binding.go).
Прежде рядом стояла вторая запись — зеркало публичного набора прежнего издателя. Оно жило до последней фазы отказа от внешнего сервера и ушло вместе с ним (kaname#361): без внешнего издателя зеркалить нечего, а запись без источника — находка, а не запас. Привязка теперь отвергает вторую запись в старте: вернувшись, она снова поставила бы ключ другого издателя рядом с нашим на том же слушателе.
Снятие authN — задокументированное исключение ¶
Маршрут НАБОРА КЛЮЧЕЙ не требует аутентификации осознанно: на проводе — ТОЛЬКО ПУБЛИЧНЫЙ МАТЕРИАЛ проверки подписи, в форме стандартного документа, на cluster-ВНУТРЕННЕМ слушателе (:9097, никогда внешнем — ban #6), под односторонней TLS с сертификатом внутреннего удостоверяющего центра.
Требовать сертификат у ПОТРЕБИТЕЛЯ набора нельзя: он обязан оставаться origin-agnostic, и взаимная TLS сломала бы ровно это свойство.
Но соседняя поверхность того же слушателя обоснованием НЕ пользуется ¶
Авторитет отзыва (internal/handler/tokenintrospecthttp) стоит на этом же слушателе и принимает ПРЕДЪЯВЛЕННЫЙ ТОКЕН, а не отдаёт публичный материал. Обоснование выше на него не распространяется, поэтому он требует проверенного клиентского сертификата САМ, а слушатель переводится в режим «сертификат запрашивается и верифицируется, если предъявлен» — набор ключей при этом остаётся доступен без сертификата.
Не добавляйте здесь authN-гейт, не пересмотрев это решение (и TLS-клиента потребителя набора).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Binding ¶
type Binding struct {
// contains filtered or unexported fields
}
Binding — объявленная привязка «издатель → путь → обработчик».
func NewBinding ¶
NewBinding строит привязку, ОТКАЗЫВАЯ в вырожденной.
Это и есть страж старта записи источника: издатель, объявленный публикуемым, но не имеющий записи, — отказ в старте, а не молчаливый перебор записей подряд. Отказ на пустом перечне — третий экземпляр класса «пустое значение означает „не сужаем“», который дерево уже закрывает на двух других перечнях.
ВТОРАЯ ЗАПИСЬ — ТОЖЕ ОТКАЗ, и отказывает мощность, а не форма: каждая запись по отдельности законна, и потому вернувшееся зеркало чужого набора прошло бы все остальные проверки молча.
func (Binding) PathOf ¶
PathOf резолвит объявленного издателя в путь его записи.
Издатель употребляется ТОЛЬКО как ключ поиска: не совпал с объявленным — отказ. Ни одна часть пути, имени файла, ключа кэша или исходящего адреса из него не строится.
type KeySetConfig ¶
type KeySetConfig struct {
Source KeySetSource
Logger *slog.Logger
}
KeySetConfig — настройка обработчика нашей записи.
type KeySetHandler ¶
type KeySetHandler struct {
// contains filtered or unexported fields
}
KeySetHandler — НАША запись публикуемого набора: ПРОЕКЦИЯ ключницы.
Почему проекция, а не собственный кэш ¶
Порядок «ключ в наборе раньше, чем им подписан первый токен» становится верен ПО ПОСТРОЕНИЮ: опубликованный и подписывающий читаются из одних и тех же строк, и состояния, в котором ключ подписывает, а в ответе его нет, не существует — его не допускает схема.
Собственный кэш вернул бы состояние «строка есть, в ответе нет», и тогда вступление ключа в подпись пришлось бы гейтить на видимость eventually-consistent проекции — запрещённый подтверждающий барьер. С несколькими репликами публикатора «ответ эндпоинта» перестал бы быть одной величиной.
Цена решения названа ¶
Каждый запрос публикации становится чтением своей базы. Величина ограничена сверху числом потребителей, помноженным на частоту их перезапроса, — то есть единицами запросов в минуту, а не функцией от пользовательской нагрузки.
func NewKeySetHandler ¶
func NewKeySetHandler(cfg KeySetConfig) *KeySetHandler
NewKeySetHandler — построитель.
func (*KeySetHandler) ServeHTTP ¶
func (h *KeySetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
func (*KeySetHandler) Stats ¶
func (h *KeySetHandler) Stats() KeySetStats
Stats возвращает величины по каждому исходу.
type KeySetSource ¶
type KeySetSource interface {
PublishedSet(ctx context.Context) ([]domain.PublishedKey, error)
}
KeySetSource — источник НАШЕЙ записи публикуемого набора.
Реализуется ключницей. Порт назван здесь, у вызывающего: публикатор не знает ни про базу, ни про обёртку приватной половины — да и не может знать, потому что тип, которым ключ сюда приезжает, поля приватной половины не имеет вовсе.
type KeySetStats ¶
KeySetStats — величины по каждому исходу.
«Ноль отказов за всё время жизни» обязано быть отличимо от «контроль не исполнялся», поэтому величины читаются всегда, включая нулевые.
type Record ¶
type Record struct {
// Issuer — издатель записи. Ключ поиска, и ТОЛЬКО ключ поиска.
Issuer string
// Path — путь записи. ОБЪЯВЛЯЕТСЯ здесь, а не выводится из издателя.
//
// Производная конструкция «взять базовый адрес и приклеить издателя»
// короче и запрещена по двум причинам сразу. Первая — про безопасность:
// издатель приходит от предъявителя, то есть это недоверенный вход, и
// значению от предъявителя не место в построении пути. Вторая — про
// проверяемость: производный путь получается У ВСЯКОГО издателя, поэтому
// состояние «записи источника нет» не наступает никогда, и страж старта
// становится тождественно истинным — проверка остаётся в тексте, не имея
// возможности упасть.
Path string
// Handler — обработчик записи: проекция ключницы.
Handler http.Handler
}
Record — запись привязки «издатель → источник набора».
Запись у публикатора ОДНА — наша. Вторая существовала ради прежнего издателя и ушла вместе с ним (kaname#361); объединять наборы разных издателей в один документ запрещено по той же причине, по которой их прежде разводили: ключ одного издателя проверял бы токен, объявляющий другого.