- пришедшего называет заголовок Remote-User от прокси, и верят ему только с адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе - учётная запись заводится первым обращением: EnsureUser в пакете хранилища, шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users - cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт унаследованный DL3066 — пользователь образа назван числом
93 lines
4.9 KiB
Go
93 lines
4.9 KiB
Go
package http
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/pocketbase/pocketbase/core"
|
|
)
|
|
|
|
// Своё правило ограничителя частоты под корнем приложения.
|
|
//
|
|
// Заводится потому, что правило хранилища настроено на **его** корень и наших
|
|
// адресов больше не покрывает: приложение уехало в своё пространство, и вместе с
|
|
// переездом ограничитель перестал бы существовать для него вовсе. Потеря тихая —
|
|
// заметить её нечем, пока кто-нибудь не начнёт опрашивать карточку в цикле.
|
|
//
|
|
// Числа скромные намеренно: сервисом пользуются единицы человек, а экран
|
|
// опрашивает карточку, пока запись идёт по конвейеру. Из них же выводится
|
|
// частота опроса, которую сервис объявляет приложению, — два числа об одном и
|
|
// том же разъехались бы при первой правке.
|
|
const (
|
|
appRateMaxRequests = 120
|
|
appRateWindowSec = 60
|
|
)
|
|
|
|
// ApplyAppRateLimit ставит правило ограничителя на корень приложения.
|
|
//
|
|
// Правило приводится к настройке при каждом подъёме, как и настройки провайдера:
|
|
// применённый шаг схемы не переписывается, а настройки хранилища живут в базе, и
|
|
// правило, положенное однажды, не пережило бы ни правки числа, ни чистого
|
|
// каталога данных.
|
|
func ApplyAppRateLimit(app core.App) error {
|
|
settings := app.Settings()
|
|
|
|
rule := core.RateLimitRule{
|
|
Label: AppRoot + "/",
|
|
MaxRequests: appRateMaxRequests,
|
|
Duration: appRateWindowSec,
|
|
}
|
|
|
|
replaced := false
|
|
for i, existing := range settings.RateLimits.Rules {
|
|
if existing.Label == rule.Label {
|
|
settings.RateLimits.Rules[i] = rule
|
|
replaced = true
|
|
break
|
|
}
|
|
}
|
|
if !replaced {
|
|
settings.RateLimits.Rules = append(settings.RateLimits.Rules, rule)
|
|
}
|
|
|
|
settings.RateLimits.Enabled = true
|
|
|
|
if err := app.Save(settings); err != nil {
|
|
return fmt.Errorf("failed to apply app rate limit: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ApplyTrustedProxyHeaders называет хранилищу заголовок, из которого брать
|
|
// адрес спрашивающего.
|
|
//
|
|
// Без этого ограничитель частоты ключует счётчик адресом **пира**, а пир с
|
|
// переездом входа на заголовок всегда один и тот же — обратный прокси. Бюджет
|
|
// в этом случае общий на весь сервис: восемь одновременно открытых карточек
|
|
// выбирают его целиком, и девятый человек получает отказ, не сделав ни одного
|
|
// запроса. Норма при этом требует обратного — бюджет считается по адресу
|
|
// спрашивающего.
|
|
//
|
|
// Доверие здесь той же природы, что и к `Remote-User`, и той же ширины: адрес
|
|
// берётся из пересылаемого заголовка, а верить пересылаемому можно ровно
|
|
// потому, что до нас дотянулся доверенный пир. Отсюда требование к контуру,
|
|
// записанное в модели угроз: прокси обязан `X-Forwarded-For` **перезаписывать**,
|
|
// а не дописывать к присланному, — иначе спрашивающий назначает себе ключ
|
|
// счётчика сам и обходит ограничитель, меняя значение.
|
|
//
|
|
// Барьером узнавания этот заголовок не служит и служить не может: кто пришёл,
|
|
// по-прежнему решает адрес самого соединения.
|
|
func ApplyTrustedProxyHeaders(app core.App) error {
|
|
settings := app.Settings()
|
|
|
|
settings.TrustedProxy.Headers = []string{"X-Forwarded-For"}
|
|
// Пустой заголовок означает, что прокси адреса не назвал; счётчик тогда
|
|
// падает обратно на адрес пира — то есть на общий, как было. Лучше общий,
|
|
// чем один пустой ключ на всех.
|
|
settings.TrustedProxy.UseLeftmostIP = true
|
|
|
|
if err := app.Save(settings); err != nil {
|
|
return fmt.Errorf("failed to apply trusted proxy headers: %w", err)
|
|
}
|
|
return nil
|
|
}
|