aboutsummaryrefslogtreecommitdiff
path: root/pkg/instance
diff options
context:
space:
mode:
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.go104
-rw-r--r--pkg/instance/instance_test.go98
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)
- }
- }
-}