diff options
| -rw-r--r-- | cmd/sigsum-log-primary/main.go | 12 | ||||
| -rwxr-xr-x | integration/test.sh | 1 | ||||
| -rw-r--r-- | internal/state/single.go | 62 | ||||
| -rw-r--r-- | internal/state/single_test.go | 8 | 
4 files changed, 67 insertions, 16 deletions
| diff --git a/cmd/sigsum-log-primary/main.go b/cmd/sigsum-log-primary/main.go index f64643a..539dd52 100644 --- a/cmd/sigsum-log-primary/main.go +++ b/cmd/sigsum-log-primary/main.go @@ -47,6 +47,7 @@ var (  	logColor         = flag.Bool("log-color", false, "colored logging output (Default: false)")  	secondaryURL     = flag.String("secondary-url", "", "secondary node endpoint for fetching latest replicated tree head")  	secondaryPubkey  = flag.String("secondary-pubkey", "", "hex-encoded Ed25519 public key for secondary node") +	sthStorePath     = flag.String("sth-path", "/var/lib/sigsum-log/sth", "path to file where latest published STH is being stored")  	gitCommit = "unknown"  ) @@ -66,7 +67,12 @@ func main() {  	defer cancel()  	log.Debug("configuring log-go-primary") -	node, err := setupPrimaryFromFlags() +	sthFile, err := os.OpenFile(*sthStorePath, os.O_RDWR|os.O_CREATE, 0644) +	if err != nil { +		log.Fatal("opening STH file: %v", err) +	} +	defer sthFile.Close() +	node, err := setupPrimaryFromFlags(sthFile)  	if err != nil {  		log.Fatal("setup primary: %v", err)  	} @@ -117,7 +123,7 @@ func main() {  }  // setupPrimaryFromFlags() sets up a new sigsum primary node from flags. -func setupPrimaryFromFlags() (*primary.Primary, error) { +func setupPrimaryFromFlags(sthFile *os.File) (*primary.Primary, error) {  	var p primary.Primary  	var err error @@ -167,7 +173,7 @@ func setupPrimaryFromFlags() (*primary.Primary, error) {  	}  	// Setup state manager. -	p.Stateman, err = state.NewStateManagerSingle(p.TrillianClient, p.Signer, p.Config.Interval, p.Config.Deadline, p.Secondary) +	p.Stateman, err = state.NewStateManagerSingle(p.TrillianClient, p.Signer, p.Config.Interval, p.Config.Deadline, p.Secondary, sthFile)  	if err != nil {  		return nil, fmt.Errorf("NewStateManagerSingle: %v", err)  	} diff --git a/integration/test.sh b/integration/test.sh index 6442704..7f37066 100755 --- a/integration/test.sh +++ b/integration/test.sh @@ -267,6 +267,7 @@ function sigsum_start() {  		if [[ $role = primary ]]; then  			extra_args+=" -witnesses=${nvars[$i:ssrv_witnesses]}"  			extra_args+=" -shard-interval-start=${nvars[$i:ssrv_shard_start]}" +			extra_args+=" -sth-path=${nvars[$i:log_dir]}/sth-store"  		else  			binary=sigsum-log-secondary  		fi diff --git a/internal/state/single.go b/internal/state/single.go index fd73b3f..2c6bb4b 100644 --- a/internal/state/single.go +++ b/internal/state/single.go @@ -1,10 +1,12 @@  package state  import ( +	"bytes"  	"context"  	"crypto"  	"crypto/ed25519"  	"fmt" +	"os"  	"sync"  	"time" @@ -24,6 +26,7 @@ type StateManagerSingle struct {  	interval  time.Duration  	deadline  time.Duration  	secondary client.Client +	sthFile   *os.File  	// Lock-protected access to pointers.  A write lock is only obtained once  	// per interval when doing pointer rotation.  All endpoints are readers. @@ -39,7 +42,7 @@ type StateManagerSingle struct {  // NewStateManagerSingle() sets up a new state manager, in particular its  // signedTreeHead.  An optional secondary node can be used to ensure that  // a newer primary tree is not signed unless it has been replicated. -func NewStateManagerSingle(dbcli db.Client, signer crypto.Signer, interval, deadline time.Duration, secondary client.Client) (*StateManagerSingle, error) { +func NewStateManagerSingle(dbcli db.Client, signer crypto.Signer, interval, deadline time.Duration, secondary client.Client, sthFile *os.File) (*StateManagerSingle, error) {  	sm := &StateManagerSingle{  		client:    dbcli,  		signer:    signer, @@ -47,16 +50,22 @@ func NewStateManagerSingle(dbcli db.Client, signer crypto.Signer, interval, dead  		interval:  interval,  		deadline:  deadline,  		secondary: secondary, +		sthFile:   sthFile,  	} -	sth, err := sm.restoreTreeHead() -	if err != nil { -		return nil, fmt.Errorf("restore signed tree head: %v", err) +	var err error +	if sm.signedTreeHead, err = sm.restoreSTH(); err != nil { +		return nil, err  	} -	sm.signedTreeHead = sth - -	ictx, cancel := context.WithTimeout(context.Background(), sm.deadline) -	defer cancel() -	return sm, sm.tryRotate(ictx) +	ctx := context.Background() +	for { +		err := sm.tryRotate(ctx) +		if err == nil { +			break +		} +		log.Warning("restore signed tree head: %v", err) +		time.Sleep(time.Second * 3) +	} +	return sm, nil  }  func (sm *StateManagerSingle) ToCosignTreeHead() *types.SignedTreeHead { @@ -123,6 +132,10 @@ func (sm *StateManagerSingle) tryRotate(ctx context.Context) error {  	}  	log.Debug("wanted to advance to size %d, chose size %d", th.TreeSize, nextSTH.TreeSize) +	if err := sm.storeSTH(nextSTH); err != nil { +		return err +	} +  	sm.rotate(nextSTH)  	return nil  } @@ -250,9 +263,34 @@ func (sm *StateManagerSingle) treeStatusString() string {  	return fmt.Sprintf("signed at %d, cosigned at %d", sm.signedTreeHead.TreeSize, cosigned)  } -func (sm *StateManagerSingle) restoreTreeHead() (*types.SignedTreeHead, error) { -	th := zeroTreeHead() // TODO: restore from disk, stored when advanced the tree; zero tree head if "bootstrap" -	return refreshTreeHead(*th).Sign(sm.signer, &sm.namespace) +func (sm *StateManagerSingle) restoreSTH() (*types.SignedTreeHead, error) { +	var th types.TreeHead +	b := make([]byte, 1024) +	n, err := sm.sthFile.Read(b) +	if err != nil { +		th = *zeroTreeHead() +	} else if err := th.FromASCII(bytes.NewBuffer(b[:n])); err != nil { +		th = *zeroTreeHead() +	} +	th = *refreshTreeHead(th) +	return th.Sign(sm.signer, &sm.namespace) +} + +func (sm *StateManagerSingle) storeSTH(sth *types.SignedTreeHead) error { +	buf := bytes.NewBuffer(nil) +	if err := sth.ToASCII(buf); err != nil { +		return err +	} +	if err := sm.sthFile.Truncate(int64(buf.Len())); err != nil { +		return err +	} +	if _, err := sm.sthFile.WriteAt(buf.Bytes(), 0); err != nil { +		return err +	} +	if err := sm.sthFile.Sync(); err != nil { +		return err +	} +	return nil  }  func zeroTreeHead() *types.TreeHead { diff --git a/internal/state/single_test.go b/internal/state/single_test.go index 9442fdc..a60795c 100644 --- a/internal/state/single_test.go +++ b/internal/state/single_test.go @@ -8,6 +8,7 @@ import (  	"crypto/rand"  	"fmt"  	"io" +	"os"  	"reflect"  	"testing"  	"time" @@ -65,7 +66,12 @@ func TestNewStateManagerSingle(t *testing.T) {  				secondary.EXPECT().Initiated().Return(false)  			} -			sm, err := NewStateManagerSingle(trillianClient, table.signer, time.Duration(0), time.Duration(0), secondary) +			tmpFile, err := os.CreateTemp("", "sigsum-log-test-sth") +			if err != nil { +				t.Fatal(err) +			} +			defer os.Remove(tmpFile.Name()) +			sm, err := NewStateManagerSingle(trillianClient, table.signer, time.Duration(0), time.Duration(0), secondary, tmpFile)  			if got, want := err != nil, table.description != "valid"; got != want {  				t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err)  			} | 
