2016-09-26 18:48:55 +00:00
|
|
|
package service
|
2016-08-28 03:59:17 +00:00
|
|
|
|
|
|
|
import (
|
2017-03-15 15:55:30 +00:00
|
|
|
"context"
|
2023-01-16 20:06:30 +00:00
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
2021-11-22 14:13:26 +00:00
|
|
|
"errors"
|
2023-01-16 20:06:30 +00:00
|
|
|
"fmt"
|
2016-08-28 03:59:17 +00:00
|
|
|
"net/http"
|
2022-03-08 16:27:38 +00:00
|
|
|
"regexp"
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/config"
|
2021-08-02 22:06:27 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
2022-03-21 16:29:52 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/publicip"
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2022-10-05 22:53:54 +00:00
|
|
|
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/authzcheck"
|
2023-03-27 19:30:29 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/mdmconfigured"
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/ratelimit"
|
2016-09-26 17:14:39 +00:00
|
|
|
"github.com/go-kit/kit/endpoint"
|
2016-08-28 03:59:17 +00:00
|
|
|
kitlog "github.com/go-kit/kit/log"
|
2021-02-10 20:13:11 +00:00
|
|
|
"github.com/go-kit/kit/log/level"
|
2016-08-28 03:59:17 +00:00
|
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
|
|
|
"github.com/gorilla/mux"
|
2023-01-16 20:06:30 +00:00
|
|
|
"github.com/micromdm/nanomdm/certverify"
|
|
|
|
httpmdm "github.com/micromdm/nanomdm/http/mdm"
|
|
|
|
nanomdm_log "github.com/micromdm/nanomdm/log"
|
|
|
|
nanomdm_service "github.com/micromdm/nanomdm/service"
|
|
|
|
"github.com/micromdm/nanomdm/service/certauth"
|
|
|
|
"github.com/micromdm/nanomdm/service/multi"
|
|
|
|
"github.com/micromdm/nanomdm/service/nanomdm"
|
|
|
|
nanomdm_storage "github.com/micromdm/nanomdm/storage"
|
|
|
|
scep_depot "github.com/micromdm/scep/v2/depot"
|
|
|
|
scepserver "github.com/micromdm/scep/v2/server"
|
2016-12-22 17:39:44 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2021-12-20 14:20:58 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2021-03-26 18:23:29 +00:00
|
|
|
"github.com/throttled/throttled/v2"
|
2023-04-24 05:13:15 +00:00
|
|
|
"go.elastic.co/apm/module/apmgorilla/v2"
|
2022-02-15 17:42:22 +00:00
|
|
|
otmiddleware "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
|
2023-06-22 20:31:17 +00:00
|
|
|
|
|
|
|
microsoft_mdm "github.com/fleetdm/fleet/v4/server/mdm/microsoft"
|
2016-08-28 03:59:17 +00:00
|
|
|
)
|
|
|
|
|
2021-03-26 18:23:29 +00:00
|
|
|
type errorHandler struct {
|
|
|
|
logger kitlog.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *errorHandler) Handle(ctx context.Context, err error) {
|
|
|
|
// get the request path
|
|
|
|
path, _ := ctx.Value(kithttp.ContextKeyRequestPath).(string)
|
|
|
|
logger := level.Info(kitlog.With(h.logger, "path", path))
|
|
|
|
|
2021-11-15 14:11:38 +00:00
|
|
|
var ewi fleet.ErrWithInternal
|
|
|
|
if errors.As(err, &ewi) {
|
|
|
|
logger = kitlog.With(logger, "internal", ewi.Internal())
|
2021-06-03 23:24:15 +00:00
|
|
|
}
|
|
|
|
|
2021-11-15 14:11:38 +00:00
|
|
|
var ewlf fleet.ErrWithLogFields
|
|
|
|
if errors.As(err, &ewlf) {
|
|
|
|
logger = kitlog.With(logger, ewlf.LogFields()...)
|
2021-06-05 13:22:13 +00:00
|
|
|
}
|
|
|
|
|
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- ~[ ] Documented any permissions changes~
- ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)~
- ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.~
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- ~[ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
|
|
|
var uuider fleet.ErrorUUIDer
|
|
|
|
if errors.As(err, &uuider) {
|
|
|
|
logger = kitlog.With(logger, "uuid", uuider.UUID())
|
|
|
|
}
|
|
|
|
|
2021-11-15 14:11:38 +00:00
|
|
|
var rle ratelimit.Error
|
|
|
|
if errors.As(err, &rle) {
|
|
|
|
res := rle.Result()
|
2021-03-26 18:23:29 +00:00
|
|
|
logger.Log("err", "limit exceeded", "retry_after", res.RetryAfter)
|
2021-11-15 14:11:38 +00:00
|
|
|
} else {
|
2021-03-26 18:23:29 +00:00
|
|
|
logger.Log("err", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
func logRequestEnd(logger kitlog.Logger) func(context.Context, http.ResponseWriter) context.Context {
|
|
|
|
return func(ctx context.Context, w http.ResponseWriter) context.Context {
|
|
|
|
logCtx, ok := logging.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
logCtx.Log(ctx, logger)
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 13:28:53 +00:00
|
|
|
func checkLicenseExpiration(svc fleet.Service) func(context.Context, http.ResponseWriter) context.Context {
|
|
|
|
return func(ctx context.Context, w http.ResponseWriter) context.Context {
|
|
|
|
license, err := svc.License(ctx)
|
|
|
|
if err != nil || license == nil {
|
|
|
|
return ctx
|
|
|
|
}
|
2021-09-03 16:05:23 +00:00
|
|
|
if license.IsPremium() && license.IsExpired() {
|
2021-08-26 13:28:53 +00:00
|
|
|
w.Header().Set(fleet.HeaderLicenseKey, fleet.HeaderLicenseValueExpired)
|
|
|
|
}
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-19 13:35:53 +00:00
|
|
|
type extraHandlerOpts struct {
|
|
|
|
loginRateLimit *throttled.Rate
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtraHandlerOption allows adding extra configuration to the HTTP handler.
|
|
|
|
type ExtraHandlerOption func(*extraHandlerOpts)
|
|
|
|
|
|
|
|
// WithLoginRateLimit configures the rate limit for the login endpoint.
|
|
|
|
func WithLoginRateLimit(r throttled.Rate) ExtraHandlerOption {
|
|
|
|
return func(o *extraHandlerOpts) {
|
|
|
|
o.loginRateLimit = &r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-24 17:39:32 +00:00
|
|
|
// MakeHandler creates an HTTP handler for the Fleet server endpoints.
|
2022-04-19 13:35:53 +00:00
|
|
|
func MakeHandler(
|
|
|
|
svc fleet.Service,
|
|
|
|
config config.FleetConfig,
|
|
|
|
logger kitlog.Logger,
|
|
|
|
limitStore throttled.GCRAStore,
|
|
|
|
extra ...ExtraHandlerOption,
|
|
|
|
) http.Handler {
|
|
|
|
var eopts extraHandlerOpts
|
|
|
|
for _, fn := range extra {
|
|
|
|
fn(&eopts)
|
|
|
|
}
|
|
|
|
|
2021-06-04 23:51:18 +00:00
|
|
|
fleetAPIOptions := []kithttp.ServerOption{
|
2016-09-04 19:43:12 +00:00
|
|
|
kithttp.ServerBefore(
|
2017-12-01 00:52:23 +00:00
|
|
|
kithttp.PopulateRequestContext, // populate the request context with common fields
|
2021-06-07 01:10:58 +00:00
|
|
|
setRequestsContexts(svc),
|
2016-09-04 19:43:12 +00:00
|
|
|
),
|
2021-03-26 18:23:29 +00:00
|
|
|
kithttp.ServerErrorHandler(&errorHandler{logger}),
|
2022-01-20 19:41:02 +00:00
|
|
|
kithttp.ServerErrorEncoder(encodeErrorAndTrySentry(config.Sentry.Dsn != "")),
|
2016-09-04 19:43:12 +00:00
|
|
|
kithttp.ServerAfter(
|
|
|
|
kithttp.SetContentType("application/json; charset=utf-8"),
|
2021-08-02 22:06:27 +00:00
|
|
|
logRequestEnd(logger),
|
2021-08-26 13:28:53 +00:00
|
|
|
checkLicenseExpiration(svc),
|
2016-09-04 19:43:12 +00:00
|
|
|
),
|
|
|
|
}
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-09-04 19:43:12 +00:00
|
|
|
r := mux.NewRouter()
|
2023-04-24 05:13:15 +00:00
|
|
|
if config.Logging.TracingEnabled {
|
|
|
|
if config.Logging.TracingType == "opentelemetry" {
|
|
|
|
r.Use(otmiddleware.Middleware("fleet"))
|
|
|
|
} else {
|
|
|
|
apmgorilla.Instrument(r)
|
|
|
|
}
|
2022-02-15 17:42:22 +00:00
|
|
|
}
|
2021-02-10 20:13:11 +00:00
|
|
|
|
2022-03-21 16:29:52 +00:00
|
|
|
r.Use(publicIP)
|
|
|
|
|
2022-04-19 13:35:53 +00:00
|
|
|
attachFleetAPIRoutes(r, svc, config, logger, limitStore, fleetAPIOptions, eopts)
|
2020-11-13 03:06:56 +00:00
|
|
|
addMetrics(r)
|
|
|
|
|
2016-08-28 03:59:17 +00:00
|
|
|
return r
|
|
|
|
}
|
2016-09-26 17:14:39 +00:00
|
|
|
|
2022-03-21 16:29:52 +00:00
|
|
|
func publicIP(handler http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ip := extractIP(r)
|
|
|
|
if ip != "" {
|
|
|
|
r.RemoteAddr = ip
|
|
|
|
}
|
|
|
|
handler.ServeHTTP(w, r.WithContext(publicip.NewContext(r.Context(), ip)))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-07 12:40:53 +00:00
|
|
|
// PrometheusMetricsHandler wraps the provided handler with prometheus metrics
|
2021-12-20 14:20:58 +00:00
|
|
|
// middleware and returns the resulting handler that should be mounted for that
|
|
|
|
// route.
|
2022-04-07 12:40:53 +00:00
|
|
|
func PrometheusMetricsHandler(name string, handler http.Handler) http.Handler {
|
2021-12-20 14:20:58 +00:00
|
|
|
reg := prometheus.DefaultRegisterer
|
|
|
|
registerOrExisting := func(coll prometheus.Collector) prometheus.Collector {
|
|
|
|
if err := reg.Register(coll); err != nil {
|
|
|
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
|
|
|
return are.ExistingCollector
|
|
|
|
}
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return coll
|
|
|
|
}
|
|
|
|
|
|
|
|
// this configuration is to keep prometheus metrics as close as possible to
|
|
|
|
// what the v0.9.3 (that we used to use) provided via the now-deprecated
|
|
|
|
// prometheus.InstrumentHandler.
|
|
|
|
|
|
|
|
reqCnt := registerOrExisting(prometheus.NewCounterVec(
|
|
|
|
prometheus.CounterOpts{
|
|
|
|
Subsystem: "http",
|
|
|
|
Name: "requests_total",
|
|
|
|
Help: "Total number of HTTP requests made.",
|
|
|
|
ConstLabels: prometheus.Labels{"handler": name},
|
|
|
|
},
|
|
|
|
[]string{"method", "code"},
|
|
|
|
)).(*prometheus.CounterVec)
|
|
|
|
|
|
|
|
reqDur := registerOrExisting(prometheus.NewHistogramVec(
|
|
|
|
prometheus.HistogramOpts{
|
|
|
|
Subsystem: "http",
|
|
|
|
Name: "request_duration_seconds",
|
|
|
|
Help: "The HTTP request latencies in seconds.",
|
|
|
|
ConstLabels: prometheus.Labels{"handler": name},
|
|
|
|
// Use default buckets, as they are suited for durations.
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
)).(*prometheus.HistogramVec)
|
|
|
|
|
|
|
|
// 1KB, 100KB, 1MB, 100MB, 1GB
|
|
|
|
sizeBuckets := []float64{1024, 100 * 1024, 1024 * 1024, 100 * 1024 * 1024, 1024 * 1024 * 1024}
|
|
|
|
|
|
|
|
resSz := registerOrExisting(prometheus.NewHistogramVec(
|
|
|
|
prometheus.HistogramOpts{
|
|
|
|
Subsystem: "http",
|
|
|
|
Name: "response_size_bytes",
|
|
|
|
Help: "The HTTP response sizes in bytes.",
|
|
|
|
ConstLabels: prometheus.Labels{"handler": name},
|
|
|
|
Buckets: sizeBuckets,
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
)).(*prometheus.HistogramVec)
|
|
|
|
|
|
|
|
reqSz := registerOrExisting(prometheus.NewHistogramVec(
|
|
|
|
prometheus.HistogramOpts{
|
|
|
|
Subsystem: "http",
|
|
|
|
Name: "request_size_bytes",
|
|
|
|
Help: "The HTTP request sizes in bytes.",
|
|
|
|
ConstLabels: prometheus.Labels{"handler": name},
|
|
|
|
Buckets: sizeBuckets,
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
)).(*prometheus.HistogramVec)
|
|
|
|
|
|
|
|
return promhttp.InstrumentHandlerDuration(reqDur,
|
|
|
|
promhttp.InstrumentHandlerCounter(reqCnt,
|
|
|
|
promhttp.InstrumentHandlerResponseSize(resSz,
|
|
|
|
promhttp.InstrumentHandlerRequestSize(reqSz, handler))))
|
|
|
|
}
|
|
|
|
|
|
|
|
// addMetrics decorates each handler with prometheus instrumentation
|
2016-12-22 17:39:44 +00:00
|
|
|
func addMetrics(r *mux.Router) {
|
|
|
|
walkFn := func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
2022-04-07 12:40:53 +00:00
|
|
|
route.Handler(PrometheusMetricsHandler(route.GetName(), route.GetHandler()))
|
2016-12-22 17:39:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
2022-12-05 22:50:49 +00:00
|
|
|
r.Walk(walkFn) //nolint:errcheck
|
2016-12-22 17:39:44 +00:00
|
|
|
}
|
|
|
|
|
2023-06-15 19:41:04 +00:00
|
|
|
// These are defined as const so that they can be used in tests.
|
|
|
|
const (
|
|
|
|
desktopRateLimitMaxBurst = 100 // Max burst used for device request rate limiting.
|
|
|
|
forgotPasswordRateLimitMaxBurst = 9 // Max burst used for rate limiting on the the forgot_password endpoint.
|
|
|
|
)
|
2022-07-18 17:22:49 +00:00
|
|
|
|
2022-03-08 16:27:38 +00:00
|
|
|
func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetConfig,
|
Add read replica testing helpers and fix non-sso login bug (#4908)
not set on the INSERT.
- OUT: Only sets the ID on the passed session and returns it. (`CreatedAt`, `AccessedAt`, are not set.)
New version:
```go
func (ds *Datastore) NewSession(ctx context.Context, userID uint, sessionKey string) (*fleet.Session, error) {
sqlStatement := `
INSERT INTO sessions (
user_id,
` + "`key`" + `
)
VALUES(?,?)
`
result, err := ds.writer.ExecContext(ctx, sqlStatement, userID, sessionKey)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "inserting session")
}
id, _ := result.LastInsertId() // cannot fail with the mysql driver
return ds.sessionByID(ctx, ds.writer, uint(id))
}
```
- IN: Define arguments that are truly used when creating a session.
- OUT: Load and return the fleet.Session struct with all values set (using the `ds.writer` to support read replicas correctly).
PS: The new `NewSession` version mimics what we already do with other entities, like policies (`Datastore.NewGlobalPolicy`).
2022-04-04 23:52:05 +00:00
|
|
|
logger kitlog.Logger, limitStore throttled.GCRAStore, opts []kithttp.ServerOption,
|
2022-04-19 13:35:53 +00:00
|
|
|
extra extraHandlerOpts,
|
Add read replica testing helpers and fix non-sso login bug (#4908)
not set on the INSERT.
- OUT: Only sets the ID on the passed session and returns it. (`CreatedAt`, `AccessedAt`, are not set.)
New version:
```go
func (ds *Datastore) NewSession(ctx context.Context, userID uint, sessionKey string) (*fleet.Session, error) {
sqlStatement := `
INSERT INTO sessions (
user_id,
` + "`key`" + `
)
VALUES(?,?)
`
result, err := ds.writer.ExecContext(ctx, sqlStatement, userID, sessionKey)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "inserting session")
}
id, _ := result.LastInsertId() // cannot fail with the mysql driver
return ds.sessionByID(ctx, ds.writer, uint(id))
}
```
- IN: Define arguments that are truly used when creating a session.
- OUT: Load and return the fleet.Session struct with all values set (using the `ds.writer` to support read replicas correctly).
PS: The new `NewSession` version mimics what we already do with other entities, like policies (`Datastore.NewGlobalPolicy`).
2022-04-04 23:52:05 +00:00
|
|
|
) {
|
2022-04-05 15:35:53 +00:00
|
|
|
apiVersions := []string{"v1", "2022-04"}
|
|
|
|
|
2022-03-07 18:10:55 +00:00
|
|
|
// user-authenticated endpoints
|
2022-04-05 15:35:53 +00:00
|
|
|
ue := newUserAuthenticatedEndpointer(svc, opts, r, apiVersions...)
|
2022-03-07 18:10:55 +00:00
|
|
|
|
2022-11-30 17:57:42 +00:00
|
|
|
ue.POST("/api/_version_/fleet/trigger", triggerEndpoint, triggerRequest{})
|
2022-11-28 19:28:06 +00:00
|
|
|
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/me", meEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/sessions/{id:[0-9]+}", getInfoAboutSessionEndpoint, getInfoAboutSessionRequest{})
|
|
|
|
ue.DELETE("/api/_version_/fleet/sessions/{id:[0-9]+}", deleteSessionEndpoint, deleteSessionRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/config/certificate", getCertificateEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/config", getAppConfigEndpoint, nil)
|
|
|
|
ue.PATCH("/api/_version_/fleet/config", modifyAppConfigEndpoint, modifyAppConfigRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/spec/enroll_secret", applyEnrollSecretSpecEndpoint, applyEnrollSecretSpecRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/spec/enroll_secret", getEnrollSecretSpecEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/version", versionEndpoint, nil)
|
|
|
|
|
|
|
|
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.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.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{})
|
|
|
|
ue.GET("/api/_version_/fleet/teams/{id:[0-9]+}/secrets", teamEnrollSecretsEndpoint, teamEnrollSecretsRequest{})
|
2021-12-21 15:23:12 +00:00
|
|
|
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/users", listUsersEndpoint, listUsersRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/users/admin", createUserEndpoint, createUserRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/users/{id:[0-9]+}", getUserEndpoint, getUserRequest{})
|
|
|
|
ue.PATCH("/api/_version_/fleet/users/{id:[0-9]+}", modifyUserEndpoint, modifyUserRequest{})
|
|
|
|
ue.DELETE("/api/_version_/fleet/users/{id:[0-9]+}", deleteUserEndpoint, deleteUserRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/users/{id:[0-9]+}/require_password_reset", requirePasswordResetEndpoint, requirePasswordResetRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/users/{id:[0-9]+}/sessions", getInfoAboutSessionsForUserEndpoint, getInfoAboutSessionsForUserRequest{})
|
|
|
|
ue.DELETE("/api/_version_/fleet/users/{id:[0-9]+}/sessions", deleteSessionsForUserEndpoint, deleteSessionsForUserRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/change_password", changePasswordEndpoint, changePasswordRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/email/change/{token}", changeEmailEndpoint, changeEmailRequest{})
|
2022-06-10 18:29:45 +00:00
|
|
|
// TODO: searchTargetsEndpoint will be removed in Fleet 5.0
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.POST("/api/_version_/fleet/targets", searchTargetsEndpoint, searchTargetsRequest{})
|
2022-06-10 18:29:45 +00:00
|
|
|
ue.POST("/api/_version_/fleet/targets/count", countTargetsEndpoint, countTargetsRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
|
|
|
|
ue.POST("/api/_version_/fleet/invites", createInviteEndpoint, createInviteRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/invites", listInvitesEndpoint, listInvitesRequest{})
|
|
|
|
ue.DELETE("/api/_version_/fleet/invites/{id:[0-9]+}", deleteInviteEndpoint, deleteInviteRequest{})
|
|
|
|
ue.PATCH("/api/_version_/fleet/invites/{id:[0-9]+}", updateInviteEndpoint, updateInviteRequest{})
|
|
|
|
|
2022-04-05 15:35:53 +00:00
|
|
|
ue.EndingAtVersion("v1").POST("/api/_version_/fleet/global/policies", globalPolicyEndpoint, globalPolicyRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").POST("/api/_version_/fleet/policies", globalPolicyEndpoint, globalPolicyRequest{})
|
2023-08-30 22:30:17 +00:00
|
|
|
ue.EndingAtVersion("v1").GET("/api/_version_/fleet/global/policies", listGlobalPoliciesEndpoint, listGlobalPoliciesRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").GET("/api/_version_/fleet/policies", listGlobalPoliciesEndpoint, listGlobalPoliciesRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/policies/count", countGlobalPoliciesEndpoint, countGlobalPoliciesRequest{})
|
2022-04-05 15:35:53 +00:00
|
|
|
ue.EndingAtVersion("v1").GET("/api/_version_/fleet/global/policies/{policy_id}", getPolicyByIDEndpoint, getPolicyByIDRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").GET("/api/_version_/fleet/policies/{policy_id}", getPolicyByIDEndpoint, getPolicyByIDRequest{})
|
|
|
|
ue.EndingAtVersion("v1").POST("/api/_version_/fleet/global/policies/delete", deleteGlobalPoliciesEndpoint, deleteGlobalPoliciesRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").POST("/api/_version_/fleet/policies/delete", deleteGlobalPoliciesEndpoint, deleteGlobalPoliciesRequest{})
|
|
|
|
ue.EndingAtVersion("v1").PATCH("/api/_version_/fleet/global/policies/{policy_id}", modifyGlobalPolicyEndpoint, modifyGlobalPolicyRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").PATCH("/api/_version_/fleet/policies/{policy_id}", modifyGlobalPolicyEndpoint, modifyGlobalPolicyRequest{})
|
2022-12-16 21:00:54 +00:00
|
|
|
ue.POST("/api/_version_/fleet/automations/reset", resetAutomationEndpoint, resetAutomationRequest{})
|
2021-12-21 15:23:12 +00:00
|
|
|
|
|
|
|
// Alias /api/_version_/fleet/team/ -> /api/_version_/fleet/teams/
|
2022-04-05 15:35:53 +00:00
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/policies").
|
|
|
|
POST("/api/_version_/fleet/teams/{team_id}/policies", teamPolicyEndpoint, teamPolicyRequest{})
|
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/policies").
|
|
|
|
GET("/api/_version_/fleet/teams/{team_id}/policies", listTeamPoliciesEndpoint, listTeamPoliciesRequest{})
|
2023-08-30 22:30:17 +00:00
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/policies/count").
|
|
|
|
GET("/api/_version_/fleet/teams/{team_id}/policies/count", countTeamPoliciesEndpoint, countTeamPoliciesRequest{})
|
2022-04-05 15:35:53 +00:00
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/policies/{policy_id}").
|
|
|
|
GET("/api/_version_/fleet/teams/{team_id}/policies/{policy_id}", getTeamPolicyByIDEndpoint, getTeamPolicyByIDRequest{})
|
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/policies/delete").
|
|
|
|
POST("/api/_version_/fleet/teams/{team_id}/policies/delete", deleteTeamPoliciesEndpoint, deleteTeamPoliciesRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.PATCH("/api/_version_/fleet/teams/{team_id}/policies/{policy_id}", modifyTeamPolicyEndpoint, modifyTeamPolicyRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/spec/policies", applyPolicySpecsEndpoint, applyPolicySpecsRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/queries/{id:[0-9]+}", getQueryEndpoint, getQueryRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/queries", listQueriesEndpoint, listQueriesRequest{})
|
2023-10-10 12:44:03 +00:00
|
|
|
ue.GET("/api/_version_/fleet/queries/{id:[0-9]+}/report", getQueryReportEndpoint, getQueryReportRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.POST("/api/_version_/fleet/queries", createQueryEndpoint, createQueryRequest{})
|
|
|
|
ue.PATCH("/api/_version_/fleet/queries/{id:[0-9]+}", modifyQueryEndpoint, modifyQueryRequest{})
|
|
|
|
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{})
|
2023-07-25 00:17:20 +00:00
|
|
|
ue.GET("/api/_version_/fleet/spec/queries", getQuerySpecsEndpoint, getQuerySpecsRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/spec/queries/{name}", getQuerySpecEndpoint, getQuerySpecRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/packs/{id:[0-9]+}", getPackEndpoint, getPackRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/packs", createPackEndpoint, createPackRequest{})
|
|
|
|
ue.PATCH("/api/_version_/fleet/packs/{id:[0-9]+}", modifyPackEndpoint, modifyPackRequest{})
|
|
|
|
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.GET("/api/_version_/fleet/spec/packs", getPackSpecsEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/spec/packs/{name}", getPackSpecEndpoint, getGenericSpecRequest{})
|
|
|
|
|
2023-12-06 14:30:49 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software/versions", listSoftwareVersionsEndpoint, listSoftwareRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/software/versions/{id:[0-9]+}", getSoftwareEndpoint, getSoftwareRequest{})
|
|
|
|
|
|
|
|
// DEPRECATED: use /api/_version_/fleet/software/versions instead
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software", listSoftwareEndpoint, listSoftwareRequest{})
|
2023-12-06 14:30:49 +00:00
|
|
|
// DEPRECATED: use /api/_version_/fleet/software/versions{id:[0-9]+} instead
|
2022-05-20 16:58:40 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software/{id:[0-9]+}", getSoftwareEndpoint, getSoftwareRequest{})
|
2023-12-06 14:30:49 +00:00
|
|
|
// DEPRECATED: software version counts are now included directly in the software version list
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software/count", countSoftwareEndpoint, countSoftwareRequest{})
|
|
|
|
|
2023-12-06 18:28:31 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software/titles", listSoftwareTitlesEndpoint, listSoftwareTitlesRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/software/titles/{id:[0-9]+}", getSoftwareTitleEndpoint, getSoftwareTitleRequest{})
|
|
|
|
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/host_summary", getHostSummaryEndpoint, getHostSummaryRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/hosts", listHostsEndpoint, listHostsRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/hosts/delete", deleteHostsEndpoint, deleteHostsRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}", getHostEndpoint, getHostRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/hosts/count", countHostsEndpoint, countHostsRequest{})
|
2022-06-10 18:29:45 +00:00
|
|
|
ue.POST("/api/_version_/fleet/hosts/search", searchHostsEndpoint, searchHostsRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/hosts/identifier/{identifier}", hostByIdentifierEndpoint, hostByIdentifierRequest{})
|
|
|
|
ue.DELETE("/api/_version_/fleet/hosts/{id:[0-9]+}", deleteHostEndpoint, deleteHostRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/hosts/transfer", addHostsToTeamEndpoint, addHostsToTeamRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/hosts/transfer/filter", addHostsToTeamByFilterEndpoint, addHostsToTeamByFilterRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/hosts/{id:[0-9]+}/refetch", refetchHostEndpoint, refetchHostRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/device_mapping", listHostDeviceMappingEndpoint, listHostDeviceMappingRequest{})
|
2023-12-21 17:21:39 +00:00
|
|
|
ue.PUT("/api/_version_/fleet/hosts/{id:[0-9]+}/device_mapping", putHostDeviceMappingEndpoint, putHostDeviceMappingRequest{})
|
2022-03-15 19:14:42 +00:00
|
|
|
ue.GET("/api/_version_/fleet/hosts/report", hostsReportEndpoint, hostsReportRequest{})
|
2022-03-28 15:15:45 +00:00
|
|
|
ue.GET("/api/_version_/fleet/os_versions", osVersionsEndpoint, osVersionsRequest{})
|
2023-12-11 22:33:31 +00:00
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/queries/{query_id:[0-9]+}", getHostQueryReportEndpoint, getHostQueryReportRequest{})
|
2023-12-06 19:42:29 +00:00
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/health", getHostHealthEndpoint, getHostHealthRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
|
2022-11-01 17:22:07 +00:00
|
|
|
ue.GET("/api/_version_/fleet/hosts/summary/mdm", getHostMDMSummary, getHostMDMSummaryRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/mdm", getHostMDM, getHostMDMRequest{})
|
|
|
|
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.POST("/api/_version_/fleet/labels", createLabelEndpoint, createLabelRequest{})
|
|
|
|
ue.PATCH("/api/_version_/fleet/labels/{id:[0-9]+}", modifyLabelEndpoint, modifyLabelRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/labels/{id:[0-9]+}", getLabelEndpoint, getLabelRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/labels", listLabelsEndpoint, listLabelsRequest{})
|
2022-06-10 18:29:45 +00:00
|
|
|
ue.GET("/api/_version_/fleet/labels/summary", getLabelsSummaryEndpoint, nil)
|
2022-03-07 18:10:55 +00:00
|
|
|
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.GET("/api/_version_/fleet/spec/labels", getLabelSpecsEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/spec/labels/{name}", getLabelSpecEndpoint, getGenericSpecRequest{})
|
|
|
|
|
2024-01-03 15:39:16 +00:00
|
|
|
// This endpoint runs live queries synchronously (with a configured timeout).
|
|
|
|
ue.POST("/api/_version_/fleet/queries/{id:[0-9]+}/run", runOneLiveQueryEndpoint, runOneLiveQueryRequest{})
|
|
|
|
// Old endpoint, removed from docs. This GET endpoint runs live queries synchronously (with a configured timeout).
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/queries/run", runLiveQueryEndpoint, runLiveQueryRequest{})
|
2023-01-30 21:35:56 +00:00
|
|
|
// The following two POST APIs are the asynchronous way to run live queries.
|
|
|
|
// The live queries are created with these two endpoints and their results can be queried via
|
|
|
|
// websockets via the `GET /api/_version_/fleet/results/` endpoint.
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.POST("/api/_version_/fleet/queries/run", createDistributedQueryCampaignEndpoint, createDistributedQueryCampaignRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/queries/run_by_names", createDistributedQueryCampaignByNamesEndpoint, createDistributedQueryCampaignByNamesRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/activities", listActivitiesEndpoint, listActivitiesRequest{})
|
|
|
|
|
2022-08-16 15:54:41 +00:00
|
|
|
ue.POST("/api/_version_/fleet/download_installer/{kind}", getInstallerEndpoint, getInstallerRequest{})
|
|
|
|
ue.HEAD("/api/_version_/fleet/download_installer/{kind}", checkInstallerEndpoint, checkInstallerRequest{})
|
2022-07-18 16:44:30 +00:00
|
|
|
|
2022-04-05 15:35:53 +00:00
|
|
|
ue.GET("/api/_version_/fleet/packs/{id:[0-9]+}/scheduled", getScheduledQueriesInPackEndpoint, getScheduledQueriesInPackRequest{})
|
|
|
|
ue.EndingAtVersion("v1").POST("/api/_version_/fleet/schedule", scheduleQueryEndpoint, scheduleQueryRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").POST("/api/_version_/fleet/packs/schedule", scheduleQueryEndpoint, scheduleQueryRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/schedule/{id:[0-9]+}", getScheduledQueryEndpoint, getScheduledQueryRequest{})
|
|
|
|
ue.EndingAtVersion("v1").PATCH("/api/_version_/fleet/schedule/{id:[0-9]+}", modifyScheduledQueryEndpoint, modifyScheduledQueryRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").PATCH("/api/_version_/fleet/packs/schedule/{id:[0-9]+}", modifyScheduledQueryEndpoint, modifyScheduledQueryRequest{})
|
|
|
|
ue.EndingAtVersion("v1").DELETE("/api/_version_/fleet/schedule/{id:[0-9]+}", deleteScheduledQueryEndpoint, deleteScheduledQueryRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").DELETE("/api/_version_/fleet/packs/schedule/{id:[0-9]+}", deleteScheduledQueryEndpoint, deleteScheduledQueryRequest{})
|
|
|
|
|
|
|
|
ue.EndingAtVersion("v1").GET("/api/_version_/fleet/global/schedule", getGlobalScheduleEndpoint, getGlobalScheduleRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").GET("/api/_version_/fleet/schedule", getGlobalScheduleEndpoint, getGlobalScheduleRequest{})
|
|
|
|
ue.EndingAtVersion("v1").POST("/api/_version_/fleet/global/schedule", globalScheduleQueryEndpoint, globalScheduleQueryRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").POST("/api/_version_/fleet/schedule", globalScheduleQueryEndpoint, globalScheduleQueryRequest{})
|
|
|
|
ue.EndingAtVersion("v1").PATCH("/api/_version_/fleet/global/schedule/{id:[0-9]+}", modifyGlobalScheduleEndpoint, modifyGlobalScheduleRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").PATCH("/api/_version_/fleet/schedule/{id:[0-9]+}", modifyGlobalScheduleEndpoint, modifyGlobalScheduleRequest{})
|
|
|
|
ue.EndingAtVersion("v1").DELETE("/api/_version_/fleet/global/schedule/{id:[0-9]+}", deleteGlobalScheduleEndpoint, deleteGlobalScheduleRequest{})
|
|
|
|
ue.StartingAtVersion("2022-04").DELETE("/api/_version_/fleet/schedule/{id:[0-9]+}", deleteGlobalScheduleEndpoint, deleteGlobalScheduleRequest{})
|
|
|
|
|
|
|
|
// Alias /api/_version_/fleet/team/ -> /api/_version_/fleet/teams/
|
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/schedule").
|
|
|
|
GET("/api/_version_/fleet/teams/{team_id}/schedule", getTeamScheduleEndpoint, getTeamScheduleRequest{})
|
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/schedule").
|
|
|
|
POST("/api/_version_/fleet/teams/{team_id}/schedule", teamScheduleQueryEndpoint, teamScheduleQueryRequest{})
|
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/schedule/{scheduled_query_id}").
|
|
|
|
PATCH("/api/_version_/fleet/teams/{team_id}/schedule/{scheduled_query_id}", modifyTeamScheduleEndpoint, modifyTeamScheduleRequest{})
|
|
|
|
ue.WithAltPaths("/api/_version_/fleet/team/{team_id}/schedule/{scheduled_query_id}").
|
|
|
|
DELETE("/api/_version_/fleet/teams/{team_id}/schedule/{scheduled_query_id}", deleteTeamScheduleEndpoint, deleteTeamScheduleRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/carves", listCarvesEndpoint, listCarvesRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/carves/{id:[0-9]+}", getCarveEndpoint, getCarveRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/carves/{id:[0-9]+}/block/{block_id}", getCarveBlockEndpoint, getCarveBlockRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/macadmins", getMacadminsDataEndpoint, getMacadminsDataRequest{})
|
|
|
|
ue.GET("/api/_version_/fleet/macadmins", getAggregatedMacadminsDataEndpoint, getAggregatedMacadminsDataRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/status/result_store", statusResultStoreEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/status/live_query", statusLiveQueryEndpoint, nil)
|
|
|
|
|
2023-08-21 18:47:19 +00:00
|
|
|
ue.POST("/api/_version_/fleet/scripts/run", runScriptEndpoint, runScriptRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/scripts/run/sync", runScriptSyncEndpoint, runScriptRequest{})
|
2023-09-05 20:38:53 +00:00
|
|
|
ue.GET("/api/_version_/fleet/scripts/results/{execution_id}", getScriptResultEndpoint, getScriptResultRequest{})
|
2023-10-10 22:00:45 +00:00
|
|
|
ue.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.DELETE("/api/_version_/fleet/scripts/{script_id:[0-9]+}", deleteScriptEndpoint, deleteScriptRequest{})
|
|
|
|
ue.POST("/api/_version_/fleet/scripts/batch", batchSetScriptsEndpoint, batchSetScriptsRequest{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/scripts", getHostScriptDetailsEndpoint, getHostScriptDetailsRequest{})
|
2023-08-21 18:47:19 +00:00
|
|
|
|
2023-01-24 16:57:22 +00:00
|
|
|
// Only Fleet MDM specific endpoints should be within the root /mdm/ path.
|
2023-05-02 13:09:33 +00:00
|
|
|
// NOTE: remember to update
|
2023-10-09 21:28:35 +00:00
|
|
|
// `service.mdmConfigurationRequiredEndpoints` when you add an
|
2023-05-02 13:09:33 +00:00
|
|
|
// endpoint that's behind the mdmConfiguredMiddleware, this applies
|
|
|
|
// both to this set of endpoints and to any public/token-authenticated
|
|
|
|
// endpoints using `neMDM` below in this file.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmConfiguredMiddleware := mdmconfigured.NewMDMConfigMiddleware(svc)
|
|
|
|
mdmAppleMW := ue.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyAppleMDM())
|
2023-11-01 14:13:12 +00:00
|
|
|
|
|
|
|
// Deprecated: POST /mdm/apple/enqueue is now deprecated, replaced by the
|
|
|
|
// platform-agnostic POST /mdm/commands/run. It is still supported
|
|
|
|
// indefinitely for backwards compatibility.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/enqueue", enqueueMDMAppleCommandEndpoint, enqueueMDMAppleCommandRequest{})
|
2023-11-01 14:13:12 +00:00
|
|
|
// Deprecated: POST /mdm/apple/commandresults is now deprecated, replaced by the
|
|
|
|
// platform-agnostic POST /mdm/commands/commandresults. It is still supported
|
|
|
|
// indefinitely for backwards compatibility.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/commandresults", getMDMAppleCommandResultsEndpoint, getMDMAppleCommandResultsRequest{})
|
2023-11-01 14:13:12 +00:00
|
|
|
// Deprecated: POST /mdm/apple/commands is now deprecated, replaced by the
|
|
|
|
// platform-agnostic POST /mdm/commands/commands. It is still supported
|
|
|
|
// indefinitely for backwards compatibility.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/commands", listMDMAppleCommandsEndpoint, listMDMAppleCommandsRequest{})
|
2023-11-15 15:58:59 +00:00
|
|
|
// Deprecated: those /mdm/apple/profiles/... endpoints are now deprecated,
|
|
|
|
// replaced by the platform-agnostic /mdm/profiles/... It is still supported
|
|
|
|
// indefinitely for backwards compatibility.
|
2023-11-08 16:36:57 +00:00
|
|
|
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{})
|
2023-11-15 15:58:59 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/profiles", newMDMAppleConfigProfileEndpoint, newMDMAppleConfigProfileRequest{})
|
2023-11-15 20:36:20 +00:00
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/profiles", listMDMAppleConfigProfilesEndpoint, listMDMAppleConfigProfilesRequest{})
|
2023-11-01 14:13:12 +00:00
|
|
|
|
2023-11-17 16:49:30 +00:00
|
|
|
// Deprecated: GET /mdm/apple/filevault/summary is now deprecated, replaced by the
|
|
|
|
// platform-agnostic GET /mdm/disk_encryption/summary. It is still supported indefinitely
|
|
|
|
// for backwards compatibility.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/filevault/summary", getMdmAppleFileVaultSummaryEndpoint, getMDMAppleFileVaultSummaryRequest{})
|
2023-11-17 16:49:30 +00:00
|
|
|
|
|
|
|
// Deprecated: GET /mdm/apple/profiles/summary is now deprecated, replaced by the
|
|
|
|
// platform-agnostic GET /mdm/profiles/summary. It is still supported indefinitely
|
|
|
|
// for backwards compatibility.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/profiles/summary", getMDMAppleProfilesSummaryEndpoint, getMDMAppleProfilesSummaryRequest{})
|
2023-11-17 16:49:30 +00:00
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/enrollment_profile", createMDMAppleSetupAssistantEndpoint, createMDMAppleSetupAssistantRequest{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/enrollment_profile", getMDMAppleSetupAssistantEndpoint, getMDMAppleSetupAssistantRequest{})
|
|
|
|
mdmAppleMW.DELETE("/api/_version_/fleet/mdm/apple/enrollment_profile", deleteMDMAppleSetupAssistantEndpoint, deleteMDMAppleSetupAssistantRequest{})
|
2023-03-27 19:30:29 +00:00
|
|
|
|
2023-04-17 15:45:16 +00:00
|
|
|
// TODO: are those undocumented endpoints still needed? I think they were only used
|
|
|
|
// by 'fleetctl apple-mdm' sub-commands.
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.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{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/devices", listMDMAppleDevicesEndpoint, listMDMAppleDevicesRequest{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/dep/devices", listMDMAppleDEPDevicesEndpoint, listMDMAppleDEPDevicesRequest{})
|
2023-04-17 15:45:16 +00:00
|
|
|
|
2023-04-07 20:31:02 +00:00
|
|
|
// bootstrap-package routes
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/bootstrap/{team_id:[0-9]+}/metadata", bootstrapPackageMetadataEndpoint, bootstrapPackageMetadataRequest{})
|
|
|
|
mdmAppleMW.DELETE("/api/_version_/fleet/mdm/apple/bootstrap/{team_id:[0-9]+}", deleteBootstrapPackageEndpoint, deleteBootstrapPackageRequest{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/bootstrap/summary", getMDMAppleBootstrapPackageSummaryEndpoint, getMDMAppleBootstrapPackageSummaryRequest{})
|
2023-04-07 20:31:02 +00:00
|
|
|
|
2023-03-27 19:30:29 +00:00
|
|
|
// host-specific mdm routes
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.PATCH("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/unenroll", mdmAppleCommandRemoveEnrollmentProfileEndpoint, mdmAppleCommandRemoveEnrollmentProfileRequest{})
|
2023-10-09 21:28:35 +00:00
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/lock", deviceLockEndpoint, deviceLockRequest{})
|
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/wipe", deviceWipeEndpoint, deviceWipeRequest{})
|
2023-07-14 15:53:03 +00:00
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/profiles", getHostProfilesEndpoint, getHostProfilesRequest{})
|
2023-03-27 19:30:29 +00:00
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.PATCH("/api/_version_/fleet/mdm/apple/setup", updateMDMAppleSetupEndpoint, updateMDMAppleSetupRequest{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple", getAppleMDMEndpoint, nil)
|
2023-03-27 19:30:29 +00:00
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/setup/eula", createMDMAppleEULAEndpoint, createMDMAppleEULARequest{})
|
|
|
|
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/setup/eula/metadata", getMDMAppleEULAMetadataEndpoint, getMDMAppleEULAMetadataRequest{})
|
|
|
|
mdmAppleMW.DELETE("/api/_version_/fleet/mdm/apple/setup/eula/{token}", deleteMDMAppleEULAEndpoint, deleteMDMAppleEULARequest{})
|
2023-05-02 13:09:33 +00:00
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileEndpoint, preassignMDMAppleProfileRequest{})
|
|
|
|
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/profiles/match", matchMDMApplePreassignmentEndpoint, matchMDMApplePreassignmentRequest{})
|
2023-05-31 13:24:22 +00:00
|
|
|
|
2023-11-01 14:13:12 +00:00
|
|
|
mdmAnyMW := ue.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyAppleOrWindowsMDM())
|
|
|
|
mdmAnyMW.POST("/api/_version_/fleet/mdm/commands/run", runMDMCommandEndpoint, runMDMCommandRequest{})
|
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/commandresults", getMDMCommandResultsEndpoint, getMDMCommandResultsRequest{})
|
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/commands", listMDMCommandsEndpoint, listMDMCommandsRequest{})
|
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/disk_encryption/summary", getMDMDiskEncryptionSummaryEndpoint, getMDMDiskEncryptionSummaryRequest{})
|
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/hosts/{id:[0-9]+}/encryption_key", getHostEncryptionKey, getHostEncryptionKeyRequest{})
|
2023-11-14 13:19:29 +00:00
|
|
|
|
2023-11-29 14:32:42 +00:00
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles/summary", getMDMProfilesSummaryEndpoint, getMDMProfilesSummaryRequest{})
|
|
|
|
mdmAnyMW.POST("/api/_version_/fleet/mdm/profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{})
|
2023-12-04 15:04:06 +00:00
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles/{profile_uuid}", getMDMConfigProfileEndpoint, getMDMConfigProfileRequest{})
|
|
|
|
mdmAnyMW.DELETE("/api/_version_/fleet/mdm/profiles/{profile_uuid}", deleteMDMConfigProfileEndpoint, deleteMDMConfigProfileRequest{})
|
2023-11-29 14:32:42 +00:00
|
|
|
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles", listMDMConfigProfilesEndpoint, listMDMConfigProfilesRequest{})
|
2024-01-03 19:15:09 +00:00
|
|
|
// TODO: this endpoint is being used as a platform-agnostic endpoint, but it
|
|
|
|
// contains "apple" in the path and the implementation is possibly very
|
|
|
|
// apple-biased. See issue #15884
|
|
|
|
mdmAnyMW.PATCH("/api/_version_/fleet/mdm/apple/settings", updateMDMAppleSettingsEndpoint, updateMDMAppleSettingsRequest{})
|
2023-10-09 21:28:35 +00:00
|
|
|
|
2023-03-27 19:30:29 +00:00
|
|
|
// the following set of mdm endpoints must always be accessible (even
|
|
|
|
// if MDM is not configured) as it bootstraps the setup of MDM
|
|
|
|
// (generates CSR request for APNs, plus the SCEP and ABM keypairs).
|
|
|
|
ue.POST("/api/_version_/fleet/mdm/apple/request_csr", requestMDMAppleCSREndpoint, requestMDMAppleCSRRequest{})
|
2023-02-03 19:02:50 +00:00
|
|
|
ue.POST("/api/_version_/fleet/mdm/apple/dep/key_pair", newMDMAppleDEPKeyPairEndpoint, nil)
|
2022-12-12 20:45:53 +00:00
|
|
|
ue.GET("/api/_version_/fleet/mdm/apple_bm", getAppleBMEndpoint, nil)
|
2023-11-15 12:37:19 +00:00
|
|
|
// Deprecated: POST /mdm/apple/profiles/batch is now deprecated, replaced by the
|
|
|
|
// platform-agnostic POST /mdm/apple/profiles/batch. It is still supported
|
|
|
|
// indefinitely for backwards compatibility.
|
|
|
|
//
|
2023-03-27 19:30:29 +00:00
|
|
|
// 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`
|
2023-02-15 18:01:44 +00:00
|
|
|
ue.POST("/api/_version_/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesEndpoint, batchSetMDMAppleProfilesRequest{})
|
2022-10-05 22:53:54 +00:00
|
|
|
|
2023-11-29 14:32:42 +00:00
|
|
|
// 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{})
|
|
|
|
|
2022-07-11 13:49:05 +00:00
|
|
|
errorLimiter := ratelimit.NewErrorMiddleware(limitStore)
|
|
|
|
|
2022-03-09 21:13:56 +00:00
|
|
|
// device-authenticated endpoints
|
2022-04-05 15:35:53 +00:00
|
|
|
de := newDeviceAuthenticatedEndpointer(svc, logger, opts, r, apiVersions...)
|
2022-07-11 13:49:05 +00:00
|
|
|
// We allow a quota of 720 because in the onboarding of a Fleet Desktop takes a few tries until it authenticates
|
|
|
|
// properly
|
2022-07-18 17:22:49 +00:00
|
|
|
desktopQuota := throttled.RateQuota{MaxRate: throttled.PerHour(720), MaxBurst: desktopRateLimitMaxBurst}
|
2022-07-11 13:49:05 +00:00
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_device_host", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}", getDeviceHostEndpoint, getDeviceHostRequest{})
|
2022-09-12 19:37:38 +00:00
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_fleet_desktop", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}/desktop", getFleetDesktopEndpoint, getFleetDesktopRequest{})
|
2023-12-13 20:31:48 +00:00
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("ping_device_auth", desktopQuota),
|
|
|
|
).HEAD("/api/_version_/fleet/device/{token}/ping", devicePingEndpoint, deviceAuthPingRequest{})
|
2022-07-11 13:49:05 +00:00
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("refetch_device_host", desktopQuota),
|
|
|
|
).POST("/api/_version_/fleet/device/{token}/refetch", refetchDeviceHostEndpoint, refetchDeviceHostRequest{})
|
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_device_mapping", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}/device_mapping", listDeviceHostDeviceMappingEndpoint, listDeviceHostDeviceMappingRequest{})
|
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_device_macadmins", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}/macadmins", getDeviceMacadminsDataEndpoint, getDeviceMacadminsDataRequest{})
|
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_device_policies", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}/policies", listDevicePoliciesEndpoint, listDevicePoliciesRequest{})
|
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_device_transparency", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}/transparency", transparencyURL, transparencyURLRequest{})
|
2023-08-24 16:04:27 +00:00
|
|
|
de.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("send_device_error", desktopQuota),
|
|
|
|
).POST("/api/_version_/fleet/device/{token}/debug/errors", fleetdError, fleetdErrorRequest{})
|
2022-03-09 21:13:56 +00:00
|
|
|
|
2023-03-27 19:30:29 +00:00
|
|
|
// mdm-related endpoints available via device authentication
|
2023-06-22 20:31:17 +00:00
|
|
|
demdm := de.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyAppleMDM())
|
2023-03-27 19:30:29 +00:00
|
|
|
demdm.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("get_device_mdm", desktopQuota),
|
|
|
|
).GET("/api/_version_/fleet/device/{token}/mdm/apple/manual_enrollment_profile", getDeviceMDMManualEnrollProfileEndpoint, getDeviceMDMManualEnrollProfileRequest{})
|
2023-03-20 19:14:07 +00:00
|
|
|
|
2023-03-27 19:30:29 +00:00
|
|
|
demdm.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("post_device_rotate_encryption_key", desktopQuota),
|
|
|
|
).POST("/api/_version_/fleet/device/{token}/rotate_encryption_key", rotateEncryptionKeyEndpoint, rotateEncryptionKeyRequest{})
|
2023-01-16 15:22:12 +00:00
|
|
|
|
2023-05-17 14:16:26 +00:00
|
|
|
demdm.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("post_device_migrate_mdm", desktopQuota),
|
|
|
|
).POST("/api/_version_/fleet/device/{token}/migrate_mdm", migrateMDMDeviceEndpoint, deviceMigrateMDMRequest{})
|
|
|
|
|
2022-03-07 18:10:55 +00:00
|
|
|
// host-authenticated endpoints
|
2022-04-05 15:35:53 +00:00
|
|
|
he := newHostAuthenticatedEndpointer(svc, logger, opts, r, apiVersions...)
|
|
|
|
|
|
|
|
// Note that the /osquery/ endpoints are *not* versioned, i.e. there is no
|
|
|
|
// `_version_` placeholder in the path. This is deliberate, see
|
|
|
|
// https://github.com/fleetdm/fleet/pull/4731#discussion_r838931732 For now
|
|
|
|
// we add an alias to `/api/v1/osquery` so that it is backwards compatible,
|
|
|
|
// but even that `v1` is *not* part of the standard versioning, it will still
|
|
|
|
// work even after we remove support for the `v1` version for the rest of the
|
|
|
|
// API. This allows us to deprecate osquery endpoints separately.
|
|
|
|
he.WithAltPaths("/api/v1/osquery/config").
|
|
|
|
POST("/api/osquery/config", getClientConfigEndpoint, getClientConfigRequest{})
|
|
|
|
he.WithAltPaths("/api/v1/osquery/distributed/read").
|
|
|
|
POST("/api/osquery/distributed/read", getDistributedQueriesEndpoint, getDistributedQueriesRequest{})
|
|
|
|
he.WithAltPaths("/api/v1/osquery/distributed/write").
|
|
|
|
POST("/api/osquery/distributed/write", submitDistributedQueryResultsEndpoint, submitDistributedQueryResultsRequestShim{})
|
|
|
|
he.WithAltPaths("/api/v1/osquery/carve/begin").
|
|
|
|
POST("/api/osquery/carve/begin", carveBeginEndpoint, carveBeginRequest{})
|
|
|
|
he.WithAltPaths("/api/v1/osquery/log").
|
|
|
|
POST("/api/osquery/log", submitLogsEndpoint, submitLogsRequest{})
|
2022-03-08 16:27:38 +00:00
|
|
|
|
2022-09-23 19:00:23 +00:00
|
|
|
// orbit authenticated endpoints
|
|
|
|
oe := newOrbitAuthenticatedEndpointer(svc, logger, opts, r, apiVersions...)
|
2022-10-10 20:15:35 +00:00
|
|
|
oe.POST("/api/fleet/orbit/device_token", setOrUpdateDeviceTokenEndpoint, setOrUpdateDeviceTokenRequest{})
|
2022-09-23 19:00:23 +00:00
|
|
|
oe.POST("/api/fleet/orbit/config", getOrbitConfigEndpoint, orbitGetConfigRequest{})
|
2023-08-23 20:47:47 +00:00
|
|
|
// using POST to get a script execution request since all authenticated orbit
|
|
|
|
// endpoints are POST due to passing the device token in the JSON body.
|
|
|
|
oe.POST("/api/fleet/orbit/scripts/request", getOrbitScriptEndpoint, orbitGetScriptRequest{})
|
|
|
|
oe.POST("/api/fleet/orbit/scripts/result", postOrbitScriptResultEndpoint, orbitPostScriptResultRequest{})
|
2023-12-21 17:21:39 +00:00
|
|
|
oe.PUT("/api/fleet/orbit/device_mapping", putOrbitDeviceMappingEndpoint, orbitPutDeviceMappingRequest{})
|
2022-09-23 19:00:23 +00:00
|
|
|
|
2023-10-09 21:28:35 +00:00
|
|
|
oeWindowsMDM := oe.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyWindowsMDM())
|
|
|
|
oeWindowsMDM.POST("/api/fleet/orbit/disk_encryption_key", postOrbitDiskEncryptionKeyEndpoint, orbitPostDiskEncryptionKeyRequest{})
|
|
|
|
|
2022-03-08 16:27:38 +00:00
|
|
|
// unauthenticated endpoints - most of those are either login-related,
|
|
|
|
// invite-related or host-enrolling. So they typically do some kind of
|
|
|
|
// one-time authentication by verifying that a valid secret token is provided
|
|
|
|
// with the request.
|
2022-04-05 15:35:53 +00:00
|
|
|
ne := newNoAuthEndpointer(svc, opts, r, apiVersions...)
|
|
|
|
ne.WithAltPaths("/api/v1/osquery/enroll").
|
|
|
|
POST("/api/osquery/enroll", enrollAgentEndpoint, enrollAgentRequest{})
|
|
|
|
|
2023-03-27 19:30:29 +00:00
|
|
|
// These endpoint are token authenticated.
|
2023-05-02 13:09:33 +00:00
|
|
|
// NOTE: remember to update
|
2023-10-09 21:28:35 +00:00
|
|
|
// `service.mdmConfigurationRequiredEndpoints` when you add an
|
2023-05-02 13:09:33 +00:00
|
|
|
// endpoint that's behind the mdmConfiguredMiddleware, this applies
|
|
|
|
// both to this set of endpoints and to any user authenticated
|
2023-06-22 20:31:17 +00:00
|
|
|
// endpoints using `mdmAppleMW.*` above in this file.
|
|
|
|
neAppleMDM := ne.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyAppleMDM())
|
|
|
|
neAppleMDM.GET(apple_mdm.EnrollPath, mdmAppleEnrollEndpoint, mdmAppleEnrollRequest{})
|
|
|
|
neAppleMDM.GET(apple_mdm.InstallerPath, mdmAppleGetInstallerEndpoint, mdmAppleGetInstallerRequest{})
|
|
|
|
neAppleMDM.HEAD(apple_mdm.InstallerPath, mdmAppleHeadInstallerEndpoint, mdmAppleHeadInstallerRequest{})
|
|
|
|
neAppleMDM.GET("/api/_version_/fleet/mdm/apple/bootstrap", downloadBootstrapPackageEndpoint, downloadBootstrapPackageRequest{})
|
|
|
|
neAppleMDM.GET("/api/_version_/fleet/mdm/apple/setup/eula/{token}", getMDMAppleEULAEndpoint, getMDMAppleEULARequest{})
|
|
|
|
|
|
|
|
// These endpoint are used by Microsoft devices during MDM device enrollment phase
|
2023-06-27 15:59:33 +00:00
|
|
|
neWindowsMDM := ne.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyWindowsMDM())
|
2023-06-22 20:31:17 +00:00
|
|
|
|
2023-07-19 16:30:24 +00:00
|
|
|
// Microsoft MS-MDE2 Endpoints
|
|
|
|
// This endpoint is unauthenticated and is used by Microsoft devices to discover the MDM server endpoints
|
2023-06-27 15:59:33 +00:00
|
|
|
neWindowsMDM.POST(microsoft_mdm.MDE2DiscoveryPath, mdmMicrosoftDiscoveryEndpoint, SoapRequestContainer{})
|
|
|
|
|
2023-07-19 16:30:24 +00:00
|
|
|
// This endpoint is unauthenticated and is used by Microsoft devices to retrieve the opaque STS auth token
|
|
|
|
neWindowsMDM.GET(microsoft_mdm.MDE2AuthPath, mdmMicrosoftAuthEndpoint, SoapRequestContainer{})
|
|
|
|
|
2023-06-27 15:59:33 +00:00
|
|
|
// This endpoint is authenticated using the BinarySecurityToken header field
|
|
|
|
neWindowsMDM.POST(microsoft_mdm.MDE2PolicyPath, mdmMicrosoftPolicyEndpoint, SoapRequestContainer{})
|
2022-10-05 22:53:54 +00:00
|
|
|
|
2023-07-05 13:06:37 +00:00
|
|
|
// This endpoint is authenticated using the BinarySecurityToken header field
|
|
|
|
neWindowsMDM.POST(microsoft_mdm.MDE2EnrollPath, mdmMicrosoftEnrollEndpoint, SoapRequestContainer{})
|
|
|
|
|
2023-07-20 14:54:04 +00:00
|
|
|
// 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{})
|
|
|
|
|
2023-07-21 17:36:26 +00:00
|
|
|
// This endpoint is unauthenticated and is used by to retrieve the MDM enrollment Terms of Use
|
|
|
|
neWindowsMDM.GET(microsoft_mdm.MDE2TOSPath, mdmMicrosoftTOSEndpoint, MDMWebContainer{})
|
|
|
|
|
2022-10-28 17:27:21 +00:00
|
|
|
ne.POST("/api/fleet/orbit/enroll", enrollOrbitEndpoint, EnrollOrbitRequest{})
|
2022-09-23 19:00:23 +00:00
|
|
|
|
2022-04-19 13:35:53 +00:00
|
|
|
// 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.
|
2022-04-05 15:35:53 +00:00
|
|
|
ne.WithAltPaths("/api/v1/osquery/carve/block").
|
|
|
|
POST("/api/osquery/carve/block", carveBlockEndpoint, carveBlockRequest{})
|
2022-03-08 16:27:38 +00:00
|
|
|
|
|
|
|
ne.POST("/api/_version_/fleet/perform_required_password_reset", performRequiredPasswordResetEndpoint, performRequiredPasswordResetRequest{})
|
|
|
|
ne.POST("/api/_version_/fleet/users", createUserFromInviteEndpoint, createUserRequest{})
|
|
|
|
ne.GET("/api/_version_/fleet/invites/{token}", verifyInviteEndpoint, verifyInviteRequest{})
|
|
|
|
ne.POST("/api/_version_/fleet/reset_password", resetPasswordEndpoint, resetPasswordRequest{})
|
|
|
|
ne.POST("/api/_version_/fleet/logout", logoutEndpoint, nil)
|
2022-04-20 16:46:45 +00:00
|
|
|
ne.POST("/api/v1/fleet/sso", initiateSSOEndpoint, initiateSSORequest{})
|
|
|
|
ne.POST("/api/v1/fleet/sso/callback", makeCallbackSSOEndpoint(config.Server.URLPrefix), callbackSSORequest{})
|
|
|
|
ne.GET("/api/v1/fleet/sso", settingsSSOEndpoint, nil)
|
2022-07-01 19:52:55 +00:00
|
|
|
|
2022-04-20 19:57:26 +00:00
|
|
|
// the websocket distributed query results endpoint is a bit different - the
|
|
|
|
// provided path is a prefix, not an exact match, and it is not a go-kit
|
|
|
|
// endpoint but a raw http.Handler. It uses the NoAuthEndpointer because
|
|
|
|
// authentication is done when the websocket session is established, inside
|
|
|
|
// the handler.
|
2023-03-28 05:23:29 +00:00
|
|
|
ne.UsePathPrefix().PathHandler("GET", "/api/_version_/fleet/results/", makeStreamDistributedQueryCampaignResultsHandler(config.Server, svc, logger))
|
2022-03-08 16:27:38 +00:00
|
|
|
|
2023-06-15 19:41:04 +00:00
|
|
|
quota := throttled.RateQuota{MaxRate: throttled.PerHour(10), MaxBurst: forgotPasswordRateLimitMaxBurst}
|
2022-03-08 16:27:38 +00:00
|
|
|
limiter := ratelimit.NewMiddleware(limitStore)
|
|
|
|
ne.
|
2022-07-11 13:49:05 +00:00
|
|
|
WithCustomMiddleware(limiter.Limit("forgot_password", quota)).
|
2022-03-08 16:27:38 +00:00
|
|
|
POST("/api/_version_/fleet/forgot_password", forgotPasswordEndpoint, forgotPasswordRequest{})
|
|
|
|
|
2022-04-19 13:35:53 +00:00
|
|
|
loginRateLimit := throttled.PerMin(10)
|
|
|
|
if extra.loginRateLimit != nil {
|
|
|
|
loginRateLimit = *extra.loginRateLimit
|
|
|
|
}
|
|
|
|
|
|
|
|
ne.WithCustomMiddleware(limiter.Limit("login", throttled.RateQuota{MaxRate: loginRateLimit, MaxBurst: 9})).
|
2022-03-08 16:27:38 +00:00
|
|
|
POST("/api/_version_/fleet/login", loginEndpoint, loginRequest{})
|
2022-07-01 19:52:55 +00:00
|
|
|
|
2022-07-27 19:47:39 +00:00
|
|
|
// Fleet Sandbox demo login (always errors unless config.server.sandbox_enabled is set)
|
2022-07-01 19:52:55 +00:00
|
|
|
ne.WithCustomMiddleware(limiter.Limit("login", throttled.RateQuota{MaxRate: loginRateLimit, MaxBurst: 9})).
|
|
|
|
POST("/api/_version_/fleet/demologin", makeDemologinEndpoint(config.Server.URLPrefix), demologinRequest{})
|
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
|
|
|
|
|
|
|
ne.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("ping_device", desktopQuota),
|
2022-09-26 17:39:56 +00:00
|
|
|
).HEAD("/api/fleet/device/ping", devicePingEndpoint, devicePingRequest{})
|
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
|
|
|
|
|
|
|
ne.WithCustomMiddleware(
|
|
|
|
errorLimiter.Limit("ping_orbit", desktopQuota),
|
2022-09-26 17:39:56 +00:00
|
|
|
).HEAD("/api/fleet/orbit/ping", orbitPingEndpoint, orbitPingRequest{})
|
2023-04-27 12:43:20 +00:00
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
neAppleMDM.WithCustomMiddleware(limiter.Limit("login", throttled.RateQuota{MaxRate: loginRateLimit, MaxBurst: 9})).
|
2023-04-27 12:43:20 +00:00
|
|
|
POST("/api/_version_/fleet/mdm/sso", initiateMDMAppleSSOEndpoint, initiateMDMAppleSSORequest{})
|
|
|
|
|
2023-06-22 20:31:17 +00:00
|
|
|
neAppleMDM.WithCustomMiddleware(limiter.Limit("login", throttled.RateQuota{MaxRate: loginRateLimit, MaxBurst: 9})).
|
2023-04-27 12:43:20 +00:00
|
|
|
POST("/api/_version_/fleet/mdm/sso/callback", callbackMDMAppleSSOEndpoint, callbackMDMAppleSSORequest{})
|
2021-07-16 18:28:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newServer(e endpoint.Endpoint, decodeFn kithttp.DecodeRequestFunc, opts []kithttp.ServerOption) http.Handler {
|
2022-03-08 16:27:38 +00:00
|
|
|
// TODO: some handlers don't have authz checks, and because the SkipAuth call is done only in the
|
|
|
|
// endpoint handler, any middleware that raises errors before the handler is reached will end up
|
|
|
|
// returning authz check missing instead of the more relevant error. Should be addressed as part
|
|
|
|
// of #4406.
|
2021-07-16 18:28:13 +00:00
|
|
|
e = authzcheck.NewMiddleware().AuthzCheck()(e)
|
|
|
|
return kithttp.NewServer(e, decodeFn, encodeResponse, opts...)
|
|
|
|
}
|
|
|
|
|
2020-11-18 19:10:55 +00:00
|
|
|
// WithSetup is an http middleware that checks if setup procedures have been completed.
|
2017-02-09 18:43:45 +00:00
|
|
|
// If setup hasn't been completed it serves the API with a setup middleware.
|
2016-11-09 17:19:07 +00:00
|
|
|
// If the server is already configured, the default API handler is exposed.
|
2021-06-06 22:07:29 +00:00
|
|
|
func WithSetup(svc fleet.Service, logger kitlog.Logger, next http.Handler) http.HandlerFunc {
|
2022-03-08 16:27:38 +00:00
|
|
|
rxOsquery := regexp.MustCompile(`^/api/[^/]+/osquery`)
|
2016-12-02 18:46:31 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
configRouter := http.NewServeMux()
|
2022-04-20 19:57:26 +00:00
|
|
|
srv := kithttp.NewServer(
|
Add read replica testing helpers and fix non-sso login bug (#4908)
not set on the INSERT.
- OUT: Only sets the ID on the passed session and returns it. (`CreatedAt`, `AccessedAt`, are not set.)
New version:
```go
func (ds *Datastore) NewSession(ctx context.Context, userID uint, sessionKey string) (*fleet.Session, error) {
sqlStatement := `
INSERT INTO sessions (
user_id,
` + "`key`" + `
)
VALUES(?,?)
`
result, err := ds.writer.ExecContext(ctx, sqlStatement, userID, sessionKey)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "inserting session")
}
id, _ := result.LastInsertId() // cannot fail with the mysql driver
return ds.sessionByID(ctx, ds.writer, uint(id))
}
```
- IN: Define arguments that are truly used when creating a session.
- OUT: Load and return the fleet.Session struct with all values set (using the `ds.writer` to support read replicas correctly).
PS: The new `NewSession` version mimics what we already do with other entities, like policies (`Datastore.NewGlobalPolicy`).
2022-04-04 23:52:05 +00:00
|
|
|
makeSetupEndpoint(svc, logger),
|
2016-12-02 18:46:31 +00:00
|
|
|
decodeSetupRequest,
|
|
|
|
encodeResponse,
|
2022-04-20 19:57:26 +00:00
|
|
|
)
|
|
|
|
// NOTE: support setup on both /v1/ and version-less, in the future /v1/
|
|
|
|
// will be dropped.
|
|
|
|
configRouter.Handle("/api/v1/setup", srv)
|
|
|
|
configRouter.Handle("/api/setup", srv)
|
|
|
|
|
2017-01-12 00:40:58 +00:00
|
|
|
// whitelist osqueryd endpoints
|
2022-03-08 16:27:38 +00:00
|
|
|
if rxOsquery.MatchString(r.URL.Path) {
|
2017-01-12 00:40:58 +00:00
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
2021-06-03 23:24:15 +00:00
|
|
|
requireSetup, err := svc.SetupRequired(context.Background())
|
2017-02-09 18:43:45 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Log("msg", "fetching setup info from db", "err", err)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if requireSetup {
|
2016-12-02 18:46:31 +00:00
|
|
|
configRouter.ServeHTTP(w, r)
|
2017-02-09 18:43:45 +00:00
|
|
|
return
|
2016-12-02 18:46:31 +00:00
|
|
|
}
|
2017-02-09 18:43:45 +00:00
|
|
|
next.ServeHTTP(w, r)
|
2016-11-09 17:19:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-29 23:36:36 +00:00
|
|
|
// RedirectLoginToSetup detects if the setup endpoint should be used. If setup is required it redirect all
|
|
|
|
// frontend urls to /setup, otherwise the frontend router is used.
|
2021-06-06 22:07:29 +00:00
|
|
|
func RedirectLoginToSetup(svc fleet.Service, logger kitlog.Logger, next http.Handler, urlPrefix string) http.HandlerFunc {
|
2016-12-29 23:36:36 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
redirect := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2017-09-01 16:42:46 +00:00
|
|
|
if r.URL.Path == "/setup" {
|
2016-12-29 23:36:36 +00:00
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newURL := r.URL
|
2019-10-16 23:40:45 +00:00
|
|
|
newURL.Path = urlPrefix + "/setup"
|
2016-12-29 23:36:36 +00:00
|
|
|
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
|
|
|
})
|
2017-02-09 18:43:45 +00:00
|
|
|
|
2021-06-03 23:24:15 +00:00
|
|
|
setupRequired, err := svc.SetupRequired(context.Background())
|
2017-02-09 18:43:45 +00:00
|
|
|
if err != nil {
|
2017-09-01 16:42:46 +00:00
|
|
|
logger.Log("msg", "fetching setupinfo from db", "err", err)
|
2017-02-09 18:43:45 +00:00
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if setupRequired {
|
2016-12-29 23:36:36 +00:00
|
|
|
redirect.ServeHTTP(w, r)
|
2017-02-09 18:43:45 +00:00
|
|
|
return
|
2016-12-29 23:36:36 +00:00
|
|
|
}
|
2019-10-16 23:40:45 +00:00
|
|
|
RedirectSetupToLogin(svc, logger, next, urlPrefix).ServeHTTP(w, r)
|
2016-11-09 17:19:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-01 16:42:46 +00:00
|
|
|
// RedirectSetupToLogin forces the /setup path to be redirected to login. This middleware is used after
|
2017-01-11 19:05:07 +00:00
|
|
|
// the app has been setup.
|
2021-06-06 22:07:29 +00:00
|
|
|
func RedirectSetupToLogin(svc fleet.Service, logger kitlog.Logger, next http.Handler, urlPrefix string) http.HandlerFunc {
|
2017-01-11 19:05:07 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.URL.Path == "/setup" {
|
|
|
|
newURL := r.URL
|
2019-10-16 23:40:45 +00:00
|
|
|
newURL.Path = urlPrefix + "/login"
|
2017-01-11 19:05:07 +00:00
|
|
|
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
}
|
2023-01-16 20:06:30 +00:00
|
|
|
|
|
|
|
// RegisterAppleMDMProtocolServices registers the HTTP handlers that serve
|
|
|
|
// the MDM services to Apple devices.
|
|
|
|
func RegisterAppleMDMProtocolServices(
|
|
|
|
mux *http.ServeMux,
|
2023-03-23 10:30:28 +00:00
|
|
|
scepConfig config.MDMConfig,
|
2023-01-16 20:06:30 +00:00
|
|
|
mdmStorage nanomdm_storage.AllStorage,
|
2023-01-31 14:46:01 +00:00
|
|
|
scepStorage scep_depot.Depot,
|
2023-01-16 20:06:30 +00:00
|
|
|
logger kitlog.Logger,
|
|
|
|
checkinAndCommandService nanomdm_service.CheckinAndCommandService,
|
|
|
|
) error {
|
|
|
|
scepCACerts, scepCAKey, err := scepStorage.CA([]byte{})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("load SCEP CA certificates and key: %w", err)
|
|
|
|
}
|
|
|
|
if err := registerSCEP(mux, scepConfig, scepCACerts[0], scepCAKey, scepStorage, logger); err != nil {
|
|
|
|
return fmt.Errorf("scep: %w", err)
|
|
|
|
}
|
|
|
|
if err := registerMDM(mux, scepCACerts[0], mdmStorage, checkinAndCommandService, logger); err != nil {
|
|
|
|
return fmt.Errorf("mdm: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// registerSCEP registers the HTTP handler for SCEP service needed for enrollment to MDM.
|
|
|
|
// Returns the SCEP CA certificate that can be used by verifiers.
|
|
|
|
func registerSCEP(
|
|
|
|
mux *http.ServeMux,
|
2023-03-23 10:30:28 +00:00
|
|
|
scepConfig config.MDMConfig,
|
2023-01-16 20:06:30 +00:00
|
|
|
scepCert *x509.Certificate,
|
|
|
|
scepKey *rsa.PrivateKey,
|
2023-01-31 14:46:01 +00:00
|
|
|
scepStorage scep_depot.Depot,
|
2023-01-16 20:06:30 +00:00
|
|
|
logger kitlog.Logger,
|
|
|
|
) error {
|
|
|
|
var signer scepserver.CSRSigner = scep_depot.NewSigner(
|
|
|
|
scepStorage,
|
2023-03-23 10:30:28 +00:00
|
|
|
scep_depot.WithValidityDays(scepConfig.AppleSCEPSignerValidityDays),
|
|
|
|
scep_depot.WithAllowRenewalDays(scepConfig.AppleSCEPSignerAllowRenewalDays),
|
2023-01-16 20:06:30 +00:00
|
|
|
)
|
2023-03-23 10:30:28 +00:00
|
|
|
scepChallenge := scepConfig.AppleSCEPChallenge
|
2023-01-16 20:06:30 +00:00
|
|
|
if scepChallenge == "" {
|
|
|
|
return errors.New("missing SCEP challenge")
|
|
|
|
}
|
|
|
|
|
|
|
|
signer = scepserver.ChallengeMiddleware(scepChallenge, signer)
|
|
|
|
scepService, err := scepserver.NewService(scepCert, scepKey, signer,
|
|
|
|
scepserver.WithLogger(kitlog.With(logger, "component", "mdm-apple-scep")),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("initialize SCEP service: %w", err)
|
|
|
|
}
|
|
|
|
scepLogger := kitlog.With(logger, "component", "http-mdm-apple-scep")
|
|
|
|
e := scepserver.MakeServerEndpoints(scepService)
|
|
|
|
e.GetEndpoint = scepserver.EndpointLoggingMiddleware(scepLogger)(e.GetEndpoint)
|
|
|
|
e.PostEndpoint = scepserver.EndpointLoggingMiddleware(scepLogger)(e.PostEndpoint)
|
|
|
|
scepHandler := scepserver.MakeHTTPHandler(e, scepService, scepLogger)
|
|
|
|
mux.Handle(apple_mdm.SCEPPath, scepHandler)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NanoMDMLogger is a logger adapter for nanomdm.
|
|
|
|
type NanoMDMLogger struct {
|
|
|
|
logger kitlog.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewNanoMDMLogger(logger kitlog.Logger) *NanoMDMLogger {
|
|
|
|
return &NanoMDMLogger{
|
|
|
|
logger: logger,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *NanoMDMLogger) Info(keyvals ...interface{}) {
|
|
|
|
level.Info(l.logger).Log(keyvals...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *NanoMDMLogger) Debug(keyvals ...interface{}) {
|
|
|
|
level.Debug(l.logger).Log(keyvals...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *NanoMDMLogger) With(keyvals ...interface{}) nanomdm_log.Logger {
|
|
|
|
newLogger := kitlog.With(l.logger, keyvals...)
|
|
|
|
return &NanoMDMLogger{
|
|
|
|
logger: newLogger,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// registerMDM registers the HTTP handlers that serve core MDM services (like checking in for MDM commands).
|
|
|
|
func registerMDM(
|
|
|
|
mux *http.ServeMux,
|
|
|
|
scepCACert *x509.Certificate,
|
|
|
|
mdmStorage nanomdm_storage.AllStorage,
|
|
|
|
checkinAndCommandService nanomdm_service.CheckinAndCommandService,
|
|
|
|
logger kitlog.Logger,
|
|
|
|
) error {
|
|
|
|
certVerifier, err := certverify.NewPoolVerifier(
|
|
|
|
apple_mdm.EncodeCertPEM(scepCACert),
|
|
|
|
x509.ExtKeyUsageClientAuth,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("certificate pool verifier: %w", err)
|
|
|
|
}
|
|
|
|
mdmLogger := NewNanoMDMLogger(kitlog.With(logger, "component", "http-mdm-apple-mdm"))
|
|
|
|
|
|
|
|
// As usual, handlers are applied from bottom to top:
|
|
|
|
// 1. Extract and verify MDM signature.
|
|
|
|
// 2. Verify signer certificate with CA.
|
|
|
|
// 3. Verify new or enrolled certificate (certauth.CertAuth which wraps the MDM service).
|
|
|
|
// 4. Pass a copy of the request to Fleet middleware that ingests new hosts from pending MDM
|
|
|
|
// enrollments and updates the Fleet hosts table accordingly with the UDID and serial number of
|
|
|
|
// the device.
|
|
|
|
// 5. Run actual MDM service operation (checkin handler or command and results handler).
|
|
|
|
coreMDMService := nanomdm.New(mdmStorage, nanomdm.WithLogger(mdmLogger))
|
2023-03-27 18:43:01 +00:00
|
|
|
// NOTE: it is critical that the coreMDMService runs first, as the first
|
|
|
|
// service in the multi-service feature is run to completion _before_ running
|
|
|
|
// the other ones in parallel. This way, subsequent services have access to
|
|
|
|
// the result of the core service, e.g. the device is enrolled, etc.
|
2023-01-16 20:06:30 +00:00
|
|
|
var mdmService nanomdm_service.CheckinAndCommandService = multi.New(mdmLogger, coreMDMService, checkinAndCommandService)
|
|
|
|
|
|
|
|
mdmService = certauth.New(mdmService, mdmStorage)
|
|
|
|
var mdmHandler http.Handler = httpmdm.CheckinAndCommandHandler(mdmService, mdmLogger.With("handler", "checkin-command"))
|
|
|
|
mdmHandler = httpmdm.CertVerifyMiddleware(mdmHandler, certVerifier, mdmLogger.With("handler", "cert-verify"))
|
|
|
|
mdmHandler = httpmdm.CertExtractMdmSignatureMiddleware(mdmHandler, mdmLogger.With("handler", "cert-extract"))
|
|
|
|
mux.Handle(apple_mdm.MDMPath, mdmHandler)
|
|
|
|
return nil
|
|
|
|
}
|