Skip to content

Commit

Permalink
Make signed root duration a per-tree option
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddrysdale committed Jun 16, 2017
1 parent ae07d64 commit d4b0815
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 47 deletions.
43 changes: 24 additions & 19 deletions cmd/createtree/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"flag"
"fmt"
"os"
"time"

"github.com/golang/glog"
"github.com/golang/protobuf/ptypes"
Expand All @@ -59,6 +60,7 @@ var (
signatureAlgorithm = flag.String("signature_algorithm", sigpb.DigitallySigned_RSA.String(), "Signature algorithm of the new tree")
displayName = flag.String("display_name", "", "Display name of the new tree")
description = flag.String("description", "", "Description of the new tree")
maxRootDuration = flag.Duration("max_root_duration", 0, "Interval after which a new signed root is produced despite no submissions; zero means never")

privateKeyFormat = flag.String("private_key_format", "PrivateKey", "Type of private key to be used")
pemKeyPath = flag.String("pem_key_path", "", "Path to the private key PEM file")
Expand All @@ -72,6 +74,7 @@ var (
type createOpts struct {
addr string
treeState, treeType, hashStrategy, hashAlgorithm, sigAlgorithm, displayName, description string
maxRootDuration time.Duration
privateKeyType, pemKeyPath, pemKeyPass string
}

Expand Down Expand Up @@ -130,14 +133,15 @@ func newRequest(opts *createOpts) (*trillian.CreateTreeRequest, error) {
}

tree := &trillian.Tree{
TreeState: trillian.TreeState(ts),
TreeType: trillian.TreeType(tt),
HashStrategy: trillian.HashStrategy(hs),
HashAlgorithm: sigpb.DigitallySigned_HashAlgorithm(ha),
SignatureAlgorithm: sigpb.DigitallySigned_SignatureAlgorithm(sa),
DisplayName: opts.displayName,
Description: opts.description,
PrivateKey: pk,
TreeState: trillian.TreeState(ts),
TreeType: trillian.TreeType(tt),
HashStrategy: trillian.HashStrategy(hs),
HashAlgorithm: sigpb.DigitallySigned_HashAlgorithm(ha),
SignatureAlgorithm: sigpb.DigitallySigned_SignatureAlgorithm(sa),
DisplayName: opts.displayName,
Description: opts.description,
PrivateKey: pk,
MaxRootDurationMillis: opts.maxRootDuration.Nanoseconds() / int64(time.Millisecond),
}
return &trillian.CreateTreeRequest{Tree: tree}, nil
}
Expand Down Expand Up @@ -177,17 +181,18 @@ func newPK(opts *createOpts) (*any.Any, error) {

func newOptsFromFlags() *createOpts {
return &createOpts{
addr: *adminServerAddr,
treeState: *treeState,
treeType: *treeType,
hashStrategy: *hashStrategy,
hashAlgorithm: *hashAlgorithm,
sigAlgorithm: *signatureAlgorithm,
displayName: *displayName,
description: *description,
privateKeyType: *privateKeyFormat,
pemKeyPath: *pemKeyPath,
pemKeyPass: *pemKeyPassword,
addr: *adminServerAddr,
treeState: *treeState,
treeType: *treeType,
hashStrategy: *hashStrategy,
hashAlgorithm: *hashAlgorithm,
sigAlgorithm: *signatureAlgorithm,
displayName: *displayName,
description: *description,
maxRootDuration: *maxRootDuration,
privateKeyType: *privateKeyFormat,
pemKeyPath: *pemKeyPath,
pemKeyPass: *pemKeyPassword,
}
}

Expand Down
15 changes: 11 additions & 4 deletions log/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ func createMetrics(mf monitoring.MetricFactory) {
seqCommitLatency = mf.NewHistogram("sequencer_latency_commit", "Latency of commit part of sequencer batch operation in ms", logIDLabel)
}

// TODO(daviddrysdale): Make this configurable
var maxRootDurationInterval = 12 * time.Hour

// TODO(Martin2112): Add admin support for safely changing params like guard window during operation
// TODO(Martin2112): Add support for enabling and controlling sequencing as part of admin API

Expand All @@ -89,6 +86,9 @@ type Sequencer struct {
// sequencerGuardWindow is used to ensure entries newer than the guard window will not be
// sequenced until they fall outside it. By default there is no guard window.
sequencerGuardWindow time.Duration
// maxRootDurationInterval is used to ensure that a new signed log root is generated after a while,
// even if no entries have been added to the log. Zero duration disables this behavior.
maxRootDurationInterval time.Duration
}

// maxTreeDepth sets an upper limit on the size of Log trees.
Expand Down Expand Up @@ -123,6 +123,13 @@ func (s *Sequencer) SetGuardWindow(sequencerGuardWindow time.Duration) {
s.sequencerGuardWindow = sequencerGuardWindow
}

// SetMaxRootDurationInterval changes the interval after which a log root is generated regardless of
// whether entries have been added to the log. The default is a zero interval, which
// disables the behavior.
func (s *Sequencer) SetMaxRootDurationInterval(interval time.Duration) {
s.maxRootDurationInterval = interval
}

// TODO: This currently doesn't use the batch api for fetching the required nodes. This
// would be more efficient but requires refactoring.
func (s Sequencer) buildMerkleTreeFromStorageAtRoot(ctx context.Context, root trillian.SignedLogRoot, tx storage.TreeTX) (*merkle.CompactMerkleTree, error) {
Expand Down Expand Up @@ -270,7 +277,7 @@ func (s Sequencer) SequenceBatch(ctx context.Context, logID int64, limit int) (i
if len(leaves) == 0 {
nowNanos := s.timeSource.Now().UnixNano()
interval := time.Duration(nowNanos - currentRoot.TimestampNanos)
if maxRootDurationInterval == 0 || interval < maxRootDurationInterval {
if s.maxRootDurationInterval == 0 || interval < s.maxRootDurationInterval {
// We have nothing to integrate into the tree
glog.V(1).Infof("No leaves sequenced in this signing operation.")
return 0, tx.Commit()
Expand Down
4 changes: 1 addition & 3 deletions log/sequencer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,14 +335,12 @@ func TestSequenceWithNothingQueuedNewRoot(t *testing.T) {
},
}
c, ctx := createTestContext(ctrl, params)
saved := maxRootDurationInterval
maxRootDurationInterval = 1 * time.Millisecond
c.sequencer.SetMaxRootDurationInterval(1 * time.Millisecond)

leaves, err := c.sequencer.SequenceBatch(ctx, params.logID, 1)
if leaves != 0 || err != nil {
t.Errorf("SequenceBatch()=(%v,%v); want (0,nil)", leaves, err)
}
maxRootDurationInterval = saved
}

// Tests that the guard interval is being passed to storage correctly. Actual operation of the
Expand Down
6 changes: 6 additions & 0 deletions server/admin/admin_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ func (s *Server) CreateTree(ctx context.Context, request *trillian.CreateTreeReq
tree.PublicKey = &keyspb.PublicKey{Der: publicKeyDER}
}

if tree.MaxRootDurationMillis < 0 {
return nil, status.Error(codes.InvalidArgument, "the max root duration must be >= 0")
}

tx, err := s.registry.AdminStorage.Begin(ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -204,6 +208,8 @@ func applyUpdateMask(from, to *trillian.Tree, mask *field_mask.FieldMask) error
to.Description = from.Description
case "storage_settings":
to.StorageSettings = from.StorageSettings
case "max_root_duration_millis":
to.MaxRootDurationMillis = from.MaxRootDurationMillis
default:
return status.Errorf(codes.InvalidArgument, "invalid update_mask path: %q", path)
}
Expand Down
23 changes: 17 additions & 6 deletions server/admin/admin_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ func TestServer_CreateTree(t *testing.T) {
keySignatureMismatch := validTree
keySignatureMismatch.SignatureAlgorithm = sigpb.DigitallySigned_RSA

negRootDuration := validTree
negRootDuration.MaxRootDurationMillis = -1

tests := []struct {
desc string
req *trillian.CreateTreeRequest
Expand All @@ -340,6 +343,11 @@ func TestServer_CreateTree(t *testing.T) {
req: &trillian.CreateTreeRequest{Tree: &omittedPrivateKey},
wantErr: true,
},
{
desc: "negativeMaxRootDuration",
req: &trillian.CreateTreeRequest{Tree: &negRootDuration},
wantErr: true,
},
{
desc: "privateKeySpec",
req: &trillian.CreateTreeRequest{
Expand Down Expand Up @@ -477,7 +485,7 @@ func TestServer_CreateTree(t *testing.T) {
reqCopy := proto.Clone(test.req).(*trillian.CreateTreeRequest)
tree, err := s.CreateTree(ctx, reqCopy)
if hasErr := err != nil; hasErr != test.wantErr {
t.Errorf("%v: CreateTree() = (_, %q), wantErr = %v", test.desc, err, test.wantErr)
t.Errorf("%v: CreateTree() = (_, %v), wantErr = %v", test.desc, err, test.wantErr)
continue
} else if hasErr {
continue
Expand Down Expand Up @@ -514,6 +522,7 @@ func TestServer_UpdateTree(t *testing.T) {
existingTree.TreeId = 12345
existingTree.CreateTimeMillisSinceEpoch = 10
existingTree.UpdateTimeMillisSinceEpoch = 10
existingTree.MaxRootDurationMillis = 1

// Any valid proto works here, the type doesn't matter for this test.
settings, err := ptypes.MarshalAny(&keyspb.PEMKeyFile{})
Expand All @@ -523,19 +532,21 @@ func TestServer_UpdateTree(t *testing.T) {

// successTree specifies changes in all rw fields
successTree := &trillian.Tree{
TreeState: trillian.TreeState_FROZEN,
DisplayName: "Brand New Tree Name",
Description: "Brand New Tree Desc",
StorageSettings: settings,
TreeState: trillian.TreeState_FROZEN,
DisplayName: "Brand New Tree Name",
Description: "Brand New Tree Desc",
StorageSettings: settings,
MaxRootDurationMillis: 2,
}
successMask := &field_mask.FieldMask{Paths: []string{"tree_state", "display_name", "description", "storage_settings"}}
successMask := &field_mask.FieldMask{Paths: []string{"tree_state", "display_name", "description", "storage_settings", "max_root_duration_millis"}}

successWant := existingTree
successWant.TreeState = successTree.TreeState
successWant.DisplayName = successTree.DisplayName
successWant.Description = successTree.Description
successWant.StorageSettings = successTree.StorageSettings
successWant.PrivateKey = nil // redacted on responses
successWant.MaxRootDurationMillis = successTree.MaxRootDurationMillis

tests := []struct {
desc string
Expand Down
1 change: 1 addition & 0 deletions server/sequencer_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (s *SequencerManager) ExecutePass(ctx context.Context, logID int64, info *L
sequencer := log.NewSequencer(
hasher, info.TimeSource, s.registry.LogStorage, signer, s.registry.MetricFactory, s.registry.QuotaManager)
sequencer.SetGuardWindow(s.guardWindow)
sequencer.SetMaxRootDurationInterval(time.Duration(tree.MaxRootDurationMillis * int64(time.Millisecond)))

leaves, err := sequencer.SequenceBatch(ctx, logID, info.BatchSize)
if err != nil {
Expand Down
16 changes: 11 additions & 5 deletions storage/mysql/admin_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const (
CreateTimeMillis,
UpdateTimeMillis,
PrivateKey,
PublicKey
PublicKey,
MaxRootDurationMillis
FROM Trees`
selectTreeByID = selectTrees + " WHERE TreeId = ?"
)
Expand Down Expand Up @@ -156,7 +157,7 @@ func readTree(row row) (*trillian.Tree, error) {

// Enums and Datetimes need an extra conversion step
var treeState, treeType, hashStrategy, hashAlgorithm, signatureAlgorithm string
var createMillis, updateMillis int64
var createMillis, updateMillis, maxRootDurationMillis int64
var displayName, description sql.NullString
var privateKey, publicKey []byte
err := row.Scan(
Expand All @@ -172,6 +173,7 @@ func readTree(row row) (*trillian.Tree, error) {
&updateMillis,
&privateKey,
&publicKey,
&maxRootDurationMillis,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -222,6 +224,7 @@ func readTree(row row) (*trillian.Tree, error) {

tree.CreateTimeMillisSinceEpoch = createMillis
tree.UpdateTimeMillisSinceEpoch = updateMillis
tree.MaxRootDurationMillis = maxRootDurationMillis

tree.PrivateKey = &any.Any{}
if err := proto.Unmarshal(privateKey, tree.PrivateKey); err != nil {
Expand Down Expand Up @@ -319,8 +322,9 @@ func (t *adminTX) CreateTree(ctx context.Context, tree *trillian.Tree) (*trillia
CreateTimeMillis,
UpdateTimeMillis,
PrivateKey,
PublicKey)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
PublicKey,
MaxRootDurationMillis)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return nil, err
}
Expand All @@ -345,6 +349,7 @@ func (t *adminTX) CreateTree(ctx context.Context, tree *trillian.Tree) (*trillia
newTree.UpdateTimeMillisSinceEpoch,
privateKey,
newTree.PublicKey.GetDer(),
newTree.MaxRootDurationMillis,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -406,7 +411,7 @@ func (t *adminTX) UpdateTree(ctx context.Context, treeID int64, updateFunc func(
stmt, err := t.tx.PrepareContext(
ctx,
`UPDATE Trees
SET TreeState = ?, DisplayName = ?, Description = ?, UpdateTimeMillis = ?
SET TreeState = ?, DisplayName = ?, Description = ?, UpdateTimeMillis = ?, MaxRootDurationMillis = ?
WHERE TreeId = ?`)
if err != nil {
return nil, err
Expand All @@ -419,6 +424,7 @@ func (t *adminTX) UpdateTree(ctx context.Context, treeID int64, updateFunc func(
tree.DisplayName,
tree.Description,
tree.UpdateTimeMillisSinceEpoch,
tree.MaxRootDurationMillis,
tree.TreeId); err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion storage/mysql/storage.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS Trees(
Description VARCHAR(200),
CreateTimeMillis BIGINT NOT NULL,
UpdateTimeMillis BIGINT NOT NULL,
MaxRootDurationMillis BIGINT NOT NULL,
PrivateKey MEDIUMBLOB NOT NULL,
PublicKey MEDIUMBLOB NOT NULL,
PRIMARY KEY(TreeId)
Expand Down Expand Up @@ -153,4 +154,3 @@ CREATE TABLE IF NOT EXISTS MapHead(
UNIQUE INDEX TreeRevisionIdx(TreeId, MapRevision),
FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE
);

2 changes: 2 additions & 0 deletions storage/testonly/admin_storage_tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ var (
PublicKey: &keyspb.PublicKey{
Der: publicPEMToDER(ttestonly.DemoPublicKey),
},
MaxRootDurationMillis: 0,
}

// MapTree is a valid, MAP-type trillian.Tree for tests.
Expand All @@ -105,6 +106,7 @@ var (
PublicKey: &keyspb.PublicKey{
Der: publicPEMToDER(ttestonly.DemoPublicKey),
},
MaxRootDurationMillis: 0,
}
)

Expand Down
2 changes: 2 additions & 0 deletions storage/tree_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ func validateMutableTreeFields(tree *trillian.Tree) error {
return errors.Errorf(errors.InvalidArgument, "display_name too big, max length is %v: %v", maxDisplayNameLength, tree.DisplayName)
case len(tree.Description) > maxDescriptionLength:
return errors.Errorf(errors.InvalidArgument, "description too big, max length is %v: %v", maxDescriptionLength, tree.Description)
case tree.MaxRootDurationMillis < 0:
return errors.Errorf(errors.InvalidArgument, "max_root_duration negative: %v", tree.MaxRootDurationMillis)
}

// Implementations may vary, so let's assume storage_settings is mutable.
Expand Down
Loading

0 comments on commit d4b0815

Please sign in to comment.