mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
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:
parent
657e8159a2
commit
da43bf8371
26 changed files with 306 additions and 116 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
20
server/fleet/request.go
Normal 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
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
12
server/platform/http/request.go
Normal file
12
server/platform/http/request.go
Normal 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
|
||||
)
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue