diff --git a/ee/server/service/software_installers_test.go b/ee/server/service/software_installers_test.go index 72aabbb898..f11e1c8de9 100644 --- a/ee/server/service/software_installers_test.go +++ b/ee/server/service/software_installers_test.go @@ -2,8 +2,17 @@ package service import ( "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" "testing" + ma "github.com/fleetdm/fleet/v4/ee/maintained-apps" "github.com/fleetdm/fleet/v4/server/authz" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" @@ -201,6 +210,54 @@ func TestSoftwareInstallerPayloadFromSlug(t *testing.T) { ds := new(mock.Store) svc := newTestService(t, ds) + installerBytes := []byte("1password") + h := sha256.New() + _, err := h.Write(installerBytes) + require.NoError(t, err) + onePasswordSHA := hex.EncodeToString(h.Sum(nil)) + + manifestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + slug := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, ".json"), "/") + + var versions []*ma.FMAManifestApp + versions = append(versions, &ma.FMAManifestApp{ + Version: "1", + Queries: ma.FMAQueries{ + Exists: "SELECT 1 FROM osquery_info;", + }, + InstallerURL: fmt.Sprintf("/installer-%s.zip", slug), + InstallScriptRef: "installscript", + UninstallScriptRef: "uninstallscript", + DefaultCategories: []string{"Productivity"}, + }) + + manifest := ma.FMAManifestFile{ + Versions: versions, + Refs: map[string]string{ + "installscript": "echo 'installing'", + "uninstallscript": "echo 'uninstalling'", + }, + } + + switch slug { + case "": + w.WriteHeader(http.StatusNotFound) + return + + case "1password/darwin": + manifest.Versions[0].SHA256 = onePasswordSHA + + case "google-chrome/darwin": + manifest.Versions[0].SHA256 = "no_check" + } + + err := json.NewEncoder(w).Encode(manifest) + require.NoError(t, err) + })) + t.Cleanup(manifestServer.Close) + os.Setenv("FLEET_DEV_MAINTAINED_APPS_BASE_URL", manifestServer.URL) + defer os.Unsetenv("FLEET_DEV_MAINTAINED_APPS_BASE_URL") + ds.GetMaintainedAppBySlugFunc = func(ctx context.Context, slug string, teamID *uint) (*fleet.MaintainedApp, error) { return &fleet.MaintainedApp{ ID: 1, @@ -211,10 +268,10 @@ func TestSoftwareInstallerPayloadFromSlug(t *testing.T) { }, nil } payload := fleet.SoftwareInstallerPayload{Slug: ptr.String("1password/darwin")} - err := svc.softwareInstallerPayloadFromSlug(context.Background(), &payload, nil) + err = svc.softwareInstallerPayloadFromSlug(context.Background(), &payload, nil) require.NoError(t, err) - assert.Contains(t, payload.URL, "1password") - assert.NotEmpty(t, payload.SHA256) + assert.NotEmpty(t, payload.URL) + assert.Equal(t, onePasswordSHA, payload.SHA256) assert.NotEmpty(t, payload.InstallScript) assert.NotEmpty(t, payload.UninstallScript) assert.True(t, payload.FleetMaintained) @@ -232,7 +289,7 @@ func TestSoftwareInstallerPayloadFromSlug(t *testing.T) { payload = fleet.SoftwareInstallerPayload{Slug: ptr.String("google-chrome/darwin")} err = svc.softwareInstallerPayloadFromSlug(context.Background(), &payload, nil) require.NoError(t, err) - assert.Contains(t, payload.URL, "google.com") + assert.NotEmpty(t, payload.URL) assert.Empty(t, payload.SHA256) assert.NotEmpty(t, payload.InstallScript) assert.NotEmpty(t, payload.UninstallScript) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 1f8851d2b6..32a4b10173 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -11828,6 +11828,22 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { ////////////////////////// // Do a request with a fleet maintained app ////////////////////////// + oldTransport := http.DefaultTransport + onePassMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if _, err := w.Write([]byte("mocked content")); err != nil { + // Handle the error, e.g., log it or fail the test + t.Fatalf("failed to write response: %v", err) + } + })) + defer onePassMockServer.Close() + mockTransport := &mockRoundTripper{ + mockServer: onePassMockServer.URL, + origBaseURL: "https://downloads.1password.com", + next: http.DefaultTransport, + } + http.DefaultTransport = mockTransport + // https://downloads.1password.com/mac/1Password-8.10.82-aarch64.zip maintained1, err := s.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ Name: "1Password", Slug: "1password/darwin", @@ -11835,13 +11851,6 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { UniqueIdentifier: "com.1password.1password", }) require.NoError(t, err) - maintained2, err := s.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ - Name: "Google Chrome", - Slug: "google-chrome/darwin", - Platform: "darwin", - UniqueIdentifier: "com.google.Chrome", - }) - require.NoError(t, err) // basic fleet maintained app softwareToInstall = []*fleet.SoftwareInstallerPayload{ @@ -11854,19 +11863,6 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { require.NotNil(t, packages[0].URL) require.Nil(t, packages[0].TeamID) - // maintained app with no_check for sha - softwareToInstall = []*fleet.SoftwareInstallerPayload{ - {Slug: &maintained2.Slug}, - } - s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse) - packages = waitBatchSetSoftwareInstallersCompleted(t, s, "", batchResponse.RequestUUID) - require.Len(t, packages, 1) - require.NotNil(t, packages[0].TitleID) - require.NotNil(t, packages[0].URL) - require.NotNil(t, packages[0].HashSHA256) - require.NotEqual(t, "no_check", packages[0].HashSHA256) - require.Nil(t, packages[0].TeamID) - // with a team s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse, "team_name", tm.Name) packages = waitBatchSetSoftwareInstallersCompleted(t, s, tm.Name, batchResponse.RequestUUID) @@ -11892,6 +11888,50 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() { require.Len(t, meta.LabelsIncludeAny, 1) require.Equal(t, lblA.ID, meta.LabelsIncludeAny[0].LabelID) require.Equal(t, lblA.Name, meta.LabelsIncludeAny[0].LabelName) + + // maintained app with no_check for sha + // https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg + maintained2, err := s.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ + Name: "Google Chrome", + Slug: "google-chrome/darwin", + Platform: "darwin", + UniqueIdentifier: "com.google.Chrome", + }) + require.NoError(t, err) + + chromeBytes := []byte("chrome installer content") + h := sha256.New() + _, err = h.Write(chromeBytes) + require.NoError(t, err) + chromeSHA := hex.EncodeToString(h.Sum(nil)) + chromeMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if _, err := w.Write(chromeBytes); err != nil { + // Handle the error, e.g., log it or fail the test + t.Fatalf("failed to write response: %v", err) + } + })) + defer chromeMockServer.Close() + mockTransport = &mockRoundTripper{ + mockServer: chromeMockServer.URL, + origBaseURL: "https://dl.google.com", + next: http.DefaultTransport, + } + http.DefaultTransport = mockTransport + + softwareToInstall = []*fleet.SoftwareInstallerPayload{ + {Slug: &maintained2.Slug}, + } + s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse) + packages = waitBatchSetSoftwareInstallersCompleted(t, s, "", batchResponse.RequestUUID) + require.Len(t, packages, 1) + require.NotNil(t, packages[0].TitleID) + require.NotNil(t, packages[0].URL) + // Given that the manifest returns no_check for chrome, the SHA should be taken of the downloaded file. + require.Equal(t, chromeSHA, packages[0].HashSHA256) + require.Nil(t, packages[0].TeamID) + + http.DefaultTransport = oldTransport } func waitBatchSetSoftwareInstallersCompleted(t *testing.T, s *integrationEnterpriseTestSuite, teamName string, requestUUID string) []fleet.SoftwarePackageResponse {