diff options
Diffstat (limited to 'pkg/instance')
-rw-r--r-- | pkg/instance/experimental.go (renamed from pkg/instance/experimental_endpoint.go) | 2 | ||||
-rw-r--r-- | pkg/instance/handler.go (renamed from pkg/instance/endpoint.go) | 63 | ||||
-rw-r--r-- | pkg/instance/handler_test.go (renamed from pkg/instance/endpoint_test.go) | 271 | ||||
-rw-r--r-- | pkg/instance/instance.go | 104 | ||||
-rw-r--r-- | pkg/instance/instance_test.go | 98 |
5 files changed, 257 insertions, 281 deletions
diff --git a/pkg/instance/experimental_endpoint.go b/pkg/instance/experimental.go index 2986a27..ab81ada 100644 --- a/pkg/instance/experimental_endpoint.go +++ b/pkg/instance/experimental.go @@ -11,7 +11,7 @@ import ( "fmt" "net/http" - "git.sigsum.org/sigsum-log-go/pkg/types" + "git.sigsum.org/sigsum-lib-go/pkg/types" "github.com/golang/glog" ) diff --git a/pkg/instance/endpoint.go b/pkg/instance/handler.go index a6d424d..66a20a5 100644 --- a/pkg/instance/endpoint.go +++ b/pkg/instance/handler.go @@ -2,11 +2,58 @@ package instance import ( "context" + "fmt" "net/http" + "time" + "git.sigsum.org/sigsum-lib-go/pkg/types" "github.com/golang/glog" ) +// 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 (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("Error=%s\n", err.Error()), statusCode) + } +} + 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(ctx, r) @@ -25,8 +72,8 @@ func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r * if err != nil { return http.StatusBadRequest, err } - vk := i.Witnesses[*req.KeyHash] - if err := i.Stateman.AddCosignature(ctx, &vk, req.Signature); err != nil { + vk := i.Witnesses[req.KeyHash] + if err := i.Stateman.AddCosignature(ctx, &vk, &req.Cosignature); err != nil { return http.StatusBadRequest, err } return http.StatusOK, nil @@ -38,7 +85,7 @@ func getTreeHeadLatest(ctx context.Context, i *Instance, w http.ResponseWriter, if err != nil { return http.StatusInternalServerError, err } - if err := sth.MarshalASCII(w); err != nil { + if err := sth.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil @@ -50,7 +97,7 @@ func getTreeHeadToSign(ctx context.Context, i *Instance, w http.ResponseWriter, if err != nil { return http.StatusInternalServerError, err } - if err := sth.MarshalASCII(w); err != nil { + if err := sth.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil @@ -62,7 +109,7 @@ func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter if err != nil { return http.StatusInternalServerError, err } - if err := cth.MarshalASCII(w); err != nil { + if err := cth.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil @@ -79,7 +126,7 @@ func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter if err != nil { return http.StatusInternalServerError, err } - if err := proof.MarshalASCII(w); err != nil { + if err := proof.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil @@ -96,7 +143,7 @@ func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter, if err != nil { return http.StatusInternalServerError, err } - if err := proof.MarshalASCII(w); err != nil { + if err := proof.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil @@ -114,7 +161,7 @@ func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http. return http.StatusInternalServerError, err } for _, leaf := range *leaves { - if err := leaf.MarshalASCII(w); err != nil { + if err := leaf.ToASCII(w); err != nil { return http.StatusInternalServerError, err } } diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/handler_test.go index 18a6c27..ba5b60c 100644 --- a/pkg/instance/endpoint_test.go +++ b/pkg/instance/handler_test.go @@ -4,60 +4,137 @@ import ( "bytes" "crypto/ed25519" "crypto/rand" - "encoding/hex" "fmt" "io" "net/http" "net/http/httptest" + "reflect" "testing" "time" - "git.sigsum.org/sigsum-log-go/pkg/mocks" - "git.sigsum.org/sigsum-log-go/pkg/types" + "git.sigsum.org/sigsum-lib-go/pkg/types" + mocksDB "git.sigsum.org/sigsum-log-go/pkg/db/mocks" + mocksDNS "git.sigsum.org/sigsum-log-go/pkg/dns/mocks" + mocksState "git.sigsum.org/sigsum-log-go/pkg/state/mocks" "github.com/golang/mock/gomock" ) var ( - testWitVK = [types.VerificationKeySize]byte{} + testWitVK = types.PublicKey{} testConfig = Config{ - LogID: hex.EncodeToString(types.Hash([]byte("logid"))[:]), + LogID: fmt.Sprintf("%x", types.HashFn([]byte("logid"))[:]), TreeID: 0, Prefix: "testonly", MaxRange: 3, Deadline: 10, Interval: 10, ShardStart: 10, - Witnesses: map[[types.HashSize]byte][types.VerificationKeySize]byte{ - *types.Hash(testWitVK[:]): testWitVK, + Witnesses: map[types.Hash]types.PublicKey{ + *types.HashFn(testWitVK[:]): testWitVK, }, } testSTH = &types.SignedTreeHead{ TreeHead: types.TreeHead{ Timestamp: 0, TreeSize: 0, - RootHash: types.Hash(nil), + RootHash: *types.HashFn([]byte("root hash")), }, - Signature: &[types.SignatureSize]byte{}, + Signature: types.Signature{}, } testCTH = &types.CosignedTreeHead{ SignedTreeHead: *testSTH, - SigIdent: []*types.SigIdent{ - &types.SigIdent{ - KeyHash: &[types.HashSize]byte{}, - Signature: &[types.SignatureSize]byte{}, - }, - }, + Cosignature: []types.Signature{types.Signature{}}, + KeyHash: []types.Hash{types.Hash{}}, } ) -func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler { +// 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.EndpointGetTreeHeadLatest: false, + types.EndpointGetTreeHeadToSign: 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 handler.Endpoint == e { - return handler + 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) + } + } +} + +// TestServeHTTP checks that invalid HTTP methods are rejected +func TestServeHTTP(t *testing.T) { + i := &Instance{ + Config: testConfig, + } + for _, handler := range i.Handlers() { + // Prepare invalid HTTP request + method := http.MethodPost + if method == handler.Method { + method = http.MethodGet + } + url := handler.Endpoint.Path("http://example.com", i.Prefix) + req, err := http.NewRequest(method, url, nil) + if err != nil { + t.Fatalf("must create HTTP request: %v", err) + } + w := httptest.NewRecorder() + + // Check that it is rejected + handler.ServeHTTP(w, req) + if got, want := w.Code, http.StatusMethodNotAllowed; got != want { + t.Errorf("got HTTP code %v but wanted %v for endpoint %q", got, want, handler.Endpoint) + } + } +} + +// 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) } } - t.Fatalf("must handle endpoint: %v", e) - return Handler{} } func TestAddLeaf(t *testing.T) { @@ -77,29 +154,29 @@ func TestAddLeaf(t *testing.T) { }, { description: "invalid: bad request (signature error)", - ascii: mustLeafBuffer(t, 10, &[types.HashSize]byte{}, false), + 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.HashSize]byte{}, true), + 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.HashSize]byte{}, true), + 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.HashSize]byte{}, true), + 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.HashSize]byte{}, true), + ascii: mustLeafBuffer(t, 10, types.Hash{}, true), expectDNS: true, expectTrillian: true, errTrillian: fmt.Errorf("something went wrong"), @@ -107,7 +184,7 @@ func TestAddLeaf(t *testing.T) { }, { description: "valid", - ascii: mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), + ascii: mustLeafBuffer(t, 10, types.Hash{}, true), expectDNS: true, expectTrillian: true, wantCode: http.StatusOK, @@ -117,11 +194,11 @@ func TestAddLeaf(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - dns := mocks.NewMockVerifier(ctrl) + dns := mocksDNS.NewMockVerifier(ctrl) if table.expectDNS { dns.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.errDNS) } - client := mocks.NewMockClient(ctrl) + client := mocksDB.NewMockClient(ctrl) if table.expectTrillian { client.EXPECT().AddLeaf(gomock.Any(), gomock.Any()).Return(table.errTrillian) } @@ -150,10 +227,9 @@ func TestAddLeaf(t *testing.T) { func TestAddCosignature(t *testing.T) { buf := func() io.Reader { - return bytes.NewBufferString(fmt.Sprintf( - "%s%s%x%s"+"%s%s%x%s", - types.Cosignature, types.Delim, make([]byte, types.SignatureSize), types.EOL, - types.KeyHash, types.Delim, *types.Hash(testWitVK[:]), types.EOL, + return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n", + "cosignature", types.Signature{}, + "key_hash", *types.HashFn(testWitVK[:]), )) } for _, table := range []struct { @@ -170,10 +246,9 @@ func TestAddCosignature(t *testing.T) { }, { 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, + ascii: bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n", + "cosignature", types.Signature{}, + "key_hash", *types.HashFn(testWitVK[1:]), )), wantCode: http.StatusBadRequest, }, @@ -195,7 +270,7 @@ func TestAddCosignature(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - stateman := mocks.NewMockStateManager(ctrl) + stateman := mocksState.NewMockStateManager(ctrl) if table.expect { stateman.EXPECT().AddCosignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.err) } @@ -246,7 +321,7 @@ func TestGetTreeHeadLatest(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - stateman := mocks.NewMockStateManager(ctrl) + stateman := mocksState.NewMockStateManager(ctrl) if table.expect { stateman.EXPECT().Latest(gomock.Any()).Return(table.rsp, table.err) } @@ -297,7 +372,7 @@ func TestGetTreeToSign(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - stateman := mocks.NewMockStateManager(ctrl) + stateman := mocksState.NewMockStateManager(ctrl) if table.expect { stateman.EXPECT().ToSign(gomock.Any()).Return(table.rsp, table.err) } @@ -348,7 +423,7 @@ func TestGetTreeCosigned(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - stateman := mocks.NewMockStateManager(ctrl) + stateman := mocksState.NewMockStateManager(ctrl) if table.expect { stateman.EXPECT().Cosigned(gomock.Any()).Return(table.rsp, table.err) } @@ -376,10 +451,9 @@ func TestGetTreeCosigned(t *testing.T) { 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, + return bytes.NewBufferString(fmt.Sprintf("%s=%d\n%s=%d\n", + "old_size", oldSize, + "new_size", newSize, )) } for _, table := range []struct { @@ -419,8 +493,8 @@ func TestGetConsistencyProof(t *testing.T) { rsp: &types.ConsistencyProof{ OldSize: 1, NewSize: 2, - Path: []*[types.HashSize]byte{ - types.Hash(nil), + Path: []types.Hash{ + *types.HashFn([]byte{}), }, }, wantCode: http.StatusOK, @@ -430,7 +504,7 @@ func TestGetConsistencyProof(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) + client := mocksDB.NewMockClient(ctrl) if table.expect { client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) } @@ -457,11 +531,10 @@ func TestGetConsistencyProof(t *testing.T) { } func TestGetInclusionProof(t *testing.T) { - buf := func(hash *[types.HashSize]byte, treeSize int) io.Reader { - return bytes.NewBufferString(fmt.Sprintf( - "%s%s%x%s"+"%s%s%d%s", - types.LeafHash, types.Delim, hash[:], types.EOL, - types.TreeSize, types.Delim, treeSize, types.EOL, + buf := func(hash *types.Hash, treeSize int) io.Reader { + return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%d\n", + "leaf_hash", hash[:], + "tree_size", treeSize, )) } for _, table := range []struct { @@ -479,25 +552,25 @@ func TestGetInclusionProof(t *testing.T) { }, { description: "invalid: bad request (no proof for tree size)", - ascii: buf(types.Hash(nil), 1), + ascii: buf(types.HashFn([]byte{}), 1), wantCode: http.StatusBadRequest, }, { description: "invalid: backend failure", - ascii: buf(types.Hash(nil), 2), + ascii: buf(types.HashFn([]byte{}), 2), expect: true, err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "valid", - ascii: buf(types.Hash(nil), 2), + ascii: buf(types.HashFn([]byte{}), 2), expect: true, rsp: &types.InclusionProof{ TreeSize: 2, LeafIndex: 0, - Path: []*[types.HashSize]byte{ - types.Hash(nil), + Path: []types.Hash{ + *types.HashFn([]byte{}), }, }, wantCode: http.StatusOK, @@ -507,7 +580,7 @@ func TestGetInclusionProof(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) + client := mocksDB.NewMockClient(ctrl) if table.expect { client.EXPECT().GetInclusionProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) } @@ -535,19 +608,18 @@ func TestGetInclusionProof(t *testing.T) { func TestGetLeaves(t *testing.T) { buf := func(startSize, endSize int64) io.Reader { - return bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%d%s", - types.StartSize, types.Delim, startSize, types.EOL, - types.EndSize, types.Delim, endSize, types.EOL, + return bytes.NewBufferString(fmt.Sprintf("%s=%d\n%s=%d\n", + "start_size", startSize, + "end_size", endSize, )) } 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.LeafList // list of leaves from Trillian client - err error // error from Trillian client - wantCode int // HTTP status ok + ascii io.Reader // buffer used to populate HTTP request + 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)", @@ -570,18 +642,16 @@ func TestGetLeaves(t *testing.T) { description: "valid: one more entry than the configured MaxRange", ascii: buf(0, testConfig.MaxRange), // query will be pruned expect: true, - rsp: func() *types.LeafList { - var list types.LeafList + rsp: func() *types.Leaves { + var list types.Leaves for i := int64(0); i < testConfig.MaxRange; i++ { - list = append(list[:], &types.Leaf{ - Message: types.Message{ + list = append(list[:], types.Leaf{ + Statement: types.Statement{ ShardHint: 0, - Checksum: types.Hash(nil), - }, - SigIdent: types.SigIdent{ - Signature: &[types.SignatureSize]byte{}, - KeyHash: types.Hash(nil), + Checksum: types.Hash{}, }, + Signature: types.Signature{}, + KeyHash: types.Hash{}, }) } return &list @@ -593,7 +663,7 @@ func TestGetLeaves(t *testing.T) { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) + client := mocksDB.NewMockClient(ctrl) if table.expect { client.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) } @@ -619,43 +689,48 @@ func TestGetLeaves(t *testing.T) { return } - // TODO: check that we got the right leaves back. It is especially - // important that we check that we got the right number of leaves. - // - // Pseuducode for when we have types.LeafList.UnmarshalASCII() - // - //list := &types.LeafList{} - //if err := list.UnmarshalASCII(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) - //} + 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 mustLeafBuffer(t *testing.T, shardHint uint64, checksum *[types.HashSize]byte, wantSig bool) io.Reader { +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, checksum 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.Message{ + msg := types.Statement{ ShardHint: shardHint, Checksum: checksum, } - sig := ed25519.Sign(sk, msg.Marshal()) + sig := ed25519.Sign(sk, msg.ToBinary()) if !wantSig { sig[0] += 1 } return bytes.NewBufferString(fmt.Sprintf( - "%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", - types.ShardHint, types.Delim, shardHint, types.EOL, - types.Checksum, types.Delim, checksum[:], types.EOL, - types.Signature, types.Delim, sig, types.EOL, - types.VerificationKey, types.Delim, vk, types.EOL, - types.DomainHint, types.Delim, "example.com", types.EOL, + "%s=%d\n"+"%s=%x\n"+"%s=%x\n"+"%s=%x\n"+"%s=%s\n", + "shard_hint", shardHint, + "checksum", checksum[:], + "signature", sig, + "verification_key", vk, + "domain_hint", "example.com", )) } diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index 4dff31a..9f71aa3 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -3,16 +3,15 @@ package instance import ( "context" "crypto" - "crypto/ed25519" "fmt" "net/http" "time" + "git.sigsum.org/sigsum-lib-go/pkg/requests" + "git.sigsum.org/sigsum-lib-go/pkg/types" + "git.sigsum.org/sigsum-log-go/pkg/db" "git.sigsum.org/sigsum-log-go/pkg/dns" "git.sigsum.org/sigsum-log-go/pkg/state" - "git.sigsum.org/sigsum-log-go/pkg/trillian" - "git.sigsum.org/sigsum-log-go/pkg/types" - "github.com/golang/glog" ) // Config is a collection of log parameters @@ -26,27 +25,18 @@ type Config struct { ShardStart uint64 // Shard interval start (num seconds since UNIX epoch) // Witnesses map trusted witness identifiers to public verification keys - Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte + Witnesses map[types.Hash]types.PublicKey } // 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 + 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 } -// 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) -} - // Handlers returns a list of sigsum handlers func (i *Instance) Handlers() []Handler { return []Handler{ @@ -62,51 +52,13 @@ func (i *Instance) Handlers() []Handler { } } -// 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 (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(ctx context.Context, 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) +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) } - vk := ed25519.PublicKey(req.VerificationKey[:]) - msg := req.Message.Marshal() - sig := req.Signature[:] - if !ed25519.Verify(vk, msg, sig) { + if !req.Statement.Verify(&req.VerificationKey, &req.Signature) { return nil, fmt.Errorf("invalid signature") } shardEnd := uint64(time.Now().Unix()) @@ -116,27 +68,27 @@ func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*t 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.VerificationKey); err != nil { + if err := i.DNS.Verify(ctx, req.DomainHint, &req.VerificationKey); err != nil { return nil, fmt.Errorf("invalid domain hint: %v", err) } 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("UnmarshalASCII: %v", err) +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 { + 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) +func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*requests.ConsistencyProof, error) { + var req requests.ConsistencyProof + if err := req.FromASCII(r.Body); 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) @@ -147,10 +99,10 @@ func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.Cons 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) +func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*requests.InclusionProof, error) { + var req requests.InclusionProof + if err := req.FromASCII(r.Body); err != nil { + return nil, fmt.Errorf("FromASCII: %v", err) } if req.TreeSize < 2 { // TreeSize:0 => not possible to prove inclusion of anything @@ -160,10 +112,10 @@ func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.Inclus 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) +func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*requests.Leaves, error) { + var req requests.Leaves + if err := req.FromASCII(r.Body); err != nil { + return nil, fmt.Errorf("FromASCII: %v", err) } if req.StartSize > req.EndSize { diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go deleted file mode 100644 index 6ca7baf..0000000 --- a/pkg/instance/instance_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package instance - -import ( - "net/http" - "net/http/httptest" - "testing" - - "git.sigsum.org/sigsum-log-go/pkg/types" -) - -// 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.EndpointGetTreeHeadLatest: false, - types.EndpointGetTreeHeadToSign: 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) - } - } -} - -// TestServeHTTP checks that invalid HTTP methods are rejected -func TestServeHTTP(t *testing.T) { - i := &Instance{ - Config: testConfig, - } - for _, handler := range i.Handlers() { - // Prepare invalid HTTP request - method := http.MethodPost - if method == handler.Method { - method = http.MethodGet - } - url := handler.Endpoint.Path("http://example.com", i.Prefix) - req, err := http.NewRequest(method, url, nil) - if err != nil { - t.Fatalf("must create HTTP request: %v", err) - } - w := httptest.NewRecorder() - - // Check that it is rejected - handler.ServeHTTP(w, req) - if got, want := w.Code, http.StatusMethodNotAllowed; got != want { - t.Errorf("got HTTP code %v but wanted %v for endpoint %q", got, want, handler.Endpoint) - } - } -} - -// 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) - } - } -} |