вход переехал на доверенный заголовок Authelia вместо собственного OIDC
- пришедшего называет заголовок Remote-User от прокси, и верят ему только с адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе - учётная запись заводится первым обращением: EnsureUser в пакете хранилища, шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users - cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт унаследованный DL3066 — пользователь образа назван числом
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Command devtools — оснастка разработчика: то, что нужно для локального
|
||||
// прогона и никогда не едет в боевой образ.
|
||||
//
|
||||
// Пакет один на все такие инструменты, а не по пакету на инструмент. Причина
|
||||
// счётная: каждый отдельный пакет стоит четырёх мест — строка сборки образа,
|
||||
// «Деплой» в устройстве, «Команды» в памятке, README, — и забытая строка сборки
|
||||
// тихо кладёт инструмент разработчика в боевой образ. Один пакет платит эти
|
||||
// четыре места **однажды**, сколько бы подкоманд в нём ни завелось.
|
||||
//
|
||||
// Сегодня подкоманда одна — `proxy`. Заведение владельца панели придёт
|
||||
// подкомандой `admin` вместе с задачей о запуске одной командой.
|
||||
//
|
||||
// Имена заголовков берутся **константами транспорта**, а не литералами: они
|
||||
// нормативны, и второй список разошёлся бы с первым молча — локальный вход
|
||||
// перестал бы узнавать кого бы то ни было, а искали бы поломку в сервисе.
|
||||
//
|
||||
// Вывод идёт stdlib-логом в поток ошибок, а не `slog`: его читает человек в
|
||||
// терминале, в сбор он не едет. Изъятие названо строкой в конвенции журнала.
|
||||
//
|
||||
// В образ пакет не едет: ступень сборки называет `./cmd/transcriber` поимённо.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
httpcontroller "git.vakhrushev.me/av/transcriber/internal/controller/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "proxy":
|
||||
runProxy(os.Args[2:])
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "неизвестная подкоманда: %s\n\n", os.Args[1])
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprint(os.Stderr, `Оснастка разработчика.
|
||||
|
||||
Подкоманды:
|
||||
proxy подставной обратный прокси: ставит заголовок и шлёт запрос сервису
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
// runProxy поднимает подставной обратный прокси.
|
||||
//
|
||||
// Зачем он нужен: сервис узнаёт пришедшего по заголовку, который на сервере
|
||||
// ставит Caddy, сходив к Authelia. На машине разработчика ни того ни другого
|
||||
// нет, а браузер заголовков не ставит — значит приложение локально не
|
||||
// открывалось бы вовсе. Прокси встаёт на их место: слушает свой порт, ставит
|
||||
// заголовок и переправляет запрос сервису.
|
||||
//
|
||||
// Проверок он не делает никаких — ни пароля, ни группы, ни срока. Это его
|
||||
// назначение, а не упущение: пускать он должен всякого, кто до него дошёл, а
|
||||
// сам он слушает петлевой адрес.
|
||||
func runProxy(args []string) {
|
||||
flags := flag.NewFlagSet("proxy", flag.ExitOnError)
|
||||
listen := flags.String("listen", "127.0.0.1:9000", "адрес, на котором слушать")
|
||||
target := flags.String("target", "http://127.0.0.1:8080", "адрес сервиса")
|
||||
login := flags.String("user", "dev", "логин, которым называть пришедшего")
|
||||
name := flags.String("name", "Разработчик", "имя, пригодное к показу")
|
||||
email := flags.String("email", "", "адрес почты; пустой не ставится вовсе")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Слушаем петлевой адрес по умолчанию, и это часть назначения: прокси
|
||||
// пускает всякого, кто до него дошёл, и в чужой сети становится открытым
|
||||
// входом в сервис.
|
||||
upstream, err := url.Parse(*target)
|
||||
if err != nil {
|
||||
log.Fatalf("адрес сервиса не читается: %v", err)
|
||||
}
|
||||
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Rewrite: func(r *httputil.ProxyRequest) {
|
||||
r.SetURL(upstream)
|
||||
|
||||
// Именно Set, а не Add. Прокси, который **добавляет** заголовок к
|
||||
// присланному, оставляет рядом со своим значением чужое — и сервис
|
||||
// отвергает такой запрос целиком, потому что двух значений он не
|
||||
// разбирает. Настоящий Caddy обязан делать то же самое, и это
|
||||
// записано требованием к контуру в модели угроз.
|
||||
r.Out.Header.Set(httpcontroller.LoginHeader, *login)
|
||||
r.Out.Header.Set(httpcontroller.NameHeader, *name)
|
||||
if *email != "" {
|
||||
r.Out.Header.Set(httpcontroller.EmailHeader, *email)
|
||||
} else {
|
||||
r.Out.Header.Del(httpcontroller.EmailHeader)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
log.Printf("подставной прокси: %s → %s, пришедший — %q", *listen, *target, *login)
|
||||
log.Printf("приложение открывать по http://%s", *listen)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: *listen,
|
||||
Handler: proxy,
|
||||
// Таймаута чтения нет намеренно: сервис принимает записи на несколько
|
||||
// часов, и прокси, обрывающий такую загрузку, ловил бы разработчика на
|
||||
// поломке, которой в сервисе нет.
|
||||
ReadTimeout: 0,
|
||||
}
|
||||
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
log.Fatalf("подставной прокси остановлен: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user