mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
19ad7cc637
* Set interface for response types * Fix TestEndpointer test
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 notFoundError{}
|
|
}
|
|
|
|
return nil
|
|
}
|