mirror of
https://github.com/vee1e/gittuf.git
synced 2026-09-02 18:57:10 +00:00
Restructure storage so gittuf verification can run over backends other
than the git binary (e.g. go-git):
- pkg/githash: concrete Git object hash, stdlib-only.
gitinterface.Hash aliases it.
- pkg/gitstore: the single Storer interface (24 methods) that all
storage consumers program against, plus the shared
ErrReferenceNotFound sentinel. *gitinterface.Repository satisfies it
structurally (compile-time asserted). Also defines ConfigKey, the
canonical type for the Git config settings gittuf reads.
- pkg/rsl (from internal/rsl): entry model, codec, and readers over
gitstore.Storer; zero gitinterface/sigstore dependencies. rsl.Hash
aliases githash.Hash; nil is the unset-Hash sentinel and IsZero
matches nil and empty as well as both format zeros (no
object-format-unaware ZeroHash). Entry commits (empty tree on the
RSL ref) are owned by the package; no storer adapter.
- internal/signerverifier/gitobject: verifies commit/tag signatures
over (payload, signature) bytes, Rekor URL as an option. The storage
half is Repository.GetObjectSignature. Removes sigstore, cosign, and
gitsign from gitinterface's dependency tree.
- internal/propagation: propagation workflow, moved off pkg/rsl's
public API (its tuf directive types are internal).
- internal/{attestations,cache,policy}: storage via gitstore.Storer;
tree writing via WriteTree(blobs, subtrees).
Breaking changes to pkg/gitinterface: Repository.VerifySignature and
the verification sentinels are removed (use gitobject.Verify);
ErrReferenceNotFound now aliases gitstore's. Repository.GetGitConfig
(which returned the whole config map) is replaced by
LookupConfig(gitstore.ConfigKey), returning a single setting's value.
Policy resolves the Rekor override from git config once per
verification and extracts signed payloads once per object instead of
per key attempt.
Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
279 lines
8.7 KiB
Go
279 lines
8.7 KiB
Go
// Copyright The gittuf Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package cache
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/gittuf/gittuf/internal/attestations"
|
|
"github.com/gittuf/gittuf/pkg/gitinterface"
|
|
"github.com/gittuf/gittuf/pkg/rsl"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCommit(t *testing.T) {
|
|
t.Run("empty cache", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
p := &Persistent{}
|
|
err := p.Commit(repo)
|
|
assert.Nil(t, err) // an empty cache should do nothing
|
|
|
|
_, err = repo.GetReference(Ref)
|
|
assert.Error(t, err) // no reference should be created for an empty cache
|
|
})
|
|
|
|
t.Run("non-empty cache", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
p := &Persistent{
|
|
PolicyEntries: []RSLEntryIndex{
|
|
{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"},
|
|
},
|
|
}
|
|
|
|
err := p.Commit(repo)
|
|
require.Nil(t, err) // there should be no error for non-empty cache
|
|
|
|
refID, err := repo.GetReference(Ref)
|
|
require.Nil(t, err) // a reference should be created for a non-empty cache
|
|
assert.False(t, refID.IsZero()) // the reference should not be zero
|
|
})
|
|
|
|
t.Run("no changes causes noop", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
p := &Persistent{
|
|
AttestationEntries: []RSLEntryIndex{
|
|
{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"},
|
|
},
|
|
}
|
|
|
|
err := p.Commit(repo)
|
|
require.Nil(t, err)
|
|
|
|
firstRef, err := repo.GetReference(Ref)
|
|
require.Nil(t, err)
|
|
|
|
err = p.Commit(repo)
|
|
require.Nil(t, err)
|
|
|
|
secondRef, err := repo.GetReference(Ref)
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, firstRef, secondRef) // the references should be equal to each other because noop
|
|
})
|
|
|
|
t.Run("cache changes causes new commit", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
p := &Persistent{
|
|
AttestationEntries: []RSLEntryIndex{
|
|
{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"},
|
|
},
|
|
}
|
|
|
|
err := p.Commit(repo)
|
|
require.Nil(t, err)
|
|
|
|
firstRef, err := repo.GetReference(Ref)
|
|
require.Nil(t, err)
|
|
|
|
p.PolicyEntries = append(p.PolicyEntries, RSLEntryIndex{
|
|
EntryNumber: 2,
|
|
EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c1234",
|
|
})
|
|
|
|
err = p.Commit(repo)
|
|
require.Nil(t, err)
|
|
|
|
secondRef, err := repo.GetReference(Ref)
|
|
require.Nil(t, err)
|
|
|
|
assert.NotEqual(t, firstRef, secondRef) // the reference should not be equal when data changes
|
|
})
|
|
}
|
|
|
|
func TestBinarySearch(t *testing.T) {
|
|
t.Run("equals", func(t *testing.T) {
|
|
a := RSLEntryIndex{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"}
|
|
b := RSLEntryIndex{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c1234"}
|
|
|
|
c := RSLEntryIndex{EntryNumber: 2, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c9080"}
|
|
d := RSLEntryIndex{EntryNumber: 2, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c9080"}
|
|
|
|
res := binarySearch(a, b)
|
|
assert.Equal(t, 0, res) // the result should be 0 for equal entry numbers and unequal IDs
|
|
|
|
res = binarySearch(c, d)
|
|
assert.Equal(t, 0, res) // the result should be 0 for equal entry numbers and equal IDs
|
|
})
|
|
|
|
t.Run("precedes", func(t *testing.T) {
|
|
a := RSLEntryIndex{EntryNumber: 0, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"}
|
|
b := RSLEntryIndex{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c1234"}
|
|
|
|
c := RSLEntryIndex{EntryNumber: 2, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c9080"}
|
|
d := RSLEntryIndex{EntryNumber: 3, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c9080"}
|
|
|
|
res := binarySearch(a, b)
|
|
assert.Equal(t, -1, res) // the result should be -1 for cases with unequal IDs but first entry number is smaller than the second
|
|
|
|
res = binarySearch(c, d)
|
|
assert.Equal(t, -1, res) // the result should be -1 for cases with equal IDs but first entry number is smaller than the second
|
|
})
|
|
|
|
t.Run("succeeds", func(t *testing.T) {
|
|
a := RSLEntryIndex{EntryNumber: 0, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"}
|
|
b := RSLEntryIndex{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c1234"}
|
|
|
|
c := RSLEntryIndex{EntryNumber: 2, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c9080"}
|
|
d := RSLEntryIndex{EntryNumber: 3, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c9080"}
|
|
|
|
res := binarySearch(b, a)
|
|
assert.Equal(t, 1, res) // the result should be 1 for cases with unequal IDs but second entry number is smaller than the first
|
|
|
|
res = binarySearch(d, c)
|
|
assert.Equal(t, 1, res) // the result should be 1 for cases with equal IDs but second entry number is smaller than the first
|
|
})
|
|
}
|
|
|
|
func TestPopulatePersistentCache(t *testing.T) {
|
|
t.Run("repo with reference and policy", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
err := rsl.NewReferenceEntry(policyRef, gitinterface.ZeroHash).Commit(repo, false)
|
|
require.Nil(t, err)
|
|
|
|
err = PopulatePersistentCache(repo)
|
|
assert.Nil(t, err)
|
|
|
|
sampleCache := Persistent{
|
|
PolicyEntries: []RSLEntryIndex{
|
|
{EntryNumber: 1, EntryID: "a2f603abf945588e8ad0d6b1f71a37bdcaf87e13"},
|
|
},
|
|
AttestationEntries: []RSLEntryIndex{},
|
|
AddedAttestationsBeforeNumber: 1,
|
|
}
|
|
cache, err := LoadPersistentCache(repo)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, &sampleCache, cache)
|
|
})
|
|
|
|
t.Run("empty repo", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
err := PopulatePersistentCache(repo) // since this empty repo has no references or commits yet so it should throw an error
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestLoadPersistantCache(t *testing.T) {
|
|
t.Run("empty repo", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
_, err := LoadPersistentCache(repo)
|
|
require.ErrorIs(t, ErrNoPersistentCache, err) // empty repo with no reference throws ErrNoPersistentCache error
|
|
})
|
|
|
|
t.Run("repo with policy reference", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
err := rsl.NewReferenceEntry(policyRef, gitinterface.ZeroHash).Commit(repo, false)
|
|
require.Nil(t, err)
|
|
|
|
err = PopulatePersistentCache(repo)
|
|
require.Nil(t, err)
|
|
|
|
_, err = repo.GetReference(Ref)
|
|
require.Nil(t, err)
|
|
|
|
persistentCache, err := LoadPersistentCache(repo)
|
|
require.Nil(t, err)
|
|
assert.NotNil(t, persistentCache) // the cache should be loaded successfully if the persistentCache exists
|
|
|
|
sampleCache := Persistent{
|
|
PolicyEntries: []RSLEntryIndex{
|
|
{EntryNumber: 1, EntryID: "a2f603abf945588e8ad0d6b1f71a37bdcaf87e13"},
|
|
},
|
|
AttestationEntries: []RSLEntryIndex{},
|
|
AddedAttestationsBeforeNumber: 1,
|
|
}
|
|
assert.Equal(t, &sampleCache, persistentCache)
|
|
})
|
|
|
|
t.Run("repo with attestation reference", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
err := rsl.NewReferenceEntry(attestations.Ref, gitinterface.ZeroHash).Commit(repo, false)
|
|
require.Nil(t, err)
|
|
|
|
err = PopulatePersistentCache(repo)
|
|
require.Nil(t, err)
|
|
|
|
_, err = repo.GetReference(Ref)
|
|
require.Nil(t, err)
|
|
|
|
persistentCache, err := LoadPersistentCache(repo)
|
|
require.Nil(t, err)
|
|
assert.NotNil(t, persistentCache)
|
|
|
|
sampleCache := Persistent{
|
|
PolicyEntries: []RSLEntryIndex{},
|
|
AttestationEntries: []RSLEntryIndex{
|
|
{EntryNumber: 1, EntryID: "4f6fd1c67daa2acf4f2dd8626ddd8d6a51fcd026"},
|
|
},
|
|
AddedAttestationsBeforeNumber: 1,
|
|
}
|
|
assert.Equal(t, &sampleCache, persistentCache)
|
|
})
|
|
}
|
|
|
|
func TestDeletePersistentCache(t *testing.T) {
|
|
t.Run("delete cache for empty repo", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
err := DeletePersistentCache(repo)
|
|
require.ErrorIs(t, ErrNoPersistentCache, err)
|
|
})
|
|
|
|
t.Run("delete existing cache", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
repo := gitinterface.CreateTestGitRepository(t, tempDir, false)
|
|
|
|
err := rsl.NewReferenceEntry(policyRef, gitinterface.ZeroHash).Commit(repo, false)
|
|
require.Nil(t, err)
|
|
|
|
err = PopulatePersistentCache(repo)
|
|
require.Nil(t, err)
|
|
|
|
err = DeletePersistentCache(repo)
|
|
assert.Nil(t, err)
|
|
|
|
_, err = repo.GetReference(Ref)
|
|
assert.ErrorIs(t, gitinterface.ErrReferenceNotFound, err)
|
|
})
|
|
}
|
|
|
|
func TestRSLEntryMethods(t *testing.T) {
|
|
r := RSLEntryIndex{EntryNumber: 1, EntryID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"}
|
|
|
|
number := r.GetEntryNumber()
|
|
assert.Equal(t, uint64(1), number)
|
|
|
|
id := r.GetEntryID()
|
|
expectedID, _ := gitinterface.NewHash("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
|
|
assert.Equal(t, expectedID, id)
|
|
}
|