Documentation
¶
Overview ¶
Package secondfactorwrap — переобёртка секретов второго фактора под ПЕРВЫЙ ключ перечня `authn.second-factor-encryption-key-hex` (kaname#259 п.3).
Зачем ¶
Перечень ключей обёртки даёт ЗАМЕЩЕНИЕ ключа — новый ставится первым, все открывают, — но не ВЫВОД: прежний ключ открывает каждый секрет, записанный им, и снять его из перечня значит отдать недоступность (503) каждому, чей секрет им обёрнут. Проход переносит каждый такой секрет под первый ключ; после него прежний ключ не читает ни одной строки и снимается.
Как проход держит свои свойства ¶
- БЕЗ ПОТЕРИ: секрет не меняется, меняется только обёртка — приложение человека даёт те же коды, принятый шаг строки остаётся в силе;
- КАЖДАЯ ЗАМЕНА — CAS ПО ПРОЧИТАННОМУ: материал строки сменяется, только пока он побайтово тот, что проход прочитал (ban #10). Служба, заведшая фактор заново или снявшая его, пока проход держал прежнее значение, выигрывает: переобёрнутый ПРЕЖНИЙ секрет поверх нового не ложится, снятая строка не воскресает. Промах замены — перечитывание строки;
- СРЫВ НА СЕРЕДИНЕ БЕЗОПАСЕН: у каждой строки своя замена, зафиксированная отдельно, поэтому в любой момент каждая строка — либо под первым ключом, либо под прежним, который ещё назван в перечне. Повтор довершает;
- ПОВТОР ИДЕМПОТЕНТЕН: строка, уже обёрнутая первым ключом, не пишется.
Исходы — по типу, а не по значению ¶
`Run` возвращает счёт (`Report`) и отказ. Отказ — «не смог спросить»: хранилище не ответило, сделанное до него названо счётом и зафиксировано. Счёт без отказа различает «каждый секрет под первым ключом» (`Settled`) и «проход прошёл, но не всё переехало»: секрет, которого не открывает ни один ключ перечня, и строка, не устоявшаяся за бюджет замен, — остаток, и прежние ключи при нём не снимаются.
Index ¶
Constants ¶
const SwapAttempts = 3
SwapAttempts — бюджет замен одной строки.
Выведен из аргумента о прогрессе, а не выбран: промах замены доказывает, что конкурент ЗАФИКСИРОВАЛ новое значение строки (замена ждёт его замка и судит условие по зафиксированному). Значение, положенное службой с тем же первым ключом, при перечитывании уже под ним — строка устаивается второй попыткой. Третья покрывает реплику, у которой первым ещё стоял прежний ключ: её значение переобёртывается ещё раз. Строка, которую меняют перед КАЖДОЙ заменой, дольше не держит проход: она — остаток «не устоялось», и повтор команды пройдёт её снова.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Report ¶
type Report struct {
// Rows — строк `totp`, увиденных проходом.
Rows int
// Rewrapped — переобёрнуто этим проходом.
Rewrapped int
// Current — уже были под первым ключом: писать было нечего.
Current int
// Unreadable — не открываются ни одним ключом перечня: не тронуты.
Unreadable int
// Vanished — строку сняли, пока проход держал её значение.
Vanished int
// Unsettled — строку меняли перед каждой заменой дольше бюджета.
Unsettled int
}
Report — счёт прохода. Каждая строка, увиденная проходом, попадает ровно в одну клетку: Rows = Rewrapped + Current + Unreadable + Vanished + Unsettled.
type Rewrapper ¶
type Rewrapper interface {
Rewrap(stored domain.LoginVerifier) (domain.LoginVerifier, bool, error)
}
Rewrapper — переобёртка хранимого секрета под первый ключ перечня (порт по существующему типу `totpverify.Verifier`): (stored, true) — уже под ним; (новое, false) — переобёрнут; `totpverify.ErrSecretUnreadable` — не открывается перечнем.
type Store ¶
type Store interface {
// TOTPSecretsAfter — строки вида `totp` ОБОИХ состояний (подтверждённые и
// заведения — секрет под ключом обёртки есть у тех и у других) с
// человеком строго после after, по возрастанию человека, не больше limit.
TOTPSecretsAfter(ctx context.Context, after domain.UserID, limit int) ([]domain.LoginMethod, error)
// SwapTOTPSecret — замена материала строки `totp` человека на next ТОЛЬКО
// если он побайтово равен prev; меняется один материал. false — материал
// уже другой либо строки нет.
SwapTOTPSecret(ctx context.Context, user domain.UserID, prev, next domain.LoginVerifier) (bool, error)
// Get — строка человека данного вида; NOT_FOUND — строки нет.
Get(ctx context.Context, user domain.UserID, kind domain.LoginMethodKind) (domain.LoginMethod, error)
}
Store — хранилище строк второго фактора, над которым идёт проход. Исполняет его адаптер способа входа (`internal/repo/kaname/pg`), единственный файл, которому дано назвать таблицу секрета.
type UseCase ¶
type UseCase struct {
// contains filtered or unexported fields
}
UseCase — проход переобёртки.
func New ¶
New собирает проход. page — строк за одно чтение; callTimeout — предел КАЖДОГО обращения к хранилищу: вызывающий (команда оператора) срока на весь проход не ставит — работа пропорциональна числу людей, — поэтому зависшая связь ограничивается здесь, на каждом вызове.
func (*UseCase) Run ¶
Run проходит все строки `totp` страницами по возрастанию человека.
Отказ хранилища останавливает проход и возвращается вместе со счётом сделанного: каждая замена до него зафиксирована, и повтор продолжит. Строки, заведённые после того, как курсор их миновал, пишет служба своим первым ключом; проход их не видит, и это не остаток — отсюда совет повторного прогона у команды.