feat(api): implement API key management and authentication enhancements
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 28s
CI / go (push) Failing after 24s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 28s
CI / go (push) Failing after 24s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped
- Added endpoints for managing API keys, including creation, retrieval, updating, and revocation. - Introduced a new Auth session endpoint to retrieve current tenant and role information. - Updated the authentication middleware to support API key-based authentication and track last used timestamps. - Enhanced documentation to reflect new API key functionalities and usage guidelines. - Improved logging for demo authentication scenarios.
This commit is contained in:
+17
-3
@@ -22,6 +22,19 @@ opkey|01ARZ3NDEKTSV4RRFFQ69G5FAV|operator,nodekey|01ARZ3NDEKTSV4RRFFQ69G5FAV|nod
|
||||
|
||||
При включённом демо-сиде сервер при старте может вывести в лог готовую подсказку с реальным `tenant_id` из БД — см. лог `evobgp-api` / `evobgp-all`.
|
||||
|
||||
Ключи из `EVOBGP_API_KEYS` загружаются при старте и **дополняют** ключи из таблицы `api_key` в БД (break-glass / bootstrap). После первого operator-ключа можно создавать остальные через API или веб-настройки.
|
||||
|
||||
### Управление через API и UI
|
||||
|
||||
При подключённой БД operator может:
|
||||
|
||||
- `GET|POST /v1/api-keys`, `GET|PATCH|DELETE /v1/api-keys/{id}`, `POST /v1/api-keys/{id}/rotate` — см. OpenAPI, тег **API keys**.
|
||||
- В веб-панели: **Права доступа** (`/access`) → блок «API-ключи» (только для роли `operator`). Токен для браузера — в **Настройки** (`/settings`).
|
||||
|
||||
Полный токен возвращается **один раз** в ответе `201` (создание) и `200` (ротация). В списках — только `prefix` (первые 8 символов). В БД хранится SHA-256 токена, не plaintext.
|
||||
|
||||
`GET /v1/auth/session` — текущие `tenant_id` и `role` (для UI).
|
||||
|
||||
### Роли
|
||||
|
||||
| Роль | Уровень | Назначение |
|
||||
@@ -33,11 +46,11 @@ opkey|01ARZ3NDEKTSV4RRFFQ69G5FAV|operator,nodekey|01ARZ3NDEKTSV4RRFFQ69G5FAV|nod
|
||||
|
||||
Обратное ограничение: для эндпоинтов ноды требуется именно роль **`node`**; остальные роли получают отказ.
|
||||
|
||||
### Режим разработки `EVOBGP_DEV_INSECURE`
|
||||
### Токен `dev` (локальная разработка)
|
||||
|
||||
Если установлено `EVOBGP_DEV_INSECURE=1` и в store доступен демо-tenant (`DemoIDs`), то запрос с заголовком **`Authorization: Bearer dev`** получает контекст **`operator`** для этого tenant.
|
||||
Если в store доступен демо-tenant (`DemoIDs`, обычно `EVOBGP_SEED_DEMO` не равен `0`), заголовок **`Authorization: Bearer dev`** даёт роль **`operator`** для этого tenant. **Не зависит** от `EVOBGP_DEV_INSECURE`.
|
||||
|
||||
**Запрещено** в продакшене: любой, кто знает заголовок, получает полные права оператора на демо-данные. В reference Compose (`deploy/compose/docker-compose.yaml`) флаг включён только для локальной разработки.
|
||||
**Запрещено** в продакшене: не оставляйте demo-seed с известным токеном `dev` на боевых данных. Переменная `EVOBGP_DEV_INSECURE` в текущей версии **не влияет** на аутентификацию (оставлена в compose для совместимости; не включайте в production — см. SEC-02 в инженерных правилах).
|
||||
|
||||
### Синхронные «тяжёлые» GET (control plane)
|
||||
|
||||
@@ -93,6 +106,7 @@ http://localhost:5173,http://127.0.0.1:5173,https://ui.example.com
|
||||
| GET модули, ревизии, peers, speakers | да | да | да | нет |
|
||||
| POST/PATCH/DELETE CRUD сущностей | нет | да | да | нет |
|
||||
| apply, rollback, PATCH settings | нет | нет | да | нет |
|
||||
| Управление API-ключами (`/v1/api-keys`) | нет | нет | да | нет |
|
||||
| bundle, latest revision, enroll | нет | нет | нет | да |
|
||||
|
||||
Точные проверки по каждому маршруту — в коде `internal/httpapi` и в схеме безопасности операций в OpenAPI.
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
- `GET|POST /v1/communities`
|
||||
- `GET|PATCH|DELETE /v1/communities/{id}`
|
||||
|
||||
### API keys
|
||||
|
||||
- `GET /v1/auth/session` — tenant и роль текущего ключа
|
||||
- `GET|POST /v1/api-keys` — список и создание (operator)
|
||||
- `GET|PATCH|DELETE /v1/api-keys/{id}`, `POST /v1/api-keys/{id}/rotate`
|
||||
|
||||
### Peers
|
||||
|
||||
- `GET /v1/peers`, `POST /v1/peers`
|
||||
|
||||
@@ -45,6 +45,10 @@ tags:
|
||||
description: "API для evobgp-node (бандлы ревизий и enrollment). Отдельный ключ или mTLS, роль node."
|
||||
- name: Settings
|
||||
description: Глобальные настройки и feature flags; изменение - только operator.
|
||||
- name: API keys
|
||||
description: Управление API-ключами tenant (operator). Секрет возвращается только при создании и ротации.
|
||||
- name: Auth
|
||||
description: Сессия текущего API-ключа (tenant и роль).
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
@@ -135,6 +139,12 @@ components:
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/components/schemas/ResourceId"
|
||||
ApiKeyId:
|
||||
name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/components/schemas/ResourceId"
|
||||
SourceId:
|
||||
name: source_id
|
||||
in: path
|
||||
@@ -639,6 +649,82 @@ components:
|
||||
vault_secret_ref:
|
||||
type: ["string", "null"]
|
||||
|
||||
AuthSession:
|
||||
type: object
|
||||
required: [tenant_id, role]
|
||||
properties:
|
||||
tenant_id:
|
||||
$ref: "#/components/schemas/ResourceId"
|
||||
role:
|
||||
type: string
|
||||
enum: [viewer, editor, operator, node]
|
||||
|
||||
ApiKey:
|
||||
type: object
|
||||
required: [id, name, role, prefix, created_at, updated_at]
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/ResourceId"
|
||||
name:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [viewer, editor, operator, node]
|
||||
prefix:
|
||||
type: string
|
||||
description: Первые 8 символов токена для идентификации в UI.
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
expires_at:
|
||||
type: ["string", "null"]
|
||||
format: date-time
|
||||
revoked_at:
|
||||
type: ["string", "null"]
|
||||
format: date-time
|
||||
last_used_at:
|
||||
type: ["string", "null"]
|
||||
format: date-time
|
||||
additionalProperties: true
|
||||
|
||||
ApiKeyCreate:
|
||||
type: object
|
||||
required: [name, role]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [viewer, editor, operator, node]
|
||||
expires_at:
|
||||
type: ["string", "null"]
|
||||
format: date-time
|
||||
|
||||
ApiKeyPatch:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [viewer, editor, operator, node]
|
||||
expires_at:
|
||||
type: ["string", "null"]
|
||||
format: date-time
|
||||
|
||||
ApiKeyCreated:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ApiKey"
|
||||
- type: object
|
||||
required: [token]
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: Полный Bearer-токен; показывается один раз.
|
||||
|
||||
BgpCommunity:
|
||||
type: object
|
||||
required:
|
||||
@@ -2643,6 +2729,170 @@ paths:
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
|
||||
/v1/auth/session:
|
||||
get:
|
||||
tags: [Auth]
|
||||
summary: Текущая сессия API-ключа
|
||||
operationId: getAuthSession
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TenantId"
|
||||
responses:
|
||||
"200":
|
||||
description: Успешно.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AuthSession"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
|
||||
/v1/api-keys:
|
||||
get:
|
||||
tags: [API keys]
|
||||
summary: Список API-ключей tenant
|
||||
description: Только роль **operator**. Секреты не возвращаются.
|
||||
operationId: listApiKeys
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TenantId"
|
||||
- $ref: "#/components/parameters/Cursor"
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
responses:
|
||||
"200":
|
||||
description: Успешно.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [items, has_more]
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ApiKey"
|
||||
next_cursor:
|
||||
type: ["string", "null"]
|
||||
has_more:
|
||||
type: boolean
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
post:
|
||||
tags: [API keys]
|
||||
summary: Создать API-ключ
|
||||
operationId: createApiKey
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TenantId"
|
||||
- $ref: "#/components/parameters/IdempotencyKey"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiKeyCreate"
|
||||
responses:
|
||||
"201":
|
||||
description: Ключ создан; token в ответе один раз.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiKeyCreated"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"422":
|
||||
$ref: "#/components/responses/UnprocessableEntity"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
|
||||
/v1/api-keys/{id}:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TenantId"
|
||||
- $ref: "#/components/parameters/ApiKeyId"
|
||||
get:
|
||||
tags: [API keys]
|
||||
summary: Получить метаданные API-ключа
|
||||
operationId: getApiKey
|
||||
responses:
|
||||
"200":
|
||||
description: Успешно.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiKey"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
patch:
|
||||
tags: [API keys]
|
||||
summary: Обновить API-ключ
|
||||
operationId: patchApiKey
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/IdempotencyKey"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiKeyPatch"
|
||||
responses:
|
||||
"200":
|
||||
description: Успешно.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiKey"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
delete:
|
||||
tags: [API keys]
|
||||
summary: Отозвать API-ключ
|
||||
operationId: revokeApiKey
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/IdempotencyKey"
|
||||
responses:
|
||||
"204":
|
||||
description: Отозван.
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
|
||||
/v1/api-keys/{id}/rotate:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TenantId"
|
||||
- $ref: "#/components/parameters/ApiKeyId"
|
||||
post:
|
||||
tags: [API keys]
|
||||
summary: Ротировать секрет API-ключа
|
||||
description: Выдаёт новый token; старый перестаёт работать сразу.
|
||||
operationId: rotateApiKey
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/IdempotencyKey"
|
||||
responses:
|
||||
"200":
|
||||
description: Успешно.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiKeyCreated"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
|
||||
/v1/settings:
|
||||
get:
|
||||
tags: [Settings]
|
||||
|
||||
Reference in New Issue
Block a user