Add hash_sha256 field to "List Software Titles" API response (#28447)

# 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] 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:
<img width="361" alt="image"
src="https://github.com/user-attachments/assets/498b0a95-f35c-4ff5-8831-e4c5c68e5f94"
/>

# Docs

See https://github.com/fleetdm/fleet/pull/28453
This commit is contained in:
Scott Gress 2025-04-24 12:08:59 -05:00 committed by GitHub
parent 2bfb97ac49
commit 5c9afd3508
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 11 additions and 3 deletions

View file

@ -0,0 +1 @@
- Added `hash_sha256` field to the response for the `GET /software/titles` API.

View file

@ -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 {

View file

@ -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.

View file

@ -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 {

View file

@ -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{