diff options
| author | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2021-06-07 00:19:40 +0200 | 
|---|---|---|
| committer | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2021-06-07 00:19:40 +0200 | 
| commit | 932d29fd08c8ff401e471b4f764537493ccbd483 (patch) | |
| tree | e840a4c62db92e84201fe9ceaa0594d99176792c /pkg/instance | |
| parent | bdf7a53d61cf044e526cc9123ca296615f838288 (diff) | |
| parent | 345fe658fa8a4306caa74f72a618e499343675c2 (diff) | |
Merge branch 'design' into main
Diffstat (limited to 'pkg/instance')
| -rw-r--r-- | pkg/instance/endpoint.go | 122 | ||||
| -rw-r--r-- | pkg/instance/endpoint_test.go | 432 | ||||
| -rw-r--r-- | pkg/instance/instance.go | 159 | ||||
| -rw-r--r-- | pkg/instance/instance_test.go | 9 | ||||
| -rw-r--r-- | pkg/instance/metric.go | 19 | 
5 files changed, 741 insertions, 0 deletions
| diff --git a/pkg/instance/endpoint.go b/pkg/instance/endpoint.go new file mode 100644 index 0000000..5085c49 --- /dev/null +++ b/pkg/instance/endpoint.go @@ -0,0 +1,122 @@ +package stfe + +import ( +	"context" +	"net/http" + +	"github.com/golang/glog" +) + +func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { +	glog.V(3).Info("handling add-entry request") +	req, err := i.leafRequestFromHTTP(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) { +	glog.V(3).Info("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.Signature); err != nil { +		return http.StatusBadRequest, err +	} +	return http.StatusOK, nil +} + +func getTreeHeadLatest(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { +	glog.V(3).Info("handling get-tree-head-latest request") +	sth, err := i.Stateman.Latest(ctx) +	if err != nil { +		return http.StatusInternalServerError, err +	} +	if err := sth.MarshalASCII(w); err != nil { +		return http.StatusInternalServerError, err +	} +	return http.StatusOK, nil +} + +func getTreeHeadToSign(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { +	glog.V(3).Info("handling get-tree-head-to-sign request") +	sth, err := i.Stateman.ToSign(ctx) +	if err != nil { +		return http.StatusInternalServerError, err +	} +	if err := sth.MarshalASCII(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) { +	glog.V(3).Info("handling get-tree-head-cosigned request") +	sth, err := i.Stateman.Cosigned(ctx) +	if err != nil { +		return http.StatusInternalServerError, err +	} +	if err := sth.MarshalASCII(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) { +	glog.V(3).Info("handling get-consistency-proof request") +	req, err := i.consistencyProofRequestFromHTTP(r) +	if err != nil { +		return http.StatusBadRequest, err +	} + +	proof, err := i.Client.GetConsistencyProof(ctx, req) +	if err != nil { +		return http.StatusInternalServerError, err +	} +	if err := proof.MarshalASCII(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) { +	glog.V(3).Info("handling get-proof-by-hash request") +	req, err := i.inclusionProofRequestFromHTTP(r) +	if err != nil { +		return http.StatusBadRequest, err +	} + +	proof, err := i.Client.GetInclusionProof(ctx, req) +	if err != nil { +		return http.StatusInternalServerError, err +	} +	if err := proof.MarshalASCII(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) { +	glog.V(3).Info("handling get-leaves request") +	req, err := i.leavesRequestFromHTTP(r) +	if err != nil { +		return http.StatusBadRequest, err +	} + +	leaves, err := i.Client.GetLeaves(ctx, req) +	if err != nil { +		return http.StatusInternalServerError, err +	} +	for _, leaf := range *leaves { +		if err := leaf.MarshalASCII(w); err != nil { +			return http.StatusInternalServerError, err +		} +	} +	return http.StatusOK, nil +} diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/endpoint_test.go new file mode 100644 index 0000000..efcd4c0 --- /dev/null +++ b/pkg/instance/endpoint_test.go @@ -0,0 +1,432 @@ +package stfe + +import ( +	"bytes" +	"encoding/hex" +	"fmt" +	"io" +	"net/http" +	"net/http/httptest" +	"testing" + +	"github.com/golang/mock/gomock" +	"github.com/system-transparency/stfe/pkg/mocks" +	"github.com/system-transparency/stfe/pkg/types" +) + +var ( +	testWitVK  = [types.VerificationKeySize]byte{} +	testConfig = Config{ +		LogID:    hex.EncodeToString(types.Hash([]byte("logid"))[:]), +		TreeID:   0, +		Prefix:   "testonly", +		MaxRange: 3, +		Deadline: 10, +		Interval: 10, +		Witnesses: map[[types.HashSize]byte][types.VerificationKeySize]byte{ +			*types.Hash(testWitVK[:]): testWitVK, +		}, +	} +	testSTH = &types.SignedTreeHead{ +		TreeHead: types.TreeHead{ +			Timestamp: 0, +			TreeSize:  0, +			RootHash:  types.Hash(nil), +		}, +		SigIdent: []*types.SigIdent{ +			&types.SigIdent{ +				Signature: &[types.SignatureSize]byte{}, +				KeyHash:   &[types.HashSize]byte{}, +			}, +		}, +	} +) + +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 TestAddLeaf(t *testing.T) { +	buf := func() io.Reader { +		// A valid leaf request that was created manually +		return bytes.NewBufferString(fmt.Sprintf( +			"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s", +			types.ShardHint, types.Delim, "0", types.EOL, +			types.Checksum, types.Delim, "0000000000000000000000000000000000000000000000000000000000000000", types.EOL, +			types.SignatureOverMessage, types.Delim, "4cb410a4d48f52f761a7c01abcc28fd71811b84ded5403caed5e21b374f6aac9637cecd36828f17529fd503413d30ab66d7bb37a31dbf09a90d23b9241c45009", types.EOL, +			types.VerificationKey, types.Delim, "f2b7a00b625469d32502e06e8b7fad1ef258d4ad0c6cd87b846142ab681957d5", types.EOL, +			types.DomainHint, types.Delim, "example.com", types.EOL, +		)) +	} +	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 (signature error)", +			ascii: bytes.NewBufferString(fmt.Sprintf( +				"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s"+"%s%s%s%s", +				types.ShardHint, types.Delim, "1", types.EOL, +				types.Checksum, types.Delim, "1111111111111111111111111111111111111111111111111111111111111111", types.EOL, +				types.SignatureOverMessage, types.Delim, "4cb410a4d48f52f761a7c01abcc28fd71811b84ded5403caed5e21b374f6aac9637cecd36828f17529fd503413d30ab66d7bb37a31dbf09a90d23b9241c45009", types.EOL, +				types.VerificationKey, types.Delim, "f2b7a00b625469d32502e06e8b7fad1ef258d4ad0c6cd87b846142ab681957d5", types.EOL, +				types.DomainHint, types.Delim, "example.com", types.EOL, +			)), +			wantCode: http.StatusBadRequest, +		}, +		{ +			description: "invalid: backend failure", +			ascii:       buf(), +			expect:      true, +			err:         fmt.Errorf("something went wrong"), +			wantCode:    http.StatusInternalServerError, +		}, +		{ +			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() +			client := mocks.NewMockClient(ctrl) +			if table.expect { +				client.EXPECT().AddLeaf(gomock.Any(), gomock.Any()).Return(table.err) +			} +			i := Instance{ +				Config: testConfig, +				Client: client, +			} + +			// 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%s%x%s"+"%s%s%x%s", +			types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, +			types.KeyHash, types.Delim, *types.Hash(testWitVK[:]), types.EOL, +		)) +	} +	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%s%x%s"+"%s%s%x%s", +				types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, +				types.KeyHash, types.Delim, *types.Hash(testWitVK[1:]), types.EOL, +			)), +			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 := mocks.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 TestGetTreeHeadLatest(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 := mocks.NewMockStateManager(ctrl) +			if table.expect { +				stateman.EXPECT().Latest(gomock.Any()).Return(table.rsp, table.err) +			} +			i := Instance{ +				Config:   testConfig, +				Stateman: stateman, +			} + +			// Create HTTP request +			url := types.EndpointGetTreeHeadLatest.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.EndpointGetTreeHeadLatest).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 := mocks.NewMockStateManager(ctrl) +			if table.expect { +				stateman.EXPECT().ToSign(gomock.Any()).Return(table.rsp, table.err) +			} +			i := Instance{ +				Config:   testConfig, +				Stateman: stateman, +			} + +			// Create HTTP request +			url := types.EndpointGetTreeHeadToSign.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.EndpointGetTreeHeadToSign).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.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 := mocks.NewMockStateManager(ctrl) +			if table.expect { +				stateman.EXPECT().Cosigned(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) { +	buf := func(oldSize, newSize int) io.Reader { +		return bytes.NewBufferString(fmt.Sprintf( +			"%s%s%d%s"+"%s%s%d%s", +			types.OldSize, types.Delim, oldSize, types.EOL, +			types.NewSize, types.Delim, newSize, types.EOL, +		)) +	} +	// values in testProof are not relevant for the test, just need a path +	testProof := &types.ConsistencyProof{ +		OldSize: 1, +		NewSize: 2, +		Path: []*[types.HashSize]byte{ +			types.Hash(nil), +		}, +	} +	for _, table := range []struct { +		description string +		ascii       io.Reader               // buffer used to populate HTTP request +		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)", +			ascii:       bytes.NewBufferString("key=value\n"), +			wantCode:    http.StatusBadRequest, +		}, +		{ +			description: "valid", +			ascii:       buf(1, 2), +			expect:      true, +			rsp:         testProof, +			wantCode:    http.StatusOK, +		}, +	} { +		// Run deferred functions at the end of each iteration +		func() { +			ctrl := gomock.NewController(t) +			defer ctrl.Finish() +			client := mocks.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("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.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) { +} + +func TestGetLeaves(t *testing.T) { +} diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go new file mode 100644 index 0000000..c2fe8fa --- /dev/null +++ b/pkg/instance/instance.go @@ -0,0 +1,159 @@ +package stfe + +import ( +	"context" +	"crypto" +	"crypto/ed25519" +	"fmt" +	"net/http" +	"time" + +	"github.com/golang/glog" +	"github.com/system-transparency/stfe/pkg/state" +	"github.com/system-transparency/stfe/pkg/trillian" +	"github.com/system-transparency/stfe/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 + +	// Witnesses map trusted witness identifiers to public verification keys +	Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte +} + +// Instance is an instance of the log's front-end +type Instance struct { +	Config                      // configuration parameters +	Client   trillian.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 +} + +// Handler implements the http.Handler interface, and contains a reference +// to an STFE 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) +} + +// Handlers returns a list of STFE 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: getTreeHeadLatest, Endpoint: types.EndpointGetTreeHeadLatest, Method: http.MethodGet}, +		Handler{Instance: i, Handler: getTreeHeadToSign, Endpoint: types.EndpointGetTreeHeadToSign, Method: http.MethodGet}, +		Handler{Instance: i, Handler: getTreeHeadCosigned, Endpoint: types.EndpointGetTreeHeadCosigned, Method: http.MethodGet}, +		Handler{Instance: i, Handler: getConsistencyProof, Endpoint: types.EndpointGetConsistencyProof, Method: http.MethodPost}, +		Handler{Instance: i, Handler: getInclusionProof, Endpoint: types.EndpointGetProofByHash, Method: http.MethodPost}, +		Handler{Instance: i, Handler: getLeaves, Endpoint: types.EndpointGetLeaves, Method: http.MethodPost}, +	} +} + +// Path returns a path that should be configured for this handler +func (h Handler) Path() string { +	return h.Endpoint.Path(h.Instance.Prefix, "st", "v0") +} + +// ServeHTTP is part of the http.Handler interface +func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +	// export prometheus metrics +	var now time.Time = time.Now() +	var statusCode int +	defer func() { +		rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) +		latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) +	}() +	reqcnt.Inc(a.Instance.LogID, string(a.Endpoint)) + +	ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline)) +	defer cancel() + +	if r.Method != a.Method { +		glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method) +		http.Error(w, "", http.StatusMethodNotAllowed) +		return +	} + +	statusCode, err := a.Handler(ctx, a.Instance, w, r) +	if err != nil { +		glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err) +		http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) +	} +} + +func (i *Instance) leafRequestFromHTTP(r *http.Request) (*types.LeafRequest, error) { +	var req types.LeafRequest +	if err := req.UnmarshalASCII(r.Body); err != nil { +		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +	} + +	vk := ed25519.PublicKey(req.VerificationKey[:]) +	msg := req.Message.Marshal() +	sig := req.Signature[:] +	if !ed25519.Verify(vk, msg, sig) { +		return nil, fmt.Errorf("invalid signature") +	} +	// TODO: check shard hint +	// TODO: check domain hint +	return &req, nil +} + +func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*types.CosignatureRequest, error) { +	var req types.CosignatureRequest +	if err := req.UnmarshalASCII(r.Body); err != nil { +		return nil, fmt.Errorf("unpackOctetPost: %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) (*types.ConsistencyProofRequest, error) { +	var req types.ConsistencyProofRequest +	if err := req.UnmarshalASCII(r.Body); err != nil { +		return nil, fmt.Errorf("UnmarshalASCII: %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) (*types.InclusionProofRequest, error) { +	var req types.InclusionProofRequest +	if err := req.UnmarshalASCII(r.Body); err != nil { +		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +	} +	if req.TreeSize < 1 { +		return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", req.TreeSize) +	} +	return &req, nil +} + +func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*types.LeavesRequest, error) { +	var req types.LeavesRequest +	if err := req.UnmarshalASCII(r.Body); err != nil { +		return nil, fmt.Errorf("UnmarshalASCII: %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 new file mode 100644 index 0000000..45a2837 --- /dev/null +++ b/pkg/instance/instance_test.go @@ -0,0 +1,9 @@ +package stfe + +import ( +	"testing" +) + +func TestHandlers(t *testing.T)  {} +func TestPath(t *testing.T)      {} +func TestServeHTTP(t *testing.T) {} diff --git a/pkg/instance/metric.go b/pkg/instance/metric.go new file mode 100644 index 0000000..db11bd2 --- /dev/null +++ b/pkg/instance/metric.go @@ -0,0 +1,19 @@ +package stfe + +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") +} | 
