Documentation
¶
Overview ¶
Package clienttokenhttp — токен-эндпоинт платформы (задача kacho#898, приёмка F2 §9.1 п. 1).
Клиент предъявляет подписанное утверждение, мы сверяем подпись его открытым ключом из своей же таблицы и выдаём токен доступа по виду выдачи «учётные данные клиента». Форма запроса и форма ответа — стандартные (RFC 6749 §4.4, RFC 7523 §2.2): предъявитель — чужая библиотека, и ответ обязан быть тем, который она умеет прочитать, иначе отказ выглядит для неё сбоем сети.
Единый тон наружу и различимость внутрь ¶
Всякий отказ, наступивший ПОСЛЕ того, как в запросе назван клиент — включая «такого клиента нет», — отдаёт побайтово ОДНО И ТО ЖЕ. Различимый отказ есть оракул: по нему устанавливают, существует ли клиент, жив ли он, какой у него алгоритм и какие идентификаторы однократности уже заняты. Каждый ответ сам по себе безобиден, а вместе они дают карту.
Различимыми остаются ровно ПЯТЬ отказов формы, и все пять решаются ДО того, как запрос назвал хоть какого-нибудь клиента: метод, потолок тела, неразбираемая форма, вид выдачи вне перечня и повторённый параметр утверждения. Они не сообщают о клиенте ничего, потому что клиента на этом шаге ещё нет, и стандартные коды у них обязаны быть свои — иначе чужая библиотека прочтёт «слишком большое тело» как «неверный клиент» и будет чинить не то.
Три отказа по темпу — тоже свои (kaname#315) ¶
Приёмка `docs/engineering/acceptance/ceremony-pace-is-named-by-number.md` называет на этом эндпоинте три оси:
- П3 — неудавшихся доказательств клиента за окно на источник: источник прислал слишком много, 429 со сроком до момента, когда в окне останется предел минус один отказ;
- П1 — потолок одновременных обменов, все четыре вида выдачи: занята НАША ёмкость, и занял её не обязательно этот вызывающий, — 503 и `Retry-After: 1`, та же форма, что у занятого проверяющего секрета церемонии;
- П2 — темп обменов на идентификатор клиента, только машинные полосы: судит проверяющий по ЗАЯВЛЕННОМУ идентификатору до реестра, 429.
Ни одна не сливается с отказом аутентификации: слитая, она заставила бы клиента чинить учётные данные, которые исправны. Все три решаются ДО обращения к реестру и потому о записи реестра не сообщают ничего.
Порядок решений — несущий (Р3) ¶
Метод → потолок тела → разбор формы → вид выдачи из закрытого перечня → П3 → П1 → полоса. Отказы формы решаются до всех осей и остаются различимыми: ось темпа не прячет «неверный метод» под «повторите позже». Источник, исчерпавший П3, места под потолком не занимает. П3 решается при входе по счёту, набранному к этому моменту, а растёт по исходу — отказом доказательства клиента (Р5): на машинных полосах это отказ проверяющего, кроме двух исходов нашей стороны (`registry-unavailable`, `replay-store-unavailable`) и отказов формы и темпа; на полосах церемонии — `invalid_client`, о котором сообщает полоса (LaneVerdict).
Различимость для НАС живёт с другой стороны провода: у каждого исхода свой счётчик и своя запись в журнале. Без счётчика мёртвый контроль невидим — проверка, не отказавшая ни разу за всё время жизни, неотличима от проверки, которая работает и просто не встречала нарушителя.
Index ¶
Constants ¶
const TokenPath = "/iam/v1/token"
TokenPath — объявленный путь эндпоинта.
#nosec G101 -- это АДРЕС поверхности, а не учётные данные: строка попадает в маршрут и в документацию клиента, её знает всякий, кто обращается к сервису. Слово в адресе называет предмет выдачи, а не хранимый секрет.
Variables ¶
This section is empty.
Functions ¶
func DeclaredOutcomes ¶ added in v0.2.0
func DeclaredOutcomes() []string
DeclaredOutcomes — закрытый словарь исходов этой полосы строками.
Отдаётся наружу ради читателя величин: набор рядов витрины обязан совпадать с набором клеток переписи by construction. Выведен из того же clientassertion.Outcomes, которым засеяна перепись, поэтому второй копией словаря не является.
Types ¶
type CeremonyLane ¶ added in v0.5.0
type CeremonyLane interface {
// Grants — виды выдачи, которые обслуживает полоса.
Grants() []string
// ServeGrant отвечает на запрос вида grant.
ServeGrant(w http.ResponseWriter, r *http.Request, grant string) LaneVerdict
}
CeremonyLane — полосы церемонии OAuth на этом же эндпоинте: `authorization_code` и `refresh_token` (приёмка LINE-A-1, группы B и G).
Эндпоинт судит метод, потолок тела и разбор формы для ВСЕХ полос, а вид выдачи — по закрытому перечню: машинные полосы названы поимённо ниже, полосы церемонии — словом церемонии (Grants). Полоса получает разобранную форму. Реализует `ceremonyhttp.TokenLane`.
type Config ¶
type Config struct {
// BodyCeiling — потолок тела запроса в байтах. ОБЯЗАТЕЛЕН.
//
// Умолчания здесь нет намеренно. Величина, которую построение подставляет
// молча, не может быть предметом стража старта: страж, требующий её
// задания, зелен при любом входе, потому что незаданной она не бывает. Тело
// этого запроса — форма с одним подписанным утверждением, и его потолок
// объявляет тот, кто поднимает сервис.
BodyCeiling int64
// InFlightCeiling — потолок одновременных обменов на этом процессе (П1),
// все четыре вида выдачи. ОБЯЗАТЕЛЕН по тому же доводу, что потолок тела:
// ноль означал бы «без потолка», а величина, подставленная построением,
// стражу старта не видна.
//
// Обмен сверх потолка отвергается сразу, а не ждёт места: ожидание держало
// бы соединение и горутину ровно тогда, когда их и так слишком много.
InFlightCeiling int
Logger *slog.Logger
// Ceremony — полосы церемонии. nil — церемония не собрана (сборка мимо
// корня либо выключенный вне боевого режима эндпоинт), и её виды выдачи —
// вне перечня.
Ceremony CeremonyLane
// FailedProofs — окно отказов доказательства клиента на источник (П3).
// ОБЯЗАТЕЛЬНО: эндпоинт без него не решал бы П3 ни разу.
FailedProofs *failurewindow.Window
// Source — адрес источника запроса по правилу Р7 (`issuingsource`).
// ОБЯЗАТЕЛЕН: ключ П3.
Source func(*http.Request) string
}
Config — настройка эндпоинта.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler — токен-эндпоинт.
func NewHandler ¶
NewHandler строит эндпоинт. Неполная провязка — отказ построения: эндпоинт без проверяющего принимал бы кого угодно, без выдачи — не выдавал бы никому, и оба состояния обнаружились бы на первом запросе, а не на старте.
type Issuer ¶
type Issuer interface {
Issue(ctx context.Context, in client_token.Input) (client_token.Output, clientassertion.Outcome, error)
}
Issuer — порт выдачи.
type LaneVerdict ¶ added in v0.5.0
type LaneVerdict uint8
LaneVerdict — что полоса церемонии сообщает эндпоинту о доказательстве клиента. Судит по нему ось П3: засчитывается отказ доказательства клиента, а не всякий отказ обмена (приёмка Р5).
const ( // LaneProofNotRefused — доказательство клиента не отвергнуто: принято либо // до него не дошло (отказ формы). `invalid_grant` сюда же: его производит // клиент, уже доказавший себя. LaneProofNotRefused LaneVerdict = iota // LaneProofRefused — отвергнуто доказательство клиента: `invalid_client`. LaneProofRefused )
type Verifier ¶
type Verifier interface {
// Verify — полоса аутентификации клиента (RFC 7523 §2.2): утверждение
// доказывает личность клиента, ключ берётся из строки реестра.
Verify(ctx context.Context, assertionType, raw string) (clientassertion.Result, error)
// VerifyFederated — федеративная полоса (RFC 7523 §2.1): утверждение
// подписал внешний издатель, ключ берётся из нашего перечня доверенных
// издателей.
VerifyFederated(ctx context.Context, raw string) (clientassertion.Result, error)
}
Verifier — порт проверяющего утверждение, ОБЕ полосы.
Один порт на две полосы, а не два: эндпоинт обязан выбирать полосу сам, по объявленному виду выдачи. Разнеси их по двум портам — и появилась бы посадка, где провязана одна, а вторая молча отвергает всё.