Files
fission-console/doc/thinking/2026-04-15.md
T

7.0 KiB
Raw Blame History

2026-04-15 — Thinking Log

Агент: GitHub Copilot (Claude Opus 4.6)


Сессия: Fixup invoke auth в fission-console

Контекст на начало сессии

Console (fission-console) — веб-UI для управления Fission serverless functions. Был полностью реализован: CRUD (create/get/update/delete), UI с модалами, unit-тесты, Docker-образ, k8s deployment, ingress. Всё работало через https://fission.kube5s.ru/console/.

Единственная нерешённая проблема: invoke функции через console API возвращал 401 unauthorized: malformed token — роутер Fission требует авторизацию.

Анализ проблемы

Invoke flow: console → POST к роутеру Fission → роутер вызывает функцию.

Роутер Fission (router.fission.svc.cluster.local) сконфигурирован с auth:

  • AUTH_USERNAME / AUTH_PASSWORD — из секрета router
  • JWT_SIGNING_KEY — из секрета router
  • ConfigMap feature-config содержит: auth.enabled: true, auth.authUriPath: "/auth/login", auth.jwtExpiryTime: 120

Значит роутер использует собственный JWT, а не K8s ServiceAccount tokens.

Первая попытка: SA token (неудачная)

Изначально решил использовать ServiceAccount token из пода:

  • Файл /var/run/secrets/kubernetes.io/serviceaccount/token автоматически монтируется в любой под
  • Добавил saTokenPath в server struct, метод readSAToken()
  • При invoke читал токен и ставил Authorization: Bearer <token>

Результат: 401 unauthorized: key is of invalid type — роутер пытался валидировать K8s JWT своим signing key (HS256), а K8s SA token — RS256. Не совместимы.

Исследование auth-механизма роутера

Прочитал секрет router:

  • username: admin
  • password: 7XG1lSg0EFqPLE4pf3He
  • jwtSigningKey: FWT3rdVUwfUn08tkC3lt

Проверил endpoint /auth/login:

POST http://router.fission.svc.cluster.local/auth/login
Body: {"username":"admin","password":"..."}
Response 201: {"accesstoken":"eyJ...","tokentype":"Bearer"}

Проверил invoke с полученным JWT:

GET http://router.fission.svc.cluster.local/auth-test2
Header: Authorization: Bearer eyJ...
Response 200: hello

Работает! Роутер принимает свой JWT.

Вторая попытка: JWT login (успешная)

Решение: console при invoke делает POST /auth/login к роутеру, получает JWT, кеширует, использует для вызова функции.

Изменения в main.go:

  1. Добавил поля в server struct: authUser, authPass, tokenMu sync.Mutex, cachedJWT, tokenExpAt
  2. Новый метод getRouterToken():
    • Если authUser/authPass заданы → делает POST /auth/login
    • Кеширует JWT на 100 сек (JWT expiry = 120 сек, берём с запасом)
    • Fallback на SA token если login не удался
  3. В handleInvokeFunction вызывает getRouterToken() и ставит Authorization: Bearer <token>
  4. Env переменные FISSION_AUTH_USERNAME / FISSION_AUTH_PASSWORD берутся из секрета router

Изменения в deploy/console.yaml:

  • Добавил env FISSION_AUTH_USERNAME и FISSION_AUTH_PASSWORD с secretKeyRef из секрета router

Баг: 201 vs 200

Первый деплой с JWT login не работал: в логах видно что login возвращает 201 Created (не 200 OK), а мой код проверял строго resp.StatusCode != http.StatusOK. JWT получался, но отбрасывался.

Фикс: if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {

Баг: кешированный Docker-образ

После первого docker push naeel/fission-console:v0.2.0 без JWT, потом пересобрал с JWT и запушил тот же тег v0.2.0. Но нод кластера использовал старый образ (imagePullPolicy по умолчанию = IfNotPresent). SHA не совпадали.

Решение: начал использовать инкрементальные теги (v0.2.1, v0.2.2).

Финальный результат

E2E тест через ingress:

=== create ===
{"httptrigger":"final-test-route","name":"final-test","package":"final-test-pkg","route":"/final-test"}

=== invoke ===
{"invoke_url":"http://router.fission.svc.cluster.local/final-test","latency_ms":138,"response_raw":"invoke works!","status":200}

=== delete ===
{"deleted":true,"name":"final-test","package":"final-test-pkg"}

Все 5 тестов пройдены. Commit 8920ba1feat/console.

Обновление unit-тестов

Тест TestInvokeFunctionWithJWTAuth:

  • Поднимает mock HTTP server, который на /auth/login возвращает {"accesstoken":"fake-jwt-token-xyz"}
  • На другие пути проверяет Authorization header
  • Создаёт функцию через CRUD, вызывает invoke
  • Проверяет что JWT был получен и отправлен

Итоговая архитектура auth flow

User → POST /console/api/functions/NAME/invoke
            ↓
    Console backend:
    1. GET trigger для NAME → route, methods
    2. getRouterToken():
       - cached JWT есть и не expired? → используем
       - иначе POST /auth/login → получаем JWT, кешируем на 100s
    3. GET/POST router_url + route
       Header: Authorization: Bearer <JWT>
            ↓
    Fission Router (валидирует JWT)
            ↓
    Function pod → response
            ↓
    Console → User (JSON: status, latency_ms, invoke_url, response_raw)

Сессия: Обновление правил репозиториев

Добавлены правила работы с файловой системой и SSH:

  • sless: обновлён .github/copilot-instructions.md — добавлена секция про маппинг путей, разрешение редактировать локально, обязательность SSH для команд
  • fission: создан .github/copilot-instructions.md с аналогичными правилами

Причина: VPN может быть активен локально, вызывая сбои при прямом выполнении команд. Монтирование sshfs делает локальное редактирование файлов эквивалентным редактированию на ВМ.