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

Move TUF test setup to new package #1188

Merged
merged 1 commit into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
183 changes: 2 additions & 181 deletions pkg/autoupdate/tuf/autoupdate_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package tuf

import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
Expand All @@ -21,9 +18,9 @@ import (
storageci "github.com/kolide/launcher/pkg/agent/storage/ci"
"github.com/kolide/launcher/pkg/agent/types"
typesmocks "github.com/kolide/launcher/pkg/agent/types/mocks"
tufci "github.com/kolide/launcher/pkg/autoupdate/tuf/ci"
"github.com/kolide/launcher/pkg/threadsafebuffer"
"github.com/stretchr/testify/require"
"github.com/theupdateframework/go-tuf"
)

func TestNewTufAutoupdater(t *testing.T) {
Expand Down Expand Up @@ -66,7 +63,7 @@ func TestExecute(t *testing.T) {

testRootDir := t.TempDir()
testReleaseVersion := "1.2.3"
tufServerUrl, rootJson := initLocalTufServer(t, testReleaseVersion)
tufServerUrl, rootJson := tufci.InitRemoteTufServer(t, testReleaseVersion)
s := setupStorage(t)
mockKnapsack := typesmocks.NewKnapsack(t)
mockKnapsack.On("RootDirectory").Return(testRootDir)
Expand Down Expand Up @@ -235,182 +232,6 @@ func Test_cleanUpOldErrors(t *testing.T) {
require.Equal(t, 1, keyCount, "cleanup routine did not clean up correct number of old errors")
}

// Sets up a local TUF repo with some targets to serve metadata about; returns the URL
// of a test HTTP server to serve that metadata and the root JSON needed to initialize
// a client.
func initLocalTufServer(t *testing.T, testReleaseVersion string) (tufServerURL string, rootJson []byte) {
tufDir := t.TempDir()

// Initialize repo with store
localStore := tuf.FileSystemStore(tufDir, nil)
repo, err := tuf.NewRepo(localStore)
require.NoError(t, err, "could not create new tuf repo")
require.NoError(t, repo.Init(false), "could not init new tuf repo")

// Gen keys
_, err = repo.GenKey("root")
require.NoError(t, err, "could not gen root key")
_, err = repo.GenKey("targets")
require.NoError(t, err, "could not gen targets key")
_, err = repo.GenKey("snapshot")
require.NoError(t, err, "could not gen snapshot key")
_, err = repo.GenKey("timestamp")
require.NoError(t, err, "could not gen timestamp key")

// Sign the root metadata file
require.NoError(t, repo.Sign("root.json"), "could not sign root metadata file")

// Create test binaries and release files per binary and per release channel
for _, b := range binaries {
for _, v := range []string{"0.1.1", "0.12.3-deadbeef", testReleaseVersion} {
binaryFileName := fmt.Sprintf("%s-%s.tar.gz", b, v)

// Create a valid test binary -- an archive of an executable with the proper directory structure
// that will actually run -- if this is the release version we care about. If this is not the
// release version we care about, then just create a small text file since it won't be downloaded
// and evaluated.
if v == testReleaseVersion {
// Create test binary and copy it to the staged targets directory
stagedTargetsDir := filepath.Join(tufDir, "staged", "targets", string(b), runtime.GOOS)
executablePath := executableLocation(stagedTargetsDir, b)
require.NoError(t, os.MkdirAll(filepath.Dir(executablePath), 0777), "could not make staging directory")
copyBinary(t, executablePath)
require.NoError(t, os.Chmod(executablePath, 0755))

// Compress the binary or app bundle
compress(t, binaryFileName, stagedTargetsDir, stagedTargetsDir, b)
} else {
// Create and commit a test binary
require.NoError(t, os.MkdirAll(filepath.Join(tufDir, "staged", "targets", string(b), runtime.GOOS), 0777), "could not make staging directory")
err = os.WriteFile(filepath.Join(tufDir, "staged", "targets", string(b), runtime.GOOS, binaryFileName), []byte("I am a test target"), 0777)
require.NoError(t, err, "could not write test target binary to temp dir")
}

// Add the target
require.NoError(t, repo.AddTarget(fmt.Sprintf("%s/%s/%s", b, runtime.GOOS, binaryFileName), nil), "could not add test target binary to tuf")

// Commit
require.NoError(t, repo.Snapshot(), "could not take snapshot")
require.NoError(t, repo.Timestamp(), "could not take timestamp")
require.NoError(t, repo.Commit(), "could not commit")

if v != testReleaseVersion {
continue
}

// If this is our release version, also create and commit a test release file
for _, c := range []string{"stable", "beta", "nightly"} {
require.NoError(t, os.MkdirAll(filepath.Join(tufDir, "staged", "targets", string(b), runtime.GOOS, c), 0777), "could not make staging directory")
err = os.WriteFile(filepath.Join(tufDir, "staged", "targets", string(b), runtime.GOOS, c, "release.json"), []byte("{}"), 0777)
require.NoError(t, err, "could not write test target release file to temp dir")
customMetadata := fmt.Sprintf("{\"target\":\"%s/%s/%s\"}", b, runtime.GOOS, binaryFileName)
require.NoError(t, repo.AddTarget(fmt.Sprintf("%s/%s/%s/release.json", b, runtime.GOOS, c), []byte(customMetadata)), "could not add test target release file to tuf")

// Commit
require.NoError(t, repo.Snapshot(), "could not take snapshot")
require.NoError(t, repo.Timestamp(), "could not take timestamp")
require.NoError(t, repo.Commit(), "could not commit")
}
}
}

// Quick validation that we set up the repo properly: key and metadata files should exist; targets should exist
require.DirExists(t, filepath.Join(tufDir, "keys"))
require.FileExists(t, filepath.Join(tufDir, "keys", "root.json"))
require.FileExists(t, filepath.Join(tufDir, "keys", "snapshot.json"))
require.FileExists(t, filepath.Join(tufDir, "keys", "targets.json"))
require.FileExists(t, filepath.Join(tufDir, "keys", "timestamp.json"))
require.DirExists(t, filepath.Join(tufDir, "repository"))
require.FileExists(t, filepath.Join(tufDir, "repository", "root.json"))
require.FileExists(t, filepath.Join(tufDir, "repository", "snapshot.json"))
require.FileExists(t, filepath.Join(tufDir, "repository", "timestamp.json"))
require.FileExists(t, filepath.Join(tufDir, "repository", "targets.json"))
require.FileExists(t, filepath.Join(tufDir, "repository", "targets", "launcher", runtime.GOOS, "stable", "release.json"))
require.FileExists(t, filepath.Join(tufDir, "repository", "targets", "launcher", runtime.GOOS, fmt.Sprintf("launcher-%s.tar.gz", testReleaseVersion)))
require.FileExists(t, filepath.Join(tufDir, "repository", "targets", "osqueryd", runtime.GOOS, "stable", "release.json"))
require.FileExists(t, filepath.Join(tufDir, "repository", "targets", "osqueryd", runtime.GOOS, fmt.Sprintf("osqueryd-%s.tar.gz", testReleaseVersion)))

// Set up a test server to serve these files
testMetadataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathComponents := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")

fileToServe := tufDir

// Allow the test server to also stand in for dl.kolide.co
if pathComponents[0] == "kolide" {
fileToServe = filepath.Join(fileToServe, "repository", "targets")
} else {
fileToServe = filepath.Join(fileToServe, pathComponents[0])
}

for i := 1; i < len(pathComponents); i += 1 {
fileToServe = filepath.Join(fileToServe, pathComponents[i])
}

http.ServeFile(w, r, fileToServe)
}))

