diff --git a/client/http/witness_client.go b/client/http/witness_client.go index c45c45a1..96df8739 100644 --- a/client/http/witness_client.go +++ b/client/http/witness_client.go @@ -28,7 +28,7 @@ import ( "strings" "github.com/transparency-dev/witness/api" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "k8s.io/klog/v2" ) diff --git a/cmd/feedwitness/main.go b/cmd/feedwitness/main.go index 6be6eff4..d63dde50 100644 --- a/cmd/feedwitness/main.go +++ b/cmd/feedwitness/main.go @@ -34,7 +34,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" w_http "github.com/transparency-dev/witness/client/http" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "github.com/transparency-dev/witness/monitoring" "github.com/transparency-dev/witness/monitoring/prometheus" "github.com/transparency-dev/witness/omniwitness" diff --git a/cmd/feedwitness/run_feeders.go b/cmd/feedwitness/run_feeders.go index 51f5909a..0fa9ad0c 100644 --- a/cmd/feedwitness/run_feeders.go +++ b/cmd/feedwitness/run_feeders.go @@ -32,7 +32,7 @@ import ( "github.com/transparency-dev/witness/internal/feeder/serverless" "github.com/transparency-dev/witness/internal/feeder/sumdb" "github.com/transparency-dev/witness/internal/feeder/tiles" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "github.com/transparency-dev/witness/monitoring" "github.com/transparency-dev/witness/omniwitness" "golang.org/x/mod/sumdb/note" diff --git a/cmd/feedwitness/run_feeders_test.go b/cmd/feedwitness/run_feeders_test.go index 2fb36d44..214bac91 100644 --- a/cmd/feedwitness/run_feeders_test.go +++ b/cmd/feedwitness/run_feeders_test.go @@ -25,7 +25,7 @@ import ( sclient "github.com/transparency-dev/serverless-log/client" "github.com/transparency-dev/serverless-log/testdata" "github.com/transparency-dev/witness/internal/feeder" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "github.com/transparency-dev/witness/monitoring" "golang.org/x/mod/sumdb/note" ) diff --git a/cmd/loadtest/loadtest.go b/cmd/loadtest/loadtest.go index 18e030b6..ff9f5844 100644 --- a/cmd/loadtest/loadtest.go +++ b/cmd/loadtest/loadtest.go @@ -34,7 +34,7 @@ import ( "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" wit_client "github.com/transparency-dev/witness/client/http" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "github.com/transparency-dev/witness/omniwitness" "golang.org/x/mod/sumdb/note" "gopkg.in/yaml.v3" diff --git a/cmd/omniwitness_gcp/persistence_test.go b/cmd/omniwitness_gcp/persistence_test.go index 5633ea1b..5a526bae 100644 --- a/cmd/omniwitness_gcp/persistence_test.go +++ b/cmd/omniwitness_gcp/persistence_test.go @@ -26,7 +26,6 @@ import ( "cloud.google.com/go/spanner" "cloud.google.com/go/spanner/spannertest" "github.com/transparency-dev/formats/log" - "github.com/transparency-dev/witness/internal/persistence" ptest "github.com/transparency-dev/witness/internal/persistence/testonly" "github.com/transparency-dev/witness/omniwitness" "golang.org/x/sync/errgroup" @@ -48,9 +47,9 @@ func newSpannerServer(t *testing.T) (string, func()) { return id, srv.Close } -func mustNewPersistence(t *testing.T) func() (persistence.LogStatePersistence, func() error) { +func mustNewPersistence(t *testing.T) func() (*spannerPersistence, func() error) { t.Helper() - return func() (persistence.LogStatePersistence, func() error) { + return func() (*spannerPersistence, func() error) { spanner, spannerShutdown := newSpannerServer(t) p, clientShutdown, err := newSpannerPersistence(t.Context(), spanner) if err != nil { @@ -110,17 +109,16 @@ func TestUpdateConcurrent(t *testing.T) { } func TestDisableLog(t *testing.T) { - p, shutdown := mustNewPersistence(t)() + sp, shutdown := mustNewPersistence(t)() defer func() { if err := shutdown(); err != nil { t.Errorf("shutdown: %v", err) } }() - if err := p.Init(t.Context()); err != nil { + if err := sp.Init(t.Context()); err != nil { t.Fatalf("Init(): %v", err) } - sp := p.(*spannerPersistence) sp.batchWrite = testBatchWrite if err := sp.AddLogs(t.Context(), @@ -161,13 +159,13 @@ func TestDisableLog(t *testing.T) { } func TestDisabledLogStaysDisabled(t *testing.T) { - p, shutdown := mustNewPersistence(t)() + sp, shutdown := mustNewPersistence(t)() defer func() { if err := shutdown(); err != nil { t.Errorf("shutdown: %v", err) } }() - if err := p.Init(t.Context()); err != nil { + if err := sp.Init(t.Context()); err != nil { t.Fatalf("Init(): %v", err) } @@ -178,8 +176,6 @@ func TestDisabledLogStaysDisabled(t *testing.T) { }, } - sp := p.(*spannerPersistence) - if err := sp.AddLogs(t.Context(), logs); err != nil { t.Fatalf("Failed to AddLogs: %v", err) } diff --git a/internal/persistence/inmemory/inmemory_test.go b/internal/persistence/inmemory/inmemory_test.go index eeda8276..2f61c2fe 100644 --- a/internal/persistence/inmemory/inmemory_test.go +++ b/internal/persistence/inmemory/inmemory_test.go @@ -19,7 +19,6 @@ import ( "strings" "testing" - "github.com/transparency-dev/witness/internal/persistence" ptest "github.com/transparency-dev/witness/internal/persistence/testonly" "golang.org/x/sync/errgroup" ) @@ -27,7 +26,7 @@ import ( var nopClose = func() error { return nil } func TestUpdate(t *testing.T) { - ptest.TestUpdate(t, func() (persistence.LogStatePersistence, func() error) { + ptest.TestUpdate(t, func() (*inMemoryPersistence, func() error) { return NewPersistence(), nopClose }) } diff --git a/internal/persistence/sql/sql.go b/internal/persistence/sql/sql.go index 6a37fe60..2499760c 100644 --- a/internal/persistence/sql/sql.go +++ b/internal/persistence/sql/sql.go @@ -24,7 +24,7 @@ import ( "github.com/mattn/go-sqlite3" "github.com/transparency-dev/formats/log" "github.com/transparency-dev/formats/note" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "github.com/transparency-dev/witness/omniwitness" "k8s.io/klog/v2" ) diff --git a/internal/persistence/sql/sql_test.go b/internal/persistence/sql/sql_test.go index 54c7b181..72953dbf 100644 --- a/internal/persistence/sql/sql_test.go +++ b/internal/persistence/sql/sql_test.go @@ -21,13 +21,12 @@ import ( _ "github.com/mattn/go-sqlite3" // Load drivers for sqlite3 "github.com/transparency-dev/formats/log" - "github.com/transparency-dev/witness/internal/persistence" - ptest "github.com/transparency-dev/witness/internal/persistence/testonly" "github.com/transparency-dev/witness/omniwitness" + ptest "github.com/transparency-dev/witness/internal/persistence/testonly" ) func TestUpdate(t *testing.T) { - ptest.TestUpdate(t, func() (persistence.LogStatePersistence, func() error) { + ptest.TestUpdate(t, func() (*sqlLogPersistence, func() error) { db, close := mustCreateDB(t) return NewPersistence(db), close }) diff --git a/internal/persistence/testonly/persistence.go b/internal/persistence/testonly/persistence.go index 06c8eab1..2cad6fc9 100644 --- a/internal/persistence/testonly/persistence.go +++ b/internal/persistence/testonly/persistence.go @@ -21,12 +21,11 @@ import ( "fmt" "testing" - "github.com/transparency-dev/witness/internal/persistence" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" ) // TestUpdate exposes a test that can be invoked by tests for specific implementations of persistence. -func TestUpdate(t *testing.T, lspFactory func() (persistence.LogStatePersistence, func() error)) { +func TestUpdate[T witness.LogStatePersistence](t *testing.T, lspFactory func() (T, func() error)) { t.Helper() origin := "foo" @@ -69,7 +68,7 @@ func TestUpdate(t *testing.T, lspFactory func() (persistence.LogStatePersistence } } -func checkAndSet(ctx context.Context, lsp persistence.LogStatePersistence, origin string, expect []byte, write []byte) error { +func checkAndSet(ctx context.Context, lsp witness.LogStatePersistence, origin string, expect []byte, write []byte) error { if err := lsp.Update(ctx, origin, func(current []byte) ([]byte, error) { if !bytes.Equal(current, expect) { return nil, fmt.Errorf("got current %x, want %x", current, expect) diff --git a/internal/witness/proof.go b/internal/witness/proof.go deleted file mode 100644 index a4e9e75e..00000000 --- a/internal/witness/proof.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2021 Google LLC. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package witness - -import ( - "encoding/base64" - "errors" - "fmt" - "strings" -) - -// Proof represents a common format proof. -// -// Interpretation of the proof bytes is ecosystem dependent. -type Proof [][]byte - -// Marshal returns the common format representation of this proof. -func (p Proof) Marshal() string { - b := strings.Builder{} - for _, l := range p { - b.WriteString(base64.StdEncoding.EncodeToString(l)) - b.WriteRune('\n') - } - return b.String() -} - -// Unmarshal parses common proof format data and stores the result in the -// Proof struct. -func (p *Proof) Unmarshal(data []byte) error { - const delim = "\n" - s := string(data) - if !strings.HasSuffix(s, delim) { - return errors.New("data should have trailing newline on last hash too") - } - lines := strings.Split(s, delim) - // We expect there to be one too many fields here since the final hash - // should be terminated with a newline too. - lines = lines[:len(lines)-1] - r := make([][]byte, len(lines)) - for i, l := range lines { - b, err := base64.StdEncoding.DecodeString(l) - if err != nil { - return fmt.Errorf("failed to decode proof line %d: %w", i, err) - } - r[i] = b - } - (*p) = r - return nil -} diff --git a/internal/witness/proof_test.go b/internal/witness/proof_test.go deleted file mode 100644 index 46a3a123..00000000 --- a/internal/witness/proof_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021 Google LLC. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package witness_test - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/transparency-dev/witness/internal/witness" -) - -func TestMarshalProof(t *testing.T) { - for _, test := range []struct { - desc string - p witness.Proof - want string - }{ - { - desc: "valid", - p: witness.Proof{ - []byte("one"), []byte("two"), []byte("three"), - }, - want: "b25l\ndHdv\ndGhyZWU=\n", - }, { - desc: "valid empty", - p: witness.Proof{}, - want: "", - }, { - desc: "valid default entry", - p: witness.Proof{ - []byte("one"), []byte{}, []byte("three"), - }, - want: "b25l\n\ndGhyZWU=\n", - }, - } { - t.Run(test.desc, func(t *testing.T) { - got := test.p.Marshal() - if got != test.want { - t.Fatalf("Got %q, want %q", got, test.want) - } - }) - } -} - -func TestUnmarshalProof(t *testing.T) { - for _, test := range []struct { - desc string - m string - want witness.Proof - wantErr bool - }{ - { - desc: "valid one", - m: "b25l\ndHdv\ndGhyZWU=\n", - want: witness.Proof{[]byte("one"), []byte("two"), []byte("three")}, - }, { - desc: "valid two", - m: "Zm91cg==\nZml2ZQ==\nc2l4\nc2V2ZW4=\nZWlnaHQ=\n", - want: witness.Proof{[]byte("four"), []byte("five"), []byte("six"), []byte("seven"), []byte("eight")}, - }, { - desc: "invalid - missing newline after last hash", - m: "c2l4\nc2V2ZW4=\nZWlnaHQ=", - wantErr: true, - }, { - desc: "invalid base64", - m: "c2l4=\nNOT-BASE64!\nZWlnaHQ=\n", - wantErr: true, - }, - } { - t.Run(test.desc, func(t *testing.T) { - var got witness.Proof - if gotErr := got.Unmarshal([]byte(test.m)); (gotErr != nil) != test.wantErr { - t.Fatalf("Unmarshal = %q, wantErr: %T", gotErr, test.wantErr) - } - if diff := cmp.Diff(test.want, got); len(diff) != 0 { - t.Fatalf("Unmarshal = diff %s", diff) - } - }) - } -} diff --git a/omniwitness/omniwitness.go b/omniwitness/omniwitness.go index 82fe9424..fa5c24b0 100644 --- a/omniwitness/omniwitness.go +++ b/omniwitness/omniwitness.go @@ -27,9 +27,7 @@ import ( "net/http" "time" - "github.com/transparency-dev/witness/api" - "github.com/transparency-dev/witness/internal/persistence" - "github.com/transparency-dev/witness/internal/witness" + "github.com/transparency-dev/witness/witness" "golang.org/x/mod/sumdb/note" "golang.org/x/sync/errgroup" "golang.org/x/time/rate" @@ -40,7 +38,7 @@ import ( // LogStatePersistence describes functionality the omniwitness requires // in order to persist its view of log state -type Persistence = persistence.LogStatePersistence +type Persistence = witness.LogStatePersistence const ( defaultDistributeInterval = 1 * time.Minute @@ -120,8 +118,6 @@ type LogConfig interface { // Main runs the omniwitness, with the witness listening using the listener, and all // outbound HTTP calls using the client provided. func Main(ctx context.Context, operatorConfig OperatorConfig, p Persistence, httpListener net.Listener, httpClient *http.Client) error { - initHTTPMetrics() - // This error group will be used to run all top level processes. // If any process dies, then all of them will be stopped via context cancellation. g, ctx := errgroup.WithContext(ctx) @@ -139,7 +135,7 @@ func Main(ctx context.Context, operatorConfig OperatorConfig, p Persistence, htt operatorConfig.Logs = l } - witness, err := witness.New(ctx, witness.Opts{ + w, err := witness.New(ctx, witness.Opts{ Persistence: p, Signers: operatorConfig.WitnessKeys, VerifierForLog: func(ctx context.Context, origin string) (note.Verifier, bool, error) { @@ -158,12 +154,6 @@ func Main(ctx context.Context, operatorConfig OperatorConfig, p Persistence, htt if operatorConfig.RateLimit > 0 { limiter = rate.NewLimiter(rate.Limit(operatorConfig.RateLimit), int(operatorConfig.RateLimit)) } - handler := &httpHandler{ - update: witness.Update, - logs: operatorConfig.Logs, - witVerifier: operatorConfig.WitnessVerifier, - limiter: limiter, - } if operatorConfig.DistributeInterval == 0 { operatorConfig.DistributeInterval = defaultDistributeInterval @@ -171,7 +161,8 @@ func Main(ctx context.Context, operatorConfig OperatorConfig, p Persistence, htt if operatorConfig.WitnessNetworkConfigInterval == 0 && len(operatorConfig.WitnessNetworkConfigURLs) > 0 { operatorConfig.WitnessNetworkConfigInterval = defaultProvisionInterval } - operatorConfig.ServeMux.Handle(api.HTTPAddCheckpoint, http.MaxBytesHandler(handler, 16*1024)) + h := witness.NewHTTPHandler(w) + operatorConfig.ServeMux.HandleFunc("POST /add-checkpoint", rateLimit(limiter, h.AddCheckpoint)) if operatorConfig.BastionAddr != "" && operatorConfig.BastionKey != nil { klog.Infof("My bastion backend ID: %064x", sha256.Sum256(operatorConfig.BastionKey.Public().(ed25519.PublicKey))) @@ -189,7 +180,7 @@ func Main(ctx context.Context, operatorConfig OperatorConfig, p Persistence, htt if operatorConfig.RestDistributorBaseURL != "" { klog.Infof("Starting RESTful distributor for %q", operatorConfig.RestDistributorBaseURL) - runRestDistributors(ctx, g, httpClient, operatorConfig.DistributeInterval, operatorConfig.Logs, operatorConfig.RestDistributorBaseURL, witness.GetCheckpoint, operatorConfig.WitnessVerifier, operatorConfig.DistributeRateLimit) + runRestDistributors(ctx, g, httpClient, operatorConfig.DistributeInterval, operatorConfig.Logs, operatorConfig.RestDistributorBaseURL, w.GetCheckpoint, operatorConfig.WitnessVerifier, operatorConfig.DistributeRateLimit) } if len(operatorConfig.WitnessNetworkConfigURLs) > 0 { g.Go(func() error { @@ -240,3 +231,14 @@ func runRestDistributors(ctx context.Context, g *errgroup.Group, httpClient *htt } }) } + +func rateLimit(limiter *rate.Limiter, delegate func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if !limiter.Allow() { + http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) + return + } + delegate(w, r) + } +} + diff --git a/omniwitness/http.go b/witness/http.go similarity index 50% rename from omniwitness/http.go rename to witness/http.go index 1796aea0..11a889fc 100644 --- a/omniwitness/http.go +++ b/witness/http.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package omniwitness +package witness import ( "bufio" @@ -22,89 +22,43 @@ import ( "fmt" "io" "net/http" - "strconv" - "strings" - "sync" - - "github.com/transparency-dev/witness/internal/witness" - "github.com/transparency-dev/witness/monitoring" - "golang.org/x/mod/sumdb/note" - "golang.org/x/time/rate" - "k8s.io/klog/v2" + ) // maxRequestBodyBytes is the limit on the number of bytes we'll read from incoming requests. -// 10KB should be more than enough, even in a PQ world. -const maxRequestBodyBytes = 10 << 10 - -var ( - httpDoOnce sync.Once - counterHTTPIncomingRequest monitoring.Counter - counterHTTPIncomingResponse monitoring.Counter - counterHTTPIncomingPushback monitoring.Counter -) +// 16 should be more than enough, even in a PQ world. +var MaxRequestBodyBytes int64 = 16 << 10 -func initHTTPMetrics() { - httpDoOnce.Do(func() { - mf := monitoring.GetMetricFactory() - const ( - origin = "origin" - status = "status" - ) - - counterHTTPIncomingRequest = mf.NewCounter("http_request", "Number of HTTP requests received") - counterHTTPIncomingResponse = mf.NewCounter("http_response", "HTTP responses", origin, status) - counterHTTPIncomingPushback = mf.NewCounter("http_pushback", "Number of pushed-back HTTP requests") - }) +func NewHTTPHandler(w *Witness) *HTTPHandler { + return &HTTPHandler{witness: w} } -// httpHandler knows how to handle tlog-witness HTTP requests. -type httpHandler struct { - update func(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, uint64, error) - logs LogConfig - witVerifier note.Verifier - limiter *rate.Limiter +// HTTPHandler provides tlog-witness compatible handlers intended to be used with the stdlib http server. +type HTTPHandler struct { + witness witness } -// ServeHTTP is a http.Handler which speaks the tlog-witness protocol. -func (a *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + +// AddCheckpoint is a http.Handler which speaks the tlog-witness protocol for add-checkpoint. +func (a *HTTPHandler) AddCheckpoint(w http.ResponseWriter, r *http.Request) { defer func() { + _, _ = io.ReadAll(r.Body) _ = r.Body.Close() }() - counterHTTPIncomingRequest.Inc() - if a.limiter != nil && !a.limiter.Allow() { - counterHTTPIncomingPushback.Inc() - klog.V(1).Infof("Too many HTTP requests, pushing back.") - w.WriteHeader(http.StatusTooManyRequests) - return - } - - oldSize, proof, cp, err := parseBody(http.MaxBytesReader(w, r.Body, maxRequestBodyBytes)) + oldSize, proof, cp, err := parseBody(http.MaxBytesReader(w, r.Body, MaxRequestBodyBytes)) if err != nil { - klog.V(1).Infof("invalid body: %v", err) w.WriteHeader(http.StatusBadRequest) - counterHTTPIncomingResponse.Inc("unknown", strconv.Itoa(http.StatusBadRequest)) - return - } - s := strings.SplitN(string(cp), "\n", 2) - if len(s) != 2 { - klog.V(1).Infof("invalid cp: %v", cp) - w.WriteHeader(http.StatusBadRequest) - counterHTTPIncomingResponse.Inc("unknown", strconv.Itoa(http.StatusBadRequest)) return } - origin := s[0] sc, body, contentType, err := a.handleUpdate(r.Context(), oldSize, cp, proof) if err != nil { status := http.StatusInternalServerError - if errors.Is(err, witness.ErrPushback) { + if errors.Is(err, ErrPushback) { status = http.StatusTooManyRequests } - klog.Errorf("handleUpdate: %v", err) w.WriteHeader(status) - counterHTTPIncomingResponse.Inc(origin, strconv.Itoa(status)) return } @@ -113,32 +67,29 @@ func (a *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(sc) if len(body) > 0 { - if _, err := w.Write(body); err != nil { - klog.Infof("Failed to write response body: %v", err) - } + _, _ = w.Write(body) } - counterHTTPIncomingResponse.Inc(origin, strconv.Itoa(sc)) } // handleUpdate submits the provided checkpoint to the witness and interprets any errors which may result. // // Returns an appropriate HTTP status code, response body, and Content Type representing the outcome. -func (a *httpHandler) handleUpdate(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) (int, []byte, string, error) { - sigs, trustedSize, updateErr := a.update(ctx, oldSize, newCP, proof) +func (a *HTTPHandler) handleUpdate(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) (int, []byte, string, error) { + sigs, trustedSize, updateErr := a.witness.Update(ctx, oldSize, newCP, proof) // Finally, handle any "soft" error from the update: if updateErr != nil { switch { - case errors.Is(updateErr, witness.ErrCheckpointStale): + case errors.Is(updateErr, ErrCheckpointStale): return http.StatusConflict, fmt.Appendf(nil, "%d\n", trustedSize), "text/x.tlog.size", nil - case errors.Is(updateErr, witness.ErrUnknownLog): + case errors.Is(updateErr, ErrUnknownLog): return http.StatusNotFound, nil, "", nil - case errors.Is(updateErr, witness.ErrNoValidSignature): + case errors.Is(updateErr, ErrNoValidSignature): return http.StatusForbidden, nil, "", nil - case errors.Is(updateErr, witness.ErrOldSizeInvalid): + case errors.Is(updateErr, ErrOldSizeInvalid): return http.StatusBadRequest, nil, "", nil - case errors.Is(updateErr, witness.ErrInvalidProof): + case errors.Is(updateErr, ErrInvalidProof): return http.StatusUnprocessableEntity, nil, "", nil - case errors.Is(updateErr, witness.ErrRootMismatch): + case errors.Is(updateErr, ErrRootMismatch): return http.StatusConflict, nil, "", nil default: return http.StatusInternalServerError, nil, "", updateErr @@ -159,19 +110,16 @@ func parseBody(r io.Reader) (uint64, [][]byte, []byte, error) { b := bufio.NewReader(r) sizeLine, _, err := b.ReadLine() if err != nil { - klog.Infof("read sizeline: %v", err) return 0, nil, nil, err } var size uint64 if n, err := fmt.Sscanf(string(sizeLine), "old %d", &size); err != nil || n != 1 { - klog.Infof("scan sizeline: %v", err) return 0, nil, nil, err } proof := [][]byte{} for { l, _, err := b.ReadLine() if err != nil { - klog.Infof("read proofline: %v", err) return 0, nil, nil, err } if len(l) == 0 { @@ -179,15 +127,20 @@ func parseBody(r io.Reader) (uint64, [][]byte, []byte, error) { } hash, err := base64.StdEncoding.DecodeString(string(l)) if err != nil { - klog.Infof("base64 proof: %v", err) return 0, nil, nil, err } proof = append(proof, hash) } cp, err := io.ReadAll(b) if err != nil { - klog.Infof("read cp: %v", err) return 0, nil, nil, err } return size, proof, cp, nil } + +// witness is the contract expected of the backend for HTTPHandler. +// This interface only really exists to make testing easier. +type witness interface { + Update(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, uint64, error) +} + diff --git a/omniwitness/http_test.go b/witness/http_test.go similarity index 81% rename from omniwitness/http_test.go rename to witness/http_test.go index fba5bfaa..b3fc63c5 100644 --- a/omniwitness/http_test.go +++ b/witness/http_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package omniwitness +package witness import ( "bytes" @@ -23,9 +23,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/transparency-dev/formats/log" - "github.com/transparency-dev/formats/note" - "github.com/transparency-dev/witness/internal/witness" ) const ( @@ -33,8 +30,6 @@ const ( testCPSize = 56 testCPRoot = "7azctENRYLlBCBQ5OX2qxxIKCTOeCda1KfTwjdt0wdA=" testCPSig = "— transparency.dev-aw-ftlog-ci-2 93xidocoWXVph2jEuzW2oovU+IjU71+FeVGKtKXQknSla2HCvr6RYHRSdJfxpo4kj5geqxkjrDXcbpiSo7lK96X4Dgc=\n" - - testCPVerifier = "transparency.dev-aw-ftlog-ci-2+f77c6276+AZXqiaARpwF4MoNOxx46kuiIRjrML0PDTm+c7BLaAMt6" ) var testCP = fmt.Sprintf("%s\n%d\n%s\n\n%s", testCPOrigin, testCPSize, testCPRoot, testCPSig) @@ -89,16 +84,6 @@ func TestParseBody(t *testing.T) { } func TestHandler(t *testing.T) { - v, err := note.NewVerifier(testCPVerifier) - if err != nil { - t.Fatalf("NewVerifier: %v", err) - } - logID := log.ID(testCPOrigin) - logs := &staticLogConfig{ - logs: map[string]Log{ - logID: Log{Origin: testCPOrigin}, - }, - } for _, test := range []struct { name string // fake witness control @@ -115,40 +100,35 @@ func TestHandler(t *testing.T) { wantBody: testCPSig, }, { name: "ErrCheckpointStale", - witness: &testWitness{updateErr: witness.ErrCheckpointStale, updateSize: testCPSize}, + witness: &testWitness{updateErr: ErrCheckpointStale, updateSize: testCPSize}, wantStatus: http.StatusConflict, wantContentType: "text/x.tlog.size", wantBody: fmt.Sprintf("%d\n", testCPSize), }, { name: "ErrNoValidSignature", - witness: &testWitness{updateErr: witness.ErrNoValidSignature}, + witness: &testWitness{updateErr: ErrNoValidSignature}, wantStatus: http.StatusForbidden, }, { name: "ErrUnknownLog", - witness: &testWitness{updateErr: witness.ErrUnknownLog}, + witness: &testWitness{updateErr: ErrUnknownLog}, wantStatus: http.StatusNotFound, }, { name: "ErrInvalidProof", - witness: &testWitness{updateErr: witness.ErrInvalidProof}, + witness: &testWitness{updateErr: ErrInvalidProof}, wantStatus: http.StatusUnprocessableEntity, }, { name: "ErrOldSizeInvalid", - witness: &testWitness{updateErr: witness.ErrOldSizeInvalid}, + witness: &testWitness{updateErr: ErrOldSizeInvalid}, wantStatus: http.StatusBadRequest, }, { name: "ErrRootMismatch", - witness: &testWitness{updateErr: witness.ErrRootMismatch}, + witness: &testWitness{updateErr: ErrRootMismatch}, wantStatus: http.StatusConflict, }, } { t.Run(test.name, func(t *testing.T) { - a := httpHandler{ - update: test.witness.Update, - witVerifier: v, - logs: logs, - } - if err != nil { - t.Fatalf("NewRequest: %v", err) + a := HTTPHandler{ + witness: test.witness, } sc, body, ct, err := a.handleUpdate(context.Background(), 0, []byte(testCP), [][]byte{}) if err != nil { diff --git a/internal/persistence/persistence.go b/witness/persistence.go similarity index 93% rename from internal/persistence/persistence.go rename to witness/persistence.go index 825a68bf..65b588d1 100644 --- a/internal/persistence/persistence.go +++ b/witness/persistence.go @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package persistence defines interfaces and tests for storing log state. -package persistence +package witness import "context" diff --git a/internal/witness/witness.go b/witness/witness.go similarity index 98% rename from internal/witness/witness.go rename to witness/witness.go index 0bacaaf1..4e036b16 100644 --- a/internal/witness/witness.go +++ b/witness/witness.go @@ -32,7 +32,6 @@ import ( "github.com/transparency-dev/formats/log" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" - "github.com/transparency-dev/witness/internal/persistence" "github.com/transparency-dev/witness/monitoring" "golang.org/x/mod/sumdb/note" "k8s.io/klog/v2" @@ -80,7 +79,7 @@ func initMetrics() { // Opts is the options passed to a witness. type Opts struct { - Persistence persistence.LogStatePersistence + Persistence LogStatePersistence Signers []note.Signer VerifierForLog func(ctx context.Context, origin string) (note.Verifier, bool, error) } @@ -88,7 +87,7 @@ type Opts struct { // Witness consists of a database for storing checkpoints, a signer, and a list // of logs for which it stores and verifies checkpoints. type Witness struct { - lsp persistence.LogStatePersistence + lsp LogStatePersistence Signers []note.Signer VerifierForLog func(ctx context.Context, origin string) (note.Verifier, bool, error) } diff --git a/internal/witness/witness_test.go b/witness/witness_test.go similarity index 100% rename from internal/witness/witness_test.go rename to witness/witness_test.go