- services/transparency-log/pkg/backend: Backend interface with in-memory append-log (RFC 6962 Merkle tree) + Trillian wrapper (prod stub). - Idempotenz-Pflicht: Wiederaufruf mit identischem content_hash liefert 200 + gleichen log_index + aktuellen inclusion_proof. - content_hash-Verifikation serverseitig; Mismatch → 409. - pkg/httpapi: ADR-0005-konformer Handler (201/200/400/404/409/503). - cmd/transparency-log: minimaler HTTP-Server, memory default, --backend trillian als Stub (503). - Interop-Test in services/admin-api/internal/tlog/interop_test.go fährt die echte transparency-log pkg gegen die admin-api HTTPSubmitter — Happy-Path, Idempotenz und content_hash-Mismatch abgedeckt. - go test: 10 Tests transparency-log + 2 Interop-Tests admin-api, go vet clean in beiden Modulen. Folge-Subtask: Trillian-Deployment + Keys (siehe ADR-0005 Open Points). Co-Authored-By: Paperclip <noreply@paperclip.ing>
97 lines
3.2 KiB
Go
97 lines
3.2 KiB
Go
package tlog
|
|
|
|
// Interop test: the real admin-api HTTPSubmitter talks to the real
|
|
// transparency-log HTTP handler + in-memory backend from the sibling
|
|
// services/transparency-log module. This proves end-to-end that the
|
|
// ADR-0005 wire shape agreed on both sides (field names, status codes,
|
|
// idempotency semantics, content_hash algorithm) is actually compatible.
|
|
//
|
|
// The transparency-log module is pulled in via a replace directive in the
|
|
// top-level go.mod.
|
|
|
|
import (
|
|
"context"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"basicstack.de/proofedmesh/services/admin-api/internal/attest"
|
|
|
|
tlbackend "basicstack.de/proofedmesh/services/transparency-log/pkg/backend"
|
|
tlhttp "basicstack.de/proofedmesh/services/transparency-log/pkg/httpapi"
|
|
)
|
|
|
|
func TestInterop_HTTPSubmitter_HappyPathAndIdempotency(t *testing.T) {
|
|
backendMem := tlbackend.NewMemory()
|
|
server := httptest.NewServer((&tlhttp.Server{Backend: backendMem}).Handler())
|
|
defer server.Close()
|
|
|
|
sub := HTTPSubmitter{BaseURL: server.URL, TokenSource: attest.StaticToken("dev")}
|
|
env := attest.Envelope{
|
|
PayloadType: "application/vnd.in-toto+json",
|
|
Payload: "aGVsbG8",
|
|
Signatures: []attest.EnvelopeSignature{{KeyID: "k", Sig: "c2ln"}},
|
|
}
|
|
ch, err := attest.EnvelopeContentHash(env)
|
|
if err != nil {
|
|
t.Fatalf("hash: %v", err)
|
|
}
|
|
|
|
r1, err := sub.Submit(context.Background(), env, ch)
|
|
if err != nil {
|
|
t.Fatalf("first submit: %v", err)
|
|
}
|
|
if r1.LogIndex != 0 {
|
|
t.Errorf("first log_index: want 0, got %d", r1.LogIndex)
|
|
}
|
|
if r1.ContentHash != ch {
|
|
t.Errorf("content_hash echoed wrong: %q vs %q", r1.ContentHash, ch)
|
|
}
|
|
if r1.InclusionProof.TreeSize != 1 {
|
|
t.Errorf("tree_size: %d", r1.InclusionProof.TreeSize)
|
|
}
|
|
|
|
// Idempotent repeat.
|
|
r2, err := sub.Submit(context.Background(), env, ch)
|
|
if err != nil {
|
|
t.Fatalf("second submit: %v", err)
|
|
}
|
|
if r2.LogIndex != r1.LogIndex {
|
|
t.Errorf("idempotent log_index drifted: %d -> %d", r1.LogIndex, r2.LogIndex)
|
|
}
|
|
if r2.InclusionProof.TreeRootHash != r1.InclusionProof.TreeRootHash {
|
|
t.Errorf("tree_root drifted without new entries")
|
|
}
|
|
|
|
// Second distinct envelope — tree grows.
|
|
envB := env
|
|
envB.Payload = "d29ybGQ"
|
|
chB, _ := attest.EnvelopeContentHash(envB)
|
|
rB, err := sub.Submit(context.Background(), envB, chB)
|
|
if err != nil {
|
|
t.Fatalf("submit B: %v", err)
|
|
}
|
|
if rB.LogIndex != 1 {
|
|
t.Errorf("second entry log_index: want 1, got %d", rB.LogIndex)
|
|
}
|
|
if rB.InclusionProof.TreeSize != 2 {
|
|
t.Errorf("tree_size after B: %d", rB.InclusionProof.TreeSize)
|
|
}
|
|
}
|
|
|
|
func TestInterop_HTTPSubmitter_ContentHashMismatchFails(t *testing.T) {
|
|
server := httptest.NewServer((&tlhttp.Server{Backend: tlbackend.NewMemory()}).Handler())
|
|
defer server.Close()
|
|
sub := HTTPSubmitter{BaseURL: server.URL, TokenSource: attest.StaticToken("dev")}
|
|
|
|
env := attest.Envelope{
|
|
PayloadType: "application/vnd.in-toto+json",
|
|
Payload: "aGVsbG8",
|
|
Signatures: []attest.EnvelopeSignature{{KeyID: "k", Sig: "c2ln"}},
|
|
}
|
|
// Wrong hash on purpose. Server must respond 409; the client surfaces
|
|
// that as an error.
|
|
_, err := sub.Submit(context.Background(), env, "sha256:00000000000000000000000000000000000000000000000000000000deadbeef")
|
|
if err == nil {
|
|
t.Fatalf("expected error on content_hash mismatch, got nil")
|
|
}
|
|
}
|