fleet/server/vulnerabilities/msrc/sync.go
Ian Littman cc352970c0
Dedupe MSRC downloads/deletes when enrolled hosts include multiple builds of the same version of Windows (#27060)
For #25090.

# 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)
- [x] Added/updated automated tests
- [x] A detailed QA plan exists on the associated ticket (if it isn't
there, work with the product group's QA engineer to add it)
- [x] Manual QA for all new/changed functionality
2025-03-12 13:22:56 -05:00

122 lines
2.8 KiB
Go

package msrc
import (
"context"
"fmt"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/io"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc/parsed"
"github.com/google/go-github/v37/github"
)
// bulletinsDelta returns what bulletins should be downloaded from GH and what bulletins should be removed
// from the local file system based what OSes are installed, what local bulletins we have and what
// remote bulletins exist.
func bulletinsDelta(
os []fleet.OperatingSystem,
local []io.MetadataFileName,
remote []io.MetadataFileName,
) (
[]io.MetadataFileName,
[]io.MetadataFileName,
) {
if len(os) == 0 {
return remote, nil
}
var matching []io.MetadataFileName
for _, r := range remote {
for _, o := range os {
product := parsed.NewProductFromOS(o)
if r.ProductName() == product.Name() {
matching = append(matching, r)
}
}
}
downloadSet := map[io.MetadataFileName]struct{}{}
deleteSet := map[io.MetadataFileName]struct{}{}
for _, m := range matching {
var found bool
for _, l := range local {
if m.ProductName() == l.ProductName() {
found = true
// out of date
if l.Before(m) {
downloadSet[m] = struct{}{}
deleteSet[l] = struct{}{}
}
break
}
}
if !found {
downloadSet[m] = struct{}{}
}
}
var toDownload []io.MetadataFileName
var toDelete []io.MetadataFileName
for filename := range downloadSet {
toDownload = append(toDownload, filename)
}
for filename := range deleteSet {
toDelete = append(toDelete, filename)
}
return toDownload, toDelete
}
// SyncFromGithub syncs the local msrc security bulletins (contained in dstDir) for one or more operating
// systems with the security bulletin published in Github.
//
// If 'os' is nil, then all security bulletins will be synched.
func SyncFromGithub(ctx context.Context, dstDir string, os []fleet.OperatingSystem) error {
client := fleethttp.NewGithubClient()
rep := github.NewClient(client).Repositories
gh := io.NewGitHubClient(client, rep, dstDir)
fs := io.NewFSClient(dstDir)
if err := sync(ctx, os, fs, gh); err != nil {
return fmt.Errorf("msrc sync: %w", err)
}
return nil
}
func sync(
ctx context.Context,
os []fleet.OperatingSystem,
fsClient io.FSAPI,
ghClient io.GitHubAPI,
) error {
remoteURLs, err := ghClient.MSRCBulletins(ctx)
if err != nil {
return err
}
var remote []io.MetadataFileName
for r := range remoteURLs {
remote = append(remote, r)
}
local, err := fsClient.MSRCBulletins()
if err != nil {
return err
}
toDownload, toDelete := bulletinsDelta(os, local, remote)
for _, b := range toDownload {
if _, err := ghClient.Download(remoteURLs[b]); err != nil {
return err
}
}
for _, d := range toDelete {
if err := fsClient.Delete(d); err != nil {
return err
}
}
return nil
}