Validate X-API-Key against a DB-backed, self-managed key store in addition
to the static Infisical keys, so new consumers (e.g. gsc_admin) no longer
require a rebuild. Keys carry scopes (e.g. {ldap:read}); the required scope
is derived per-request from path + method and enforced by ScopeEnforce.
Static Infisical keys keep an implicit wildcard scope (no regression).
- service/apikey.go: DB store (admin.api_keys, SHA-256 hashes only), 30s
validation cache, generate/list/revoke. EnsureSchema is existence-first
(to_regclass) so a least-privilege DB role starts cleanly when the table
is provisioned out-of-band; startup is non-fatal if the store is absent.
- handler/apikeys.go + routes: POST/GET/DELETE /api/v1/admin/api-keys.
- middleware/apikey.go: APIKeyWithValidator + Principal + ScopeEnforce.
- pkg/types/scopes.go: scope vocabulary + matching.
- migrations/002_api_keys.sql.
Also restore cmd/server/main.go, which the `.gitignore` `server` pattern
was silently excluding (it matched cmd/server/); anchored that pattern and
`gsc-ops-api` to the repo root so only the built binaries are ignored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
242 lines
9.0 KiB
Go
242 lines
9.0 KiB
Go
package router
|
|
|
|
import (
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/gofiber/fiber/v2/middleware/recover"
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/gosec/gsc-ops-api/internal/handler"
|
|
"github.com/gosec/gsc-ops-api/internal/middleware"
|
|
)
|
|
|
|
// Config holds all handler dependencies for route registration
|
|
type Config struct {
|
|
Logger zerolog.Logger
|
|
APIKeys []string
|
|
// APIKeyValidate backs the static APIKeys list with a dynamic, self-managed
|
|
// key store so new consumers can be added at runtime without a rebuild.
|
|
APIKeyValidate middleware.APIKeyValidator
|
|
APIKeysAdmin *handler.APIKeyHandler
|
|
Health *handler.HealthHandler
|
|
LDAPUsers *handler.LDAPUserHandler
|
|
LDAPGroups *handler.LDAPGroupHandler
|
|
LDAPEntities *handler.LDAPEntityHandler
|
|
DNSZones *handler.DNSZoneHandler
|
|
DNSRecords *handler.DNSRecordHandler
|
|
DBTenants *handler.DBTenantHandler
|
|
DBUsers *handler.DBUserHandler
|
|
Certs *handler.CertHandler
|
|
PGP *handler.PGPHandler
|
|
CardDAV *handler.CardDAVHandler
|
|
PBX *handler.PBXHandler
|
|
VoiceAgent *handler.VoiceAgentHandler
|
|
PersonalAgent *handler.PersonalAgentHandler
|
|
Persona *handler.PersonaHandler
|
|
}
|
|
|
|
// Setup registers all routes on the Fiber app
|
|
func Setup(app *fiber.App, cfg *Config) {
|
|
// Global middleware
|
|
app.Use(recover.New())
|
|
app.Use(middleware.RequestID())
|
|
app.Use(middleware.Logging(cfg.Logger))
|
|
app.Use(middleware.JWTExtract())
|
|
|
|
// Health endpoints (no API key required)
|
|
app.Get("/health", cfg.Health.Liveness)
|
|
app.Get("/ready", cfg.Health.Readiness)
|
|
|
|
// API v1 routes: authenticate (static keys + dynamic key store), then
|
|
// enforce per-key scopes. Static keys carry the wildcard scope and bypass.
|
|
api := app.Group("/api/v1",
|
|
middleware.APIKeyWithValidator(cfg.APIKeys, cfg.APIKeyValidate),
|
|
middleware.ScopeEnforce(),
|
|
)
|
|
|
|
// Dynamic API-key management (bootstrapped by any existing valid key)
|
|
if cfg.APIKeysAdmin != nil {
|
|
apiKeys := api.Group("/admin/api-keys")
|
|
apiKeys.Get("/", cfg.APIKeysAdmin.List)
|
|
apiKeys.Post("/", cfg.APIKeysAdmin.Create)
|
|
apiKeys.Delete("/:id", cfg.APIKeysAdmin.Revoke)
|
|
}
|
|
|
|
// LDAP Users
|
|
ldapUsers := api.Group("/ldap/users")
|
|
ldapUsers.Get("/", cfg.LDAPUsers.List)
|
|
ldapUsers.Get("/:uid", cfg.LDAPUsers.Get)
|
|
ldapUsers.Post("/", cfg.LDAPUsers.Create)
|
|
ldapUsers.Put("/:uid", cfg.LDAPUsers.Update)
|
|
ldapUsers.Delete("/:uid", cfg.LDAPUsers.Delete)
|
|
ldapUsers.Post("/:uid/password", cfg.LDAPUsers.ResetPassword)
|
|
ldapUsers.Get("/:uid/groups", cfg.LDAPUsers.ListGroups)
|
|
ldapUsers.Get("/:uid/services", cfg.LDAPUsers.ListServices)
|
|
ldapUsers.Get("/:uid/services/:domain", cfg.LDAPUsers.GetService)
|
|
|
|
// LDAP Groups
|
|
ldapGroups := api.Group("/ldap/groups")
|
|
ldapGroups.Get("/", cfg.LDAPGroups.List)
|
|
ldapGroups.Get("/:cn", cfg.LDAPGroups.Get)
|
|
ldapGroups.Post("/", cfg.LDAPGroups.Create)
|
|
ldapGroups.Put("/:cn", cfg.LDAPGroups.Update)
|
|
ldapGroups.Delete("/:cn", cfg.LDAPGroups.Delete)
|
|
ldapGroups.Get("/:cn/members", cfg.LDAPGroups.ListMembers)
|
|
ldapGroups.Post("/:cn/members", cfg.LDAPGroups.AddMembers)
|
|
ldapGroups.Delete("/:cn/members/:uid", cfg.LDAPGroups.RemoveMember)
|
|
|
|
// LDAP Entities (generic CRUD)
|
|
ldapEntities := api.Group("/ldap/entities")
|
|
ldapEntities.Get("/", cfg.LDAPEntities.ListTypes)
|
|
ldapEntities.Get("/:type", cfg.LDAPEntities.List)
|
|
ldapEntities.Post("/:type", cfg.LDAPEntities.Create)
|
|
ldapEntities.Get("/:type/:rdn", cfg.LDAPEntities.Get)
|
|
ldapEntities.Put("/:type/:rdn", cfg.LDAPEntities.Update)
|
|
ldapEntities.Delete("/:type/:rdn", cfg.LDAPEntities.Delete)
|
|
|
|
// DNS Zones
|
|
dnsZones := api.Group("/dns/zones")
|
|
dnsZones.Get("/", cfg.DNSZones.List)
|
|
dnsZones.Get("/:zoneId", cfg.DNSZones.Get)
|
|
dnsZones.Post("/", cfg.DNSZones.Create)
|
|
dnsZones.Put("/:zoneId", cfg.DNSZones.Update)
|
|
dnsZones.Delete("/:zoneId", cfg.DNSZones.Delete)
|
|
dnsZones.Post("/:zoneId/notify", cfg.DNSZones.Notify)
|
|
|
|
// DNS Records
|
|
dnsZones.Get("/:zoneId/records", cfg.DNSRecords.List)
|
|
dnsZones.Post("/:zoneId/records", cfg.DNSRecords.Create)
|
|
dnsZones.Put("/:zoneId/records", cfg.DNSRecords.Replace)
|
|
dnsZones.Delete("/:zoneId/records", cfg.DNSRecords.Delete)
|
|
|
|
// DNS Domains (orchestrated)
|
|
dnsDomains := api.Group("/dns/domains")
|
|
dnsDomains.Post("/setup", cfg.DNSRecords.DomainSetup)
|
|
dnsDomains.Post("/verify", cfg.DNSRecords.DomainVerify)
|
|
|
|
// DB Tenants
|
|
dbTenants := api.Group("/db/tenants")
|
|
dbTenants.Get("/", cfg.DBTenants.List)
|
|
dbTenants.Get("/:id", cfg.DBTenants.Get)
|
|
dbTenants.Post("/", cfg.DBTenants.Create)
|
|
dbTenants.Put("/:id", cfg.DBTenants.Update)
|
|
dbTenants.Delete("/:id", cfg.DBTenants.Delete)
|
|
|
|
// DB Users
|
|
dbUsers := api.Group("/db/users")
|
|
dbUsers.Get("/", cfg.DBUsers.List)
|
|
dbUsers.Get("/:id", cfg.DBUsers.Get)
|
|
dbUsers.Post("/", cfg.DBUsers.Create)
|
|
dbUsers.Put("/:id", cfg.DBUsers.Update)
|
|
dbUsers.Delete("/:id", cfg.DBUsers.Delete)
|
|
|
|
// Certificates
|
|
certs := api.Group("/certs")
|
|
certs.Get("/", cfg.Certs.List)
|
|
certs.Get("/:serialNumber", cfg.Certs.Get)
|
|
certs.Post("/request", cfg.Certs.Request)
|
|
certs.Post("/:serialNumber/renew", cfg.Certs.Renew)
|
|
certs.Post("/:serialNumber/revoke", cfg.Certs.Revoke)
|
|
|
|
// PGP Keys
|
|
pgp := api.Group("/pgp/keys")
|
|
pgp.Get("/", cfg.PGP.Search)
|
|
pgp.Get("/:keyId", cfg.PGP.Get)
|
|
pgp.Post("/", cfg.PGP.Upload)
|
|
pgp.Delete("/:keyId", cfg.PGP.Delete)
|
|
|
|
// PBX
|
|
if cfg.PBX != nil {
|
|
pbxTrunks := api.Group("/pbx/trunks")
|
|
pbxTrunks.Get("/", cfg.PBX.ListTrunks)
|
|
pbxTrunks.Post("/", cfg.PBX.CreateTrunk)
|
|
pbxTrunks.Get("/:id", cfg.PBX.GetTrunk)
|
|
pbxTrunks.Put("/:id", cfg.PBX.UpdateTrunk)
|
|
pbxTrunks.Delete("/:id", cfg.PBX.DeleteTrunk)
|
|
pbxTrunks.Post("/:id/activate", cfg.PBX.ActivateTrunk)
|
|
pbxTrunks.Post("/:id/deactivate", cfg.PBX.DeactivateTrunk)
|
|
pbxTrunks.Get("/:id/dids", cfg.PBX.ListTrunkDIDs)
|
|
pbxTrunks.Post("/:id/dids", cfg.PBX.CreateTrunkDID)
|
|
pbxTrunks.Delete("/:id/dids/:didId", cfg.PBX.DeleteTrunkDID)
|
|
|
|
pbxExts := api.Group("/pbx/extensions")
|
|
pbxExts.Get("/", cfg.PBX.ListExtensions)
|
|
pbxExts.Post("/", cfg.PBX.CreateExtension)
|
|
pbxExts.Get("/:id", cfg.PBX.GetExtension)
|
|
pbxExts.Put("/:id", cfg.PBX.UpdateExtension)
|
|
pbxExts.Delete("/:id", cfg.PBX.DeleteExtension)
|
|
|
|
pbxInbound := api.Group("/pbx/inbound-routes")
|
|
pbxInbound.Get("/", cfg.PBX.ListInboundRoutes)
|
|
pbxInbound.Post("/", cfg.PBX.CreateInboundRoute)
|
|
pbxInbound.Get("/:id", cfg.PBX.GetInboundRoute)
|
|
pbxInbound.Put("/:id", cfg.PBX.UpdateInboundRoute)
|
|
pbxInbound.Delete("/:id", cfg.PBX.DeleteInboundRoute)
|
|
|
|
pbxOutbound := api.Group("/pbx/outbound-routes")
|
|
pbxOutbound.Get("/", cfg.PBX.ListOutboundRoutes)
|
|
pbxOutbound.Post("/", cfg.PBX.CreateOutboundRoute)
|
|
pbxOutbound.Get("/:id", cfg.PBX.GetOutboundRoute)
|
|
pbxOutbound.Put("/:id", cfg.PBX.UpdateOutboundRoute)
|
|
pbxOutbound.Delete("/:id", cfg.PBX.DeleteOutboundRoute)
|
|
|
|
api.Get("/pbx/status", cfg.PBX.Status)
|
|
api.Post("/pbx/reload", cfg.PBX.Reload)
|
|
}
|
|
|
|
// Voice Agents
|
|
if cfg.VoiceAgent != nil {
|
|
voiceAgents := api.Group("/voice-agents")
|
|
voiceAgents.Get("/", cfg.VoiceAgent.ListConfigs)
|
|
voiceAgents.Post("/", cfg.VoiceAgent.CreateConfig)
|
|
voiceAgents.Get("/sessions/:sessionId", cfg.VoiceAgent.GetSession)
|
|
voiceAgents.Get("/:id", cfg.VoiceAgent.GetConfig)
|
|
voiceAgents.Put("/:id", cfg.VoiceAgent.UpdateConfig)
|
|
voiceAgents.Delete("/:id", cfg.VoiceAgent.DeleteConfig)
|
|
voiceAgents.Get("/:id/sessions", cfg.VoiceAgent.ListSessions)
|
|
}
|
|
|
|
// Personal Agents (user agent configs)
|
|
if cfg.PersonalAgent != nil {
|
|
agents := api.Group("/agents")
|
|
agents.Get("/me", cfg.PersonalAgent.GetMyConfig)
|
|
agents.Put("/me", cfg.PersonalAgent.UpsertMyConfig)
|
|
agents.Delete("/me", cfg.PersonalAgent.DeleteMyConfig)
|
|
}
|
|
|
|
// Personas
|
|
if cfg.Persona != nil {
|
|
personas := api.Group("/personas")
|
|
personas.Get("/", cfg.Persona.ListPersonas)
|
|
personas.Post("/", cfg.Persona.CreatePersona)
|
|
personas.Get("/:id", cfg.Persona.GetPersona)
|
|
personas.Put("/:id", cfg.Persona.UpdatePersona)
|
|
personas.Delete("/:id", cfg.Persona.DeletePersona)
|
|
personas.Get("/:id/self-model", cfg.Persona.GetSelfModel)
|
|
personas.Get("/:id/experiences", cfg.Persona.GetExperiences)
|
|
personas.Get("/:id/evaluations/:sessionId", cfg.Persona.GetEvaluations)
|
|
personas.Get("/:id/moral-pattern/:sessionId", cfg.Persona.GetMoralPattern)
|
|
}
|
|
|
|
// CardDAV
|
|
if cfg.CardDAV != nil {
|
|
cardDAVPrincipals := api.Group("/carddav/principals")
|
|
cardDAVPrincipals.Get("/", cfg.CardDAV.ListPrincipals)
|
|
cardDAVPrincipals.Get("/:username", cfg.CardDAV.GetPrincipal)
|
|
cardDAVPrincipals.Post("/", cfg.CardDAV.CreatePrincipal)
|
|
cardDAVPrincipals.Delete("/:username", cfg.CardDAV.DeletePrincipal)
|
|
|
|
cardDAVBooks := api.Group("/carddav/addressbooks")
|
|
cardDAVBooks.Get("/", cfg.CardDAV.ListAddressBooks)
|
|
cardDAVBooks.Get("/:id", cfg.CardDAV.GetAddressBook)
|
|
cardDAVBooks.Post("/", cfg.CardDAV.CreateAddressBook)
|
|
cardDAVBooks.Put("/:id", cfg.CardDAV.UpdateAddressBook)
|
|
cardDAVBooks.Delete("/:id", cfg.CardDAV.DeleteAddressBook)
|
|
|
|
cardDAVBooks.Get("/:id/contacts", cfg.CardDAV.ListContacts)
|
|
cardDAVBooks.Get("/:id/contacts/:uri", cfg.CardDAV.GetContact)
|
|
cardDAVBooks.Post("/:id/contacts", cfg.CardDAV.CreateContact)
|
|
cardDAVBooks.Put("/:id/contacts/:uri", cfg.CardDAV.UpdateContact)
|
|
cardDAVBooks.Delete("/:id/contacts/:uri", cfg.CardDAV.DeleteContact)
|
|
}
|
|
}
|