# Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Summary Enforced API-only endpoint restrictions on chart endpoints, matching the pattern already used by the activity bounded context. Also added `RouteTemplateRequestFunc` to chart route server options so the middleware can read the matched mux route template from context. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually ### Reproduction Created an API-only user with a restrictive endpoint allow-list (only `GET /api/v1/fleet/hosts`). Confirmed that: - Allowed endpoint (`/api/latest/fleet/hosts`) returns 200 - Non-allowed cataloged endpoint (`/api/latest/fleet/users`) returns 403 - Chart endpoint (`/api/latest/fleet/charts/uptime`) returned 200 before the fix (the bug) - After the fix, chart endpoint correctly returns 403 ### Unit test Added a test case in `server/service/middleware/auth/api_only_test.go` that verifies an API-only user with endpoint restrictions is denied access to chart endpoints not in their allow-list. The chart endpoint is included in the test catalog (matching production), so the test exercises the allow-list rejection path. All 17 tests in the auth middleware package pass. ### Local verification 1. Confirmed the chart middleware in `cmd/fleet/serve.go` previously called `auth.AuthenticatedUser(svc, next)` without `APIOnlyEndpointCheck` wrapping 2. Verified the activity bounded context (same file) already uses `auth.APIOnlyEndpointCheck(next)` as the correct pattern 3. Applied the same wrapping to the chart middleware 4. Added `RouteTemplateRequestFunc` to `server/chart/internal/service/endpoint_utils.go` so the route template is available in context (required by `APIOnlyEndpointCheck`) 5. Ran `go test ./server/service/middleware/auth/ -v` with all 17 tests passing 6. Ran `make lint-go-incremental` with 0 issues
75 lines
2.5 KiB
Go
75 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/chart/api"
|
|
eu "github.com/fleetdm/fleet/v4/server/platform/endpointer"
|
|
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
|
|
"github.com/go-kit/kit/endpoint"
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// encodeResponse encodes the response as JSON using the common Fleet encoding pattern.
|
|
func encodeResponse(ctx context.Context, w http.ResponseWriter, response any) error {
|
|
return eu.EncodeCommonResponse(ctx, w, response,
|
|
func(w http.ResponseWriter, response any) error {
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(response)
|
|
},
|
|
nil, // no domain-specific error encoder; standard fleet errors are handled by common encoder
|
|
)
|
|
}
|
|
|
|
// makeDecoder creates a decoder for the given request type.
|
|
func makeDecoder(iface any, requestBodySizeLimit int64) kithttp.DecodeRequestFunc {
|
|
return eu.MakeDecoder(iface, func(body io.Reader, req any) error {
|
|
return json.NewDecoder(body).Decode(req)
|
|
}, nil, nil, nil, nil, requestBodySizeLimit)
|
|
}
|
|
|
|
// handlerFunc is the handler function type for chart service endpoints.
|
|
type handlerFunc func(ctx context.Context, request any, svc api.Service) (platform_http.Errorer, error)
|
|
|
|
type chartEndpointer struct {
|
|
svc api.Service
|
|
}
|
|
|
|
func (e *chartEndpointer) CallHandlerFunc(f handlerFunc, ctx context.Context, request any, svc any) (platform_http.Errorer, error) {
|
|
return f(ctx, request, svc.(api.Service))
|
|
}
|
|
|
|
func (e *chartEndpointer) Service() any {
|
|
return e.svc
|
|
}
|
|
|
|
// Compile-time check to ensure chartEndpointer implements Endpointer.
|
|
var _ eu.Endpointer[handlerFunc] = &chartEndpointer{}
|
|
|
|
func newChartEndpointer(svc api.Service, authMiddleware endpoint.Middleware, opts []kithttp.ServerOption, r *mux.Router,
|
|
versions ...string,
|
|
) *eu.CommonEndpointer[handlerFunc] {
|
|
// Append RouteTemplateRequestFunc so the api_only endpoint middleware
|
|
// can read the matched mux route template from context.
|
|
//
|
|
// Full-slice expression prevents aliasing into the caller's backing array
|
|
// if it happens to have spare capacity.
|
|
opts = append(opts[:len(opts):len(opts)], kithttp.ServerBefore(eu.RouteTemplateRequestFunc))
|
|
return &eu.CommonEndpointer[handlerFunc]{
|
|
EP: &chartEndpointer{
|
|
svc: svc,
|
|
},
|
|
MakeDecoderFn: makeDecoder,
|
|
EncodeFn: encodeResponse,
|
|
Opts: opts,
|
|
AuthMiddleware: authMiddleware,
|
|
Router: r,
|
|
Versions: versions,
|
|
}
|
|
}
|