<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** For #39344 # Details As a first step to deprecating API params like `team_id` in favor of `fleet_id` and `query_id` in favor of `report_id`, this PR adds `renameto` tags to all deprecated keys. There is no logic in this PR to actually use these tags in any way. The logic and test fixes will be in the next PR, but in the interest of keeping things manageable I'm pushing this out first. There were definitely params with "query" in them that we don't want to change (mainly osquery-related), and I think I kept them all out but it's worth double-checking here. The team -> fleet changes are pretty safe in comparison. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. Deferring changelog to PR with logic changes ## Testing - [ ] Added/updated automated tests This should be a no-op. All existing tests shoud pass. - [X] QA'd all new/changed functionality manually
171 lines
5.9 KiB
Go
171 lines
5.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
|
|
)
|
|
|
|
type addFleetMaintainedAppRequest struct {
|
|
TeamID *uint `json:"team_id" renameto:"fleet_id"`
|
|
AppID uint `json:"fleet_maintained_app_id"`
|
|
InstallScript string `json:"install_script"`
|
|
PreInstallQuery string `json:"pre_install_query"`
|
|
PostInstallScript string `json:"post_install_script"`
|
|
SelfService bool `json:"self_service"`
|
|
UninstallScript string `json:"uninstall_script"`
|
|
LabelsIncludeAny []string `json:"labels_include_any"`
|
|
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
|
AutomaticInstall bool `json:"automatic_install"`
|
|
Categories []string `json:"categories"`
|
|
}
|
|
|
|
// DecodeRequest implements the RequestDecoder interface to support base64-encoded
|
|
// script fields. This allows bypassing WAF rules that may block requests containing
|
|
// shell/PowerShell script patterns. When the X-Fleet-Scripts-Encoded header is set
|
|
// to "base64", the script fields are decoded from base64.
|
|
func (addFleetMaintainedAppRequest) DecodeRequest(ctx context.Context, r *http.Request) (any, error) {
|
|
var req addFleetMaintainedAppRequest
|
|
|
|
// Decode JSON body
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
return nil, &fleet.BadRequestError{
|
|
Message: "failed to decode request body",
|
|
InternalErr: err,
|
|
}
|
|
}
|
|
|
|
// Check if scripts are base64 encoded
|
|
if isScriptsEncoded(r) {
|
|
var err error
|
|
if req.InstallScript, err = decodeBase64Script(req.InstallScript); err != nil {
|
|
return nil, fleet.NewInvalidArgumentError("install_script", "invalid base64 encoding")
|
|
}
|
|
if req.UninstallScript, err = decodeBase64Script(req.UninstallScript); err != nil {
|
|
return nil, fleet.NewInvalidArgumentError("uninstall_script", "invalid base64 encoding")
|
|
}
|
|
if req.PostInstallScript, err = decodeBase64Script(req.PostInstallScript); err != nil {
|
|
return nil, fleet.NewInvalidArgumentError("post_install_script", "invalid base64 encoding")
|
|
}
|
|
if req.PreInstallQuery, err = decodeBase64Script(req.PreInstallQuery); err != nil {
|
|
return nil, fleet.NewInvalidArgumentError("pre_install_query", "invalid base64 encoding")
|
|
}
|
|
}
|
|
|
|
return &req, nil
|
|
}
|
|
|
|
type addFleetMaintainedAppResponse struct {
|
|
SoftwareTitleID uint `json:"software_title_id,omitempty"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r addFleetMaintainedAppResponse) Error() error { return r.Err }
|
|
|
|
func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
|
|
req := request.(*addFleetMaintainedAppRequest)
|
|
ctx, cancel := context.WithTimeout(ctx, maintained_apps.InstallerTimeout)
|
|
defer cancel()
|
|
titleId, err := svc.AddFleetMaintainedApp(
|
|
ctx,
|
|
req.TeamID,
|
|
req.AppID,
|
|
req.InstallScript,
|
|
req.PreInstallQuery,
|
|
req.PostInstallScript,
|
|
req.UninstallScript,
|
|
req.SelfService,
|
|
req.AutomaticInstall,
|
|
req.LabelsIncludeAny,
|
|
req.LabelsExcludeAny,
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
|
err = fleet.NewGatewayTimeoutError("Couldn't add. Request timeout. Please make sure your server and load balancer timeout is long enough.", err)
|
|
}
|
|
|
|
return &addFleetMaintainedAppResponse{Err: err}, nil
|
|
}
|
|
return &addFleetMaintainedAppResponse{SoftwareTitleID: titleId}, nil
|
|
}
|
|
|
|
func (svc *Service) AddFleetMaintainedApp(ctx context.Context, _ *uint, _ uint, _, _, _, _ string, _ bool, _ bool, _, _ []string) (uint, error) {
|
|
// skipauth: No authorization check needed due to implementation returning
|
|
// only license error.
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
return 0, fleet.ErrMissingLicense
|
|
}
|
|
|
|
type listFleetMaintainedAppsRequest struct {
|
|
fleet.ListOptions
|
|
TeamID *uint `query:"team_id,optional" renameto:"fleet_id"`
|
|
}
|
|
|
|
type listFleetMaintainedAppsResponse struct {
|
|
FleetMaintainedApps []fleet.MaintainedApp `json:"fleet_maintained_apps"`
|
|
Meta *fleet.PaginationMetadata `json:"meta"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r listFleetMaintainedAppsResponse) Error() error { return r.Err }
|
|
|
|
func listFleetMaintainedAppsEndpoint(ctx context.Context, request any, svc fleet.Service) (fleet.Errorer, error) {
|
|
req := request.(*listFleetMaintainedAppsRequest)
|
|
|
|
apps, meta, err := svc.ListFleetMaintainedApps(ctx, req.TeamID, req.ListOptions)
|
|
if err != nil {
|
|
return listFleetMaintainedAppsResponse{Err: err}, nil
|
|
}
|
|
|
|
listResp := listFleetMaintainedAppsResponse{
|
|
FleetMaintainedApps: apps,
|
|
Meta: meta,
|
|
}
|
|
|
|
return listResp, nil
|
|
}
|
|
|
|
func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID *uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) {
|
|
// skipauth: No authorization check needed due to implementation returning
|
|
// only license error.
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
return nil, nil, fleet.ErrMissingLicense
|
|
}
|
|
|
|
type getFleetMaintainedAppRequest struct {
|
|
AppID uint `url:"app_id"`
|
|
TeamID *uint `query:"team_id,optional" renameto:"fleet_id"`
|
|
}
|
|
|
|
type getFleetMaintainedAppResponse struct {
|
|
FleetMaintainedApp *fleet.MaintainedApp `json:"fleet_maintained_app"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r getFleetMaintainedAppResponse) Error() error { return r.Err }
|
|
|
|
func getFleetMaintainedApp(ctx context.Context, request any, svc fleet.Service) (fleet.Errorer, error) {
|
|
req := request.(*getFleetMaintainedAppRequest)
|
|
|
|
app, err := svc.GetFleetMaintainedApp(ctx, req.AppID, req.TeamID)
|
|
if err != nil {
|
|
return getFleetMaintainedAppResponse{Err: err}, nil
|
|
}
|
|
|
|
return getFleetMaintainedAppResponse{FleetMaintainedApp: app}, nil
|
|
}
|
|
|
|
func (svc *Service) GetFleetMaintainedApp(ctx context.Context, appID uint, teamID *uint) (*fleet.MaintainedApp, error) {
|
|
// skipauth: No authorization check needed due to implementation returning
|
|
// only license error.
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
return nil, fleet.ErrMissingLicense
|
|
}
|