This is to allow us to store new policy files in the production TUF repository under a testing delegation, and for clients to opt-in to using this testing delegation when retrieving policy from TUF. If the prefix path is set, it is prepended to every target path on download with path.Join. For example, if the prefix path is testing and we download the target a/b, the TUF client with actually download testing/a/b. Also get the latest testdata from tuf-dev.
178 lines
6.2 KiB
Go
178 lines
6.2 KiB
Go
package tuf
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/docker/attest/internal/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/theupdateframework/go-tuf/v2/metadata"
|
|
)
|
|
|
|
var (
|
|
HTTPTUFTestDataPath = filepath.Join("..", "test", "testdata", "tuf", "test-repo")
|
|
OCITUFTestDataPath = filepath.Join("..", "test", "testdata", "tuf", "test-repo-oci")
|
|
)
|
|
|
|
func CreateTempDir(t *testing.T, dir, pattern string) string {
|
|
// Create a temporary directory for output oci layout
|
|
tempDir, err := os.MkdirTemp(dir, pattern)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp directory: %v", err)
|
|
}
|
|
|
|
// Register a cleanup function to delete the temp directory when the test exits
|
|
t.Cleanup(func() {
|
|
if err := os.RemoveAll(tempDir); err != nil {
|
|
t.Errorf("Failed to remove temp directory: %v", err)
|
|
}
|
|
})
|
|
return tempDir
|
|
}
|
|
|
|
// NewTufClient creates a new TUF client.
|
|
func TestRootInit(t *testing.T) {
|
|
tufPath := CreateTempDir(t, "", "tuf_temp")
|
|
|
|
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
|
server := httptest.NewServer(http.FileServer(http.Dir(HTTPTUFTestDataPath)))
|
|
defer server.Close()
|
|
|
|
ctx := context.Background()
|
|
regServer := test.NewLocalRegistry(ctx)
|
|
defer regServer.Close()
|
|
|
|
regAddr, err := url.Parse(regServer.URL)
|
|
require.NoError(t, err)
|
|
LoadRegistryTestData(ctx, t, regAddr, OCITUFTestDataPath)
|
|
|
|
alwaysGoodVersionChecker := &MockVersionChecker{err: nil}
|
|
alwaysBadVersionChecker := &MockVersionChecker{err: assert.AnError}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
metadataSource string
|
|
targetsSource string
|
|
}{
|
|
{"http", server.URL + "/metadata", server.URL + "/targets"},
|
|
{"oci", regAddr.Host + "/tuf-metadata:latest", regAddr.Host + "/tuf-targets"},
|
|
}
|
|
for _, tc := range testCases {
|
|
_, err := NewClient(ctx, &ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker, ""})
|
|
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
|
|
|
// recreation should work with same root
|
|
_, err = NewClient(ctx, &ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker, ""})
|
|
assert.NoErrorf(t, err, "Failed to recreate TUF client: %v", err)
|
|
|
|
_, err = NewClient(ctx, &ClientOptions{[]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker, ""})
|
|
assert.Errorf(t, err, "Expected error recreating TUF client with broken root: %v", err)
|
|
|
|
_, err = NewClient(ctx, &ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker, ""})
|
|
assert.Errorf(t, err, "Expected error recreating TUF client with bad version checker")
|
|
|
|
_, err = NewClient(ctx, &ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker, "../.."})
|
|
assert.Errorf(t, err, "Expected error recreating TUF client with bad path prefix")
|
|
}
|
|
}
|
|
|
|
func TestDownloadTarget(t *testing.T) {
|
|
tufPath := CreateTempDir(t, "", "tuf_temp")
|
|
targetFile := "test.txt"
|
|
delegatedRole := testRole
|
|
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
|
|
|
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
|
server := httptest.NewServer(http.FileServer(http.Dir(HTTPTUFTestDataPath)))
|
|
defer server.Close()
|
|
|
|
ctx := context.Background()
|
|
regServer := test.NewLocalRegistry(ctx)
|
|
defer regServer.Close()
|
|
|
|
regAddr, err := url.Parse(regServer.URL)
|
|
require.NoError(t, err)
|
|
LoadRegistryTestData(ctx, t, regAddr, OCITUFTestDataPath)
|
|
|
|
alwaysGoodVersionChecker := &MockVersionChecker{err: nil}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
metadataSource string
|
|
targetsSource string
|
|
pathPrefix string
|
|
}{
|
|
{"http", server.URL + "/metadata", server.URL + "/targets", ""},
|
|
{"oci", regAddr.Host + "/tuf-metadata:latest", regAddr.Host + "/tuf-targets", ""},
|
|
{"http, with path prefix", server.URL + "/metadata", server.URL + "/targets", "testing"},
|
|
{"oci, with path prefix", regAddr.Host + "/tuf-metadata:latest", regAddr.Host + "/tuf-targets", "testing"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tufClient, err := NewClient(ctx, &ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker, tc.pathPrefix})
|
|
require.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
|
require.NotNil(t, tufClient.updater, "Failed to create updater")
|
|
|
|
// get trusted tuf metadata
|
|
trustedMetadata := tufClient.updater.GetTrustedMetadataSet()
|
|
assert.NotNil(t, trustedMetadata, "Failed to get trusted metadata")
|
|
|
|
// download top-level target files
|
|
var roleName string
|
|
if tc.pathPrefix != "" {
|
|
// get target info for non-existent target, just to trigger a load of the delegated targets metadata
|
|
_, err = tufClient.updater.GetTargetInfo(tc.pathPrefix + "/fakefile")
|
|
assert.Error(t, err) // expect error for non-existent target
|
|
roleName = tc.pathPrefix
|
|
} else {
|
|
roleName = metadata.TARGETS
|
|
}
|
|
targets := trustedMetadata.Targets[roleName].Signed.Targets
|
|
for _, target := range targets {
|
|
path := strings.TrimPrefix(target.Path, tufClient.pathPrefix)
|
|
// download target files
|
|
_, err := tufClient.DownloadTarget(path, filepath.Join(tufPath, "download"))
|
|
assert.NoErrorf(t, err, "Failed to download target: %v", err)
|
|
}
|
|
|
|
if tc.pathPrefix == "" {
|
|
// download delegated target, only if not using a path prefix
|
|
targetInfo, err := tufClient.updater.GetTargetInfo(delegatedTargetFile)
|
|
require.NoError(t, err)
|
|
_, err = tufClient.DownloadTarget(targetInfo.Path, filepath.Join(tufPath, targetInfo.Path))
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetEmbeddedTufRootBytes(t *testing.T) {
|
|
dev, err := GetEmbeddedRoot("dev")
|
|
assert.NoError(t, err)
|
|
|
|
staging, err := GetEmbeddedRoot("staging")
|
|
assert.NoError(t, err)
|
|
assert.NotEqual(t, dev.Data, staging.Data)
|
|
|
|
prod, err := GetEmbeddedRoot("prod")
|
|
assert.NoError(t, err)
|
|
assert.NotEqual(t, dev.Data, prod.Data)
|
|
assert.NotEqual(t, staging.Data, prod.Data)
|
|
|
|
def, err := GetEmbeddedRoot("")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, def.Data, prod.Data)
|
|
|
|
_, err = GetEmbeddedRoot("invalid")
|
|
assert.Error(t, err)
|
|
}
|