Request body limits (#39080)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves
https://github.com/fleetdm/confidential/issues/13934

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [ ] 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.

## Testing

- [x] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually
This commit is contained in:
Magnus Jensen 2026-02-05 10:29:53 -05:00 committed by GitHub
parent 657e8159a2
commit da43bf8371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 306 additions and 116 deletions

View file

@ -65,6 +65,7 @@ import (
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/push/buford"
nanomdm_pushsvc "github.com/fleetdm/fleet/v4/server/mdm/nanomdm/push/service"
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
common_mysql "github.com/fleetdm/fleet/v4/server/platform/mysql"
"github.com/fleetdm/fleet/v4/server/pubsub"
"github.com/fleetdm/fleet/v4/server/service"
@ -285,6 +286,9 @@ the way that the Fleet server works.
opts = append(opts, mysql.TracingEnabled(&config.Logging))
}
// Configure default max request body size based on config
platform_http.MaxRequestBodySize = config.Server.DefaultMaxRequestBodySize
// Create database connections that can be shared across datastores
dbConns, err := mysql.NewDBConnections(config.Mysql, opts...)
if err != nil {

View file

@ -39,10 +39,10 @@ func encodeResponse(ctx context.Context, w http.ResponseWriter, response any) er
}
// makeDecoder creates a decoder for the given request type.
func makeDecoder(iface any) kithttp.DecodeRequestFunc {
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)
}, parseCustomTags, nil, nil, nil)
}, parseCustomTags, nil, nil, nil, requestBodySizeLimit)
}
// parseCustomTags handles custom URL tag values for activity requests.
@ -125,8 +125,10 @@ type endpointer struct {
svc api.Service
}
func (e *endpointer) CallHandlerFunc(f handlerFunc, ctx context.Context, request any,
svc any) (platform_http.Errorer, error) {
func (e *endpointer) CallHandlerFunc(f handlerFunc, ctx context.Context,
request any,
svc any,
) (platform_http.Errorer, error) {
return f(ctx, request, svc.(api.Service)), nil
}
@ -135,7 +137,8 @@ func (e *endpointer) Service() any {
}
func newUserAuthenticatedEndpointer(svc api.Service, authMiddleware endpoint.Middleware, opts []kithttp.ServerOption, r *mux.Router,
versions ...string) *eu.CommonEndpointer[handlerFunc] {
versions ...string,
) *eu.CommonEndpointer[handlerFunc] {
return &eu.CommonEndpointer[handlerFunc]{
EP: &endpointer{
svc: svc,

View file

@ -25,6 +25,8 @@ import (
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/server/contexts/installersize"
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki"
"github.com/spf13/cast"
"github.com/spf13/cobra"
@ -119,6 +121,7 @@ type ServerConfig struct {
MaxInstallerSizeBytes int64 `yaml:"max_installer_size"`
TrustedProxies string `yaml:"trusted_proxies"`
GzipResponses bool `yaml:"gzip_responses"`
DefaultMaxRequestBodySize int64 `yaml:"default_max_request_body_size"`
}
func (s *ServerConfig) DefaultHTTPServer(ctx context.Context, handler http.Handler) *http.Server {
@ -1211,10 +1214,11 @@ func (man Manager) addConfigs() {
man.addConfigDuration("server.vpp_verify_timeout", 10*time.Minute, "Maximum amount of time to wait for VPP app install verification")
man.addConfigDuration("server.vpp_verify_request_delay", 5*time.Second, "Delay in between requests to verify VPP app installs")
man.addConfigDuration("server.cleanup_dist_targets_age", 24*time.Hour, "Specifies the cleanup age for completed live query distributed targets.")
man.addConfigByteSize("server.max_installer_size", installersize.Human(installersize.DefaultMaxInstallerSize), "Maximum size in bytes for software installer uploads (e.g. 10GiB, 500MB, 1G)")
man.addConfigByteSize("server.max_installer_size", installersize.Human(installersize.MaxSoftwareInstallerSize), "Maximum size in bytes for software installer uploads (e.g. 10GiB, 500MB, 1G)")
man.addConfigString("server.trusted_proxies", "",
"Trusted proxy configuration for client IP extraction: 'none' (RemoteAddr only), a header name (e.g., 'True-Client-IP'), a hop count (e.g., '2'), or comma-separated IP/CIDR ranges")
man.addConfigBool("server.gzip_responses", false, "Enable gzip-compressed responses for supported clients")
man.addConfigByteSize("server.default_max_request_body_size", installersize.Human(platform_http.MaxRequestBodySize), "Default maximum size in bytes for request bodies, certain endpoints will have higher limits (e.g. 10MiB, 500KB, 1G)")
// Hide the sandbox flag as we don't want it to be discoverable for users for now
man.hideConfig("server.sandbox_enabled")
@ -1686,6 +1690,7 @@ func (man Manager) LoadConfig() FleetConfig {
MaxInstallerSizeBytes: man.getConfigByteSize("server.max_installer_size"),
TrustedProxies: man.getConfigString("server.trusted_proxies"),
GzipResponses: man.getConfigBool("server.gzip_responses"),
DefaultMaxRequestBodySize: man.getConfigByteSize("server.default_max_request_body_size"),
},
Auth: AuthConfig{
BcryptCost: man.getConfigInt("auth.bcrypt_cost"),

View file

@ -7,6 +7,8 @@ import (
"github.com/docker/go-units"
)
const MaxSoftwareInstallerSize int64 = 10 * units.GiB
// Human formats a byte size into a human-readable string.
// It evaluates both SI units (KB, MB, GB) and binary units (KiB, MiB, GiB)
// and returns whichever representation is shorter.
@ -22,9 +24,6 @@ func Human(bytes int64) string {
type key struct{}
// DefaultMaxInstallerSize is the default maximum size allowed for software installers (10 GiB).
const DefaultMaxInstallerSize int64 = 10 * units.GiB
// NewContext returns a new context with the max installer size value.
func NewContext(ctx context.Context, maxSize int64) context.Context {
return context.WithValue(ctx, key{}, maxSize)
@ -35,7 +34,7 @@ func NewContext(ctx context.Context, maxSize int64) context.Context {
func FromContext(ctx context.Context) int64 {
v, ok := ctx.Value(key{}).(int64)
if !ok {
return DefaultMaxInstallerSize
return MaxSoftwareInstallerSize
}
return v
}

View file

@ -30,7 +30,7 @@ func TestFormatSize(t *testing.T) {
{"1024 bytes", 1024, "1KiB"},
// Default max installer size (10 GiB)
{"default max (10 GiB)", DefaultMaxInstallerSize, "10GiB"},
{"default max (10 GiB)", MaxSoftwareInstallerSize, "10GiB"},
}
for _, tt := range tests {
@ -43,8 +43,8 @@ func TestFormatSize(t *testing.T) {
func TestDefaultMaxInstallerSize(t *testing.T) {
// Verify the default is 10 GiB
assert.Equal(t, int64(10*units.GiB), DefaultMaxInstallerSize)
assert.Equal(t, "10GiB", Human(DefaultMaxInstallerSize))
assert.Equal(t, int64(10*units.GiB), MaxSoftwareInstallerSize)
assert.Equal(t, "10GiB", Human(MaxSoftwareInstallerSize))
}
func TestFromContextWithValue(t *testing.T) {
@ -60,7 +60,7 @@ func TestFromContextWithoutValue(t *testing.T) {
ctx := context.Background()
result := FromContext(ctx)
assert.Equal(t, DefaultMaxInstallerSize, result)
assert.Equal(t, MaxSoftwareInstallerSize, result)
}
func TestNewContextOverwrite(t *testing.T) {

20
server/fleet/request.go Normal file
View file

@ -0,0 +1,20 @@
package fleet
import "github.com/docker/go-units"
// This file declares max request body size limits for individual or grouped endpoints, it's together for easy review and re-usage.
// The only outlier is the MaxSoftwareInstallerSize which is in the installersize context package, as it's used in multiple places outside of request handling, and to avoid circular imports.
const (
MaxSpecSize int64 = 25 * units.MiB
MaxFleetdErrorReportSize int64 = 5 * units.MiB
MaxScriptSize int64 = 1 * units.MiB
MaxBatchScriptSize int64 = 25 * units.MiB
MaxProfileSize int64 = 1 * units.MiB
MaxBatchProfileSize int64 = 25 * units.MiB
MaxEULASize int64 = 25 * units.MiB
MaxMDMCommandSize int64 = 2 * units.MiB
// MaxMultiScriptQuerySize, sets a max size for payloads that take multiple scripts and SQL queries.
MaxMultiScriptQuerySize int64 = 5 * units.MiB
MaxMicrosoftMDMSize int64 = 2 * units.MiB
)

View file

@ -26,10 +26,10 @@ func encodeResponse(ctx context.Context, w http.ResponseWriter, response interfa
)
}
func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
func makeDecoder(iface any, requestBodySizeLimit int64) kithttp.DecodeRequestFunc {
return eu.MakeDecoder(iface, func(body io.Reader, req any) error {
return json.UnmarshalRead(body, req)
}, nil, nil, nil, nil)
}, nil, nil, nil, nil, requestBodySizeLimit)
}
// handlerFunc is the handler function type for Android service endpoints.
@ -43,7 +43,8 @@ type androidEndpointer struct {
}
func (e *androidEndpointer) CallHandlerFunc(f handlerFunc, ctx context.Context, request any,
svc any) (platform_http.Errorer, error) {
svc any,
) (platform_http.Errorer, error) {
return f(ctx, request, svc.(android.Service)), nil
}
@ -52,7 +53,8 @@ func (e *androidEndpointer) Service() any {
}
func newUserAuthenticatedEndpointer(fleetSvc fleet.Service, svc android.Service, opts []kithttp.ServerOption, r *mux.Router,
versions ...string) *eu.CommonEndpointer[handlerFunc] {
versions ...string,
) *eu.CommonEndpointer[handlerFunc] {
return &eu.CommonEndpointer[handlerFunc]{
EP: &androidEndpointer{
svc: svc,
@ -69,7 +71,8 @@ func newUserAuthenticatedEndpointer(fleetSvc fleet.Service, svc android.Service,
}
func newNoAuthEndpointer(fleetSvc fleet.Service, svc android.Service, opts []kithttp.ServerOption, r *mux.Router,
versions ...string) *eu.CommonEndpointer[handlerFunc] {
versions ...string,
) *eu.CommonEndpointer[handlerFunc] {
return &eu.CommonEndpointer[handlerFunc]{
EP: &androidEndpointer{
svc: svc,

View file

@ -1,6 +1,7 @@
package service
import (
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/android"
eu "github.com/fleetdm/fleet/v4/server/platform/endpointer"
@ -33,7 +34,7 @@ func attachFleetAPIRoutes(r *mux.Router, fleetSvc fleet.Service, svc android.Ser
ne.GET("/api/_version_/fleet/android_enterprise/connect/{token}", enterpriseSignupCallbackEndpoint, enterpriseSignupCallbackRequest{})
ne.GET("/api/_version_/fleet/android_enterprise/enrollment_token", enrollmentTokenEndpoint, enrollmentTokenRequest{})
ne.POST(pubSubPushPath, pubSubPushEndpoint, PubSubPushRequest{})
ne.WithRequestBodySizeLimit(10*units.MiB).POST(pubSubPushPath, pubSubPushEndpoint, PubSubPushRequest{})
}
func apiVersions() []string {

View file

@ -28,6 +28,17 @@ import (
"github.com/gorilla/mux"
)
// We use our own wrapper here, to preserve the Close method of the original io.ReadCloser
// But also allows us to modify the limit at a laterp oint.
type LimitedReadCloser struct {
*io.LimitedReader
Closer io.Closer
}
func (lrc *LimitedReadCloser) Close() error {
return lrc.Closer.Close()
}
type HandlerRoutesFunc func(r *mux.Router, opts []kithttp.ServerOption)
// ParseTag parses a `url` tag and whether it's optional or not, which is an optional part of the tag
@ -360,6 +371,8 @@ type requestValidator interface {
//
// The customQueryDecoder parameter allows services to inject domain-specific
// query parameter decoding logic.
//
// If adding a new way to parse/decode the requset, make sure to wrap the body in a limited reader with the maxRequestBodySize
func MakeDecoder(
iface interface{},
jsonUnmarshal func(body io.Reader, req any) error,
@ -367,6 +380,7 @@ func MakeDecoder(
isBodyDecoder func(reflect.Value) bool,
decodeBody func(ctx context.Context, r *http.Request, v reflect.Value, body io.Reader) error,
customQueryDecoder DomainQueryFieldDecoder,
maxRequestBodySize int64,
) kithttp.DecodeRequestFunc {
if iface == nil {
return func(ctx context.Context, r *http.Request) (interface{}, error) {
@ -375,7 +389,19 @@ func MakeDecoder(
}
if rd, ok := iface.(RequestDecoder); ok {
return func(ctx context.Context, r *http.Request) (interface{}, error) {
return rd.DecodeRequest(ctx, r)
if maxRequestBodySize != -1 {
limitedReader := io.LimitReader(r.Body, maxRequestBodySize).(*io.LimitedReader)
r.Body = &LimitedReadCloser{
LimitedReader: limitedReader,
Closer: r.Body,
}
}
ret, err := rd.DecodeRequest(ctx, r)
if err != nil && err == io.ErrUnexpectedEOF {
return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize}
}
return ret, err
}
}
@ -388,6 +414,15 @@ func MakeDecoder(
v := reflect.New(t)
nilBody := false
if maxRequestBodySize != -1 {
limitedReader := io.LimitReader(r.Body, maxRequestBodySize).(*io.LimitedReader)
r.Body = &LimitedReadCloser{
LimitedReader: limitedReader,
Closer: r.Body,
}
}
buf := bufio.NewReader(r.Body)
var body io.Reader = buf
if _, err := buf.Peek(1); err == io.EOF {
@ -406,6 +441,10 @@ func MakeDecoder(
req := v.Interface()
err := jsonUnmarshal(body, req)
if err != nil {
if err == io.ErrUnexpectedEOF {
return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize}
}
return nil, BadRequestErr("json decoder error", err)
}
v = reflect.ValueOf(req)
@ -463,6 +502,9 @@ func MakeDecoder(
if isBodyDecoder != nil && isBodyDecoder(v) {
err := decodeBody(ctx, r, v, body)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, platform_http.PayloadTooLargeError{ContentLength: r.Header.Get("Content-Length"), MaxRequestSize: maxRequestBodySize}
}
return nil, err
}
}
@ -514,7 +556,7 @@ func WriteBrowserSecurityHeaders(w http.ResponseWriter) {
type CommonEndpointer[H any] struct {
EP Endpointer[H]
MakeDecoderFn func(iface any) kithttp.DecodeRequestFunc
MakeDecoderFn func(iface any, requestBodyLimit int64) kithttp.DecodeRequestFunc
EncodeFn kithttp.EncodeResponseFunc
Opts []kithttp.ServerOption
Router *mux.Router
@ -532,6 +574,9 @@ type CommonEndpointer[H any] struct {
endingAtVersion string
alternativePaths []string
usePathPrefix bool
// The limit of the request body size in bytes, if set to -1 there is no limit.
requestBodySizeLimit int64
}
type Endpointer[H any] interface {
@ -595,7 +640,13 @@ func (e *CommonEndpointer[H]) makeEndpoint(f H, v interface{}) http.Handler {
endp = mw(endp)
}
return newServer(endp, e.MakeDecoderFn(v), e.EncodeFn, e.Opts)
// Default to MaxRequestBodySize if no limit is set, this ensures no endpointers are forgot
// -1 = no limit, so don't default to anything if that is set, which can only be set with the appropriate SKIP method.
if e.requestBodySizeLimit != -1 && (e.requestBodySizeLimit == 0 || e.requestBodySizeLimit < platform_http.MaxRequestBodySize) {
// If no value is configured set default, or if the set endpoint value is less than global default use default.
e.requestBodySizeLimit = platform_http.MaxRequestBodySize
}
return newServer(endp, e.MakeDecoderFn(v, e.requestBodySizeLimit), e.EncodeFn, e.Opts)
}
func newServer(e endpoint.Endpoint, decodeFn kithttp.DecodeRequestFunc, encodeFn kithttp.EncodeResponseFunc,
@ -651,6 +702,21 @@ func (e *CommonEndpointer[H]) UsePathPrefix() *CommonEndpointer[H] {
return &ae
}
func (e *CommonEndpointer[H]) WithRequestBodySizeLimit(limit int64) *CommonEndpointer[H] {
ae := *e
if limit > 0 {
// Only set it when the limit is more than 0
ae.requestBodySizeLimit = limit
}
return &ae
}
func (e *CommonEndpointer[H]) SkipRequestBodySizeLimit() *CommonEndpointer[H] {
ae := *e
ae.requestBodySizeLimit = -1
return &ae
}
// PathHandler registers a handler for the verb and path. The pathHandler is
// a function that receives the actual path to which it will be mounted, and
// returns the actual http.Handler that will handle this endpoint. This is for

View file

@ -63,12 +63,12 @@ func TestCustomMiddlewareAfterAuth(t *testing.T) {
r := mux.NewRouter()
ce := &CommonEndpointer[testHandlerFunc]{
EP: nopEP{},
MakeDecoderFn: func(iface interface{}) kithttp.DecodeRequestFunc {
return func(ctx context.Context, r *http.Request) (request interface{}, err error) {
MakeDecoderFn: func(iface any, requestBodySizeLimit int64) kithttp.DecodeRequestFunc {
return func(ctx context.Context, r *http.Request) (request any, err error) {
return nopRequest{}, nil
}
},
EncodeFn: func(ctx context.Context, w http.ResponseWriter, i interface{}) error {
EncodeFn: func(ctx context.Context, w http.ResponseWriter, i any) error {
w.WriteHeader(http.StatusOK)
return nil
},

View file

@ -7,8 +7,10 @@ import (
"net/http"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/docker/go-units"
"github.com/google/uuid"
)
@ -86,6 +88,38 @@ func (e BadRequestError) IsClientError() bool {
return true
}
type PayloadTooLargeError struct {
ContentLength string
MaxRequestSize int64
}
func (e PayloadTooLargeError) Error() string {
return fmt.Sprintf("Request exceeds the max size limit of %s", units.HumanSize(float64(e.MaxRequestSize)))
}
func (e PayloadTooLargeError) Internal() string {
// This is for us to have an indication of the size we get, might be spoofable, but better than nothing
msg := fmt.Sprintf("Request exceeds the max size limit of %s", units.HumanSize(float64(e.MaxRequestSize)))
if e.ContentLength != "" {
size := e.ContentLength
contentLengthAsNumber, err := strconv.ParseFloat(e.ContentLength, 64)
if err == nil {
// We don't care if we failed to parse the number, only if we were successful
size = units.HumanSize(contentLengthAsNumber)
}
msg += fmt.Sprintf(", Incoming Content-Length: %s", size)
}
return msg
}
func (e PayloadTooLargeError) StatusCode() int {
return http.StatusRequestEntityTooLarge
}
func (e PayloadTooLargeError) IsClientError() bool {
return true
}
// UserMessageError is an error that wraps another error with a user-friendly message.
type UserMessageError struct {
error

View file

@ -0,0 +1,12 @@
package http
import "github.com/docker/go-units"
// WE need the default to be a var, since we want it configurable, and to avoid misses in the future we set it to the config value on startup/serve.
var MaxRequestBodySize int64 = units.MiB // Default which is 1 MiB
const (
// MaxMultipartFormSize represents how big the in memory elements is when parsing a multipart form data set,
// anything above that limit (primarily files) will be written to temp disk files
MaxMultipartFormSize int64 = 1 * units.MiB
)

View file

@ -24,13 +24,14 @@ import (
"sync"
"time"
"github.com/docker/go-units"
eeservice "github.com/fleetdm/fleet/v4/ee/server/service"
"github.com/fleetdm/fleet/v4/ee/server/service/digicert"
"github.com/fleetdm/fleet/v4/pkg/file"
shared_mdm "github.com/fleetdm/fleet/v4/pkg/mdm"
"github.com/fleetdm/fleet/v4/pkg/optjson"
"github.com/fleetdm/fleet/v4/server"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/config"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
@ -311,7 +312,7 @@ type newMDMAppleConfigProfileResponse struct {
func (newMDMAppleConfigProfileRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
decoded := newMDMAppleConfigProfileRequest{}
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -1417,10 +1418,8 @@ type uploadAppleInstallerResponse struct {
Err error `json:"error,omitempty"`
}
// TODO(lucas): We parse the whole body before running svc.authz.Authorize.
// An authenticated but unauthorized user could abuse this.
func (uploadAppleInstallerRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -2789,7 +2788,7 @@ type uploadBootstrapPackageResponse struct {
// An authenticated but unauthorized user could abuse this.
func (uploadBootstrapPackageRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
decoded := uploadBootstrapPackageRequest{}
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -6732,7 +6731,7 @@ type uploadABMTokenRequest struct {
}
func (uploadABMTokenRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -6923,7 +6922,7 @@ type renewABMTokenRequest struct {
}
func (renewABMTokenRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",

View file

@ -750,11 +750,9 @@ func (f *fleetdErrorRequest) deviceAuthToken() string {
// Since we're directly storing what we get in Redis, limit the request size to
// 5MB, this combined with the rate limit of this endpoint should be enough to
// prevent a malicious actor.
const maxFleetdErrorReportSize int64 = 5 * 1024 * 1024
// body limiting is done at the handler level
func (f *fleetdErrorRequest) DecodeBody(ctx context.Context, r io.Reader, u url.Values, c []*x509.Certificate) error {
limitedReader := io.LimitReader(r, maxFleetdErrorReportSize+1)
decoder := json.NewDecoder(limitedReader)
decoder := json.NewDecoder(r)
for {
if err := decoder.Decode(&f.FleetdError); err == io.EOF {

View file

@ -21,8 +21,8 @@ import (
"github.com/gorilla/mux"
)
func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
return eu.MakeDecoder(iface, jsonDecode, parseCustomTags, isBodyDecoder, decodeBody, fleetQueryDecoder)
func makeDecoder(iface any, requestBodySizeLimit int64) kithttp.DecodeRequestFunc {
return eu.MakeDecoder(iface, jsonDecode, parseCustomTags, isBodyDecoder, decodeBody, fleetQueryDecoder, requestBodySizeLimit)
}
// fleetQueryDecoder handles fleet-specific query parameter decoding, such as

View file

@ -11,9 +11,11 @@ import (
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/installersize"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service/middleware/auth"
"github.com/fleetdm/fleet/v4/server/service/middleware/log"
@ -30,7 +32,7 @@ func TestUniversalDecoderIDs(t *testing.T) {
ID1 uint `url:"some-id"`
OptionalID uint `url:"some-other-id,optional"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
req := httptest.NewRequest("POST", "/target", nil)
req = mux.SetURLVars(req, map[string]string{"some-id": "999"})
@ -54,7 +56,7 @@ func TestUniversalDecoderIDsAndJSON(t *testing.T) {
ID1 uint `url:"some-id"`
SomeString string `json:"some_string"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
body := `{"some_string": "hello"}`
req := httptest.NewRequest("POST", "/target", strings.NewReader(body))
@ -77,7 +79,7 @@ func TestUniversalDecoderIDsAndJSONEmbedded(t *testing.T) {
ID1 uint `url:"some-id"`
EmbeddedJSON
}
decoder := makeDecoder(UniversalStruct{})
decoder := makeDecoder(UniversalStruct{}, installersize.MaxSoftwareInstallerSize)
body := `{"some_string": "hello"}`
req := httptest.NewRequest("POST", "/target", strings.NewReader(body))
@ -98,7 +100,7 @@ func TestUniversalDecoderIDsAndListOptions(t *testing.T) {
Opts fleet.ListOptions `url:"list_options"`
SomeString string `json:"some_string"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
body := `{"some_string": "bye"}`
req := httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(body))
@ -124,7 +126,7 @@ func TestUniversalDecoderHandlersEmbeddedAndNot(t *testing.T) {
Opts fleet.ListOptions `url:"list_options"`
EmbeddedJSON
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
body := `{"some_string": "o/"}`
req := httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(body))
@ -146,7 +148,7 @@ func TestUniversalDecoderListOptions(t *testing.T) {
ID1 uint `url:"some-id"`
Opts fleet.ListOptions `url:"list_options"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
req := httptest.NewRequest("POST", "/target", nil)
req = mux.SetURLVars(req, map[string]string{"some-id": "123"})
@ -161,7 +163,7 @@ func TestUniversalDecoderOptionalQueryParams(t *testing.T) {
type universalStruct struct {
ID1 *uint `query:"some_id,optional"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
req := httptest.NewRequest("POST", "/target", nil)
@ -187,7 +189,7 @@ func TestUniversalDecoderOptionalQueryParamString(t *testing.T) {
type universalStruct struct {
ID1 *string `query:"some_val,optional"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
req := httptest.NewRequest("POST", "/target", nil)
@ -213,7 +215,7 @@ func TestUniversalDecoderOptionalQueryParamNotPtr(t *testing.T) {
type universalStruct struct {
ID1 string `query:"some_val,optional"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
req := httptest.NewRequest("POST", "/target", nil)
@ -239,7 +241,7 @@ func TestUniversalDecoderQueryAndListPlayNice(t *testing.T) {
ID1 *uint `query:"some_id"`
Opts fleet.ListOptions `url:"list_options"`
}
decoder := makeDecoder(universalStruct{})
decoder := makeDecoder(universalStruct{}, installersize.MaxSoftwareInstallerSize)
req := httptest.NewRequest("POST", "/target?per_page=77&page=4&some_id=444", nil)
@ -254,6 +256,29 @@ func TestUniversalDecoderQueryAndListPlayNice(t *testing.T) {
assert.Equal(t, uint(444), *casted.ID1)
}
func TestUniversalDecoderSizeLimit(t *testing.T) {
type universalStruct struct {
ID1 uint `url:"some-id"`
Opts fleet.ListOptions `url:"list_options"`
}
decoder := makeDecoder(universalStruct{}, platform_http.MaxRequestBodySize)
largeBody := `{"key": "` + strings.Repeat("A", int(platform_http.MaxRequestBodySize)+1) + `"}`
req := httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(largeBody))
req = mux.SetURLVars(req, map[string]string{"some-id": "123"})
_, err := decoder(context.Background(), req)
require.Error(t, err)
require.IsType(t, platform_http.PayloadTooLargeError{}, err)
largeBody = `{"key": "` + strings.Repeat("A", int(platform_http.MaxRequestBodySize)-11) + `"}` // -11 to account for the wrapping JSON
req = httptest.NewRequest("POST", "/target?per_page=77&page=4", strings.NewReader(largeBody))
req = mux.SetURLVars(req, map[string]string{"some-id": "123"})
_, err = decoder(context.Background(), req)
require.NoError(t, err)
}
type stringErrorer string
func (s stringErrorer) Error() error { return nil }

View file

@ -13,6 +13,7 @@ import (
"github.com/klauspost/compress/gzhttp"
"github.com/docker/go-units"
eeservice "github.com/fleetdm/fleet/v4/ee/server/service"
"github.com/fleetdm/fleet/v4/server/config"
"github.com/fleetdm/fleet/v4/server/contexts/publicip"
@ -35,6 +36,7 @@ import (
"github.com/fleetdm/fleet/v4/server/service/middleware/log"
"github.com/fleetdm/fleet/v4/server/service/middleware/mdmconfigured"
"github.com/fleetdm/fleet/v4/server/service/middleware/otel"
kithttp "github.com/go-kit/kit/transport/http"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
@ -291,14 +293,14 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.POST("/api/_version_/fleet/users/roles/spec", applyUserRoleSpecsEndpoint, applyUserRoleSpecsRequest{})
ue.POST("/api/_version_/fleet/translate", translatorEndpoint, translatorRequest{})
ue.POST("/api/_version_/fleet/spec/teams", applyTeamSpecsEndpoint, applyTeamSpecsRequest{})
ue.WithRequestBodySizeLimit(5*units.MiB).POST("/api/_version_/fleet/spec/teams", applyTeamSpecsEndpoint, applyTeamSpecsRequest{})
ue.PATCH("/api/_version_/fleet/teams/{team_id:[0-9]+}/secrets", modifyTeamEnrollSecretsEndpoint, modifyTeamEnrollSecretsRequest{})
ue.POST("/api/_version_/fleet/teams", createTeamEndpoint, createTeamRequest{})
ue.GET("/api/_version_/fleet/teams", listTeamsEndpoint, listTeamsRequest{})
ue.GET("/api/_version_/fleet/teams/{id:[0-9]+}", getTeamEndpoint, getTeamRequest{})
ue.PATCH("/api/_version_/fleet/teams/{id:[0-9]+}", modifyTeamEndpoint, modifyTeamRequest{})
ue.DELETE("/api/_version_/fleet/teams/{id:[0-9]+}", deleteTeamEndpoint, deleteTeamRequest{})
ue.POST("/api/_version_/fleet/teams/{id:[0-9]+}/agent_options", modifyTeamAgentOptionsEndpoint, modifyTeamAgentOptionsRequest{})
ue.WithRequestBodySizeLimit(2*units.MiB).POST("/api/_version_/fleet/teams/{id:[0-9]+}/agent_options", modifyTeamAgentOptionsEndpoint, modifyTeamAgentOptionsRequest{})
ue.GET("/api/_version_/fleet/teams/{id:[0-9]+}/users", listTeamUsersEndpoint, listTeamUsersRequest{})
ue.PATCH("/api/_version_/fleet/teams/{id:[0-9]+}/users", addTeamUsersEndpoint, modifyTeamUsersRequest{})
ue.DELETE("/api/_version_/fleet/teams/{id:[0-9]+}/users", deleteTeamUsersEndpoint, modifyTeamUsersRequest{})
@ -349,7 +351,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/policies/delete").
POST("/api/_version_/fleet/teams/{team_id}/policies/delete", deleteTeamPoliciesEndpoint, deleteTeamPoliciesRequest{})
ue.PATCH("/api/_version_/fleet/teams/{team_id}/policies/{policy_id}", modifyTeamPolicyEndpoint, modifyTeamPolicyRequest{})
ue.POST("/api/_version_/fleet/spec/policies", applyPolicySpecsEndpoint, applyPolicySpecsRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxSpecSize).POST("/api/_version_/fleet/spec/policies", applyPolicySpecsEndpoint, applyPolicySpecsRequest{})
ue.POST("/api/_version_/fleet/certificates", createCertificateTemplateEndpoint, createCertificateTemplateRequest{})
ue.GET("/api/_version_/fleet/certificates", listCertificateTemplatesEndpoint, listCertificateTemplatesRequest{})
@ -366,7 +368,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.DELETE("/api/_version_/fleet/queries/{name}", deleteQueryEndpoint, deleteQueryRequest{})
ue.DELETE("/api/_version_/fleet/queries/id/{id:[0-9]+}", deleteQueryByIDEndpoint, deleteQueryByIDRequest{})
ue.POST("/api/_version_/fleet/queries/delete", deleteQueriesEndpoint, deleteQueriesRequest{})
ue.POST("/api/_version_/fleet/spec/queries", applyQuerySpecsEndpoint, applyQuerySpecsRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxSpecSize).POST("/api/_version_/fleet/spec/queries", applyQuerySpecsEndpoint, applyQuerySpecsRequest{})
ue.GET("/api/_version_/fleet/spec/queries", getQuerySpecsEndpoint, getQuerySpecsRequest{})
ue.GET("/api/_version_/fleet/spec/queries/{name}", getQuerySpecEndpoint, getQuerySpecRequest{})
@ -376,7 +378,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.GET("/api/_version_/fleet/packs", listPacksEndpoint, listPacksRequest{})
ue.DELETE("/api/_version_/fleet/packs/{name}", deletePackEndpoint, deletePackRequest{})
ue.DELETE("/api/_version_/fleet/packs/id/{id:[0-9]+}", deletePackByIDEndpoint, deletePackByIDRequest{})
ue.POST("/api/_version_/fleet/spec/packs", applyPackSpecsEndpoint, applyPackSpecsRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxSpecSize).POST("/api/_version_/fleet/spec/packs", applyPackSpecsEndpoint, applyPackSpecsRequest{})
ue.GET("/api/_version_/fleet/spec/packs", getPackSpecsEndpoint, nil)
ue.GET("/api/_version_/fleet/spec/packs/{name}", getPackSpecEndpoint, getGenericSpecRequest{})
@ -401,9 +403,11 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.GET("/api/_version_/fleet/software/titles/{title_id:[0-9]+}/package", getSoftwareInstallerEndpoint, getSoftwareInstallerRequest{})
ue.POST("/api/_version_/fleet/software/titles/{title_id:[0-9]+}/package/token", getSoftwareInstallerTokenEndpoint,
getSoftwareInstallerRequest{})
ue.POST("/api/_version_/fleet/software/package", uploadSoftwareInstallerEndpoint, uploadSoftwareInstallerRequest{})
// Software package endpoints are already limited to max installer size in serve.go
ue.SkipRequestBodySizeLimit().POST("/api/_version_/fleet/software/package", uploadSoftwareInstallerEndpoint, uploadSoftwareInstallerRequest{})
ue.PATCH("/api/_version_/fleet/software/titles/{id:[0-9]+}/name", updateSoftwareNameEndpoint, updateSoftwareNameRequest{})
ue.PATCH("/api/_version_/fleet/software/titles/{id:[0-9]+}/package", updateSoftwareInstallerEndpoint, updateSoftwareInstallerRequest{})
// Software package endpoints are already limited to max installer size in serve.go
ue.SkipRequestBodySizeLimit().PATCH("/api/_version_/fleet/software/titles/{id:[0-9]+}/package", updateSoftwareInstallerEndpoint, updateSoftwareInstallerRequest{})
ue.DELETE("/api/_version_/fleet/software/titles/{title_id:[0-9]+}/available_for_install", deleteSoftwareInstallerEndpoint, deleteSoftwareInstallerRequest{})
ue.GET("/api/_version_/fleet/software/install/{install_uuid}/results", getSoftwareInstallResultsEndpoint,
getSoftwareInstallResultsRequest{})
@ -430,11 +434,11 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Setup experience script endpoints:
ue.GET("/api/_version_/fleet/setup_experience/script", getSetupExperienceScriptEndpoint, getSetupExperienceScriptRequest{})
ue.POST("/api/_version_/fleet/setup_experience/script", setSetupExperienceScriptEndpoint, setSetupExperienceScriptRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxScriptSize).POST("/api/_version_/fleet/setup_experience/script", setSetupExperienceScriptEndpoint, setSetupExperienceScriptRequest{})
ue.DELETE("/api/_version_/fleet/setup_experience/script", deleteSetupExperienceScriptEndpoint, deleteSetupExperienceScriptRequest{})
// Fleet-maintained apps
ue.POST("/api/_version_/fleet/software/fleet_maintained_apps", addFleetMaintainedAppEndpoint, addFleetMaintainedAppRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxMultiScriptQuerySize).POST("/api/_version_/fleet/software/fleet_maintained_apps", addFleetMaintainedAppEndpoint, addFleetMaintainedAppRequest{})
ue.GET("/api/_version_/fleet/software/fleet_maintained_apps", listFleetMaintainedAppsEndpoint, listFleetMaintainedAppsRequest{})
ue.GET("/api/_version_/fleet/software/fleet_maintained_apps/{app_id}", getFleetMaintainedApp, getFleetMaintainedAppRequest{})
@ -481,7 +485,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.GET("/api/_version_/fleet/labels/{id:[0-9]+}/hosts", listHostsInLabelEndpoint, listHostsInLabelRequest{})
ue.DELETE("/api/_version_/fleet/labels/{name}", deleteLabelEndpoint, deleteLabelRequest{})
ue.DELETE("/api/_version_/fleet/labels/id/{id:[0-9]+}", deleteLabelByIDEndpoint, deleteLabelByIDRequest{})
ue.POST("/api/_version_/fleet/spec/labels", applyLabelSpecsEndpoint, applyLabelSpecsRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxSpecSize).POST("/api/_version_/fleet/spec/labels", applyLabelSpecsEndpoint, applyLabelSpecsRequest{})
ue.GET("/api/_version_/fleet/spec/labels", getLabelSpecsEndpoint, getLabelSpecsRequest{})
ue.GET("/api/_version_/fleet/spec/labels/{name}", getLabelSpecEndpoint, getGenericSpecRequest{})
@ -535,16 +539,16 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.GET("/api/_version_/fleet/status/result_store", statusResultStoreEndpoint, nil)
ue.GET("/api/_version_/fleet/status/live_query", statusLiveQueryEndpoint, nil)
ue.POST("/api/_version_/fleet/scripts/run", runScriptEndpoint, runScriptRequest{})
ue.POST("/api/_version_/fleet/scripts/run/sync", runScriptSyncEndpoint, runScriptSyncRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxScriptSize).POST("/api/_version_/fleet/scripts/run", runScriptEndpoint, runScriptRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxScriptSize).POST("/api/_version_/fleet/scripts/run/sync", runScriptSyncEndpoint, runScriptSyncRequest{})
ue.POST("/api/_version_/fleet/scripts/run/batch", batchScriptRunEndpoint, batchScriptRunRequest{})
ue.GET("/api/_version_/fleet/scripts/results/{execution_id}", getScriptResultEndpoint, getScriptResultRequest{})
ue.POST("/api/_version_/fleet/scripts", createScriptEndpoint, createScriptRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxScriptSize).POST("/api/_version_/fleet/scripts", createScriptEndpoint, createScriptRequest{})
ue.GET("/api/_version_/fleet/scripts", listScriptsEndpoint, listScriptsRequest{})
ue.GET("/api/_version_/fleet/scripts/{script_id:[0-9]+}", getScriptEndpoint, getScriptRequest{})
ue.PATCH("/api/_version_/fleet/scripts/{script_id:[0-9]+}", updateScriptEndpoint, updateScriptRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxScriptSize).PATCH("/api/_version_/fleet/scripts/{script_id:[0-9]+}", updateScriptEndpoint, updateScriptRequest{})
ue.DELETE("/api/_version_/fleet/scripts/{script_id:[0-9]+}", deleteScriptEndpoint, deleteScriptRequest{})
ue.POST("/api/_version_/fleet/scripts/batch", batchSetScriptsEndpoint, batchSetScriptsRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxBatchScriptSize).POST("/api/_version_/fleet/scripts/batch", batchSetScriptsEndpoint, batchSetScriptsRequest{})
ue.POST("/api/_version_/fleet/scripts/batch/{batch_execution_id:[a-zA-Z0-9-]+}/cancel", batchScriptCancelEndpoint, batchScriptCancelRequest{})
// Deprecated, will remove in favor of batchScriptExecutionStatusEndpoint when batch script details page is ready.
ue.GET("/api/_version_/fleet/scripts/batch/summary/{batch_execution_id:[a-zA-Z0-9-]+}", batchScriptExecutionSummaryEndpoint, batchScriptExecutionSummaryRequest{})
@ -612,7 +616,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// indefinitely for backwards compatibility.
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/profiles/{profile_id:[0-9]+}", getMDMAppleConfigProfileEndpoint, getMDMAppleConfigProfileRequest{})
mdmAppleMW.DELETE("/api/_version_/fleet/mdm/apple/profiles/{profile_id:[0-9]+}", deleteMDMAppleConfigProfileEndpoint, deleteMDMAppleConfigProfileRequest{})
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/profiles", newMDMAppleConfigProfileEndpoint, newMDMAppleConfigProfileRequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxProfileSize).POST("/api/_version_/fleet/mdm/apple/profiles", newMDMAppleConfigProfileEndpoint, newMDMAppleConfigProfileRequest{})
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/profiles", listMDMAppleConfigProfilesEndpoint, listMDMAppleConfigProfilesRequest{})
// Deprecated: GET /mdm/apple/filevault/summary is now deprecated, replaced by the
@ -627,8 +631,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Deprecated: POST /mdm/apple/enrollment_profile is now deprecated, replaced by the
// POST /enrollment_profiles/automatic endpoint.
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/enrollment_profile", createMDMAppleSetupAssistantEndpoint, createMDMAppleSetupAssistantRequest{})
mdmAppleMW.POST("/api/_version_/fleet/enrollment_profiles/automatic", createMDMAppleSetupAssistantEndpoint, createMDMAppleSetupAssistantRequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxProfileSize).POST("/api/_version_/fleet/mdm/apple/enrollment_profile", createMDMAppleSetupAssistantEndpoint, createMDMAppleSetupAssistantRequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxProfileSize).POST("/api/_version_/fleet/enrollment_profiles/automatic", createMDMAppleSetupAssistantEndpoint, createMDMAppleSetupAssistantRequest{})
// Deprecated: GET /mdm/apple/enrollment_profile is now deprecated, replaced by the
// GET /enrollment_profiles/automatic endpoint.
@ -642,7 +646,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// TODO: are those undocumented endpoints still needed? I think they were only used
// by 'fleetctl apple-mdm' sub-commands.
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/installers", uploadAppleInstallerEndpoint, uploadAppleInstallerRequest{})
// Generous limit for these unknown old unused endpoints-
mdmAppleMW.WithRequestBodySizeLimit(512*units.MiB).POST("/api/_version_/fleet/mdm/apple/installers", uploadAppleInstallerEndpoint, uploadAppleInstallerRequest{})
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/installers/{installer_id:[0-9]+}", getAppleInstallerEndpoint, getAppleInstallerDetailsRequest{})
mdmAppleMW.DELETE("/api/_version_/fleet/mdm/apple/installers/{installer_id:[0-9]+}", deleteAppleInstallerEndpoint, deleteAppleInstallerDetailsRequest{})
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/installers", listMDMAppleInstallersEndpoint, listMDMAppleInstallersRequest{})
@ -658,8 +663,9 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Deprecated: POST /mdm/bootstrap is now deprecated, replaced by the
// POST /bootstrap endpoint.
mdmAppleMW.POST("/api/_version_/fleet/mdm/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
mdmAppleMW.POST("/api/_version_/fleet/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
// Bootstrap endpoints are already max size limited to installer size in serve.go
mdmAppleMW.SkipRequestBodySizeLimit().POST("/api/_version_/fleet/mdm/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
mdmAppleMW.SkipRequestBodySizeLimit().POST("/api/_version_/fleet/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
// Deprecated: GET /mdm/bootstrap/:team_id/metadata is now deprecated, replaced by the
// GET /bootstrap/:team_id/metadata endpoint.
@ -677,7 +683,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
mdmAppleMW.GET("/api/_version_/fleet/bootstrap/summary", getMDMAppleBootstrapPackageSummaryEndpoint, getMDMAppleBootstrapPackageSummaryRequest{})
// Deprecated: POST /mdm/apple/bootstrap is now deprecated, replaced by the platform agnostic /mdm/bootstrap
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
// Bootstrap endpoints are already max size limited to installer size in serve.go
mdmAppleMW.SkipRequestBodySizeLimit().POST("/api/_version_/fleet/mdm/apple/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
// Deprecated: GET /mdm/apple/bootstrap/:team_id/metadata is now deprecated, replaced by the platform agnostic /mdm/bootstrap/:team_id/metadata
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/bootstrap/{team_id:[0-9]+}/metadata", bootstrapPackageMetadataEndpoint, bootstrapPackageMetadataRequest{})
// Deprecated: DELETE /mdm/apple/bootstrap/:team_id is now deprecated, replaced by the platform agnostic /mdm/bootstrap/:team_id
@ -707,8 +714,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Deprecated: POST /mdm/setup/eula is now deprecated, replaced by the
// POST /setup_experience/eula endpoint.
mdmAppleMW.POST("/api/_version_/fleet/mdm/setup/eula", createMDMEULAEndpoint, createMDMEULARequest{})
mdmAppleMW.POST("/api/_version_/fleet/setup_experience/eula", createMDMEULAEndpoint, createMDMEULARequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxEULASize).POST("/api/_version_/fleet/mdm/setup/eula", createMDMEULAEndpoint, createMDMEULARequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxEULASize).POST("/api/_version_/fleet/setup_experience/eula", createMDMEULAEndpoint, createMDMEULARequest{})
// Deprecated: GET /mdm/setup/eula/metadata is now deprecated, replaced by the
// GET /setup_experience/eula/metadata endpoint.
@ -721,21 +728,21 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
mdmAppleMW.DELETE("/api/_version_/fleet/setup_experience/eula/{token}", deleteMDMEULAEndpoint, deleteMDMEULARequest{})
// Deprecated: POST /mdm/apple/setup/eula is now deprecated, replaced by the platform agnostic /mdm/setup/eula
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/setup/eula", createMDMEULAEndpoint, createMDMEULARequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxEULASize).POST("/api/_version_/fleet/mdm/apple/setup/eula", createMDMEULAEndpoint, createMDMEULARequest{})
// Deprecated: GET /mdm/apple/setup/eula/metadata is now deprecated, replaced by the platform agnostic /mdm/setup/eula/metadata
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/setup/eula/metadata", getMDMEULAMetadataEndpoint, getMDMEULAMetadataRequest{})
// Deprecated: DELETE /mdm/apple/setup/eula/:token is now deprecated, replaced by the platform agnostic /mdm/setup/eula/:token
mdmAppleMW.DELETE("/api/_version_/fleet/mdm/apple/setup/eula/{token}", deleteMDMEULAEndpoint, deleteMDMEULARequest{})
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileEndpoint, preassignMDMAppleProfileRequest{})
mdmAppleMW.WithRequestBodySizeLimit(fleet.MaxProfileSize).POST("/api/_version_/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileEndpoint, preassignMDMAppleProfileRequest{})
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/profiles/match", matchMDMApplePreassignmentEndpoint, matchMDMApplePreassignmentRequest{})
mdmAnyMW := ue.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyAnyMDM())
// Deprecated: POST /mdm/commands/run is now deprecated, replaced by the
// POST /commands/run endpoint.
mdmAnyMW.POST("/api/_version_/fleet/mdm/commands/run", runMDMCommandEndpoint, runMDMCommandRequest{})
mdmAnyMW.POST("/api/_version_/fleet/commands/run", runMDMCommandEndpoint, runMDMCommandRequest{})
mdmAnyMW.WithRequestBodySizeLimit(fleet.MaxMDMCommandSize).POST("/api/_version_/fleet/mdm/commands/run", runMDMCommandEndpoint, runMDMCommandRequest{})
mdmAnyMW.WithRequestBodySizeLimit(fleet.MaxMDMCommandSize).POST("/api/_version_/fleet/commands/run", runMDMCommandEndpoint, runMDMCommandRequest{})
// Deprecated: GET /mdm/commandresults is now deprecated, replaced by the
// GET /commands/results endpoint.
@ -784,10 +791,10 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Deprecated: POST /mdm/profiles is now deprecated, replaced by the
// POST /configuration_profiles endpoint.
mdmAnyMW.POST("/api/_version_/fleet/mdm/profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{})
mdmAnyMW.POST("/api/_version_/fleet/configuration_profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{})
mdmAnyMW.WithRequestBodySizeLimit(fleet.MaxProfileSize).POST("/api/_version_/fleet/mdm/profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{})
mdmAnyMW.WithRequestBodySizeLimit(fleet.MaxProfileSize).POST("/api/_version_/fleet/configuration_profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{})
// Batch needs to allow being called without any MDM enabled, to support deleting profiles, but will fail later if trying to add
ue.POST("/api/_version_/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesEndpoint, batchModifyMDMConfigProfilesRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxBatchProfileSize).POST("/api/_version_/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesEndpoint, batchModifyMDMConfigProfilesRequest{})
// Deprecated: POST /hosts/{host_id:[0-9]+}/configuration_profiles/resend/{profile_uuid} is now deprecated, replaced by the
// POST /hosts/{host_id:[0-9]+}/configuration_profiles/{profile_uuid}/resend endpoint.
@ -845,12 +852,12 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// batch-apply is accessible even though MDM is not enabled, it needs
// to support the case where `fleetctl get config`'s output is used as
// input to `fleetctl apply`
ue.POST("/api/_version_/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesEndpoint, batchSetMDMAppleProfilesRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxBatchProfileSize).POST("/api/_version_/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesEndpoint, batchSetMDMAppleProfilesRequest{})
// batch-apply is accessible even though MDM is not enabled, it needs
// to support the case where `fleetctl get config`'s output is used as
// input to `fleetctl apply`
ue.POST("/api/_version_/fleet/mdm/profiles/batch", batchSetMDMProfilesEndpoint, batchSetMDMProfilesRequest{})
ue.WithRequestBodySizeLimit(fleet.MaxBatchProfileSize).POST("/api/_version_/fleet/mdm/profiles/batch", batchSetMDMProfilesEndpoint, batchSetMDMProfilesRequest{})
// Certificate Authority endpoints
ue.POST("/api/_version_/fleet/certificate_authorities", createCertificateAuthorityEndpoint, createCertificateAuthorityRequest{})
@ -880,7 +887,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
de.WithCustomMiddleware(errorLimiter).GET("/api/_version_/fleet/device/{token}/macadmins", getDeviceMacadminsDataEndpoint, getDeviceMacadminsDataRequest{})
de.WithCustomMiddleware(errorLimiter).GET("/api/_version_/fleet/device/{token}/policies", listDevicePoliciesEndpoint, listDevicePoliciesRequest{})
de.WithCustomMiddleware(errorLimiter).GET("/api/_version_/fleet/device/{token}/transparency", transparencyURL, transparencyURLRequest{})
de.WithCustomMiddleware(errorLimiter).POST("/api/_version_/fleet/device/{token}/debug/errors", fleetdError, fleetdErrorRequest{})
de.WithCustomMiddleware(errorLimiter).WithRequestBodySizeLimit(fleet.MaxFleetdErrorReportSize).POST("/api/_version_/fleet/device/{token}/debug/errors", fleetdError, fleetdErrorRequest{})
de.WithCustomMiddleware(errorLimiter).GET("/api/_version_/fleet/device/{token}/software", getDeviceSoftwareEndpoint, getDeviceSoftwareRequest{})
de.WithCustomMiddleware(errorLimiter).POST("/api/_version_/fleet/device/{token}/software/install/{software_title_id}", submitSelfServiceSoftwareInstall, fleetSelfServiceSoftwareInstallRequest{})
de.WithCustomMiddleware(errorLimiter).POST("/api/_version_/fleet/device/{token}/software/uninstall/{software_title_id}", submitDeviceSoftwareUninstall, fleetDeviceSoftwareUninstallRequest{})
@ -938,7 +945,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
oe.POST("/api/fleet/orbit/scripts/request", getOrbitScriptEndpoint, orbitGetScriptRequest{})
oe.POST("/api/fleet/orbit/scripts/result", postOrbitScriptResultEndpoint, orbitPostScriptResultRequest{})
oe.PUT("/api/fleet/orbit/device_mapping", putOrbitDeviceMappingEndpoint, orbitPutDeviceMappingRequest{})
oe.POST("/api/fleet/orbit/software_install/result", postOrbitSoftwareInstallResultEndpoint, orbitPostSoftwareInstallResultRequest{})
oe.WithRequestBodySizeLimit(fleet.MaxMultiScriptQuerySize).POST("/api/fleet/orbit/software_install/result", postOrbitSoftwareInstallResultEndpoint, orbitPostSoftwareInstallResultRequest{})
oe.POST("/api/fleet/orbit/software_install/package", orbitDownloadSoftwareInstallerEndpoint, orbitDownloadSoftwareInstallerRequest{})
oe.POST("/api/fleet/orbit/software_install/details", getOrbitSoftwareInstallDetails, orbitGetSoftwareInstallRequest{})
oe.POST("/api/fleet/orbit/setup_experience/init", orbitSetupExperienceInitEndpoint, orbitSetupExperienceInitRequest{})
@ -1004,23 +1011,23 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Microsoft MS-MDE2 Endpoints
// This endpoint is unauthenticated and is used by Microsoft devices to discover the MDM server endpoints
neWindowsMDM.POST(microsoft_mdm.MDE2DiscoveryPath, mdmMicrosoftDiscoveryEndpoint, SoapRequestContainer{})
neWindowsMDM.WithRequestBodySizeLimit(fleet.MaxMicrosoftMDMSize).POST(microsoft_mdm.MDE2DiscoveryPath, mdmMicrosoftDiscoveryEndpoint, SoapRequestContainer{})
// This endpoint is unauthenticated and is used by Microsoft devices to retrieve the opaque STS auth token
neWindowsMDM.GET(microsoft_mdm.MDE2AuthPath, mdmMicrosoftAuthEndpoint, SoapRequestContainer{})
neWindowsMDM.WithRequestBodySizeLimit(fleet.MaxMicrosoftMDMSize).GET(microsoft_mdm.MDE2AuthPath, mdmMicrosoftAuthEndpoint, SoapRequestContainer{})
// This endpoint is authenticated using the BinarySecurityToken header field
neWindowsMDM.POST(microsoft_mdm.MDE2PolicyPath, mdmMicrosoftPolicyEndpoint, SoapRequestContainer{})
neWindowsMDM.WithRequestBodySizeLimit(fleet.MaxMicrosoftMDMSize).POST(microsoft_mdm.MDE2PolicyPath, mdmMicrosoftPolicyEndpoint, SoapRequestContainer{})
// This endpoint is authenticated using the BinarySecurityToken header field
neWindowsMDM.POST(microsoft_mdm.MDE2EnrollPath, mdmMicrosoftEnrollEndpoint, SoapRequestContainer{})
neWindowsMDM.WithRequestBodySizeLimit(fleet.MaxMicrosoftMDMSize).POST(microsoft_mdm.MDE2EnrollPath, mdmMicrosoftEnrollEndpoint, SoapRequestContainer{})
// This endpoint is unauthenticated for now
// It should be authenticated through TLS headers once proper implementation is in place
neWindowsMDM.POST(microsoft_mdm.MDE2ManagementPath, mdmMicrosoftManagementEndpoint, SyncMLReqMsgContainer{})
neWindowsMDM.WithRequestBodySizeLimit(fleet.MaxMicrosoftMDMSize).POST(microsoft_mdm.MDE2ManagementPath, mdmMicrosoftManagementEndpoint, SyncMLReqMsgContainer{})
// This endpoint is unauthenticated and is used by to retrieve the MDM enrollment Terms of Use
neWindowsMDM.GET(microsoft_mdm.MDE2TOSPath, mdmMicrosoftTOSEndpoint, MDMWebContainer{})
neWindowsMDM.WithRequestBodySizeLimit(fleet.MaxMicrosoftMDMSize).GET(microsoft_mdm.MDE2TOSPath, mdmMicrosoftTOSEndpoint, MDMWebContainer{})
// These endpoints are unauthenticated and made from orbit, and add the orbit capabilities header.
neOrbit := newOrbitNoAuthEndpointer(svc, opts, r, apiVersions...)
@ -1031,7 +1038,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// For some reason osquery does not provide a node key with the block data.
// Instead the carve session ID should be verified in the service method.
ne.WithAltPaths("/api/v1/osquery/carve/block").
// Since []byte slices is encoded as base64 in JSON, increase the limit to 1.5x
ne.SkipRequestBodySizeLimit().WithAltPaths("/api/v1/osquery/carve/block").
POST("/api/osquery/carve/block", carveBlockEndpoint, carveBlockRequest{})
ne.GET("/api/_version_/fleet/software/titles/{title_id:[0-9]+}/package/token/{token}", downloadSoftwareInstallerEndpoint,

View file

@ -18,6 +18,8 @@ import (
"github.com/fleetdm/fleet/v4/server"
"github.com/fleetdm/fleet/v4/server/authz"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
authzctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
@ -3400,7 +3402,7 @@ func (r getHostSoftwareRequest) DecodeRequest(ctx context.Context, req *http.Req
fleet.HostSoftwareTitleListOptions
}
defaultDecoder := makeDecoder(defaultDecodeRequest{})
defaultDecoder := makeDecoder(defaultDecodeRequest{}, platform_http.MaxRequestBodySize)
decoded, err := defaultDecoder(ctx, req)
if err != nil {
return nil, err

View file

@ -13,6 +13,7 @@ import (
"github.com/fleetdm/fleet/v4/server/datastore/redis"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
@ -318,7 +319,7 @@ func (s *integrationTestSuite) TestErrorReporting() {
res.Body.Close()
data := make(map[string]interface{})
for i := int64(0); i < (maxFleetdErrorReportSize+1024)/20; i++ {
for i := range (fleet.MaxFleetdErrorReportSize + 1024) / 20 {
key := fmt.Sprintf("key%d", i)
value := fmt.Sprintf("value%d", i)
data[key] = value

View file

@ -21,7 +21,6 @@ import (
"time"
"github.com/VividCortex/mysqlerr"
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/pkg/certificate"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server"
@ -34,6 +33,8 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/logging"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/server/mdm"
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
@ -248,10 +249,8 @@ type createMDMEULARequest struct {
DryRun bool `query:"dry_run,optional"` // if true, apply validation but do not save changes
}
// TODO: We parse the whole body before running svc.authz.Authorize.
// An authenticated but unauthorized user could abuse this.
func (createMDMEULARequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -266,12 +265,20 @@ func (createMDMEULARequest) DecodeRequest(ctx context.Context, r *http.Request)
}
}
eula := r.MultipartForm.File["eula"][0]
if eula.Size > fleet.MaxEULASize {
return nil, &fleet.BadRequestError{
Message: "Uploaded EULA exceeds maximum allowed size of 500 MiB",
}
}
dryRun := false
if v := r.URL.Query().Get("dry_run"); v != "" {
dryRun, _ = strconv.ParseBool(v)
}
return &createMDMEULARequest{
EULA: r.MultipartForm.File["eula"][0],
EULA: eula,
DryRun: dryRun,
}, nil
}
@ -1556,7 +1563,7 @@ type newMDMConfigProfileRequest struct {
func (newMDMConfigProfileRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
decoded := newMDMConfigProfileRequest{}
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -1584,7 +1591,7 @@ func (newMDMConfigProfileRequest) DecodeRequest(ctx context.Context, r *http.Req
}
decoded.Profile = fhs[0]
if decoded.Profile.Size > 1024*1024 {
if decoded.Profile.Size > fleet.MaxProfileSize {
return nil, fleet.NewInvalidArgumentError("mdm", "maximum configuration profile file size is 1 MB")
}
@ -3099,7 +3106,7 @@ type uploadMDMAppleAPNSCertRequest struct {
func (uploadMDMAppleAPNSCertRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
decoded := uploadMDMAppleAPNSCertRequest{}
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",

View file

@ -14,8 +14,9 @@ import (
"strconv"
"time"
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/pkg/file"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/pkg/scripts"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
@ -459,7 +460,7 @@ type createScriptRequest struct {
func (createScriptRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var decoded createScriptRequest
err := r.ParseMultipartForm(512 * units.MiB) // same in-memory size as for other multipart requests we have
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -764,7 +765,7 @@ type updateScriptRequest struct {
func (updateScriptRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var decoded updateScriptRequest
err := r.ParseMultipartForm(512 * units.MiB) // same in-memory size as for other multipart requests we have
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",

View file

@ -12,9 +12,9 @@ import (
"strings"
"time"
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/server/ptr"
)
@ -138,7 +138,7 @@ type setSetupExperienceScriptRequest struct {
func (setSetupExperienceScriptRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var decoded setSetupExperienceScriptRequest
err := r.ParseMultipartForm(512 * units.MiB) // same in-memory size as for other multipart requests we have
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",

View file

@ -13,7 +13,6 @@ import (
"net/url"
"strconv"
"github.com/docker/go-units"
authzctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
@ -21,6 +20,8 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/logging"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/fleetdm/fleet/v4/server/ptr"
)
@ -70,7 +71,7 @@ func (updateSoftwareInstallerRequest) DecodeRequest(ctx context.Context, r *http
decoded.TitleID = uint(titleID)
maxInstallerSize := installersize.FromContext(ctx)
err = r.ParseMultipartForm(512 * units.MiB)
err = r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
var mbe *http.MaxBytesError
if errors.As(err, &mbe) {
@ -276,7 +277,7 @@ func (uploadSoftwareInstallerRequest) DecodeRequest(ctx context.Context, r *http
decoded := uploadSoftwareInstallerRequest{}
maxInstallerSize := installersize.FromContext(ctx)
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
var mbe *http.MaxBytesError
if errors.As(err, &mbe) {

View file

@ -12,8 +12,9 @@ import (
"net/http"
"strconv"
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/server/fleet"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/gorilla/mux"
)
@ -143,7 +144,7 @@ func (putSoftwareTitleIconRequest) DecodeRequest(ctx context.Context, r *http.Re
TeamID: &teamIDUint,
}
err = r.ParseMultipartForm(6 * units.MiB)
err = r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",

View file

@ -7,11 +7,11 @@ import (
"mime/multipart"
"net/http"
"github.com/docker/go-units"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/platform/endpointer"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
)
//////////////////////////////////////////////////////////////////////////////
@ -197,7 +197,7 @@ type uploadVPPTokenRequest struct {
func (uploadVPPTokenRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
decoded := uploadVPPTokenRequest{}
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",
@ -264,7 +264,7 @@ type patchVPPTokenRenewRequest struct {
func (patchVPPTokenRenewRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
decoded := patchVPPTokenRenewRequest{}
err := r.ParseMultipartForm(512 * units.MiB)
err := r.ParseMultipartForm(platform_http.MaxMultipartFormSize)
if err != nil {
return nil, &fleet.BadRequestError{
Message: "failed to parse multipart form",

View file

@ -75,7 +75,7 @@ func output(metadata *file.InstallerMetadata) {
func processPackageFromUrl(url string) (*file.InstallerMetadata, error) {
client := fleethttp.NewClient()
client.Transport = fleethttp.NewSizeLimitTransport(installersize.DefaultMaxInstallerSize)
client.Transport = fleethttp.NewSizeLimitTransport(installersize.MaxSoftwareInstallerSize)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {