CI / changes (push) Successful in 4s
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 58s
CI / commitlint (push) Skipped
CI / go (push) Successful in 1m3s
CI / bird2 (push) Successful in 16s
CI / release (push) Successful in 4m15s
Updated the authentication logic to prioritize tenant resolution from JWT claims, specifically using `tenants.bgp` or `bgp_tenant_id` as the primary source. If these claims are absent, the system will fallback to the configured `EVOBGP_PORTAL_TENANT_ID`. This change improves the flexibility of tenant management in the authentication process. Additionally, updated related documentation to reflect these changes and added tests to ensure proper functionality.
171 lines
5.4 KiB
Go
171 lines
5.4 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"evobgp/internal/jobs"
|
|
"evobgp/internal/maintenance"
|
|
"evobgp/internal/pgmonitor"
|
|
"evobgp/internal/runtimelogs"
|
|
"evobgp/internal/store"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// Server implements EvoBGP control-plane HTTP API.
|
|
type Server struct {
|
|
store store.Backend
|
|
pgPool *pgxpool.Pool
|
|
pgMonitor *pgmonitor.Service
|
|
maintConfig *maintenance.ConfigProvider
|
|
maintStats *maintenance.DBStatsProvider
|
|
jobs *jobs.Registry
|
|
bundlePriv ed25519.PrivateKey
|
|
keyResolver *apiKeyResolver
|
|
firewallResolver *firewallTokenResolver
|
|
bundleSeedHex string
|
|
corsOrigins []string
|
|
cdnHTTP *http.Client
|
|
runtimeLogs *runtimelogs.Service
|
|
runtimeLogsPolicyTenant string
|
|
mux *http.ServeMux
|
|
|
|
// Portal / dual-auth (JWT) configuration.
|
|
jwtSecret string
|
|
authIssuer string
|
|
authPortalURL string
|
|
portalTenantID string
|
|
authRequired bool
|
|
}
|
|
|
|
// Options configures the API server.
|
|
type Options struct {
|
|
APIKeys string
|
|
// DatabaseURL enables PostgreSQL-backed store (migrations applied on connect).
|
|
DatabaseURL string
|
|
InsecureDev bool
|
|
SeedDemo bool
|
|
BundleSeedHex string
|
|
CORSAllowedOrigins string
|
|
// RuntimeLogsPolicyTenant overrides tenant for auto-cleanup scheduler settings (optional).
|
|
RuntimeLogsPolicyTenant string
|
|
|
|
// Portal / dual-auth (JWT) — leave empty to disable JWT path.
|
|
JWTSecret string // AUTH_JWT_SECRET / EVOBGP_AUTH_JWT_SECRET (HS256 shared secret)
|
|
AuthIssuer string // AUTH_ISSUER (expected iss claim; default https://auth.shnt.top)
|
|
AuthPortalURL string // AUTH_PORTAL_URL (returned by /v1/auth/config for the UI)
|
|
PortalTenantID string // fallback when JWT has no bgp_tenant_id / tenants.bgp
|
|
AuthRequired bool // AUTH_REQUIRED / EVOBGP_AUTH_REQUIRED (surfaced via /v1/auth/config)
|
|
}
|
|
|
|
// New constructs Server and wiring for async jobs.
|
|
func New(opts Options) (*Server, error) {
|
|
backend, reg, pool, err := BootstrapWorkers(context.Background(), opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var priv ed25519.PrivateKey
|
|
if strings.TrimSpace(opts.BundleSeedHex) != "" {
|
|
seed, err := hex.DecodeString(strings.TrimSpace(opts.BundleSeedHex))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(seed) != ed25519.SeedSize {
|
|
return nil, errors.New("httpapi: BundleSeedHex must decode to 32 bytes")
|
|
}
|
|
priv = ed25519.NewKeyFromSeed(seed)
|
|
} else {
|
|
_, priv, _ = ed25519.GenerateKey(rand.Reader)
|
|
}
|
|
|
|
resolver, err := newAPIKeyResolver(opts.APIKeys, backend)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fwResolver, err := newFirewallTokenResolver(backend)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pgMon *pgmonitor.Service
|
|
var maintCfg *maintenance.ConfigProvider
|
|
var maintStats *maintenance.DBStatsProvider
|
|
if pool != nil {
|
|
pgMon = pgmonitor.NewService(pool)
|
|
maintCfg = maintenance.NewConfigProvider(backend)
|
|
_ = maintCfg.Reload(context.Background())
|
|
maintStats = maintenance.NewDBStatsProvider(pgMon)
|
|
}
|
|
s := &Server{
|
|
store: backend,
|
|
pgPool: pool,
|
|
pgMonitor: pgMon,
|
|
maintConfig: maintCfg,
|
|
maintStats: maintStats,
|
|
jobs: reg,
|
|
bundlePriv: priv,
|
|
keyResolver: resolver,
|
|
firewallResolver: fwResolver,
|
|
bundleSeedHex: strings.TrimSpace(opts.BundleSeedHex),
|
|
corsOrigins: parseCORSOrigins(opts.CORSAllowedOrigins),
|
|
cdnHTTP: NewCDNHTTPClient(),
|
|
runtimeLogs: runtimelogs.NewService(runtimelogs.ConfigFromEnv()),
|
|
runtimeLogsPolicyTenant: strings.TrimSpace(opts.RuntimeLogsPolicyTenant),
|
|
jwtSecret: strings.TrimSpace(opts.JWTSecret),
|
|
authIssuer: strings.TrimSpace(opts.AuthIssuer),
|
|
authPortalURL: strings.TrimSpace(opts.AuthPortalURL),
|
|
portalTenantID: strings.TrimSpace(opts.PortalTenantID),
|
|
authRequired: opts.AuthRequired,
|
|
}
|
|
if s.authIssuer == "" {
|
|
s.authIssuer = "https://auth.shnt.top"
|
|
}
|
|
s.mux = http.NewServeMux()
|
|
s.registerRoutes()
|
|
return s, nil
|
|
}
|
|
|
|
// Close releases database resources.
|
|
func (s *Server) Close() {
|
|
if s.pgPool != nil {
|
|
s.pgPool.Close()
|
|
}
|
|
}
|
|
|
|
// Store exposes the backing store (for operators / tests).
|
|
func (s *Server) Store() store.Backend { return s.store }
|
|
|
|
// Jobs exposes the in-process async job registry (for scheduler / evobgp-all).
|
|
func (s *Server) Jobs() *jobs.Registry { return s.jobs }
|
|
|
|
// StartBackground starts PostgreSQL monitoring and maintenance schedulers until ctx is cancelled.
|
|
func (s *Server) StartBackground(ctx context.Context) {
|
|
if s != nil && s.pgPool != nil {
|
|
pgmonitor.StartScheduler(ctx, s.pgPool)
|
|
}
|
|
if s != nil && s.maintConfig != nil && s.jobs != nil {
|
|
maintenance.StartScheduler(ctx, s.maintConfig, func(policyID string, dryRun bool, idem string) {
|
|
key := idem
|
|
_, _, _ = s.jobs.Enqueue("", jobs.KindMaintenancePolicyRun, &key, nil, map[string]any{
|
|
"policy_id": policyID,
|
|
"dry_run": dryRun,
|
|
"trigger": "scheduler",
|
|
})
|
|
}, 30*time.Second)
|
|
}
|
|
if s != nil && s.runtimeLogs != nil && s.store != nil {
|
|
runtimelogs.StartAutoCleanupScheduler(ctx, runtimelogs.SchedulerDeps{
|
|
Service: s.runtimeLogs,
|
|
Store: s.store,
|
|
PolicyTenant: s.runtimeLogsPolicyTenant,
|
|
}, 30*time.Second)
|
|
}
|
|
}
|