fleet/pkg/patch_policy/patch_policy.go
Jonathan Katz 0d15fd6cd6
Override patch policy query (#42322)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #41815
### Changes
- Extracted patch policy creation to `pkg/patch_policy`
- Added a `patch_query` column to the `software_installers` table
- By default that column is empty, and patch policies will generate with
the default query if so
- On app manifest ingestion, the appropriate entry in
`software_installers` will save the override "patch" query from the
manifest in patch_query

# 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/guides/committing-changes.md#changes-files)
for more information.

- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [ ] QA'd all new/changed functionality manually
- Relied on integration test for FMA version pinning

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [ ] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
2026-03-25 10:32:41 -04:00

87 lines
2.3 KiB
Go

package patch_policy
import (
"errors"
"fmt"
"strings"
"github.com/fleetdm/fleet/v4/server/fleet"
)
type PolicyData struct {
Name string
Query string
Platform string
Description string
Resolution string
Version string
}
const versionVariable = "$FMA_VERSION"
var (
ErrEmptyQuery = errors.New("query should not be empty")
ErrWrongPlatform = errors.New("platform should be darwin or windows")
)
// GenerateFromManifest replaces the $FMA_VERSION variable and checks platform
func GenerateFromManifest(p PolicyData) (string, error) {
if p.Query == "" {
return "", ErrEmptyQuery
}
// Version is extracted from the manifest so this should be safe to run as an osquery query
query := strings.ReplaceAll(p.Query, versionVariable, p.Version)
switch p.Platform {
case "darwin":
case "windows":
default:
return "", ErrWrongPlatform
}
return query, nil
}
// GenerateFromInstaller creates a patch policy with all fields from an installer
func GenerateFromInstaller(p PolicyData, installer *fleet.SoftwareInstaller) (*PolicyData, error) {
// use the patch policy query from the app manifest if available
query := installer.PatchQuery
if p.Description == "" {
p.Description = "Outdated software might introduce security vulnerabilities or compatibility issues."
}
if p.Resolution == "" {
p.Resolution = "Install the latest version from self-service."
}
switch installer.Platform {
case "darwin":
if p.Name == "" {
p.Name = fmt.Sprintf("macOS - %s up to date", installer.SoftwareTitle)
}
if installer.PatchQuery == "" {
query = fmt.Sprintf(
"SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = '%s' AND version_compare(bundle_short_version, '%s') < 0);",
installer.BundleIdentifier,
installer.Version,
)
}
case "windows":
if p.Name == "" {
p.Name = fmt.Sprintf("Windows - %s up to date", installer.SoftwareTitle)
}
if installer.PatchQuery == "" {
// TODO: use upgrade code to improve accuracy?
query = fmt.Sprintf(
"SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = '%s' AND version_compare(version, '%s') < 0);",
installer.SoftwareTitle,
installer.Version,
)
}
default:
return nil, ErrWrongPlatform
}
return &PolicyData{Query: query, Platform: installer.Platform, Name: p.Name, Description: p.Description, Resolution: p.Resolution}, nil
}