Skip to content

Commit d4b0815

Browse files
committed
Make signed root duration a per-tree option
1 parent ae07d64 commit d4b0815

File tree

13 files changed

+121
-47
lines changed

13 files changed

+121
-47
lines changed

cmd/createtree/main.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"flag"
3838
"fmt"
3939
"os"
40+
"time"
4041

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

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

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

132135
tree := &trillian.Tree{
133-
TreeState: trillian.TreeState(ts),
134-
TreeType: trillian.TreeType(tt),
135-
HashStrategy: trillian.HashStrategy(hs),
136-
HashAlgorithm: sigpb.DigitallySigned_HashAlgorithm(ha),
137-
SignatureAlgorithm: sigpb.DigitallySigned_SignatureAlgorithm(sa),
138-
DisplayName: opts.displayName,
139-
Description: opts.description,
140-
PrivateKey: pk,
136+
TreeState: trillian.TreeState(ts),
137+
TreeType: trillian.TreeType(tt),
138+
HashStrategy: trillian.HashStrategy(hs),
139+
HashAlgorithm: sigpb.DigitallySigned_HashAlgorithm(ha),
140+
SignatureAlgorithm: sigpb.DigitallySigned_SignatureAlgorithm(sa),
141+
DisplayName: opts.displayName,
142+
Description: opts.description,
143+
PrivateKey: pk,
144+
MaxRootDurationMillis: opts.maxRootDuration.Nanoseconds() / int64(time.Millisecond),
141145
}
142146
return &trillian.CreateTreeRequest{Tree: tree}, nil
143147
}
@@ -177,17 +181,18 @@ func newPK(opts *createOpts) (*any.Any, error) {
177181

178182
func newOptsFromFlags() *createOpts {
179183
return &createOpts{
180-
addr: *adminServerAddr,
181-
treeState: *treeState,
182-
treeType: *treeType,
183-
hashStrategy: *hashStrategy,
184-
hashAlgorithm: *hashAlgorithm,
185-
sigAlgorithm: *signatureAlgorithm,
186-
displayName: *displayName,
187-
description: *description,
188-
privateKeyType: *privateKeyFormat,
189-
pemKeyPath: *pemKeyPath,
190-
pemKeyPass: *pemKeyPassword,
184+
addr: *adminServerAddr,
185+
treeState: *treeState,
186+
treeType: *treeType,
187+
hashStrategy: *hashStrategy,
188+
hashAlgorithm: *hashAlgorithm,
189+
sigAlgorithm: *signatureAlgorithm,
190+
displayName: *displayName,
191+
description: *description,
192+
maxRootDuration: *maxRootDuration,
193+
privateKeyType: *privateKeyFormat,
194+
pemKeyPath: *pemKeyPath,
195+
pemKeyPass: *pemKeyPassword,
191196
}
192197
}
193198

log/sequencer.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,6 @@ func createMetrics(mf monitoring.MetricFactory) {
6868
seqCommitLatency = mf.NewHistogram("sequencer_latency_commit", "Latency of commit part of sequencer batch operation in ms", logIDLabel)
6969
}
7070

71-
// TODO(daviddrysdale): Make this configurable
72-
var maxRootDurationInterval = 12 * time.Hour
73-
7471
// TODO(Martin2112): Add admin support for safely changing params like guard window during operation
7572
// TODO(Martin2112): Add support for enabling and controlling sequencing as part of admin API
7673

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

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

126+
// SetMaxRootDurationInterval changes the interval after which a log root is generated regardless of
127+
// whether entries have been added to the log. The default is a zero interval, which
128+
// disables the behavior.
129+
func (s *Sequencer) SetMaxRootDurationInterval(interval time.Duration) {
130+
s.maxRootDurationInterval = interval
131+
}
132+
126133
// TODO: This currently doesn't use the batch api for fetching the required nodes. This
127134
// would be more efficient but requires refactoring.
128135
func (s Sequencer) buildMerkleTreeFromStorageAtRoot(ctx context.Context, root trillian.SignedLogRoot, tx storage.TreeTX) (*merkle.CompactMerkleTree, error) {
@@ -270,7 +277,7 @@ func (s Sequencer) SequenceBatch(ctx context.Context, logID int64, limit int) (i
270277
if len(leaves) == 0 {
271278
nowNanos := s.timeSource.Now().UnixNano()
272279
interval := time.Duration(nowNanos - currentRoot.TimestampNanos)
273-
if maxRootDurationInterval == 0 || interval < maxRootDurationInterval {
280+
if s.maxRootDurationInterval == 0 || interval < s.maxRootDurationInterval {
274281
// We have nothing to integrate into the tree
275282
glog.V(1).Infof("No leaves sequenced in this signing operation.")
276283
return 0, tx.Commit()

log/sequencer_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,12 @@ func TestSequenceWithNothingQueuedNewRoot(t *testing.T) {
335335
},
336336
}
337337
c, ctx := createTestContext(ctrl, params)
338-
saved := maxRootDurationInterval
339-
maxRootDurationInterval = 1 * time.Millisecond
338+
c.sequencer.SetMaxRootDurationInterval(1 * time.Millisecond)
340339

341340
leaves, err := c.sequencer.SequenceBatch(ctx, params.logID, 1)
342341
if leaves != 0 || err != nil {
343342
t.Errorf("SequenceBatch()=(%v,%v); want (0,nil)", leaves, err)
344343
}
345-
maxRootDurationInterval = saved
346344
}
347345

348346
// Tests that the guard interval is being passed to storage correctly. Actual operation of the

server/admin/admin_server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ func (s *Server) CreateTree(ctx context.Context, request *trillian.CreateTreeReq
143143
tree.PublicKey = &keyspb.PublicKey{Der: publicKeyDER}
144144
}
145145

146+
if tree.MaxRootDurationMillis < 0 {
147+
return nil, status.Error(codes.InvalidArgument, "the max root duration must be >= 0")
148+
}
149+
146150
tx, err := s.registry.AdminStorage.Begin(ctx)
147151
if err != nil {
148152
return nil, err
@@ -204,6 +208,8 @@ func applyUpdateMask(from, to *trillian.Tree, mask *field_mask.FieldMask) error
204208
to.Description = from.Description
205209
case "storage_settings":
206210
to.StorageSettings = from.StorageSettings
211+
case "max_root_duration_millis":
212+
to.MaxRootDurationMillis = from.MaxRootDurationMillis
207213
default:
208214
return status.Errorf(codes.InvalidArgument, "invalid update_mask path: %q", path)
209215
}

server/admin/admin_server_test.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ func TestServer_CreateTree(t *testing.T) {
314314
keySignatureMismatch := validTree
315315
keySignatureMismatch.SignatureAlgorithm = sigpb.DigitallySigned_RSA
316316

317+
negRootDuration := validTree
318+
negRootDuration.MaxRootDurationMillis = -1
319+
317320
tests := []struct {
318321
desc string
319322
req *trillian.CreateTreeRequest
@@ -340,6 +343,11 @@ func TestServer_CreateTree(t *testing.T) {
340343
req: &trillian.CreateTreeRequest{Tree: &omittedPrivateKey},
341344
wantErr: true,
342345
},
346+
{
347+
desc: "negativeMaxRootDuration",
348+
req: &trillian.CreateTreeRequest{Tree: &negRootDuration},
349+
wantErr: true,
350+
},
343351
{
344352
desc: "privateKeySpec",
345353
req: &trillian.CreateTreeRequest{
@@ -477,7 +485,7 @@ func TestServer_CreateTree(t *testing.T) {
477485
reqCopy := proto.Clone(test.req).(*trillian.CreateTreeRequest)
478486
tree, err := s.CreateTree(ctx, reqCopy)
479487
if hasErr := err != nil; hasErr != test.wantErr {
480-
t.Errorf("%v: CreateTree() = (_, %q), wantErr = %v", test.desc, err, test.wantErr)
488+
t.Errorf("%v: CreateTree() = (_, %v), wantErr = %v", test.desc, err, test.wantErr)
481489
continue
482490
} else if hasErr {
483491
continue
@@ -514,6 +522,7 @@ func TestServer_UpdateTree(t *testing.T) {
514522
existingTree.TreeId = 12345
515523
existingTree.CreateTimeMillisSinceEpoch = 10
516524
existingTree.UpdateTimeMillisSinceEpoch = 10
525+
existingTree.MaxRootDurationMillis = 1
517526

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

524533
// successTree specifies changes in all rw fields
525534
successTree := &trillian.Tree{
526-
TreeState: trillian.TreeState_FROZEN,
527-
DisplayName: "Brand New Tree Name",
528-
Description: "Brand New Tree Desc",
529-
StorageSettings: settings,
535+
TreeState: trillian.TreeState_FROZEN,
536+
DisplayName: "Brand New Tree Name",
537+
Description: "Brand New Tree Desc",
538+
StorageSettings: settings,
539+
MaxRootDurationMillis: 2,
530540
}
531-
successMask := &field_mask.FieldMask{Paths: []string{"tree_state", "display_name", "description", "storage_settings"}}
541+
successMask := &field_mask.FieldMask{Paths: []string{"tree_state", "display_name", "description", "storage_settings", "max_root_duration_millis"}}
532542

533543
successWant := existingTree
534544
successWant.TreeState = successTree.TreeState
535545
successWant.DisplayName = successTree.DisplayName
536546
successWant.Description = successTree.Description
537547
successWant.StorageSettings = successTree.StorageSettings
538548
successWant.PrivateKey = nil // redacted on responses
549+
successWant.MaxRootDurationMillis = successTree.MaxRootDurationMillis
539550

540551
tests := []struct {
541552
desc string

server/sequencer_manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (s *SequencerManager) ExecutePass(ctx context.Context, logID int64, info *L
7878
sequencer := log.NewSequencer(
7979
hasher, info.TimeSource, s.registry.LogStorage, signer, s.registry.MetricFactory, s.registry.QuotaManager)
8080
sequencer.SetGuardWindow(s.guardWindow)
81+
sequencer.SetMaxRootDurationInterval(time.Duration(tree.MaxRootDurationMillis * int64(time.Millisecond)))
8182

8283
leaves, err := sequencer.SequenceBatch(ctx, logID, info.BatchSize)
8384
if err != nil {

storage/mysql/admin_storage.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ const (
4646
CreateTimeMillis,
4747
UpdateTimeMillis,
4848
PrivateKey,
49-
PublicKey
49+
PublicKey,
50+
MaxRootDurationMillis
5051
FROM Trees`
5152
selectTreeByID = selectTrees + " WHERE TreeId = ?"
5253
)
@@ -156,7 +157,7 @@ func readTree(row row) (*trillian.Tree, error) {
156157

157158
// Enums and Datetimes need an extra conversion step
158159
var treeState, treeType, hashStrategy, hashAlgorithm, signatureAlgorithm string
159-
var createMillis, updateMillis int64
160+
var createMillis, updateMillis, maxRootDurationMillis int64
160161
var displayName, description sql.NullString
161162
var privateKey, publicKey []byte
162163
err := row.Scan(
@@ -172,6 +173,7 @@ func readTree(row row) (*trillian.Tree, error) {
172173
&updateMillis,
173174
&privateKey,
174175
&publicKey,
176+
&maxRootDurationMillis,
175177
)
176178
if err != nil {
177179
return nil, err
@@ -222,6 +224,7 @@ func readTree(row row) (*trillian.Tree, error) {
222224

223225
tree.CreateTimeMillisSinceEpoch = createMillis
224226
tree.UpdateTimeMillisSinceEpoch = updateMillis
227+
tree.MaxRootDurationMillis = maxRootDurationMillis
225228

226229
tree.PrivateKey = &any.Any{}
227230
if err := proto.Unmarshal(privateKey, tree.PrivateKey); err != nil {
@@ -319,8 +322,9 @@ func (t *adminTX) CreateTree(ctx context.Context, tree *trillian.Tree) (*trillia
319322
CreateTimeMillis,
320323
UpdateTimeMillis,
321324
PrivateKey,
322-
PublicKey)
323-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
325+
PublicKey,
326+
MaxRootDurationMillis)
327+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
324328
if err != nil {
325329
return nil, err
326330
}
@@ -345,6 +349,7 @@ func (t *adminTX) CreateTree(ctx context.Context, tree *trillian.Tree) (*trillia
345349
newTree.UpdateTimeMillisSinceEpoch,
346350
privateKey,
347351
newTree.PublicKey.GetDer(),
352+
newTree.MaxRootDurationMillis,
348353
)
349354
if err != nil {
350355
return nil, err
@@ -406,7 +411,7 @@ func (t *adminTX) UpdateTree(ctx context.Context, treeID int64, updateFunc func(
406411
stmt, err := t.tx.PrepareContext(
407412
ctx,
408413
`UPDATE Trees
409-
SET TreeState = ?, DisplayName = ?, Description = ?, UpdateTimeMillis = ?
414+
SET TreeState = ?, DisplayName = ?, Description = ?, UpdateTimeMillis = ?, MaxRootDurationMillis = ?
410415
WHERE TreeId = ?`)
411416
if err != nil {
412417
return nil, err
@@ -419,6 +424,7 @@ func (t *adminTX) UpdateTree(ctx context.Context, treeID int64, updateFunc func(
419424
tree.DisplayName,
420425
tree.Description,
421426
tree.UpdateTimeMillisSinceEpoch,
427+
tree.MaxRootDurationMillis,
422428
tree.TreeId); err != nil {
423429
return nil, err
424430
}

storage/mysql/storage.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS Trees(
2222
Description VARCHAR(200),
2323
CreateTimeMillis BIGINT NOT NULL,
2424
UpdateTimeMillis BIGINT NOT NULL,
25+
MaxRootDurationMillis BIGINT NOT NULL,
2526
PrivateKey MEDIUMBLOB NOT NULL,
2627
PublicKey MEDIUMBLOB NOT NULL,
2728
PRIMARY KEY(TreeId)
@@ -153,4 +154,3 @@ CREATE TABLE IF NOT EXISTS MapHead(
153154
UNIQUE INDEX TreeRevisionIdx(TreeId, MapRevision),
154155
FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE
155156
);
156-

storage/testonly/admin_storage_tester.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ var (
8888
PublicKey: &keyspb.PublicKey{
8989
Der: publicPEMToDER(ttestonly.DemoPublicKey),
9090
},
91+
MaxRootDurationMillis: 0,
9192
}
9293

9394
// MapTree is a valid, MAP-type trillian.Tree for tests.
@@ -105,6 +106,7 @@ var (
105106
PublicKey: &keyspb.PublicKey{
106107
Der: publicPEMToDER(ttestonly.DemoPublicKey),
107108
},
109+
MaxRootDurationMillis: 0,
108110
}
109111
)
110112

storage/tree_validation.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ func validateMutableTreeFields(tree *trillian.Tree) error {
108108
return errors.Errorf(errors.InvalidArgument, "display_name too big, max length is %v: %v", maxDisplayNameLength, tree.DisplayName)
109109
case len(tree.Description) > maxDescriptionLength:
110110
return errors.Errorf(errors.InvalidArgument, "description too big, max length is %v: %v", maxDescriptionLength, tree.Description)
111+
case tree.MaxRootDurationMillis < 0:
112+
return errors.Errorf(errors.InvalidArgument, "max_root_duration negative: %v", tree.MaxRootDurationMillis)
111113
}
112114

113115
// Implementations may vary, so let's assume storage_settings is mutable.

0 commit comments

Comments
 (0)