aboutsummaryrefslogtreecommitdiff
path: root/pkg/instance
diff options
context:
space:
mode:
authorLinus Nordberg <linus@nordberg.se>2022-05-24 23:33:38 +0200
committerRasmus Dahlberg <rasmus@mullvad.net>2022-06-23 11:33:17 +0200
commit559bccccd40d028e412d9f11709ded0250ba6dcd (patch)
tree50f3193dbe70fec21357963c11e5f663013f4b4c /pkg/instance
parent4b20ef0c1732bcef633c0ed7104501898aa84e2c (diff)
implement primary and secondary role, for replicationv0.5.0
Diffstat (limited to 'pkg/instance')
-rw-r--r--pkg/instance/experimental.go85
-rw-r--r--pkg/instance/handler.go184
-rw-r--r--pkg/instance/handler_test.go688
-rw-r--r--pkg/instance/instance.go135
-rw-r--r--pkg/instance/instance_test.go23
-rw-r--r--pkg/instance/metric.go19
6 files changed, 0 insertions, 1134 deletions
diff --git a/pkg/instance/experimental.go b/pkg/instance/experimental.go
deleted file mode 100644
index 24feeaf..0000000
--- a/pkg/instance/experimental.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package instance
-
-import (
- "bytes"
- "context"
- "crypto"
- "crypto/ed25519"
- "crypto/sha256"
- "encoding/base64"
- "encoding/binary"
- "fmt"
- "net/http"
-
- "git.sigsum.org/sigsum-go/pkg/log"
- "git.sigsum.org/sigsum-go/pkg/types"
-)
-
-// algEd25519 identifies a checkpoint signature algorithm
-const algEd25519 byte = 1
-
-// getCheckpoint is an experimental endpoint that is not part of the official
-// Sigsum API. Documentation can be found in the transparency-dev repo.
-func getCheckpoint(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
- log.Debug("handling get-checkpoint request")
- sth, err := i.Stateman.ToCosignTreeHead(ctx)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- if err := i.signWriteNote(w, sth); err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusOK, nil
-}
-
-// signWriteNote signs and writes a checkpoint which uses "sigsum.org:<prefix>"
-// as origin string. Origin string is also used as ID in the note signature.
-// This means that a sigsum log's prefix (say, "glass-frog"), must be unique.
-func (i *Instance) signWriteNote(w http.ResponseWriter, sth *types.SignedTreeHead) error {
- origin := fmt.Sprintf("sigsum.org:%s", i.Prefix)
- msg := fmt.Sprintf("%s\n%d\n%s\n",
- origin,
- sth.TreeSize,
- base64.StdEncoding.EncodeToString(sth.RootHash[:]),
- )
- sig, err := noteSign(i.Signer, origin, msg)
- if err != nil {
- return err
- }
-
- fmt.Fprintf(w, "%s\n\u2014 %s %s\n", msg, origin, sig)
- return nil
-}
-
-// noteSign returns a note signature for the provided origin and message
-func noteSign(signer crypto.Signer, origin, msg string) (string, error) {
- sig, err := signer.Sign(nil, []byte(msg), crypto.Hash(0))
- if err != nil {
- return "", err
- }
-
- var hbuf [4]byte
- binary.BigEndian.PutUint32(hbuf[:], noteKeyHash(origin, notePubKeyEd25519(signer)))
- sig = append(hbuf[:], sig...)
- return base64.StdEncoding.EncodeToString(sig), nil
-}
-
-// See:
-// https://cs.opensource.google/go/x/mod/+/refs/tags/v0.5.1:sumdb/note/note.go;l=336
-func notePubKeyEd25519(signer crypto.Signer) []byte {
- return bytes.Join([][]byte{
- []byte{algEd25519},
- signer.Public().(ed25519.PublicKey),
- }, nil)
-}
-
-// Source:
-// https://cs.opensource.google/go/x/mod/+/refs/tags/v0.5.1:sumdb/note/note.go;l=222
-func noteKeyHash(name string, key []byte) uint32 {
- h := sha256.New()
- h.Write([]byte(name))
- h.Write([]byte("\n"))
- h.Write(key)
- sum := h.Sum(nil)
- return binary.BigEndian.Uint32(sum)
-}
diff --git a/pkg/instance/handler.go b/pkg/instance/handler.go
deleted file mode 100644
index fa465ee..0000000
--- a/pkg/instance/handler.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package instance
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- "git.sigsum.org/sigsum-go/pkg/log"
- "git.sigsum.org/sigsum-go/pkg/types"
-)
-
-// Handler implements the http.Handler interface, and contains a reference
-// to a sigsum server instance as well as a function that uses it.
-type Handler struct {
- Instance *Instance
- Endpoint types.Endpoint
- Method string
- Handler func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error)
-}
-
-// Path returns a path that should be configured for this handler
-func (h Handler) Path() string {
- if len(h.Instance.Prefix) == 0 {
- return h.Endpoint.Path("", "sigsum", "v0")
- }
- return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0")
-}
-
-// ServeHTTP is part of the http.Handler interface
-func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- start := time.Now()
- code := 0
- defer func() {
- end := time.Now().Sub(start).Seconds()
- sc := fmt.Sprintf("%d", code)
-
- rspcnt.Inc(h.Instance.LogID, string(h.Endpoint), sc)
- latency.Observe(end, h.Instance.LogID, string(h.Endpoint), sc)
- }()
- reqcnt.Inc(h.Instance.LogID, string(h.Endpoint))
-
- code = h.verifyMethod(w, r)
- if code != 0 {
- return
- }
- code = h.handle(w, r)
-}
-
-// verifyMethod checks that an appropriate HTTP method is used. Error handling
-// is based on RFC 7231, see Sections 6.5.5 (Status 405) and 6.5.1 (Status 400).
-func (h *Handler) verifyMethod(w http.ResponseWriter, r *http.Request) int {
- if h.Method == r.Method {
- return 0
- }
-
- code := http.StatusBadRequest
- if ok := h.Instance.checkHTTPMethod(r.Method); ok {
- w.Header().Set("Allow", h.Method)
- code = http.StatusMethodNotAllowed
- }
-
- http.Error(w, fmt.Sprintf("error=%s", http.StatusText(code)), code)
- return code
-}
-
-// handle handles an HTTP request for which the HTTP method is already verified
-func (h Handler) handle(w http.ResponseWriter, r *http.Request) int {
- deadline := time.Now().Add(h.Instance.Deadline)
- ctx, cancel := context.WithDeadline(r.Context(), deadline)
- defer cancel()
-
- code, err := h.Handler(ctx, h.Instance, w, r)
- if err != nil {
- log.Debug("%s/%s: %v", h.Instance.Prefix, h.Endpoint, err)
- http.Error(w, fmt.Sprintf("error=%s", err.Error()), code)
- }
- return code
-}
-
-func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
- log.Debug("handling add-leaf request")
- req, err := i.leafRequestFromHTTP(ctx, r)
- if err != nil {
- return http.StatusBadRequest, err
- }
- if err := i.Client.AddLeaf(ctx, req); err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusOK, nil
-}
-
-func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
- log.Debug("handling add-cosignature request")
- req, err := i.cosignatureRequestFromHTTP(r)
- if err != nil {
- return http.StatusBadRequest, err
- }
- vk := i.Witnesses[req.KeyHash]
- if err := i.Stateman.AddCosignature(ctx, &vk, &req.Cosignature); err != nil {
- return http.StatusBadRequest, err
- }
- return http.StatusOK, nil
-}
-
-func getTreeHeadToCosign(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) {
- log.Debug("handling get-tree-head-to-cosign request")
- sth, err := i.Stateman.ToCosignTreeHead(ctx)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- if err := sth.ToASCII(w); err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusOK, nil
-}
-
-func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) {
- log.Debug("handling get-tree-head-cosigned request")
- cth, err := i.Stateman.CosignedTreeHead(ctx)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- if err := cth.ToASCII(w); err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusOK, nil
-}
-
-func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
- log.Debug("handling get-consistency-proof request")
- req, err := i.consistencyProofRequestFromHTTP(r)
- if err != nil {
- return http.StatusBadRequest, err
- }
- // XXX: check tree size of latest thing we signed?
-
- proof, err := i.Client.GetConsistencyProof(ctx, req)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- if err := proof.ToASCII(w); err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusOK, nil
-}
-
-func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
- log.Debug("handling get-inclusion-proof request")
- req, err := i.inclusionProofRequestFromHTTP(r)
- if err != nil {
- return http.StatusBadRequest, err
- }
- // XXX: check tree size of latest thing we signed?
-
- proof, err := i.Client.GetInclusionProof(ctx, req)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- if err := proof.ToASCII(w); err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusOK, nil
-}
-
-func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
- log.Debug("handling get-leaves request")
- req, err := i.leavesRequestFromHTTP(r)
- if err != nil {
- return http.StatusBadRequest, err
- }
- // XXX: check tree size of latest thing we signed?
-
- leaves, err := i.Client.GetLeaves(ctx, req)
- if err != nil {
- return http.StatusInternalServerError, err
- }
- for _, leaf := range *leaves {
- if err := leaf.ToASCII(w); err != nil {
- return http.StatusInternalServerError, err
- }
- }
- return http.StatusOK, nil
-}
diff --git a/pkg/instance/handler_test.go b/pkg/instance/handler_test.go
deleted file mode 100644
index 50bd3a4..0000000
--- a/pkg/instance/handler_test.go
+++ /dev/null
@@ -1,688 +0,0 @@
-package instance
-
-import (
- "bytes"
- "crypto/ed25519"
- "crypto/rand"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "reflect"
- "testing"
- "time"
-
- mocksDB "git.sigsum.org/log-go/pkg/db/mocks"
- mocksDNS "git.sigsum.org/log-go/internal/mocks/dns"
- mocksState "git.sigsum.org/log-go/pkg/state/mocks"
- "git.sigsum.org/sigsum-go/pkg/types"
- "github.com/golang/mock/gomock"
-)
-
-var (
- testWitVK = types.PublicKey{}
- testConfig = Config{
- LogID: fmt.Sprintf("%x", types.HashFn([]byte("logid"))[:]),
- TreeID: 0,
- Prefix: "testonly",
- MaxRange: 3,
- Deadline: 10,
- Interval: 10,
- ShardStart: 10,
- Witnesses: map[types.Hash]types.PublicKey{
- *types.HashFn(testWitVK[:]): testWitVK,
- },
- }
- testSTH = &types.SignedTreeHead{
- TreeHead: types.TreeHead{
- Timestamp: 0,
- TreeSize: 0,
- RootHash: *types.HashFn([]byte("root hash")),
- },
- Signature: types.Signature{},
- }
- testCTH = &types.CosignedTreeHead{
- SignedTreeHead: *testSTH,
- Cosignature: []types.Signature{types.Signature{}},
- KeyHash: []types.Hash{types.Hash{}},
- }
-)
-
-// TestHandlers check that the expected handlers are configured
-func TestHandlers(t *testing.T) {
- endpoints := map[types.Endpoint]bool{
- types.EndpointAddLeaf: false,
- types.EndpointAddCosignature: false,
- types.EndpointGetTreeHeadToCosign: false,
- types.EndpointGetTreeHeadCosigned: false,
- types.EndpointGetConsistencyProof: false,
- types.EndpointGetInclusionProof: false,
- types.EndpointGetLeaves: false,
- types.Endpoint("get-checkpoint"): false,
- }
- i := &Instance{
- Config: testConfig,
- }
- for _, handler := range i.Handlers() {
- if _, ok := endpoints[handler.Endpoint]; !ok {
- t.Errorf("got unexpected endpoint: %s", handler.Endpoint)
- }
- endpoints[handler.Endpoint] = true
- }
- for endpoint, ok := range endpoints {
- if !ok {
- t.Errorf("endpoint %s is not configured", endpoint)
- }
- }
-}
-
-func TestVerifyMethod(t *testing.T) {
- badMethod := http.MethodHead
- instance := Instance{Config: testConfig}
- for _, handler := range instance.Handlers() {
- for _, method := range []string{
- http.MethodGet,
- http.MethodPost,
- badMethod,
- } {
- url := handler.Endpoint.Path("http://log.example.com", instance.Prefix)
- req, err := http.NewRequest(method, url, nil)
- if err != nil {
- t.Fatalf("must create HTTP request: %v", err)
- }
-
- w := httptest.NewRecorder()
- code := handler.verifyMethod(w, req)
- if got, want := code == 0, handler.Method == method; got != want {
- t.Errorf("%s %s: got %v but wanted %v: %v", method, url, got, want, err)
- continue
- }
- if code == 0 {
- continue
- }
-
- if method == badMethod {
- if got, want := code, http.StatusBadRequest; got != want {
- t.Errorf("%s %s: got status %d, wanted %d", method, url, got, want)
- }
- if _, ok := w.Header()["Allow"]; ok {
- t.Errorf("%s %s: got Allow header, wanted none", method, url)
- }
- continue
- }
-
- if got, want := code, http.StatusMethodNotAllowed; got != want {
- t.Errorf("%s %s: got status %d, wanted %d", method, url, got, want)
- } else if methods, ok := w.Header()["Allow"]; !ok {
- t.Errorf("%s %s: got no allow header, expected one", method, url)
- } else if got, want := len(methods), 1; got != want {
- t.Errorf("%s %s: got %d allowed method(s), wanted %d", method, url, got, want)
- } else if got, want := methods[0], handler.Method; got != want {
- t.Errorf("%s %s: got allowed method %s, wanted %s", method, url, got, want)
- }
- }
- }
-}
-
-// TestPath checks that Path works for an endpoint (add-leaf)
-func TestPath(t *testing.T) {
- for _, table := range []struct {
- description string
- prefix string
- want string
- }{
- {
- description: "no prefix",
- want: "/sigsum/v0/add-leaf",
- },
- {
- description: "a prefix",
- prefix: "test-prefix",
- want: "/test-prefix/sigsum/v0/add-leaf",
- },
- } {
- instance := &Instance{
- Config: Config{
- Prefix: table.prefix,
- },
- }
- handler := Handler{
- Instance: instance,
- Handler: addLeaf,
- Endpoint: types.EndpointAddLeaf,
- Method: http.MethodPost,
- }
- if got, want := handler.Path(), table.want; got != want {
- t.Errorf("got path %v but wanted %v", got, want)
- }
- }
-}
-
-func TestAddLeaf(t *testing.T) {
- for _, table := range []struct {
- description string
- ascii io.Reader // buffer used to populate HTTP request
- expectTrillian bool // expect Trillian client code path
- errTrillian error // error from Trillian client
- expectDNS bool // expect DNS verifier code path
- errDNS error // error from DNS verifier
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: bad request (parser error)",
- ascii: bytes.NewBufferString("key=value\n"),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (signature error)",
- ascii: mustLeafBuffer(t, 10, types.Hash{}, false),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (shard hint is before shard start)",
- ascii: mustLeafBuffer(t, 9, types.Hash{}, true),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (shard hint is after shard end)",
- ascii: mustLeafBuffer(t, uint64(time.Now().Unix())+1024, types.Hash{}, true),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: failed verifying domain hint",
- ascii: mustLeafBuffer(t, 10, types.Hash{}, true),
- expectDNS: true,
- errDNS: fmt.Errorf("something went wrong"),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: backend failure",
- ascii: mustLeafBuffer(t, 10, types.Hash{}, true),
- expectDNS: true,
- expectTrillian: true,
- errTrillian: fmt.Errorf("something went wrong"),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid",
- ascii: mustLeafBuffer(t, 10, types.Hash{}, true),
- expectDNS: true,
- expectTrillian: true,
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- dns := mocksDNS.NewMockVerifier(ctrl)
- if table.expectDNS {
- dns.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.errDNS)
- }
- client := mocksDB.NewMockClient(ctrl)
- if table.expectTrillian {
- client.EXPECT().AddLeaf(gomock.Any(), gomock.Any()).Return(table.errTrillian)
- }
- i := Instance{
- Config: testConfig,
- Client: client,
- DNS: dns,
- }
-
- // Create HTTP request
- url := types.EndpointAddLeaf.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest("POST", url, table.ascii)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointAddLeaf).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestAddCosignature(t *testing.T) {
- buf := func() io.Reader {
- return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n",
- "cosignature", types.Signature{},
- "key_hash", *types.HashFn(testWitVK[:]),
- ))
- }
- for _, table := range []struct {
- description string
- ascii io.Reader // buffer used to populate HTTP request
- expect bool // set if a mock answer is expected
- err error // error from Trillian client
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: bad request (parser error)",
- ascii: bytes.NewBufferString("key=value\n"),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (unknown witness)",
- ascii: bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n",
- "cosignature", types.Signature{},
- "key_hash", *types.HashFn(testWitVK[1:]),
- )),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: backend failure",
- ascii: buf(),
- expect: true,
- err: fmt.Errorf("something went wrong"),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "valid",
- ascii: buf(),
- expect: true,
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- stateman := mocksState.NewMockStateManager(ctrl)
- if table.expect {
- stateman.EXPECT().AddCosignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.err)
- }
- i := Instance{
- Config: testConfig,
- Stateman: stateman,
- }
-
- // Create HTTP request
- url := types.EndpointAddCosignature.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest("POST", url, table.ascii)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointAddCosignature).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestGetTreeToSign(t *testing.T) {
- for _, table := range []struct {
- description string
- expect bool // set if a mock answer is expected
- rsp *types.SignedTreeHead // signed tree head from Trillian client
- err error // error from Trillian client
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: backend failure",
- expect: true,
- err: fmt.Errorf("something went wrong"),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid",
- expect: true,
- rsp: testSTH,
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- stateman := mocksState.NewMockStateManager(ctrl)
- if table.expect {
- stateman.EXPECT().ToCosignTreeHead(gomock.Any()).Return(table.rsp, table.err)
- }
- i := Instance{
- Config: testConfig,
- Stateman: stateman,
- }
-
- // Create HTTP request
- url := types.EndpointGetTreeHeadToCosign.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointGetTreeHeadToCosign).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestGetTreeCosigned(t *testing.T) {
- for _, table := range []struct {
- description string
- expect bool // set if a mock answer is expected
- rsp *types.CosignedTreeHead // cosigned tree head from Trillian client
- err error // error from Trillian client
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: backend failure",
- expect: true,
- err: fmt.Errorf("something went wrong"),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid",
- expect: true,
- rsp: testCTH,
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- stateman := mocksState.NewMockStateManager(ctrl)
- if table.expect {
- stateman.EXPECT().CosignedTreeHead(gomock.Any()).Return(table.rsp, table.err)
- }
- i := Instance{
- Config: testConfig,
- Stateman: stateman,
- }
-
- // Create HTTP request
- url := types.EndpointGetTreeHeadCosigned.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointGetTreeHeadCosigned).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestGetConsistencyProof(t *testing.T) {
- for _, table := range []struct {
- description string
- params string // params is the query's url params
- expect bool // set if a mock answer is expected
- rsp *types.ConsistencyProof // consistency proof from Trillian client
- err error // error from Trillian client
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: bad request (parser error)",
- params: "a/1",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (OldSize is zero)",
- params: "0/1",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (OldSize > NewSize)",
- params: "2/1",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: backend failure",
- params: "1/2",
- expect: true,
- err: fmt.Errorf("something went wrong"),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid",
- params: "1/2",
- expect: true,
- rsp: &types.ConsistencyProof{
- OldSize: 1,
- NewSize: 2,
- Path: []types.Hash{
- *types.HashFn([]byte{}),
- },
- },
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mocksDB.NewMockClient(ctrl)
- if table.expect {
- client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)
- }
- i := Instance{
- Config: testConfig,
- Client: client,
- }
-
- // Create HTTP request
- url := types.EndpointGetConsistencyProof.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest(http.MethodGet, url+table.params, nil)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointGetConsistencyProof).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestGetInclusionProof(t *testing.T) {
- for _, table := range []struct {
- description string
- params string // params is the query's url params
- expect bool // set if a mock answer is expected
- rsp *types.InclusionProof // inclusion proof from Trillian client
- err error // error from Trillian client
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: bad request (parser error)",
- params: "a/0000000000000000000000000000000000000000000000000000000000000000",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (no proof for tree size)",
- params: "1/0000000000000000000000000000000000000000000000000000000000000000",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: backend failure",
- params: "2/0000000000000000000000000000000000000000000000000000000000000000",
- expect: true,
- err: fmt.Errorf("something went wrong"),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid",
- params: "2/0000000000000000000000000000000000000000000000000000000000000000",
- expect: true,
- rsp: &types.InclusionProof{
- TreeSize: 2,
- LeafIndex: 0,
- Path: []types.Hash{
- *types.HashFn([]byte{}),
- },
- },
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mocksDB.NewMockClient(ctrl)
- if table.expect {
- client.EXPECT().GetInclusionProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)
- }
- i := Instance{
- Config: testConfig,
- Client: client,
- }
-
- // Create HTTP request
- url := types.EndpointGetInclusionProof.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest(http.MethodGet, url+table.params, nil)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointGetInclusionProof).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestGetLeaves(t *testing.T) {
- for _, table := range []struct {
- description string
- params string // params is the query's url params
- expect bool // set if a mock answer is expected
- rsp *types.Leaves // list of leaves from Trillian client
- err error // error from Trillian client
- wantCode int // HTTP status ok
- }{
- {
- description: "invalid: bad request (parser error)",
- params: "a/1",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: bad request (StartSize > EndSize)",
- params: "1/0",
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid: backend failure",
- params: "0/0",
- expect: true,
- err: fmt.Errorf("something went wrong"),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid: one more entry than the configured MaxRange",
- params: fmt.Sprintf("%d/%d", 0, testConfig.MaxRange), // query will be pruned
- expect: true,
- rsp: func() *types.Leaves {
- var list types.Leaves
- for i := int64(0); i < testConfig.MaxRange; i++ {
- list = append(list[:], types.Leaf{
- Statement: types.Statement{
- ShardHint: 0,
- Checksum: types.Hash{},
- },
- Signature: types.Signature{},
- KeyHash: types.Hash{},
- })
- }
- return &list
- }(),
- wantCode: http.StatusOK,
- },
- } {
- // Run deferred functions at the end of each iteration
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := mocksDB.NewMockClient(ctrl)
- if table.expect {
- client.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)
- }
- i := Instance{
- Config: testConfig,
- Client: client,
- }
-
- // Create HTTP request
- url := types.EndpointGetLeaves.Path("http://example.com", i.Prefix)
- req, err := http.NewRequest(http.MethodGet, url+table.params, nil)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
-
- // Run HTTP request
- w := httptest.NewRecorder()
- mustHandle(t, i, types.EndpointGetLeaves).ServeHTTP(w, req)
- if got, want := w.Code, table.wantCode; got != want {
- t.Errorf("got HTTP status code %v but wanted %v in test %q", got, want, table.description)
- }
- if w.Code != http.StatusOK {
- return
- }
-
- list := types.Leaves{}
- if err := list.FromASCII(w.Body); err != nil {
- t.Fatalf("must unmarshal leaf list: %v", err)
- }
- if got, want := &list, table.rsp; !reflect.DeepEqual(got, want) {
- t.Errorf("got leaf list\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler {
- for _, handler := range i.Handlers() {
- if handler.Endpoint == e {
- return handler
- }
- }
- t.Fatalf("must handle endpoint: %v", e)
- return Handler{}
-}
-
-func mustLeafBuffer(t *testing.T, shardHint uint64, message types.Hash, wantSig bool) io.Reader {
- t.Helper()
-
- vk, sk, err := ed25519.GenerateKey(rand.Reader)
- if err != nil {
- t.Fatalf("must generate ed25519 keys: %v", err)
- }
- msg := types.Statement{
- ShardHint: shardHint,
- Checksum: *types.HashFn(message[:]),
- }
- sig := ed25519.Sign(sk, msg.ToBinary())
- if !wantSig {
- sig[0] += 1
- }
- return bytes.NewBufferString(fmt.Sprintf(
- "%s=%d\n"+"%s=%x\n"+"%s=%x\n"+"%s=%x\n"+"%s=%s\n",
- "shard_hint", shardHint,
- "message", message[:],
- "signature", sig,
- "public_key", vk,
- "domain_hint", "example.com",
- ))
-}
diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go
deleted file mode 100644
index f4c0089..0000000
--- a/pkg/instance/instance.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package instance
-
-import (
- "context"
- "crypto"
- "fmt"
- "net/http"
- "time"
-
- "git.sigsum.org/log-go/pkg/db"
- "git.sigsum.org/log-go/pkg/state"
- "git.sigsum.org/sigsum-go/pkg/dns"
- "git.sigsum.org/sigsum-go/pkg/requests"
- "git.sigsum.org/sigsum-go/pkg/types"
-)
-
-// Config is a collection of log parameters
-type Config struct {
- LogID string // H(public key), then hex-encoded
- TreeID int64 // Merkle tree identifier used by Trillian
- Prefix string // The portion between base URL and st/v0 (may be "")
- MaxRange int64 // Maximum number of leaves per get-leaves request
- Deadline time.Duration // Deadline used for gRPC requests
- Interval time.Duration // Cosigning frequency
- ShardStart uint64 // Shard interval start (num seconds since UNIX epoch)
-
- // Witnesses map trusted witness identifiers to public keys
- Witnesses map[types.Hash]types.PublicKey
-}
-
-// Instance is an instance of the log's front-end
-type Instance struct {
- Config // configuration parameters
- Client db.Client // provides access to the Trillian backend
- Signer crypto.Signer // provides access to Ed25519 private key
- Stateman state.StateManager // coordinates access to (co)signed tree heads
- DNS dns.Verifier // checks if domain name knows a public key
-}
-
-// Handlers returns a list of sigsum handlers
-func (i *Instance) Handlers() []Handler {
- return []Handler{
- Handler{Instance: i, Handler: addLeaf, Endpoint: types.EndpointAddLeaf, Method: http.MethodPost},
- Handler{Instance: i, Handler: addCosignature, Endpoint: types.EndpointAddCosignature, Method: http.MethodPost},
- Handler{Instance: i, Handler: getTreeHeadToCosign, Endpoint: types.EndpointGetTreeHeadToCosign, Method: http.MethodGet},
- Handler{Instance: i, Handler: getTreeHeadCosigned, Endpoint: types.EndpointGetTreeHeadCosigned, Method: http.MethodGet},
- Handler{Instance: i, Handler: getCheckpoint, Endpoint: types.Endpoint("get-checkpoint"), Method: http.MethodGet},
- Handler{Instance: i, Handler: getConsistencyProof, Endpoint: types.EndpointGetConsistencyProof, Method: http.MethodGet},
- Handler{Instance: i, Handler: getInclusionProof, Endpoint: types.EndpointGetInclusionProof, Method: http.MethodGet},
- Handler{Instance: i, Handler: getLeaves, Endpoint: types.EndpointGetLeaves, Method: http.MethodGet},
- }
-}
-
-// checkHTTPMethod checks if an HTTP method is supported
-func (i *Instance) checkHTTPMethod(m string) bool {
- return m == http.MethodGet || m == http.MethodPost
-}
-
-func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*requests.Leaf, error) {
- var req requests.Leaf
- if err := req.FromASCII(r.Body); err != nil {
- return nil, fmt.Errorf("FromASCII: %v", err)
- }
- stmt := types.Statement{
- ShardHint: req.ShardHint,
- Checksum: *types.HashFn(req.Message[:]),
- }
- if !stmt.Verify(&req.PublicKey, &req.Signature) {
- return nil, fmt.Errorf("invalid signature")
- }
- shardEnd := uint64(time.Now().Unix())
- if req.ShardHint < i.ShardStart {
- return nil, fmt.Errorf("invalid shard hint: %d not in [%d, %d]", req.ShardHint, i.ShardStart, shardEnd)
- }
- if req.ShardHint > shardEnd {
- return nil, fmt.Errorf("invalid shard hint: %d not in [%d, %d]", req.ShardHint, i.ShardStart, shardEnd)
- }
- if err := i.DNS.Verify(ctx, req.DomainHint, &req.PublicKey); err != nil {
- return nil, fmt.Errorf("invalid domain hint: %v", err)
- }
- return &req, nil
-}
-
-func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*requests.Cosignature, error) {
- var req requests.Cosignature
- if err := req.FromASCII(r.Body); err != nil {
- return nil, fmt.Errorf("FromASCII: %v", err)
- }
- if _, ok := i.Witnesses[req.KeyHash]; !ok {
- return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash)
- }
- return &req, nil
-}
-
-func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*requests.ConsistencyProof, error) {
- var req requests.ConsistencyProof
- if err := req.FromURL(r.URL.Path); err != nil {
- return nil, fmt.Errorf("FromASCII: %v", err)
- }
- if req.OldSize < 1 {
- return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize)
- }
- if req.NewSize <= req.OldSize {
- return nil, fmt.Errorf("NewSize(%d) must be larger than OldSize(%d)", req.NewSize, req.OldSize)
- }
- return &req, nil
-}
-
-func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*requests.InclusionProof, error) {
- var req requests.InclusionProof
- if err := req.FromURL(r.URL.Path); err != nil {
- return nil, fmt.Errorf("FromASCII: %v", err)
- }
- if req.TreeSize < 2 {
- // TreeSize:0 => not possible to prove inclusion of anything
- // TreeSize:1 => you don't need an inclusion proof (it is always empty)
- return nil, fmt.Errorf("TreeSize(%d) must be larger than one", req.TreeSize)
- }
- return &req, nil
-}
-
-func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*requests.Leaves, error) {
- var req requests.Leaves
- if err := req.FromURL(r.URL.Path); err != nil {
- return nil, fmt.Errorf("FromASCII: %v", err)
- }
-
- if req.StartSize > req.EndSize {
- return nil, fmt.Errorf("StartSize(%d) must be less than or equal to EndSize(%d)", req.StartSize, req.EndSize)
- }
- if req.EndSize-req.StartSize+1 > uint64(i.MaxRange) {
- req.EndSize = req.StartSize + uint64(i.MaxRange) - 1
- }
- return &req, nil
-}
diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go
deleted file mode 100644
index 00d996d..0000000
--- a/pkg/instance/instance_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package instance
-
-import (
- "net/http"
- "testing"
-)
-
-func CheckHTTPMethod(t *testing.T) {
- var instance Instance
- for _, table := range []struct {
- method string
- wantOK bool
- }{
- {wantOK: false, method: http.MethodHead},
- {wantOK: true, method: http.MethodPost},
- {wantOK: true, method: http.MethodGet},
- } {
- ok := instance.checkHTTPMethod(table.method)
- if got, want := ok, table.wantOK; got != want {
- t.Errorf("%s: got %v but wanted %v", table.method, got, want)
- }
- }
-}
diff --git a/pkg/instance/metric.go b/pkg/instance/metric.go
deleted file mode 100644
index cbd0223..0000000
--- a/pkg/instance/metric.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package instance
-
-import (
- "github.com/google/trillian/monitoring"
- "github.com/google/trillian/monitoring/prometheus"
-)
-
-var (
- reqcnt monitoring.Counter // number of incoming http requests
- rspcnt monitoring.Counter // number of valid http responses
- latency monitoring.Histogram // request-response latency
-)
-
-func init() {
- mf := prometheus.MetricFactory{}
- reqcnt = mf.NewCounter("http_req", "number of http requests", "logid", "endpoint")
- rspcnt = mf.NewCounter("http_rsp", "number of http requests", "logid", "endpoint", "status")
- latency = mf.NewHistogram("http_latency", "http request-response latency", "logid", "endpoint", "status")
-}