mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
3757aace08
#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)).~
171 lines
5.0 KiB
Go
171 lines
5.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Retrieve an Orbit installer from storage
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type getInstallerRequest struct {
|
|
Kind string
|
|
EnrollSecret string
|
|
Desktop bool
|
|
}
|
|
|
|
func (getInstallerRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
|
k, ok := mux.Vars(r)["kind"]
|
|
if !ok {
|
|
return "", errBadRoute
|
|
}
|
|
|
|
return getInstallerRequest{
|
|
Kind: k,
|
|
EnrollSecret: r.FormValue("enroll_secret"),
|
|
Desktop: r.FormValue("desktop") == "true",
|
|
}, nil
|
|
}
|
|
|
|
type getInstallerResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
|
|
// file fields below are used in hijackRender for the response
|
|
fileReader io.ReadCloser
|
|
fileLength int64
|
|
fileExt string
|
|
}
|
|
|
|
func (r getInstallerResponse) error() error { return r.Err }
|
|
|
|
func (r getInstallerResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
|
|
w.Header().Set("Content-Length", strconv.FormatInt(r.fileLength, 10))
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="fleet-osquery.%s"`, r.fileExt))
|
|
|
|
// OK to just log the error here as writing anything on
|
|
// `http.ResponseWriter` sets the status code to 200 (and it can't be
|
|
// changed.) Clients should rely on matching content-length with the
|
|
// header provided
|
|
wl, err := io.Copy(w, r.fileReader)
|
|
if err != nil {
|
|
logging.WithExtras(ctx, "s3_copy_error", err, "bytes_copied", wl)
|
|
}
|
|
r.fileReader.Close()
|
|
}
|
|
|
|
func getInstallerEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(getInstallerRequest)
|
|
|
|
fileReader, fileLength, err := svc.GetInstaller(ctx, fleet.Installer{
|
|
EnrollSecret: req.EnrollSecret,
|
|
Kind: req.Kind,
|
|
Desktop: req.Desktop,
|
|
})
|
|
if err != nil {
|
|
return getInstallerResponse{Err: err}, nil
|
|
}
|
|
|
|
return getInstallerResponse{fileReader: fileReader, fileLength: fileLength, fileExt: req.Kind}, nil
|
|
}
|
|
|
|
// GetInstaller retrieves a blob containing the installer binary
|
|
func (svc *Service) GetInstaller(ctx context.Context, installer fleet.Installer) (io.ReadCloser, int64, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.EnrollSecret{}, fleet.ActionRead); err != nil {
|
|
return nil, int64(0), err
|
|
}
|
|
|
|
if !svc.SandboxEnabled() {
|
|
return nil, int64(0), errors.New("this endpoint only enabled in demo mode")
|
|
}
|
|
|
|
if svc.installerStore == nil {
|
|
return nil, int64(0), ctxerr.New(ctx, "installer storage has not been configured")
|
|
}
|
|
|
|
_, err := svc.ds.VerifyEnrollSecret(ctx, installer.EnrollSecret)
|
|
if err != nil {
|
|
return nil, int64(0), ctxerr.Wrap(ctx, err, "finding a matching enroll secret")
|
|
}
|
|
|
|
reader, length, err := svc.installerStore.Get(ctx, installer)
|
|
if err != nil {
|
|
return nil, int64(0), ctxerr.Wrap(ctx, err, "unable to retrieve installer from store")
|
|
}
|
|
|
|
return reader, length, nil
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Check if a prebuilt Orbit installer is available
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type checkInstallerRequest struct {
|
|
Kind string `url:"kind"`
|
|
Desktop bool `query:"desktop,optional"`
|
|
EnrollSecret string `query:"enroll_secret"`
|
|
}
|
|
|
|
type checkInstallerResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r checkInstallerResponse) error() error { return r.Err }
|
|
|
|
func checkInstallerEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*checkInstallerRequest)
|
|
|
|
err := svc.CheckInstallerExistence(ctx, fleet.Installer{
|
|
EnrollSecret: req.EnrollSecret,
|
|
Kind: req.Kind,
|
|
Desktop: req.Desktop,
|
|
})
|
|
if err != nil {
|
|
return checkInstallerResponse{Err: err}, nil
|
|
}
|
|
|
|
return checkInstallerResponse{}, nil
|
|
}
|
|
|
|
// CheckInstallerExistence checks if an installer exists in the configured storage
|
|
func (svc *Service) CheckInstallerExistence(ctx context.Context, installer fleet.Installer) error {
|
|
if err := svc.authz.Authorize(ctx, &fleet.EnrollSecret{}, fleet.ActionRead); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !svc.SandboxEnabled() {
|
|
return errors.New("this endpoint only enabled in demo mode")
|
|
}
|
|
|
|
if svc.installerStore == nil {
|
|
return ctxerr.New(ctx, "installer storage has not been configured")
|
|
}
|
|
|
|
_, err := svc.ds.VerifyEnrollSecret(ctx, installer.EnrollSecret)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "cannot find a matching enroll secret")
|
|
}
|
|
|
|
exists, err := svc.installerStore.Exists(ctx, installer)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "checking installer existence")
|
|
}
|
|
|
|
if !exists {
|
|
return newNotFoundError()
|
|
}
|
|
|
|
return nil
|
|
}
|