diff options
| author | Linus Nordberg <linus@nordberg.se> | 2022-05-24 23:33:38 +0200 | 
|---|---|---|
| committer | Rasmus Dahlberg <rasmus@mullvad.net> | 2022-06-23 11:33:17 +0200 | 
| commit | 559bccccd40d028e412d9f11709ded0250ba6dcd (patch) | |
| tree | 50f3193dbe70fec21357963c11e5f663013f4b4c /pkg/instance | |
| parent | 4b20ef0c1732bcef633c0ed7104501898aa84e2c (diff) | |
implement primary and secondary role, for replicationv0.5.0
Diffstat (limited to 'pkg/instance')
| -rw-r--r-- | pkg/instance/experimental.go | 85 | ||||
| -rw-r--r-- | pkg/instance/handler.go | 184 | ||||
| -rw-r--r-- | pkg/instance/handler_test.go | 688 | ||||
| -rw-r--r-- | pkg/instance/instance.go | 135 | ||||
| -rw-r--r-- | pkg/instance/instance_test.go | 23 | ||||
| -rw-r--r-- | pkg/instance/metric.go | 19 | 
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") -} | 
