mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #36670 Refactoring `middleware/endpoint_utils` package to remove direct dependencies on: - fleet.Service - android.Service Specific changes are: - replace AuthFunc+FleetService with AuthMiddleware - Move the definition of handler functions to the respective services and use a generic `CommonEndpointer[H any] struct` Although this was discovered as part of Activity bounded context research, this change is not directly related to bounded contexts. In retrospect, this decoupling should have been done when creating the Android service for ADR-0001. ## Testing - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Internal restructuring of endpoint handling and authentication middleware composition to improve code maintainability and type safety. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
288 lines
8 KiB
Go
288 lines
8 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/capabilities"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/auth"
|
|
eu "github.com/fleetdm/fleet/v4/server/service/middleware/endpoint_utils"
|
|
"github.com/go-kit/kit/endpoint"
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
|
"github.com/go-kit/log"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
|
|
return eu.MakeDecoder(iface, jsonDecode, parseCustomTags, isBodyDecoder, decodeBody)
|
|
}
|
|
|
|
// A value that implements bodyDecoder takes control of decoding the request body.
|
|
type bodyDecoder interface {
|
|
DecodeBody(ctx context.Context, r io.Reader, u url.Values, c []*x509.Certificate) error
|
|
}
|
|
|
|
func decodeBody(ctx context.Context, r *http.Request, v reflect.Value, body io.Reader) error {
|
|
bd := v.Interface().(bodyDecoder)
|
|
var certs []*x509.Certificate
|
|
if (r.TLS != nil) && (r.TLS.PeerCertificates != nil) {
|
|
certs = r.TLS.PeerCertificates
|
|
}
|
|
|
|
if err := bd.DecodeBody(ctx, body, r.URL.Query(), certs); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseCustomTags(urlTagValue string, r *http.Request, field reflect.Value) (bool, error) {
|
|
switch urlTagValue {
|
|
case "list_options":
|
|
opts, err := listOptionsFromRequest(r)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
field.Set(reflect.ValueOf(opts))
|
|
return true, nil
|
|
|
|
case "user_options":
|
|
opts, err := userListOptionsFromRequest(r)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
field.Set(reflect.ValueOf(opts))
|
|
return true, nil
|
|
|
|
case "host_options":
|
|
opts, err := hostListOptionsFromRequest(r)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
field.Set(reflect.ValueOf(opts))
|
|
return true, nil
|
|
|
|
case "carve_options":
|
|
opts, err := carveListOptionsFromRequest(r)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
field.Set(reflect.ValueOf(opts))
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func jsonDecode(body io.Reader, req any) error {
|
|
return json.NewDecoder(body).Decode(req)
|
|
}
|
|
|
|
func isBodyDecoder(v reflect.Value) bool {
|
|
_, ok := v.Interface().(bodyDecoder)
|
|
return ok
|
|
}
|
|
|
|
// handlerFunc is the handler function type for the main Fleet service endpoints.
|
|
type handlerFunc func(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error)
|
|
|
|
// Compile-time check to ensure that endpointer implements Endpointer.
|
|
var _ eu.Endpointer[handlerFunc] = &endpointer{}
|
|
|
|
type endpointer struct {
|
|
svc fleet.Service
|
|
}
|
|
|
|
func (e *endpointer) CallHandlerFunc(f handlerFunc, ctx context.Context, request interface{},
|
|
svc interface{},
|
|
) (fleet.Errorer, error) {
|
|
return f(ctx, request, svc.(fleet.Service))
|
|
}
|
|
|
|
func (e *endpointer) Service() interface{} {
|
|
return e.svc
|
|
}
|
|
|
|
func newUserAuthenticatedEndpointer(svc fleet.Service, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return auth.AuthenticatedUser(svc, next)
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func newNoAuthEndpointer(svc fleet.Service, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return auth.UnauthenticatedRequest(svc, next)
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func newOrbitNoAuthEndpointer(svc fleet.Service, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
// Add the capabilities reported by Orbit to the request context
|
|
opts = append(opts, capabilitiesContextFunc())
|
|
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return auth.UnauthenticatedRequest(svc, next)
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func badRequest(msg string) error {
|
|
return &fleet.BadRequestError{Message: msg}
|
|
}
|
|
|
|
func badRequestf(format string, a ...any) error {
|
|
return &fleet.BadRequestError{
|
|
Message: fmt.Sprintf(format, a...),
|
|
}
|
|
}
|
|
|
|
func newDeviceAuthenticatedEndpointer(svc fleet.Service, logger log.Logger, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
// Extract certificate serial from X-Client-Cert-Serial header for certificate-based auth
|
|
opts = append(opts, kithttp.ServerBefore(extractCertSerialFromHeader))
|
|
// Inject the fleet.CapabilitiesHeader header to the response for device endpoints
|
|
opts = append(opts, capabilitiesResponseFunc(fleet.GetServerDeviceCapabilities()))
|
|
// Add the capabilities reported by the device to the request context
|
|
opts = append(opts, capabilitiesContextFunc())
|
|
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return authenticatedDevice(svc, logger, next)
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func newHostAuthenticatedEndpointer(svc fleet.Service, logger log.Logger, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return authenticatedHost(svc, logger, next)
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func androidAuthenticatedEndpointer(
|
|
svc fleet.Service,
|
|
logger log.Logger,
|
|
opts []kithttp.ServerOption,
|
|
r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
// Inject the fleet.Capabilities header to the response for Orbit hosts
|
|
opts = append(opts, capabilitiesResponseFunc(fleet.GetServerOrbitCapabilities()))
|
|
// Add the capabilities reported by Orbit to the request context
|
|
opts = append(opts, capabilitiesContextFunc())
|
|
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return authenticatedOrbitHost(svc, logger, next, authHeaderValue("Node key "))
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func newOrbitAuthenticatedEndpointer(svc fleet.Service, logger log.Logger, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
// Inject the fleet.Capabilities header to the response for Orbit hosts
|
|
opts = append(opts, capabilitiesResponseFunc(fleet.GetServerOrbitCapabilities()))
|
|
// Add the capabilities reported by Orbit to the request context
|
|
opts = append(opts, capabilitiesContextFunc())
|
|
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &endpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: func(next endpoint.Endpoint) endpoint.Endpoint {
|
|
return authenticatedOrbitHost(svc, logger, next, getOrbitNodeKey)
|
|
},
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|
|
|
|
func capabilitiesResponseFunc(capabilities fleet.CapabilityMap) kithttp.ServerOption {
|
|
return kithttp.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
|
|
writeCapabilitiesHeader(w, capabilities)
|
|
return ctx
|
|
})
|
|
}
|
|
|
|
func capabilitiesContextFunc() kithttp.ServerOption {
|
|
return kithttp.ServerBefore(capabilities.NewContext)
|
|
}
|
|
|
|
func writeCapabilitiesHeader(w http.ResponseWriter, capabilities fleet.CapabilityMap) {
|
|
if len(capabilities) == 0 {
|
|
return
|
|
}
|
|
|
|
w.Header().Set(fleet.CapabilitiesHeader, capabilities.String())
|
|
}
|