From 5c9afd35083dacff6e7f0f445f6b470ed61f96d1 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 24 Apr 2025 12:08:59 -0500 Subject: [PATCH] Add `hash_sha256` field to "List Software Titles" API response (#28447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Checklist for submitter If some of the following don't apply, delete the relevant line. - [X] 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/Committing-Changes.md#changes-files) for more information. - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) # Details To facilitate using the work of [#23497](https://github.com/fleetdm/fleet/issues/23497) in the new fleetctl generate-gitops command, we need to be able to retrieve the hash values of the current software installers for a team. This PR adds a new `hash_sha256` field to the response for the GET /software/titles API in order to do that. # Testing Updated an existing automated test to check for the presence of the new field when expected. Other tests still pass without it, as it's omitted when the underlying `storage_id` db column is null 👍 I verified that the API response is as expected in Fleet: image # Docs See https://github.com/fleetdm/fleet/pull/28453 --- changes/28443-add-hash-to-software-title-response | 1 + server/datastore/mysql/software_titles.go | 3 ++- server/datastore/mysql/software_titles_test.go | 7 +++++-- server/fleet/software.go | 1 + server/service/integration_enterprise_test.go | 2 ++ 5 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 changes/28443-add-hash-to-software-title-response diff --git a/changes/28443-add-hash-to-software-title-response b/changes/28443-add-hash-to-software-title-response new file mode 100644 index 0000000000..278f87fce5 --- /dev/null +++ b/changes/28443-add-hash-to-software-title-response @@ -0,0 +1 @@ +- Added `hash_sha256` field to the response for the `GET /software/titles` API. diff --git a/server/datastore/mysql/software_titles.go b/server/datastore/mysql/software_titles.go index 27f76e631c..ce285103a9 100644 --- a/server/datastore/mysql/software_titles.go +++ b/server/datastore/mysql/software_titles.go @@ -320,6 +320,7 @@ SELECT si.platform as package_platform, si.url AS package_url, si.install_during_setup as package_install_during_setup, + si.storage_id as package_storage_id, vat.self_service as vpp_app_self_service, vat.adam_id as vpp_app_adam_id, vat.install_during_setup as vpp_install_during_setup, @@ -337,7 +338,7 @@ LEFT JOIN software_titles_host_counts sthc ON sthc.software_title_id = st.id AND WHERE %s -- placeholder for filter based on software installed on hosts + software installers AND (%s) -GROUP BY st.id, package_self_service, package_name, package_version, package_platform, package_url, package_install_during_setup, vpp_app_self_service, vpp_app_adam_id, vpp_app_version, vpp_app_platform, vpp_app_icon_url, vpp_install_during_setup` +GROUP BY st.id, package_self_service, package_name, package_version, package_platform, package_url, package_install_during_setup, package_storage_id, vpp_app_self_service, vpp_app_adam_id, vpp_app_version, vpp_app_platform, vpp_app_icon_url, vpp_install_during_setup` cveJoinType := "LEFT" if opt.VulnerableOnly { diff --git a/server/datastore/mysql/software_titles_test.go b/server/datastore/mysql/software_titles_test.go index e8daff883e..5ca78cdb4e 100644 --- a/server/datastore/mysql/software_titles_test.go +++ b/server/datastore/mysql/software_titles_test.go @@ -1248,6 +1248,7 @@ func testListSoftwareTitlesAllTeams(t *testing.T, ds *Datastore) { TeamID: nil, UserID: user1.ID, ValidatedLabels: &fleet.LabelIdentsWithScope{}, + StorageID: "abc123", }) require.NoError(t, err) @@ -1309,6 +1310,7 @@ func testListSoftwareTitlesAllTeams(t *testing.T, ds *Datastore) { type nameSource struct { name string source string + hash *string } names := make([]nameSource, 0, len(titles)) for _, title := range titles { @@ -1336,12 +1338,13 @@ func testListSoftwareTitlesAllTeams(t *testing.T, ds *Datastore) { assert.Len(t, titles, 3) names = make([]nameSource, 0, len(titles)) for _, title := range titles { - names = append(names, nameSource{name: title.Name, source: title.Source}) + names = append(names, nameSource{name: title.Name, source: title.Source, hash: title.HashSHA256}) } + expectedHash := "abc123" assert.ElementsMatch(t, []nameSource{ {name: "bar", source: "deb_packages"}, {name: "foo", source: "chrome_extensions"}, - {name: "foobar", source: "apps"}, + {name: "foobar", source: "apps", hash: &expectedHash}, }, names) // List software for "team1". Should list Canva for iOS and macOS. diff --git a/server/fleet/software.go b/server/fleet/software.go index 3d6dd517dd..19da31cc15 100644 --- a/server/fleet/software.go +++ b/server/fleet/software.go @@ -240,6 +240,7 @@ type SoftwareTitleListResult struct { // the software installed. It's surfaced in software_titles to match // with existing software entries. BundleIdentifier *string `json:"bundle_identifier,omitempty" db:"bundle_identifier"` + HashSHA256 *string `json:"hash_sha256,omitempty" db:"package_storage_id"` } type SoftwareTitleListOptions struct { diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 37c8b88ee0..18cd440d53 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -9063,6 +9063,8 @@ func (s *integrationEnterpriseTestSuite) TestAllSoftwareTitles() { require.Len(t, resp.SoftwareTitles, 1) require.NotNil(t, resp.SoftwareTitles[0].SoftwarePackage) + // Test that the software titles endpoint returns a SHA256 hash. + require.Equal(t, *resp.SoftwareTitles[0].HashSHA256, "df06d9ce9e2090d9cb2e8cd1f4d7754a803dc452bf93e3204e3acd3b95508628") // Upload an installer for the same software but different arch to a different team payloadRubyTm2 := &fleet.UploadSoftwareInstallerPayload{