Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TUF autoupdater] Remove osquery client dependency #1178

Merged
merged 34 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
62bad56
Split up update library manager into manager + read-only library
RebeccaMahany May 4, 2023
71b2b64
Merge remote-tracking branch 'upstream/main' into becca/tuf-split-up-…
RebeccaMahany May 4, 2023
524a76b
Test installedVersion
RebeccaMahany May 4, 2023
2ce1763
Test for PathToTargetVersionExecutable
RebeccaMahany May 4, 2023
ad45cd3
Clean up library test
RebeccaMahany May 4, 2023
9b89838
Parse tests
RebeccaMahany May 4, 2023
eb6832e
Put back findRelease for now
RebeccaMahany May 4, 2023
9ac0b92
Test sorting versions
RebeccaMahany May 4, 2023
f5c8076
Find install location for app bundle too
RebeccaMahany May 4, 2023
c19051b
Fix up last test
RebeccaMahany May 4, 2023
6da1be7
t.Parallel
RebeccaMahany May 4, 2023
6b378ed
adjust permissions for cache file
RebeccaMahany May 4, 2023
f9fc513
Make log easier to read
RebeccaMahany May 4, 2023
cad0ca5
Evaluate more potential install locations
RebeccaMahany May 4, 2023
e0a2872
Combine libraries
RebeccaMahany May 8, 2023
3809db8
Remove osquerier dependency from library entirely
RebeccaMahany May 8, 2023
f5cbafd
Combine tests
RebeccaMahany May 8, 2023
b219a98
Merge remote-tracking branch 'upstream/main' into becca/tuf-split-up-…
RebeccaMahany May 8, 2023
a9293dc
Merge branch 'main' into becca/tuf-split-up-library
RebeccaMahany May 8, 2023
20d1770
Merge remote-tracking branch 'upstream/main' into becca/tuf-split-up-…
RebeccaMahany May 8, 2023
b5df3ab
Parse osqueryd version on Windows
RebeccaMahany May 8, 2023
8f74879
Merge remote-tracking branch 'upstream/main' into becca/tuf-split-up-…
RebeccaMahany May 8, 2023
801c8d5
Log when we can't find the install location
RebeccaMahany May 9, 2023
6c8e0ec
Fix finding install location in current directory on Windows
RebeccaMahany May 9, 2023
0ea438d
Fix other tests
RebeccaMahany May 9, 2023
cffdffd
Merge remote-tracking branch 'upstream/main' into becca/tuf-split-up-…
RebeccaMahany May 10, 2023
49cf4d0
Remove install version check
RebeccaMahany May 10, 2023
f97a204
Add more useful test data to Test_sortedVersionsInLibrary
RebeccaMahany May 10, 2023
413c0e2
Remove unused var
RebeccaMahany May 10, 2023
c369898
Move retry for osquery query
RebeccaMahany May 10, 2023
72db950
Don't download current running version
RebeccaMahany May 10, 2023
89e62db
Merge remote-tracking branch 'upstream/main' into becca/tuf-split-up-…
RebeccaMahany May 10, 2023
e4755bc
Do version check earlier to avoid thinking download has been performe…
RebeccaMahany May 10, 2023
25956e4
Merge branch 'main' into becca/tuf-split-up-library
RebeccaMahany May 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions pkg/autoupdate/tuf/autoupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type ReleaseFileCustomMetadata struct {
}

type librarian interface {
IsInstallVersion(binary autoupdatableBinary, targetFilename string) bool
Available(binary autoupdatableBinary, targetFilename string) bool
AddToLibrary(binary autoupdatableBinary, targetFilename string, targetMetadata data.TargetFileMeta) error
TidyLibrary()
Expand All @@ -69,8 +70,8 @@ func WithLogger(logger log.Logger) TufAutoupdaterOption {
}
}

