docs: обновление документации 2026-03-11
- architecture/overview.md — актуальная архитектура: namespace-per-user, схема JWT->SHA256->namespace, структура кода, версии в production - decisions/log.md — новые решения: два провайдера нельзя объединять, namespace-per-user, EnsureNamespace как отдельный endpoint (SoC), JWT validation вместо статического токена, валидация через nubes API, handler.go SoC рефакторинг - progress.md — статус 2026-03-11 (all done), технический долг - architecture/agent-handoff-2026-03-11.md — подробный handoff для Opus: полная архитектура, lifecycle контроллеров, примеры кода, ТЗ, вопросы
This commit is contained in:
@@ -399,3 +399,124 @@ if h.Validator != nil {
|
||||
- False positives: пользователь получит 400 с причиной, может обратиться в support.
|
||||
- Soft-fail при недоступности LLM: security degraded, но деплой работает.
|
||||
- Prompt не идеален: LLM не ловит всё. Это дополнительный слой, не единственный.
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — Два провайдера: sless и nubes — нельзя объединять
|
||||
|
||||
**Решение:** Провайдеры `sless` и `nubes` — **два отдельных независимых провайдера**.
|
||||
Объединять их в один бинарник нельзя.
|
||||
|
||||
**Причина:**
|
||||
- Разные зоны ответственности: `nubes` — облачная инфраструктура (ВМ, сети, объектное хранилище),
|
||||
`sless` — serverless функции.
|
||||
- Разные релизные циклы.
|
||||
- В будущем — разные команды.
|
||||
|
||||
Пользователь использует оба в одном `.tf` файле — это нормально, это не значит что они один бинарник.
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — Namespace-per-user через JWT sub → SHA256
|
||||
|
||||
**Решение:** Каждый пользователь облака получает отдельный k8s namespace.
|
||||
Namespace вычисляется детерминированно из JWT sub.
|
||||
|
||||
**Алгоритм:**
|
||||
```
|
||||
namespace = "sless-" + hex(SHA256(JWT.sub)[:8])
|
||||
```
|
||||
Итоговая длина: 22 символа. Пример: `sless-cdd874dfa31ba6ca`.
|
||||
|
||||
**Почему SHA256, а не UUID напрямую:**
|
||||
- UUID (sub) напрямую в имени namespace — раскрывает внутренний ID пользователя.
|
||||
- SHA256 — необратим, namespace не позволяет восстановить sub.
|
||||
|
||||
**Реализация:**
|
||||
- `client.SubFromJWT(token)` — декодирует JWT payload → возвращает sub
|
||||
- `client.NamespaceFromSub(sub)` — SHA256(sub)[:8] → hex → "sless-{hex16}"
|
||||
- Вычисляется в `provider.Configure()` до создания Client
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — EnsureNamespace как отдельный endpoint (SoC)
|
||||
|
||||
**Проблема:** Создание namespace было в resource-хендлерах (CreateFunction, CreateTrigger, CreateJob).
|
||||
Это нарушение разделения ответственностей: ресурс должен заниматься только тем, для чего предназначен.
|
||||
|
||||
**Решение:**
|
||||
- Создан отдельный endpoint `POST /v1/namespaces/{namespace}/ensure`
|
||||
- Хендлер вынесен в отдельный файл `internal/api/handler/namespace.go`
|
||||
- Провайдер вызывает его **один раз** в `Configure()` до создания любых ресурсов
|
||||
- `handler.go` очищен от k8s-типов (corev1, k8serrors, metav1) — только инфраструктура
|
||||
|
||||
**Поведение endpoint:**
|
||||
- 200 OK `{"namespace": "...", "status": "exists"}` — namespace уже был
|
||||
- 201 Created `{"namespace": "...", "status": "created"}` — namespace создан
|
||||
- Идемпотентен: параллельные запросы не падают (IsAlreadyExists обработан)
|
||||
|
||||
**Кто отвечает за namespace:**
|
||||
Только `EnsureNamespace`. Ни один другой хендлер namespace не трогает.
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — JWT validation в операторе вместо статического токена
|
||||
|
||||
**Проблема:** Оператор сравнивал Bearer токен со статическим `apiToken` из конфига.
|
||||
JWT-токены облака не совпадали → все запросы от провайдера отклонялись с 401.
|
||||
|
||||
**Решение:** `internal/api/middleware/auth.go` — заменена проверка:
|
||||
- Было: `token == cfg.APIToken` (строковое сравнение)
|
||||
- Стало: `validateJWT(token)` — проверяет структуру JWT (3 части), наличие `sub`, срок действия `exp`
|
||||
|
||||
**Почему подпись не проверяется:**
|
||||
Оператор находится за Ingress в закрытом кластере (trusted perimeter).
|
||||
Проверка подписи требует публичный ключ issuer — усложнение без реальной пользы в данной топологии.
|
||||
Подпись проверяется косвенно через `PingNubesAPI` в провайдере при `terraform init`.
|
||||
|
||||
**Версия:** operator v0.1.20
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — Валидация токена через nubes API при Configure
|
||||
|
||||
**Решение:** При `terraform init` / `terraform apply` провайдер пингует nubes API
|
||||
для подтверждения что токен действителен.
|
||||
|
||||
**Реализация:** `client.PingNubesAPI(ctx, endpoint, token)`:
|
||||
- `GET <nubes_endpoint>` с Bearer токеном
|
||||
- 401/403 → токен отклонён → ошибка инициализации провайдера
|
||||
- Ошибка соединения → ошибка инициализации
|
||||
- Любой другой статус (200, 404, 500...) → токен не декларирован невалидным → OK
|
||||
|
||||
**Конфигурация:**
|
||||
```hcl
|
||||
provider "sless" {
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = file("./secrets/prod.token")
|
||||
nubes_endpoint = "https://deck-api.ngcloud.ru/api/v1"
|
||||
}
|
||||
```
|
||||
Env-альтернативы: SLESS_ENDPOINT, SLESS_API_TOKEN, NUBES_ENDPOINT.
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — SoC рефакторинг handler.go
|
||||
|
||||
**Решение:** Файл `handler.go` — чистая инфраструктура.
|
||||
Бизнес-логика по доменам — в отдельных файлах одного package.
|
||||
|
||||
**Структура handler/ package:**
|
||||
```
|
||||
handler.go — Handler struct + helpers (writeJSON, errResp, pathVar, namespace)
|
||||
namespace.go — EnsureNamespace (k8s namespace lifecycle)
|
||||
functions.go — CRUD Function
|
||||
triggers.go — CRUD Trigger
|
||||
jobs.go — CRUD FunctionJob
|
||||
upload.go — zip -> tar.gz -> S3 -> CRD patch
|
||||
invoke.go — прокси /fn/ -> in-cluster
|
||||
invocations.go — 501 stub
|
||||
```
|
||||
|
||||
**Принцип:** каждый файл отвечает за один домен.
|
||||
`handler.go` не импортирует `corev1/k8serrors/metav1` — эти зависимости только в `namespace.go`.
|
||||
|
||||
Reference in New Issue
Block a user