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) }