fleet/server/vulnerabilities/msrc/msrc_api.go
Ian Littman e8a6456f13
Don't reuse GitHub HTTP client to pull MSRC feeds (#22493)
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
2024-09-27 21:23:48 -05:00

83 lines
2 KiB
Go

package msrc
import (
"errors"
"fmt"
"net/http"
"net/url"
"path/filepath"
"time"
"github.com/fleetdm/fleet/v4/pkg/download"
)
const (
// Pre 2020 there are some weirdness around the way the 'Supersedes' field is defined for Vulnerabilities, sometimes
// it does not reference a KBID.
MSRCMinYear = 2020
MSRCBaseURL = `https://api.msrc.microsoft.com`
)
// MSRCAPI allows users to interact with MSRC resources
type MSRCAPI interface {
GetFeed(time.Month, int) (string, error)
}
// FeedNotFound is returned when a MSRC feed was not found.
//
// E.g. September 2024 bulleting was released on the 2nd.
var FeedNotFound = errors.New("feed not found")
type MSRCClient struct {
client *http.Client
workDir string
baseURL string
}
// NewMSRCClient returns a new MSRCClient that will store all downloaded files in 'workDir' and will
// use 'baseURL' for doing http requests.
func NewMSRCClient(
client *http.Client,
workDir string,
baseURL string,
) MSRCClient {
return MSRCClient{client: client, workDir: workDir, baseURL: baseURL}
}
func feedName(date time.Time) string {
return date.Format("2006-Jan")
}
func (msrc MSRCClient) getURL(date time.Time) (*url.URL, error) {
return url.Parse(msrc.baseURL + "/cvrf/v3.0/document/" + feedName(date))
}
// GetFeed downloads the MSRC security feed for 'month' and 'year' into 'workDir', returning the
// path of the downloaded file.
func (msrc MSRCClient) GetFeed(month time.Month, year int) (string, error) {
d := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
minD := time.Date(MSRCMinYear, time.January, 1, 0, 0, 0, 0, time.UTC)
if d.Before(minD) {
return "", fmt.Errorf("min allowed date is %s", minD)
}
if d.After(time.Now().UTC()) {
return "", errors.New("date can't be in the future")
}
dst := filepath.Join(msrc.workDir, fmt.Sprintf("%s.xml", feedName(d)))
u, err := msrc.getURL(d)
if err != nil {
return "", err
}
if err := download.Download(msrc.client, u, dst); err != nil {
if errors.Is(err, download.NotFound) {
return "", FeedNotFound
}
return "", err
}
return dst, nil
}