func NewTufAutoupdater(k types.Knapsack, metadataHttpClient *http.Client,
mirrorHttpClient *http.Client, osquerier querier, opts ...TufAutoupdaterOption) (*TufAutoupdater, error) {
func NewTufAutoupdater(k types.Knapsack, metadataHttpClient *http.Client, mirrorHttpClient *http.Client,
osquerier querier, opts ...TufAutoupdaterOption) (*TufAutoupdater, error) {
ta := &TufAutoupdater{
channel: k.UpdateChannel(),
interrupt: make(chan struct{}),
Expand All @@ -92,7 +93,7 @@ func NewTufAutoupdater(k types.Knapsack, metadataHttpClient *http.Client,
// If the update directory wasn't set by a flag, use the default location of <launcher root>/updates.
updateDirectory := k.UpdateDirectory()
if updateDirectory == "" {
updateDirectory = filepath.Join(k.RootDirectory(), "updates")
updateDirectory = DefaultLibraryDirectory(k.RootDirectory())
}
ta.libraryManager, err = newUpdateLibraryManager(k.MirrorServerURL(), mirrorHttpClient, updateDirectory, osquerier, ta.logger)
if err != nil {
Expand Down Expand Up @@ -138,6 +139,10 @@ func LocalTufDirectory(rootDirectory string) string {
return filepath.Join(rootDirectory, tufDirectoryName)
}

func DefaultLibraryDirectory(rootDirectory string) string {
return filepath.Join(rootDirectory, "updates")
}

// Execute is the TufAutoupdater run loop. It periodically checks to see if a new release
// has been published; less frequently, it removes old/outdated TUF errors from the bucket
// we store them in.
Expand Down Expand Up @@ -239,6 +244,10 @@ func (ta *TufAutoupdater) downloadUpdate(binary autoupdatableBinary, targets dat
return "", fmt.Errorf("could not find release: %w", err)
}

if ta.libraryManager.IsInstallVersion(binary, release) {
return "", nil
}

if ta.libraryManager.Available(binary, release) {
return "", nil
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/autoupdate/tuf/autoupdate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ func TestExecute(t *testing.T) {
mockLibraryManager := NewMocklibrarian(t)
autoupdater.libraryManager = mockLibraryManager
mockLibraryManager.On("TidyLibrary").Return().Once()
mockLibraryManager.On("IsInstallVersion", binaryOsqueryd, fmt.Sprintf("osqueryd-%s.tar.gz", testReleaseVersion)).Return(false)
mockLibraryManager.On("IsInstallVersion", binaryLauncher, fmt.Sprintf("launcher-%s.tar.gz", testReleaseVersion)).Return(false)
mockLibraryManager.On("Available", binaryOsqueryd, fmt.Sprintf("osqueryd-%s.tar.gz", testReleaseVersion)).Return(false)
mockLibraryManager.On("Available", binaryLauncher, fmt.Sprintf("launcher-%s.tar.gz", testReleaseVersion)).Return(false)
mockLibraryManager.On("AddToLibrary", binaryOsqueryd, fmt.Sprintf("osqueryd-%s.tar.gz", testReleaseVersion), osquerydMetadata).Return(nil)
Expand Down
101 changes: 27 additions & 74 deletions pkg/autoupdate/tuf/library_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"

"github.com/Masterminds/semver"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/kolide/kit/fsutil"
Expand All @@ -34,25 +31,16 @@ type querier interface {
// location in the library specified by the version associated with that update.
// It also ensures that old updates are removed when they are no longer needed.
type updateLibraryManager struct {
*readOnlyLibrary
mirrorUrl string // dl.kolide.co
mirrorClient *http.Client
baseDir string
stagingDir string
osquerier querier // used to query for current running osquery version
lock *libraryLock
logger log.Logger
}

func newUpdateLibraryManager(mirrorUrl string, mirrorClient *http.Client, baseDir string, osquerier querier, logger log.Logger) (*updateLibraryManager, error) {
ulm := updateLibraryManager{
mirrorUrl: mirrorUrl,
mirrorClient: mirrorClient,
baseDir: baseDir,
osquerier: osquerier,
lock: newLibraryLock(),
logger: log.With(logger, "component", "tuf_autoupdater_library_manager"),
}

// Ensure the updates directory exists
if err := os.MkdirAll(baseDir, 0755); err != nil {
return nil, fmt.Errorf("could not make base directory for updates library: %w", err)
Expand All @@ -63,45 +51,31 @@ func newUpdateLibraryManager(mirrorUrl string, mirrorClient *http.Client, baseDi
if err != nil {
return nil, fmt.Errorf("could not make staged updates directory: %w", err)
}
ulm.stagingDir = stagingDir

// Create the update library
for _, binary := range binaries {
if err := os.MkdirAll(ulm.updatesDirectory(binary), 0755); err != nil {
if err := os.MkdirAll(filepath.Join(baseDir, string(binary)), 0755); err != nil {
return nil, fmt.Errorf("could not make updates directory for %s: %w", binary, err)
}
}

return &ulm, nil
}

// updatesDirectory returns the update library location for the given binary.
func (ulm *updateLibraryManager) updatesDirectory(binary autoupdatableBinary) string {
return filepath.Join(ulm.baseDir, string(binary))
}

// Available determines if the given target is already available, either as the currently-running
// binary or within the update library.
func (ulm *updateLibraryManager) Available(binary autoupdatableBinary, targetFilename string) bool {
// Check to see if the current running version is the version we were requested to add;
// return early if it is, but don't error out if we can't determine the current version.
currentVersion, err := ulm.currentRunningVersion(binary)
// Create the nested read-only library
readOnlyLibrary, err := newReadOnlyLibrary(baseDir, logger)
if err != nil {
level.Debug(ulm.logger).Log("msg", "could not get current running version", "binary", binary, "err", err)
} else if currentVersion == ulm.versionFromTarget(binary, targetFilename) {
// We don't need to download the current running version because it already exists,
// either in this updates library or in the original install location.
return true
return nil, fmt.Errorf("could not create read-only library: %w", err)
}

return ulm.alreadyAdded(binary, targetFilename)
}

// alreadyAdded checks if the given target already exists in the update library.
func (ulm *updateLibraryManager) alreadyAdded(binary autoupdatableBinary, targetFilename string) bool {
updateDirectory := filepath.Join(ulm.updatesDirectory(binary), ulm.versionFromTarget(binary, targetFilename))
ulm := updateLibraryManager{
readOnlyLibrary: readOnlyLibrary,
mirrorUrl: mirrorUrl,
mirrorClient: mirrorClient,
stagingDir: stagingDir,
osquerier: osquerier,
lock: newLibraryLock(),
logger: log.With(logger, "component", "tuf_autoupdater_library_manager"),
}

return autoupdate.CheckExecutable(context.TODO(), executableLocation(updateDirectory, binary), "--version") == nil
return &ulm, nil
}

// AddToLibrary adds the given target file to the library for the given binary,
Expand All @@ -111,6 +85,10 @@ func (ulm *updateLibraryManager) AddToLibrary(binary autoupdatableBinary, target
ulm.lock.Lock(binary)
defer ulm.lock.Unlock(binary)

if ulm.IsInstallVersion(binary, targetFilename) {
return nil
}

if ulm.Available(binary, targetFilename) {
return nil
}
Expand All @@ -130,14 +108,6 @@ func (ulm *updateLibraryManager) AddToLibrary(binary autoupdatableBinary, target
return nil
}

// versionFromTarget extracts the semantic version for an update from its filename.
func (ulm *updateLibraryManager) versionFromTarget(binary autoupdatableBinary, targetFilename string) string {
// The target is in the form `launcher-0.13.6.tar.gz` -- trim the prefix and the file extension to return the version
prefixToTrim := fmt.Sprintf("%s-", binary)

return strings.TrimSuffix(strings.TrimPrefix(targetFilename, prefixToTrim), ".tar.gz")
}

// stageAndVerifyUpdate downloads the update indicated by `targetFilename` and verifies it against
// the given, validated local metadata.
func (ulm *updateLibraryManager) stageAndVerifyUpdate(binary autoupdatableBinary, targetFilename string, localTargetMetadata data.TargetFileMeta) (string, error) {
Expand Down Expand Up @@ -184,7 +154,7 @@ func (ulm *updateLibraryManager) stageAndVerifyUpdate(binary autoupdatableBinary
// moveVerifiedUpdate untars the update and performs final checks to make sure that it's a valid, working update.
// Finally, it moves the update to its correct versioned location in the update library for the given binary.
func (ulm *updateLibraryManager) moveVerifiedUpdate(binary autoupdatableBinary, targetFilename string, stagedUpdate string) error {
targetVersion := ulm.versionFromTarget(binary, targetFilename)
targetVersion := versionFromTarget(binary, targetFilename)
stagedVersionedDirectory := filepath.Join(ulm.stagingDir, targetVersion)
if err := os.MkdirAll(stagedVersionedDirectory, 0755); err != nil {
return fmt.Errorf("could not create directory %s for untarring and validating new update: %w", stagedVersionedDirectory, err)
Expand Down Expand Up @@ -320,50 +290,33 @@ func (ulm *updateLibraryManager) tidyUpdateLibrary(binary autoupdatableBinary, c

const numberOfVersionsToKeep = 3

rawVersionsInLibrary, err := filepath.Glob(filepath.Join(ulm.updatesDirectory(binary), "*"))
versionsInLibrary, invalidVersionsInLibrary, err := ulm.sortedVersionsInLibrary(binary)
if err != nil {
level.Debug(ulm.logger).Log("msg", "could not glob for updates to tidy updates library", "err", err)
level.Debug(ulm.logger).Log("msg", "could not get versions in library to tidy update library", "err", err)
return
}

versionsInLibrary := make([]*semver.Version, 0)
for _, rawVersion := range rawVersionsInLibrary {
v, err := semver.NewVersion(filepath.Base(rawVersion))
if err != nil {
level.Debug(ulm.logger).Log("msg", "updates library contains invalid semver", "err", err, "library_path", rawVersion)
ulm.removeUpdate(binary, filepath.Base(rawVersion))
continue
}

versionsInLibrary = append(versionsInLibrary, v)
for _, invalidVersion := range invalidVersionsInLibrary {
level.Debug(ulm.logger).Log("msg", "updates library contains invalid version", "err", err, "library_path", invalidVersion)
ulm.removeUpdate(binary, invalidVersion)
}

if len(versionsInLibrary) <= numberOfVersionsToKeep {
return
}

// Sort the versions (ascending order)
sort.Sort(semver.Collection(versionsInLibrary))

// Loop through, looking at the most recent versions first, and remove all once we hit nonCurrentlyRunningVersionsKept valid executables
nonCurrentlyRunningVersionsKept := 0
for i := len(versionsInLibrary) - 1; i >= 0; i -= 1 {
// Always keep the current running executable
if versionsInLibrary[i].Original() == currentRunningVersion {
if versionsInLibrary[i] == currentRunningVersion {
continue
}

// If we've already hit the number of versions to keep, then start to remove the older ones.
// We want to keep numberOfVersionsToKeep total, saving a spot for the currently running version.
if nonCurrentlyRunningVersionsKept >= numberOfVersionsToKeep-1 {
ulm.removeUpdate(binary, versionsInLibrary[i].Original())
continue
}

// Only keep good executables
versionDir := filepath.Join(ulm.updatesDirectory(binary), versionsInLibrary[i].Original())
if err := autoupdate.CheckExecutable(context.TODO(), executableLocation(versionDir, binary), "--version"); err != nil {
ulm.removeUpdate(binary, versionsInLibrary[i].Original())
ulm.removeUpdate(binary, versionsInLibrary[i])
continue
}

Expand Down
Loading