fleet/server/service/installer.go
Roberto Dip 8acf14ab43
adjust installers endpoint to avoid AJAX downloads (#7226)
Related to #7206, this delegates the handling of the download to the browser
2022-08-16 12:54:41 -03:00

172 lines
4.9 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("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) (interface{}, 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) (interface{}, 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
}