mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 06:48:54 +00:00
154 lines
3.9 KiB
Go
154 lines
3.9 KiB
Go
package oval
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/download"
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
"github.com/google/go-github/v37/github"
|
|
)
|
|
|
|
func ghNvdFileGetter() func(string) (io.ReadCloser, error) {
|
|
ghClient := fleethttp.NewGithubClient()
|
|
return func(file string) (io.ReadCloser, error) {
|
|
src, r, err := github.NewClient(ghClient).Repositories.DownloadContents(
|
|
context.Background(), "fleetdm", "nvd", file, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Even if err is nil, the request can fail
|
|
if r.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("github http status error: %d", r.StatusCode)
|
|
}
|
|
|
|
return src, nil
|
|
}
|
|
}
|
|
|
|
func downloadDecompressed(client *http.Client) func(string, string) error {
|
|
return func(u, dstPath string) error {
|
|
parsedUrl, err := url.Parse(u)
|
|
if err != nil {
|
|
return fmt.Errorf("url parse: %w", err)
|
|
}
|
|
|
|
err = download.DownloadAndExtract(client, parsedUrl, dstPath)
|
|
if err != nil {
|
|
return fmt.Errorf("download and extract url %s: %w", parsedUrl, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func whatToDownload(osVers *fleet.OSVersions, existing map[string]bool, date time.Time) []Platform {
|
|
var r []Platform
|
|
for _, os := range osVers.OSVersions {
|
|
platform := NewPlatform(os.Platform, os.Name)
|
|
_, ok := existing[platform.ToFilename(date, "json")]
|
|
if !ok && platform.IsSupported() {
|
|
r = append(r, platform)
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
// removeOldDefs walks 'path' removing any old oval definitions, returns a set containing
|
|
// definitions that are up to date according to 'date'
|
|
func removeOldDefs(date time.Time, path string) (map[string]bool, error) {
|
|
dateSuffix := fmt.Sprintf("-%d_%02d_%02d.json", date.Year(), date.Month(), date.Day())
|
|
upToDate := make(map[string]bool)
|
|
|
|
err := filepath.WalkDir(path, func(path string, d os.DirEntry, err error) error {
|
|
if strings.HasPrefix(filepath.Base(path), OvalFilePrefix) {
|
|
if strings.HasSuffix(path, dateSuffix) {
|
|
upToDate[filepath.Base(path)] = true
|
|
} else {
|
|
err := os.Remove(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return upToDate, nil
|
|
}
|
|
|
|
// Sync syncs the oval definitions for one or more platforms.
|
|
// If 'platforms' is nil, then all supported platforms will be synched.
|
|
func Sync(dstDir string, platforms []Platform) error {
|
|
sources, err := getOvalSources(ghNvdFileGetter())
|
|
if err != nil {
|
|
return fmt.Errorf("getOvalSources: %w", err)
|
|
}
|
|
|
|
if platforms == nil {
|
|
for s := range sources {
|
|
platforms = append(platforms, s)
|
|
}
|
|
}
|
|
|
|
client := fleethttp.NewClient()
|
|
dwn := downloadDecompressed(client)
|
|
for _, platform := range platforms {
|
|
defFile, err := downloadDefinitions(sources, platform, dwn)
|
|
if err != nil {
|
|
return fmt.Errorf("downloadDefinitions: %w", err)
|
|
}
|
|
|
|
dstFile := strings.Replace(filepath.Base(defFile), ".xml", ".json", 1)
|
|
dstPath := filepath.Join(dstDir, dstFile)
|
|
err = parseDefinitions(platform, defFile, dstPath)
|
|
if err != nil {
|
|
return fmt.Errorf("parseDefinitions: %w", err)
|
|
}
|
|
|
|
err = os.Remove(defFile)
|
|
if err != nil {
|
|
return fmt.Errorf("removing %s: %w", defFile, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Refresh checks all local OVAL artifacts contained in 'vulnPath' deleting the old and downloading
|
|
// any missing definitions based on today's date and all the hosts' platforms/os versions contained in 'osVersions'.
|
|
// Returns a slice of Platforms of the newly downloaded OVAL files.
|
|
func Refresh(
|
|
ctx context.Context,
|
|
versions *fleet.OSVersions,
|
|
vulnPath string,
|
|
) ([]Platform, error) {
|
|
now := time.Now()
|
|
|
|
existing, err := removeOldDefs(now, vulnPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
toDownload := whatToDownload(versions, existing, now)
|
|
if len(toDownload) > 0 {
|
|
err = Sync(vulnPath, toDownload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return toDownload, nil
|
|
}
|