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"
|
2021-11-22 14:13:26 +00:00
|
|
|
"errors"
|
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"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/authzcheck"
|
|
|
|
"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"
|
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"
|
2022-02-15 17:42:22 +00:00
|
|
|
otmiddleware "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
|
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
|
|
|
}
|
|
|
|
|
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()
|
2022-02-15 17:42:22 +00:00
|
|
|
if config.Logging.TracingEnabled && config.Logging.TracingType == "opentelemetry" {
|
|
|
|
r.Use(otmiddleware.Middleware("fleet"))
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
r.Walk(walkFn)
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
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{})
|
|
|
|
ue.EndingAtVersion("v1").GET("/api/_version_/fleet/global/policies", listGlobalPoliciesEndpoint, nil)
|
|
|
|
ue.StartingAtVersion("2022-04").GET("/api/_version_/fleet/policies", listGlobalPoliciesEndpoint, nil)
|
|
|
|
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{})
|
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{})
|
|
|
|
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{})
|
|
|
|
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{})
|
|
|
|
ue.GET("/api/_version_/fleet/spec/queries", getQuerySpecsEndpoint, nil)
|
|
|
|
ue.GET("/api/_version_/fleet/spec/queries/{name}", getQuerySpecEndpoint, getGenericSpecRequest{})
|
|
|
|
|
|
|
|
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{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/software", listSoftwareEndpoint, listSoftwareRequest{})
|
2022-05-20 16:58:40 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software/{id:[0-9]+}", getSoftwareEndpoint, getSoftwareRequest{})
|
2022-03-07 18:10:55 +00:00
|
|
|
ue.GET("/api/_version_/fleet/software/count", countSoftwareEndpoint, countSoftwareRequest{})
|
|
|
|
|
|
|
|
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{})
|
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{})
|
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{})
|
|
|
|
|
|
|
|
ue.GET("/api/_version_/fleet/queries/run", runLiveQueryEndpoint, runLiveQueryRequest{})
|
|
|
|
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-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)
|
|
|
|
|
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-03-09 21:13:56 +00:00
|
|
|
de.GET("/api/_version_/fleet/device/{token}", getDeviceHostEndpoint, getDeviceHostRequest{})
|
|
|
|
de.POST("/api/_version_/fleet/device/{token}/refetch", refetchDeviceHostEndpoint, refetchDeviceHostRequest{})
|
|
|
|
de.GET("/api/_version_/fleet/device/{token}/device_mapping", listDeviceHostDeviceMappingEndpoint, listDeviceHostDeviceMappingRequest{})
|
|
|
|
de.GET("/api/_version_/fleet/device/{token}/macadmins", getDeviceMacadminsDataEndpoint, getDeviceMacadminsDataRequest{})
|
add premium, device authed endpoint to retrieve policies (#5967)
This adds a new device authenticated endpoint, `/api/_version_/fleet/device/{token}/policies` to retrieve the device policies.
An example request / response looks like:
```bash
curl https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a/policies
```
```json
{
"policies": [
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Admin",
"author_email": "admin@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,windows,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-05-23T20:53:36Z",
"response": "fail"
}
]
}
```
Related to [#5685](https://github.com/fleetdm/fleet/issues/5685), in another changeset I will be adding "client" endpoints so we can consume this endpoint from Fleet Desktop
2022-05-31 17:54:43 +00:00
|
|
|
de.GET("/api/_version_/fleet/device/{token}/policies", listDevicePoliciesEndpoint, listDevicePoliciesRequest{})
|
2022-06-09 13:17:55 +00:00
|
|
|
de.GET("/api/_version_/fleet/device/{token}/api_features", deviceAPIFeaturesEndpoint, deviceAPIFeaturesRequest{})
|
2022-06-13 19:07:08 +00:00
|
|
|
de.GET("/api/_version_/fleet/device/{token}/transparency", transparencyURL, transparencyURLRequest{})
|
2022-03-09 21:13:56 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
// 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{})
|
|
|
|
|
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.
|
|
|
|
ne.UsePathPrefix().PathHandler("GET", "/api/_version_/fleet/results/", makeStreamDistributedQueryCampaignResultsHandler(svc, logger))
|
2022-03-08 16:27:38 +00:00
|
|
|
|
|
|
|
limiter := ratelimit.NewMiddleware(limitStore)
|
|
|
|
ne.
|
2022-04-19 13:35:53 +00:00
|
|
|
WithCustomMiddleware(limiter.Limit("forgot_password", throttled.RateQuota{MaxRate: throttled.PerHour(10), MaxBurst: 9})).
|
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
|
|
|
|
|
|
|
// Fleet Sandbox demo login (always errors unless FLEET_DEMO environment variable is set)
|
|
|
|
ne.WithCustomMiddleware(limiter.Limit("login", throttled.RateQuota{MaxRate: loginRateLimit, MaxBurst: 9})).
|
|
|
|
POST("/api/_version_/fleet/demologin", makeDemologinEndpoint(config.Server.URLPrefix), demologinRequest{})
|
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)
|
|
|
|
}
|
|
|
|
}
|