2026-04-10 15:12:38 +00:00
|
|
|
package apiendpoints
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
_ "embed"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
|
"github.com/ghodss/yaml"
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
//go:embed api_endpoints.yml
|
|
|
|
|
var apiEndpointsYAML []byte
|
|
|
|
|
|
|
|
|
|
var apiEndpoints []fleet.APIEndpoint
|
|
|
|
|
|
2026-04-21 11:11:33 +00:00
|
|
|
var apiEndpointsSet map[string]struct{}
|
|
|
|
|
|
2026-04-10 15:12:38 +00:00
|
|
|
// GetAPIEndpoints returns a copy of the embedded API endpoints slice.
|
|
|
|
|
func GetAPIEndpoints() []fleet.APIEndpoint {
|
|
|
|
|
result := make([]fleet.APIEndpoint, len(apiEndpoints))
|
|
|
|
|
copy(result, apiEndpoints)
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 11:11:33 +00:00
|
|
|
// IsInCatalog reports whether the given endpoint fingerprint is in the catalog.
|
|
|
|
|
func IsInCatalog(fingerprint string) bool {
|
|
|
|
|
_, ok := apiEndpointsSet[fingerprint]
|
|
|
|
|
return ok
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 15:12:38 +00:00
|
|
|
func Init(h http.Handler) error {
|
|
|
|
|
r, ok := h.(*mux.Router)
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("expected *mux.Router, got %T", h)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
registered := make(map[string]struct{})
|
|
|
|
|
_ = r.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
|
|
|
|
|
tpl, err := route.GetPathTemplate()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
meths, err := route.GetMethods()
|
|
|
|
|
if err != nil || len(meths) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, m := range meths {
|
|
|
|
|
val := fleet.NewAPIEndpointFromTpl(m, tpl)
|
|
|
|
|
registered[val.Fingerprint()] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-16 15:11:39 +00:00
|
|
|
loadedApiEndpoints, err := loadAPIEndpoints()
|
2026-04-10 15:12:38 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var missing []string
|
|
|
|
|
for _, e := range loadedApiEndpoints {
|
|
|
|
|
if _, ok := registered[e.Fingerprint()]; !ok {
|
|
|
|
|
missing = append(missing, e.Method+" "+e.Path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(missing) > 0 {
|
|
|
|
|
return fmt.Errorf("the following API endpoints are unknown: %v", missing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiEndpoints = loadedApiEndpoints
|
|
|
|
|
|
2026-04-21 11:11:33 +00:00
|
|
|
set := make(map[string]struct{}, len(loadedApiEndpoints))
|
|
|
|
|
for _, e := range loadedApiEndpoints {
|
|
|
|
|
set[e.Fingerprint()] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
apiEndpointsSet = set
|
|
|
|
|
|
2026-04-10 15:12:38 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 15:11:39 +00:00
|
|
|
func loadAPIEndpoints() ([]fleet.APIEndpoint, error) {
|
2026-04-10 15:12:38 +00:00
|
|
|
endpoints := make([]fleet.APIEndpoint, 0)
|
|
|
|
|
|
|
|
|
|
if err := yaml.Unmarshal(apiEndpointsYAML, &endpoints); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seen := make(map[string]struct{}, len(endpoints))
|
|
|
|
|
for _, e := range endpoints {
|
|
|
|
|
fp := e.Fingerprint()
|
|
|
|
|
if _, ok := seen[fp]; ok {
|
|
|
|
|
panic(fmt.Errorf("duplicate entry (%s, %s)", e.Method, e.Path))
|
|
|
|
|
}
|
|
|
|
|
seen[fp] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
return endpoints, nil
|
|
|
|
|
}
|