// Make sure we close the server at the end of our test
t.Cleanup(func() {
testMetadataServer.Close()
})

tufServerURL = testMetadataServer.URL

metadata, err := repo.GetMeta()
require.NoError(t, err, "could not get metadata from test TUF repo")
require.Contains(t, metadata, "root.json")
rootJson = metadata["root.json"]

return tufServerURL, rootJson
}

func compress(t *testing.T, outFileName string, outFileDir string, targetDir string, binary autoupdatableBinary) {
out, err := os.Create(filepath.Join(outFileDir, outFileName))
require.NoError(t, err, "creating archive: %s in %s", outFileName, outFileDir)
defer out.Close()

gw := gzip.NewWriter(out)
defer gw.Close()

tw := tar.NewWriter(gw)
defer tw.Close()

srcFilePath := string(binary)
if binary == "launcher" && runtime.GOOS == "darwin" {
srcFilePath = filepath.Join("Kolide.app", "Contents", "MacOS", string(binary))

// Create directory structure for app bundle
for _, path := range []string{"Kolide.app", "Kolide.app/Contents", "Kolide.app/Contents/MacOS"} {
pInfo, err := os.Stat(filepath.Join(targetDir, path))
require.NoError(t, err, "stat for app bundle path %s", path)

hdr, err := tar.FileInfoHeader(pInfo, path)
require.NoError(t, err, "creating header for directory %s", path)
hdr.Name = path

require.NoError(t, tw.WriteHeader(hdr), "writing tar header")
}
} else if runtime.GOOS == "windows" {
srcFilePath += ".exe"
}

srcFile, err := os.Open(filepath.Join(targetDir, srcFilePath))
require.NoError(t, err, "opening binary")
defer srcFile.Close()

srcStats, err := srcFile.Stat()
require.NoError(t, err, "getting stats to compress binary")

hdr, err := tar.FileInfoHeader(srcStats, srcStats.Name())
require.NoError(t, err, "creating header")
hdr.Name = srcFilePath

require.NoError(t, tw.WriteHeader(hdr), "writing tar header")
_, err = io.Copy(tw, srcFile)
require.NoError(t, err, "copying file to archive")
}

func setupStorage(t *testing.T) types.KVStore {
s, err := storageci.NewStore(t, log.NewNopLogger(), storage.AutoupdateErrorsStore.String())
require.NoError(t, err)
Expand Down
34 changes: 34 additions & 0 deletions pkg/autoupdate/tuf/ci/tuf_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package tufci

import (
"net/http"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"github.com/theupdateframework/go-tuf/client"
filejsonstore "github.com/theupdateframework/go-tuf/client/filejsonstore"
)

// SeedLocalTufRepo creates a local TUF repo with a valid release under the given version `testTargetVersion`
func SeedLocalTufRepo(t *testing.T, testTargetVersion string, testRootDir string) {
serverUrl, testRootJson := InitRemoteTufServer(t, testTargetVersion)

// Now set up local repo
localTufDir := filepath.Join(testRootDir, "tuf")
localStore, err := filejsonstore.NewFileJSONStore(localTufDir)
require.NoError(t, err, "could not set up local store")

// Set up our remote store i.e. tuf.kolide.com
remoteOpts := client.HTTPRemoteOptions{
MetadataPath: "/repository",
}
remoteStore, err := client.HTTPRemoteStore(serverUrl, &remoteOpts, http.DefaultClient)
require.NoError(t, err, "could not set up remote store")

metadataClient := client.NewClient(localStore, remoteStore)
require.NoError(t, err, metadataClient.Init(testRootJson), "failed to initialze TUF client")

_, err = metadataClient.Update()
require.NoError(t, err, "could not update TUF client")
}
Loading