feat(shared-sqs): fixed demo credentials + BYOC foundation (v0.1.9)
- seed.go: CreateFixed с хардкод credentials для demo-service AccessKey=SSAK-demo-shared-sqs (постоянные, README всегда актуален) - tenant_store: добавлен CreateFixed для задания произвольных credentials - deployment.yaml: v0.1.9 - doc/byoc-credentials.md: полная документация BYOC фичи (TODO для API+UI)
This commit is contained in:
@@ -1,7 +1,12 @@
|
|||||||
// app/cmd/seed.go
|
// app/cmd/seed.go
|
||||||
// Created: 2026-04-09
|
// Created: 2026-04-09
|
||||||
|
// Updated: 2026-04-09 — фиксированные credentials для demo-tenant (BYOC)
|
||||||
// Автосид демо-данных при старте через SHARED_SQS_SEED_DEMO=true.
|
// Автосид демо-данных при старте через SHARED_SQS_SEED_DEMO=true.
|
||||||
// Создаёт тенанта demo-service с 5 очередями и демо-сообщениями.
|
// Создаёт тенанта demo-service с 5 очередями и демо-сообщениями.
|
||||||
|
//
|
||||||
|
// DEMO CREDENTIALS — только для тестового стенда.
|
||||||
|
// Тенант demo-service изолирован: видит только свои очереди, не имеет доступа к
|
||||||
|
// admin API и к очередям других тенантов. Credentials открыты намеренно — стенд публичный.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -16,10 +21,21 @@ import (
|
|||||||
"shared-sqs/app/tenant"
|
"shared-sqs/app/tenant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Фиксированные credentials демо-тенанта.
|
||||||
|
// Открыты намеренно — тестовый стенд.
|
||||||
|
// Тенант ограничен 10 очередями и не имеет прав admin API.
|
||||||
|
const (
|
||||||
|
demoTenantID = "t-demo-shared-sqs-ngcloud"
|
||||||
|
demoAccessKey = "SSAK-demo-shared-sqs"
|
||||||
|
demoSecretKey = "demo-secret-key-shared-sqs-ngcloud-2026"
|
||||||
|
demoTenantName = "demo-service"
|
||||||
|
demoMaxQueues = 10
|
||||||
|
)
|
||||||
|
|
||||||
// seedDemoData создаёт тенанта demo-service с очередями и сообщениями.
|
// seedDemoData создаёт тенанта demo-service с очередями и сообщениями.
|
||||||
// Вызывается при SHARED_SQS_SEED_DEMO=true при старте сервера.
|
// Вызывается при SHARED_SQS_SEED_DEMO=true при старте сервера.
|
||||||
func seedDemoData(store *tenant.TenantStore) {
|
func seedDemoData(store *tenant.TenantStore) {
|
||||||
t, err := store.Create("demo-service", 10)
|
t, err := store.CreateFixed(demoTenantName, demoMaxQueues, demoTenantID, demoAccessKey, demoSecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("seed: не удалось создать demo-tenant: %v", err)
|
log.Warnf("seed: не удалось создать demo-tenant: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -71,6 +71,31 @@ s.mu.Unlock()
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo).
|
||||||
|
// Используется только при инициализации демо-данных; не вызывается из user-facing API.
|
||||||
|
func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKey, secretKey string) (*Tenant, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if _, exists := s.byID[tenantID]; exists {
|
||||||
|
return nil, fmt.Errorf("tenant with id %s already exists", tenantID)
|
||||||
|
}
|
||||||
|
if _, exists := s.byAccessKey[accessKey]; exists {
|
||||||
|
return nil, fmt.Errorf("tenant with access key %s already exists", accessKey)
|
||||||
|
}
|
||||||
|
t := &Tenant{
|
||||||
|
ID: tenantID,
|
||||||
|
Name: name,
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
MaxQueues: maxQueues,
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
Active: true,
|
||||||
|
}
|
||||||
|
s.byID[t.ID] = t
|
||||||
|
s.byAccessKey[t.AccessKey] = t
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetByAccessKey — поиск тенанта по AccessKeyId (используется в auth middleware).
|
// GetByAccessKey — поиск тенанта по AccessKeyId (используется в auth middleware).
|
||||||
func (s *TenantStore) GetByAccessKey(accessKey string) (*Tenant, bool) {
|
func (s *TenantStore) GetByAccessKey(accessKey string) (*Tenant, bool) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# BYOC — Bring Your Own Credentials
|
||||||
|
|
||||||
|
**Дата**: 2026-04-09
|
||||||
|
**Статус**: частично реализовано (backend), UI/API — TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Суть
|
||||||
|
|
||||||
|
Возможность создать тенанта с **произвольными** Access Key и Secret Key вместо авто-генерируемых.
|
||||||
|
|
||||||
|
Нужно для:
|
||||||
|
- демо-стенда с фиксированными credentials (README всегда актуален)
|
||||||
|
- интеграционных тестов с предсказуемыми значениями
|
||||||
|
- миграции с другого SQS-совместимого сервиса (сохранение существующих ключей)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что уже сделано
|
||||||
|
|
||||||
|
### `app/tenant/tenant_store.go` — `CreateFixed`
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *TenantStore) CreateFixed(
|
||||||
|
name string,
|
||||||
|
maxQueues int,
|
||||||
|
tenantID string,
|
||||||
|
accessKey string,
|
||||||
|
secretKey string,
|
||||||
|
) (*Tenant, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
Создаёт тенанта с заранее известными credentials.
|
||||||
|
Проверяет уникальность и `tenantID`, и `accessKey` — конфликт возвращает ошибку.
|
||||||
|
|
||||||
|
### `app/cmd/seed.go` — демо-тенант
|
||||||
|
|
||||||
|
Использует `CreateFixed` при `SHARED_SQS_SEED_DEMO=true`:
|
||||||
|
|
||||||
|
```
|
||||||
|
tenantID = "t-demo-shared-sqs-ngcloud"
|
||||||
|
accessKey = "SSAK-demo-shared-sqs"
|
||||||
|
secretKey = "demo-secret-key-shared-sqs-ngcloud-2026"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что нужно сделать (TODO)
|
||||||
|
|
||||||
|
### Admin API — `POST /admin/tenants`
|
||||||
|
|
||||||
|
Добавить в `createTenantRequest` два опциональных поля:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// app/admin/admin.go
|
||||||
|
type createTenantRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
MaxQueues int `json:"max_queues"`
|
||||||
|
AccessKey string `json:"access_key,omitempty"` // TODO: BYOC
|
||||||
|
SecretKey string `json:"secret_key,omitempty"` // TODO: BYOC
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Логика в `createTenant` handler:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var t *tenant.Tenant
|
||||||
|
var err error
|
||||||
|
if req.AccessKey != "" || req.SecretKey != "" {
|
||||||
|
// BYOC: оба поля обязательны
|
||||||
|
if req.AccessKey == "" || req.SecretKey == "" {
|
||||||
|
jsonErr(w, http.StatusBadRequest, "both access_key and secret_key required when specifying custom credentials")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Минимальная длина — защита от случайно слабых ключей
|
||||||
|
if len(req.AccessKey) < 8 || len(req.SecretKey) < 16 {
|
||||||
|
jsonErr(w, http.StatusBadRequest, "access_key min 8 chars, secret_key min 16 chars")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err = h.store.CreateFixed(req.Name, req.MaxQueues, generateTenantID(), req.AccessKey, req.SecretKey)
|
||||||
|
} else {
|
||||||
|
t, err = h.store.Create(req.Name, req.MaxQueues)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> `generateTenantID()` — уже есть в tenant_store.go, нужно экспортировать или вынести.
|
||||||
|
|
||||||
|
### UI — Web форма создания тенанта
|
||||||
|
|
||||||
|
- Добавить в модальное окно "Создать тенанта" два опциональных поля: Access Key, Secret Key
|
||||||
|
- Показывать только если нажата кнопка "задать свои credentials"
|
||||||
|
- Валидация на клиенте: оба поля заполнены, мин. длина
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- BYOC-credentials **не дают доступа к admin API** — admin защищён отдельным Bearer токеном
|
||||||
|
- Тенант видит **только свои очереди** — изоляция по AccessKey в auth middleware
|
||||||
|
- Слабые ключи отклоняются на уровне API (минимальная длина)
|
||||||
|
- Credentials передаются только по HTTPS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Демо-credentials (открыты намеренно)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Access Key** | `SSAK-demo-shared-sqs` |
|
||||||
|
| **Secret Key** | `demo-secret-key-shared-sqs-ngcloud-2026` |
|
||||||
|
| **Tenant ID** | `t-demo-shared-sqs-ngcloud` |
|
||||||
|
| **Лимит очередей** | 10 |
|
||||||
|
|
||||||
|
Эти credentials жёстко прописаны в `app/cmd/seed.go`.
|
||||||
|
Тенант создаётся только если `SHARED_SQS_SEED_DEMO=true` (env var в deployment.yaml).
|
||||||
Reference in New Issue
Block a user