Skip to content

Commit

Permalink
feat: Add a refresh command
Browse files Browse the repository at this point in the history
This command is helpful for users that would like to have automation
tooling keep the timestamp and snapshot metadata fresh.

Signed-off-by: Andy Doan <[email protected]>
  • Loading branch information
doanac committed Jul 11, 2022
1 parent 0f17236 commit 2851331
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ Adds signatures (the output of `tuf sign-payload`) to the given role metadata fi

If the signature does not verify, it will not be added.

#### `tuf refresh --snapshot-threshold=2 --timestamp-threshold=1`

Update the timestamp and/or snapshop expiration time if either is set to expire
within the given number of hours.

This command is usual for automation tools that try to keep the metadata fresh.

#### Usage of environment variables

The `tuf` CLI supports receiving passphrases via environment variables in
Expand Down
1 change: 1 addition & 0 deletions cmd/tuf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Commands:
sign Sign a role's metadata file
sign-payload Sign a file from the "payload" command.
commit Commit staged files to the repository
refresh Refresh timestamp and snapshot metadata if needed
regenerate Recreate the targets metadata file [Not supported yet]
set-threshold Sets the threshold for a role
get-threshold Outputs the threshold for a role
Expand Down
50 changes: 50 additions & 0 deletions cmd/tuf/refresh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"fmt"
"strconv"
"time"

"github.com/flynn/go-docopt"
"github.com/theupdateframework/go-tuf"
)

func init() {
register("refresh", cmdRefresh, `
usage: tuf refresh [--snapshot-threshold=<hours> --timestamp-threshold=<hours>]
Resign the snapshot and/or timestamp metadata if needed. If either metadata
is expiring in less than the number of hours given by the threshold, its
metadata will expiration will be refreshed and the change(s) will be committed.
Alternatively, passphrases can be set via environment variables in the
form of TUF_{{ROLE}}_PASSPHRASE
Options:
--snapshot-threshold=<hours> Set the threshold for when to resign expiring
snapshot metadata.
--timestamp-threshold=<hours> Set the threshold for when to resign expiring
timestamp metadata
`)
}

func cmdRefresh(args *docopt.Args, repo *tuf.Repo) error {
ssThreshold := time.Now().Add(time.Hour * -1)
tsThreshold := time.Now().Add(time.Hour * -1)

if arg := args.String["--snapshot-threshold"]; arg != "" {
hours, err := strconv.Atoi(arg)
if err != nil {
return fmt.Errorf("failed to parse --snapshot-threshold arg: %s", err)
}
ssThreshold = time.Now().Add(time.Hour * time.Duration(hours) * -1)
}
if arg := args.String["--timestamp-threshold"]; arg != "" {
hours, err := strconv.Atoi(arg)
if err != nil {
return fmt.Errorf("failed to parse --timestamp-threshold arg: %s", err)
}
tsThreshold = time.Now().Add(time.Hour * time.Duration(hours) * -1)
}
return repo.RefreshExpires(ssThreshold, tsThreshold)
}
32 changes: 32 additions & 0 deletions repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -1555,3 +1555,35 @@ func (r *Repo) Payload(roleFilename string) ([]byte, error) {

return p, nil
}

func (r *Repo) RefreshExpires(snapshotExpires, timestampExpires time.Time) error {
ss, err := r.snapshot()
if err != nil {
return err
}
ts, err := r.timestamp()
if err != nil {
return err
}

changed := false

if ss.Expires.Before(snapshotExpires) {
if err = r.Snapshot(); err != nil {
return err
}
changed = true
}

if changed || ts.Expires.Before(timestampExpires) {
if err = r.Timestamp(); err != nil {
return err
}
changed = true
}

if changed {
err = r.Commit()
}
return err
}
48 changes: 48 additions & 0 deletions repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,54 @@ func (rs *RepoSuite) TestCommit(c *C) {
c.Assert(r.Commit(), DeepEquals, ErrNotEnoughKeys{"timestamp", 0, 1})
}

func (rs *RepoSuite) TestRefreshExpires(c *C) {
files := map[string][]byte{"foo.txt": []byte("foo")}
local := MemoryStore(make(map[string]json.RawMessage), files)
r, err := NewRepo(local)
c.Assert(err, IsNil)

genKey(c, r, "root")
genKey(c, r, "targets")
genKey(c, r, "snapshot")
genKey(c, r, "timestamp")

// Set up a scenario where only the timestamp needs to be refreshed
c.Assert(r.AddTarget("foo.txt", nil), IsNil)
c.Assert(r.SnapshotWithExpires(time.Now().Add(24*time.Hour)), IsNil)
c.Assert(r.TimestampWithExpires(time.Now().Add(1*time.Hour)), IsNil)
c.Assert(r.Commit(), IsNil)

ss, _ := r.snapshot()
ssExpiresOrig := ss.Expires
ts, _ := r.timestamp()
tsExpiresOrig := ts.Expires

thresh := time.Now().Add(2 * time.Hour)
c.Assert(r.RefreshExpires(thresh, thresh), IsNil)

ss, _ = r.snapshot()
ts, _ = r.timestamp()
c.Assert(ss.Expires, Equals, ssExpiresOrig)
c.Assert(ts.Expires, Not(Equals), tsExpiresOrig)

// Now have the snapshot need refreshing (it will cause a timestamp change)
c.Assert(r.SnapshotWithExpires(time.Now().Add(1*time.Hour)), IsNil)
c.Assert(r.TimestampWithExpires(time.Now().Add(4*time.Hour)), IsNil)

ss, _ = r.snapshot()
ssExpiresOrig = ss.Expires
ts, _ = r.timestamp()
tsExpiresOrig = ts.Expires

thresh = time.Now().Add(2 * time.Hour)
c.Assert(r.RefreshExpires(thresh, thresh), IsNil)

ss, _ = r.snapshot()
ts, _ = r.timestamp()
c.Assert(ss.Expires, Not(Equals), ssExpiresOrig)
c.Assert(ts.Expires, Not(Equals), tsExpiresOrig)
}

func (rs *RepoSuite) TestCommitVersions(c *C) {
files := map[string][]byte{"foo.txt": []byte("foo")}
local := MemoryStore(make(map[string]json.RawMessage), files)
Expand Down

0 comments on commit 2851331

Please sign in to comment.