mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 00:18:27 +00:00
See #22492 As of today, MSRC's API will 401 if you pass it a JWT it doesn't expect, and by reusing the GitHub API HTTP client for MSRC pulls we were passing the API an unexpected JWT. Wasn't able to reproduce this locally because I didn't need a GitHub token locally to pull release details, while the token is populated in Actions. Was able to repro both the issue and this fix inside Actions on my fork. This also updates to call v3.0 of the API directly, which v2.0 has been redirecting to for awhile. Finally, adds slightly better logging so we know which part of the feed generation process we're in when we're running this in Actions. # 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] Manual QA for all new/changed functionality
184 lines
4.5 KiB
Go
184 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
|
"github.com/fleetdm/fleet/v4/server/vulnerabilities/io"
|
|
"github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc"
|
|
"github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc/parsed"
|
|
"github.com/google/go-github/v37/github"
|
|
)
|
|
|
|
func panicif(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
wd, err := os.Getwd()
|
|
panicif(err)
|
|
|
|
inPath := filepath.Join(wd, "msrc_in")
|
|
err = os.MkdirAll(inPath, 0o755)
|
|
panicif(err)
|
|
|
|
outPath := filepath.Join(wd, "msrc_out")
|
|
err = os.MkdirAll(outPath, 0o755)
|
|
panicif(err)
|
|
|
|
now := time.Now()
|
|
|
|
ctx := context.Background()
|
|
|
|
githubHttp := fleethttp.NewGithubClient()
|
|
ghAPI := io.NewGitHubClient(githubHttp, github.NewClient(githubHttp).Repositories, wd)
|
|
|
|
msrcHttp := fleethttp.NewClient() // don't reuse the GitHub client as it has an OAuth token baked in
|
|
msrcAPI := msrc.NewMSRCClient(msrcHttp, inPath, msrc.MSRCBaseURL)
|
|
|
|
fmt.Println("Downloading existing MSRC bulletins...")
|
|
eBulletins, err := ghAPI.MSRCBulletins(ctx)
|
|
panicif(err)
|
|
|
|
var bulletins []*parsed.SecurityBulletin
|
|
if len(eBulletins) == 0 {
|
|
fmt.Println("None found, backfilling...")
|
|
bulletins, err = backfill(now.Month(), now.Year(), msrcAPI)
|
|
panicif(err)
|
|
} else {
|
|
fmt.Println("Updating existing bulletins")
|
|
bulletins, err = update(now.Month(), now.Year(), eBulletins, msrcAPI, ghAPI)
|
|
panicif(err)
|
|
}
|
|
|
|
fmt.Println("Saving bulletins...")
|
|
for _, b := range bulletins {
|
|
err := serialize(b, now, outPath)
|
|
panicif(err)
|
|
}
|
|
|
|
fmt.Println("Done processing MSRC feed.")
|
|
}
|
|
|
|
// windowsBulletinGracePeriod returns whether we are within the grace period for a MSRC monthly feed to exist.
|
|
//
|
|
// E.g. September 2024 bulletin was released on the 2nd, thus we add some grace period (5 days)
|
|
// for Microsoft to publish the current month bulletin.
|
|
func windowsBulletinGracePeriod(month time.Month, year int) bool {
|
|
now := time.Now()
|
|
return month == now.Month() && year == now.Year() && now.Day() <= 5
|
|
}
|
|
|
|
func update(
|
|
m time.Month,
|
|
y int,
|
|
eBulletins map[io.MetadataFileName]string,
|
|
msrcClient msrc.MSRCAPI,
|
|
ghClient io.GitHubAPI,
|
|
) ([]*parsed.SecurityBulletin, error) {
|
|
fmt.Println("Downloading current feed...")
|
|
currentFeed, err := msrcClient.GetFeed(m, y)
|
|
if err != nil {
|
|
if errors.Is(err, msrc.FeedNotFound) && windowsBulletinGracePeriod(m, y) {
|
|
fmt.Printf("Current month feed %d-%d was not found, skipping...\n", y, m)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var nBulletins map[string]*parsed.SecurityBulletin
|
|
if currentFeed != "" {
|
|
fmt.Println("Parsing current feed...")
|
|
nBulletins, err = msrc.ParseFeed(currentFeed)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var bulletins []*parsed.SecurityBulletin
|
|
for _, url := range eBulletins {
|
|
fPath, err := ghClient.Download(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
eB, err := parsed.UnmarshalBulletin(fPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nB, ok := nBulletins[eB.ProductName]
|
|
if ok {
|
|
if err = eB.Merge(nB); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
bulletins = append(bulletins, eB)
|
|
}
|
|
|
|
return bulletins, nil
|
|
}
|
|
|
|
func backfill(upToM time.Month, upToY int, client msrc.MSRCAPI) ([]*parsed.SecurityBulletin, error) {
|
|
from := time.Date(msrc.MSRCMinYear, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
upTo := time.Date(upToY, upToM+1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
bulletins := make(map[string]*parsed.SecurityBulletin)
|
|
for d := from; d.Before(upTo); d = d.AddDate(0, 1, 0) {
|
|
|
|
fmt.Printf("Downloading feed for %d-%d...\n", d.Year(), d.Month())
|
|
f, err := client.GetFeed(d.Month(), d.Year())
|
|
if err != nil {
|
|
if errors.Is(err, msrc.FeedNotFound) && windowsBulletinGracePeriod(d.Month(), d.Year()) {
|
|
fmt.Printf("Current month feed %d-%d was not found, skipping...\n", d.Year(), d.Month())
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
fmt.Printf("Parsing feed for %d-%d...\n", d.Year(), d.Month())
|
|
r, err := msrc.ParseFeed(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for name, nB := range r {
|
|
eB, ok := bulletins[name]
|
|
if !ok {
|
|
bulletins[name] = nB
|
|
continue
|
|
}
|
|
|
|
if err = eB.Merge(nB); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
var r []*parsed.SecurityBulletin
|
|
for _, b := range bulletins {
|
|
r = append(r, b)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func serialize(b *parsed.SecurityBulletin, d time.Time, dir string) error {
|
|
payload, err := json.Marshal(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fileName := io.MSRCFileName(b.ProductName, d)
|
|
filePath := filepath.Join(dir, fileName)
|
|
|
|
return os.WriteFile(filePath, payload, 0o644)
|
|
}
|