feat: use a 15 minute timeout for adding a maintained app (#22247)

> Related issue: #22239

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Jahziel Villasana-Espinoza 2024-09-19 15:42:17 -04:00 committed by GitHub
parent b45c8b02c9
commit 7f39281937
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 38 additions and 9 deletions

View file

@ -5,7 +5,10 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
@ -28,7 +31,13 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app
}
// Download installer from the URL
installerBytes, filename, err := maintainedapps.DownloadInstaller(ctx, app.InstallerURL)
timeout := maintainedapps.InstallerTimeout
if v := os.Getenv("FLEET_DEV_MAINTAINED_APPS_INSTALLER_TIMEOUT"); v != "" {
timeout, _ = time.ParseDuration(v)
}
client := fleethttp.NewClient(fleethttp.WithTimeout(timeout))
installerBytes, filename, err := maintainedapps.DownloadInstaller(ctx, app.InstallerURL, client)
if err != nil {
return ctxerr.Wrap(ctx, err, "downloading app installer")
}

View file

@ -10,13 +10,15 @@ import (
"path"
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
)
// InstallerTimeout is the timeout duration for downloading and adding a maintained app.
const InstallerTimeout = 15 * time.Minute
// DownloadInstaller downloads the maintained app installer located at the given URL.
func DownloadInstaller(ctx context.Context, installerURL string) ([]byte, string, error) {
func DownloadInstaller(ctx context.Context, installerURL string, client *http.Client) ([]byte, string, error) {
// validate the URL before doing the request
_, err := url.ParseRequestURI(installerURL)
if err != nil {
@ -26,8 +28,6 @@ func DownloadInstaller(ctx context.Context, installerURL string) ([]byte, string
)
}
client := fleethttp.NewClient(fleethttp.WithTimeout(30 * time.Second))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, installerURL, nil)
if err != nil {
return nil, "", ctxerr.Wrapf(ctx, err, "creating request for URL %s", installerURL)

View file

@ -14235,11 +14235,15 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
ctx := context.Background()
installerBytes := []byte("abc")
// Mock server to serve the "installers"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/badinstaller":
_, _ = w.Write([]byte("badinstaller"))
case "/timeout":
time.Sleep(3 * time.Second)
_, _ = w.Write([]byte("timeout"))
default:
_, _ = w.Write(installerBytes)
}
@ -14270,6 +14274,8 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
_, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET sha256 = ?, installer_url = ?", spoofedSHA, srv.URL+"/installer.zip")
require.NoError(t, err)
_, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET installer_url = ? WHERE id = 2", srv.URL+"/badinstaller")
require.NoError(t, err)
_, err = q.ExecContext(ctx, "UPDATE fleet_library_apps SET installer_url = ? WHERE id = 3", srv.URL+"/timeout")
return err
})
@ -14383,10 +14389,16 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
r := s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 2}, http.StatusInternalServerError)
require.Contains(t, extractServerErrorText(r.Body), "mismatch in maintained app SHA256 hash")
// Should timeout
os.Setenv("FLEET_DEV_MAINTAINED_APPS_INSTALLER_TIMEOUT", "1s")
r = s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", &addFleetMaintainedAppRequest{AppID: 3}, http.StatusGatewayTimeout)
os.Unsetenv("FLEET_DEV_MAINTAINED_APPS_INSTALLER_TIMEOUT")
require.Contains(t, extractServerErrorText(r.Body), "Couldn't upload. Request timeout. Please make sure your server and load balancer timeout is long enough.")
// Add a maintained app to no team
req = &addFleetMaintainedAppRequest{
AppID: 3,
AppID: 4,
SelfService: true,
PreInstallQuery: "SELECT 1",
InstallScript: "echo foo",
@ -14409,7 +14421,7 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
"team_id", "0",
)
mapp, err = s.ds.GetMaintainedAppByID(ctx, 3)
mapp, err = s.ds.GetMaintainedAppByID(ctx, 4)
require.NoError(t, err)
require.Equal(t, 1, resp.Count)
title = resp.SoftwareTitles[0]
@ -14418,9 +14430,9 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
require.Equal(t, mapp.Version, title.SoftwarePackage.Version)
require.Equal(t, "installer.zip", title.SoftwarePackage.Name)
i, err = s.ds.GetSoftwareInstallerMetadataByID(context.Background(), getSoftwareInstallerIDByMAppID(3))
i, err = s.ds.GetSoftwareInstallerMetadataByID(context.Background(), getSoftwareInstallerIDByMAppID(4))
require.NoError(t, err)
require.Equal(t, ptr.Uint(3), i.FleetLibraryAppID)
require.Equal(t, ptr.Uint(4), i.FleetLibraryAppID)
require.Equal(t, mapp.SHA256, i.StorageID)
require.Equal(t, "darwin", i.Platform)
require.NotEmpty(t, i.InstallScriptContentID)

View file

@ -2,8 +2,10 @@ package service
import (
"context"
"errors"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
)
type addFleetMaintainedAppRequest struct {
@ -23,8 +25,14 @@ func (r addFleetMaintainedAppResponse) error() error { return r.Err }
func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*addFleetMaintainedAppRequest)
ctx, cancel := context.WithTimeout(ctx, maintainedapps.InstallerTimeout)
defer cancel()
err := svc.AddFleetMaintainedApp(ctx, req.TeamID, req.AppID, req.InstallScript, req.PreInstallQuery, req.PostInstallScript, req.SelfService)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
err = fleet.NewGatewayTimeoutError("Couldn't upload. Request timeout. Please make sure your server and load balancer timeout is long enough.", err)
}
return &addFleetMaintainedAppResponse{Err: err}, nil
}
return &addFleetMaintainedAppResponse{}, nil