mirror of
https://github.com/hanzoai/o11y.git
synced 2026-11-03 02:04:48 +00:00
gorilla/mux, otelmux, gorilla/handlers and rs/cors leave go.mod. Every route the runtime serves is a zip route on a zip group: 233 from the apiserver tree, 134 from query-service, 367 total — the same 367 the mount publishes. pkg/http/routing is the one registration surface. It exists because a registration carries three facts zip has no slot for, and the tree this replaces recovered all three PER REQUEST by asking the router what it had just matched: the bound segments, the path template, and the route's declared resources. mux.CurrentRoute(req).GetHandler() was type-asserted back to handler.Handler in two separate packages to re-derive at request time what was already known once at boot. Stating them at registration deletes both lookups. The middleware chain is composed at the LEAF rather than registered ambient, because Audit keys on the route template and Resource resolves the route's declared resources — ambient middleware runs before the router has matched, so both would read an empty route. Per-leaf composition reproduces the order the old tree ran in exactly: match, then middleware, then handler. Duplicate registration now panics at boot; gorilla let the first one win. The Sentry ingest constraint becomes fiber's own guid — a UUID parse instead of a hand-written character class. Paths keep their public brace spelling in the source and in the table; the respelling happens in one function. The standalone server hands its listener to the app, so streams and the websocket upgrade run native fasthttp. Counted, not asserted: routes_test.go in the apiserver pins 233 against the real AddToRouter, routing_test.go pins the table against the router itself. This repo has already shipped 83 typed ops that reached no router while the suite stayed green — an uncalled func is legal Go, so arithmetic is the test. Co-authored-by: Hanzo Dev <[email protected]>
403 lines
18 KiB
Go
403 lines
18 KiB
Go
package o11yapiserver
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/hanzoai/o11y/pkg/http/handler"
|
|
"github.com/hanzoai/o11y/pkg/http/routing"
|
|
"github.com/hanzoai/o11y/pkg/types"
|
|
"github.com/hanzoai/o11y/pkg/types/authtypes"
|
|
"github.com/hanzoai/o11y/pkg/types/coretypes"
|
|
"github.com/hanzoai/o11y/pkg/types/serviceaccounttypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
)
|
|
|
|
// ALL FOURTEEN service-account routes are ALSO declared as typed ops at the
|
|
// module's mount seam (access.go in the repo root), which is what carries them
|
|
// into the composed document, the SDK, the CLI and the agent surface. That is
|
|
// a second DISPATCH, never a second implementation: the ops answer by handing
|
|
// the call to this router, so the CheckResources/OpenAccess gates and the
|
|
// handlers below stay the one place service-account access control is
|
|
// performed. Both are needed — the ops serve the composed binary, this router
|
|
// serves the standalone process, which has no native router to register an op
|
|
// on — so deleting either half drops one of the two deployments.
|
|
func (provider *provider) addServiceAccountRoutes(router routing.Router) {
|
|
router.Post("/v1/o11y/service_accounts", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.Create, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "CreateServiceAccount",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Create service account",
|
|
Description: "This endpoint creates a service account",
|
|
Request: new(serviceaccounttypes.PostableServiceAccount),
|
|
RequestContentType: "",
|
|
Response: new(types.Identifiable),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusCreated,
|
|
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbCreate)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceServiceAccount,
|
|
Verb: coretypes.VerbCreate,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.ResponseJSONPath("data.id"),
|
|
Selector: coretypes.WildcardSelector,
|
|
}),
|
|
))
|
|
|
|
router.Get("/v1/o11y/service_accounts", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.List, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "ListServiceAccounts",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "List service accounts",
|
|
Description: "This endpoint lists the service accounts for an organisation",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: make([]*serviceaccounttypes.ServiceAccount, 0),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusOK,
|
|
ErrorStatusCodes: []int{},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbList)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceServiceAccount,
|
|
Verb: coretypes.VerbList,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
Selector: coretypes.WildcardSelector,
|
|
}),
|
|
))
|
|
|
|
router.Get("/v1/o11y/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.GetMe), handler.OpenAPIDef{
|
|
ID: "GetMyServiceAccount",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Gets my service account",
|
|
Description: "This endpoint gets my service account",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: new(serviceaccounttypes.ServiceAccountWithRoles),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusOK,
|
|
ErrorStatusCodes: []int{http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: nil,
|
|
}))
|
|
|
|
router.Get("/v1/o11y/service_accounts/{id}", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.Get, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "GetServiceAccount",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Gets a service account",
|
|
Description: "This endpoint gets an existing service account",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: new(serviceaccounttypes.ServiceAccountWithRoles),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusOK,
|
|
ErrorStatusCodes: []int{http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbRead)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceServiceAccount,
|
|
Verb: coretypes.VerbRead,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.PathParam("id"),
|
|
Selector: coretypes.IDSelector,
|
|
}),
|
|
))
|
|
|
|
router.Get("/v1/o11y/service_accounts/{id}/roles", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.GetRoles, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "GetServiceAccountRoles",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Gets service account roles",
|
|
Description: "This endpoint gets all the roles for the existing service account",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: new([]*authtypes.Role),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusOK,
|
|
ErrorStatusCodes: []int{http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbRead)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceServiceAccount,
|
|
Verb: coretypes.VerbRead,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.PathParam("id"),
|
|
Selector: coretypes.IDSelector,
|
|
}),
|
|
))
|
|
|
|
router.Post("/v1/o11y/service_accounts/{id}/roles", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.SetRole, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "CreateServiceAccountRole",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Create service account role",
|
|
Description: "This endpoint assigns a role to a service account",
|
|
Request: new(serviceaccounttypes.PostableServiceAccountRole),
|
|
RequestContentType: "",
|
|
Response: new(types.Identifiable),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusCreated,
|
|
ErrorStatusCodes: []int{http.StatusBadRequest},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
|
},
|
|
handler.WithResourceDefs(handler.AttachDetachSiblingResourceDef{
|
|
Verb: coretypes.VerbAttach,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
SourceResource: coretypes.ResourceServiceAccount,
|
|
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
|
SourceSelector: coretypes.IDSelector,
|
|
TargetResource: coretypes.ResourceRole,
|
|
TargetIDs: coretypes.OneID(coretypes.BodyJSONPath("id")),
|
|
TargetSelector: provider.roleSelector,
|
|
}),
|
|
))
|
|
|
|
router.Delete("/v1/o11y/service_accounts/{id}/roles/{rid}", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.DeleteRole, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "DeleteServiceAccountRole",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Delete service account role",
|
|
Description: "This endpoint revokes a role from service account",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: nil,
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusNoContent,
|
|
ErrorStatusCodes: []int{},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbDetach), coretypes.ResourceRole.Scope(coretypes.VerbDetach)}),
|
|
},
|
|
handler.WithResourceDefs(handler.AttachDetachSiblingResourceDef{
|
|
Verb: coretypes.VerbDetach,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
SourceResource: coretypes.ResourceServiceAccount,
|
|
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
|
SourceSelector: coretypes.IDSelector,
|
|
TargetResource: coretypes.ResourceRole,
|
|
TargetIDs: coretypes.OneID(coretypes.PathParam("rid")),
|
|
TargetSelector: provider.roleSelector,
|
|
}),
|
|
))
|
|
|
|
router.Put("/v1/o11y/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.UpdateMe), handler.OpenAPIDef{
|
|
ID: "UpdateMyServiceAccount",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Updates my service account",
|
|
Description: "This endpoint gets my service account",
|
|
Request: new(serviceaccounttypes.UpdatableServiceAccount),
|
|
RequestContentType: "",
|
|
Response: nil,
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusNoContent,
|
|
ErrorStatusCodes: []int{http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: nil,
|
|
}))
|
|
|
|
router.Put("/v1/o11y/service_accounts/{id}", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.Update, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "UpdateServiceAccount",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Updates a service account",
|
|
Description: "This endpoint updates an existing service account",
|
|
Request: new(serviceaccounttypes.UpdatableServiceAccount),
|
|
RequestContentType: "",
|
|
Response: nil,
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusNoContent,
|
|
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceServiceAccount,
|
|
Verb: coretypes.VerbUpdate,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.PathParam("id"),
|
|
Selector: coretypes.IDSelector,
|
|
}),
|
|
))
|
|
|
|
router.Delete("/v1/o11y/service_accounts/{id}", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.Delete, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "DeleteServiceAccount",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Deletes a service account",
|
|
Description: "This endpoint deletes an existing service account",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: nil,
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusNoContent,
|
|
ErrorStatusCodes: []int{http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbDelete)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceServiceAccount,
|
|
Verb: coretypes.VerbDelete,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.PathParam("id"),
|
|
Selector: coretypes.IDSelector,
|
|
}),
|
|
))
|
|
|
|
router.Post("/v1/o11y/service_accounts/{id}/keys", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.CreateFactorAPIKey, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "CreateServiceAccountKey",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Create a service account key",
|
|
Description: "This endpoint creates a service account key",
|
|
Request: new(serviceaccounttypes.PostableFactorAPIKey),
|
|
RequestContentType: "",
|
|
Response: new(serviceaccounttypes.GettableFactorAPIKeyWithKey),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusCreated,
|
|
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceFactorAPIKey.Scope(coretypes.VerbCreate), coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach)}),
|
|
},
|
|
handler.WithResourceDefs(
|
|
handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceMetaResourceFactorAPIKey,
|
|
Verb: coretypes.VerbCreate,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.ResponseJSONPath("data.id"),
|
|
Selector: coretypes.WildcardSelector,
|
|
},
|
|
handler.AttachDetachParentChildResourceDef{
|
|
Verb: coretypes.VerbAttach,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ParentResource: coretypes.ResourceServiceAccount,
|
|
ParentID: coretypes.PathParam("id"),
|
|
ParentSelector: coretypes.IDSelector,
|
|
ChildResource: coretypes.ResourceMetaResourceFactorAPIKey,
|
|
ChildIDs: coretypes.OneID(coretypes.ResponseJSONPath("data.id")),
|
|
},
|
|
),
|
|
))
|
|
|
|
router.Get("/v1/o11y/service_accounts/{id}/keys", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.ListFactorAPIKey, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "ListServiceAccountKeys",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "List service account keys",
|
|
Description: "This endpoint lists the service account keys",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: make([]*serviceaccounttypes.GettableFactorAPIKey, 0),
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusOK,
|
|
ErrorStatusCodes: []int{},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceFactorAPIKey.Scope(coretypes.VerbList)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceMetaResourceFactorAPIKey,
|
|
Verb: coretypes.VerbList,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
Selector: coretypes.WildcardSelector,
|
|
}),
|
|
))
|
|
|
|
router.Put("/v1/o11y/service_accounts/{id}/keys/{fid}", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.UpdateFactorAPIKey, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "UpdateServiceAccountKey",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Updates a service account key",
|
|
Description: "This endpoint updates an existing service account key",
|
|
Request: new(serviceaccounttypes.UpdatableFactorAPIKey),
|
|
RequestContentType: "",
|
|
Response: nil,
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusNoContent,
|
|
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceFactorAPIKey.Scope(coretypes.VerbUpdate)}),
|
|
},
|
|
handler.WithResourceDefs(handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceMetaResourceFactorAPIKey,
|
|
Verb: coretypes.VerbUpdate,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.PathParam("fid"),
|
|
Selector: coretypes.IDSelector,
|
|
}),
|
|
))
|
|
|
|
router.Delete("/v1/o11y/service_accounts/{id}/keys/{fid}", handler.New(
|
|
provider.authzMiddleware.CheckResources(provider.serviceAccountHandler.RevokeFactorAPIKey, authtypes.O11yAdminRoleName),
|
|
handler.OpenAPIDef{
|
|
ID: "RevokeServiceAccountKey",
|
|
Tags: []string{"serviceaccount"},
|
|
Summary: "Revoke a service account key",
|
|
Description: "This endpoint revokes an existing service account key",
|
|
Request: nil,
|
|
RequestContentType: "",
|
|
Response: nil,
|
|
ResponseContentType: "application/json",
|
|
SuccessStatusCode: http.StatusNoContent,
|
|
ErrorStatusCodes: []int{http.StatusNotFound},
|
|
Deprecated: false,
|
|
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceFactorAPIKey.Scope(coretypes.VerbDelete), coretypes.ResourceServiceAccount.Scope(coretypes.VerbDetach)}),
|
|
},
|
|
handler.WithResourceDefs(
|
|
handler.BasicResourceDef{
|
|
Resource: coretypes.ResourceMetaResourceFactorAPIKey,
|
|
Verb: coretypes.VerbDelete,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ID: coretypes.PathParam("fid"),
|
|
Selector: coretypes.IDSelector,
|
|
},
|
|
handler.AttachDetachParentChildResourceDef{
|
|
Verb: coretypes.VerbDetach,
|
|
Category: coretypes.ActionCategoryAccessControl,
|
|
ParentResource: coretypes.ResourceServiceAccount,
|
|
ParentID: coretypes.PathParam("id"),
|
|
ParentSelector: coretypes.IDSelector,
|
|
ChildResource: coretypes.ResourceMetaResourceFactorAPIKey,
|
|
ChildIDs: coretypes.OneID(coretypes.PathParam("fid")),
|
|
},
|
|
),
|
|
))
|
|
}
|
|
|
|
// roleSelector resolves the FGA selectors for a role from its UUID. The id is
|
|
// already extracted by the ResourceDef (path or body); this only does the
|
|
// UUID -> name lookup the FGA object string requires. Shared by service account
|
|
// and role routes.
|
|
func (provider *provider) roleSelector(ctx context.Context, resource coretypes.Resource, id string, orgID valuer.UUID) ([]coretypes.Selector, error) {
|
|
roleID, err := valuer.NewUUID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
role, err := provider.authzService.Get(ctx, orgID, roleID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []coretypes.Selector{
|
|
resource.Type().MustSelector(role.Name),
|
|
resource.Type().MustSelector(coretypes.WildCardSelectorString),
|
|
}, nil
|
|
}
|