Skip to content

Commit

Permalink
Expose target custom data in the NotaryRepository API
Browse files Browse the repository at this point in the history
- Update Target definition and NewTarget to store custom data

- tufAdd and tufAddByHash now take a filename as a flag, open and read the file, and pass the custom data bytes to
NewTarget.

- Client APIs now understand Target.Custom

- Test that client lists and gets targets with custom data

Signed-off-by: Ashwini Oruganti <[email protected]>
  • Loading branch information
ashfall committed May 4, 2017
1 parent 3b3e77c commit 8be2b1d
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 14 deletions.
38 changes: 30 additions & 8 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/Sirupsen/logrus"
canonicaljson "github.com/docker/go/canonical/json"
"github.com/docker/notary"
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice"
Expand Down Expand Up @@ -128,9 +129,10 @@ func (r *NotaryRepository) GetGUN() data.GUN {
// Target represents a simplified version of the data TUF operates on, so external
// applications don't have to depend on TUF data types.
type Target struct {
Name string // the name of the target
Hashes data.Hashes // the hash of the target
Length int64 // the size in bytes of the target
Name string // the name of the target
Hashes data.Hashes // the hash of the target
Length int64 // the size in bytes of the target
Custom canonicaljson.RawMessage // the custom data provided to describe the file at TARGETPATH
}

// TargetWithRole represents a Target that exists in a particular role - this is
Expand All @@ -141,7 +143,7 @@ type TargetWithRole struct {
}

// NewTarget is a helper method that returns a Target
func NewTarget(targetName string, targetPath string) (*Target, error) {
func NewTarget(targetName, targetPath string, targetCustom canonicaljson.RawMessage) (*Target, error) {
b, err := ioutil.ReadFile(targetPath)
if err != nil {
return nil, err
Expand All @@ -152,7 +154,7 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
return nil, err
}

return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length, Custom: targetCustom}, nil
}

func rootCertKey(gun data.GUN, privKey data.PrivateKey) (data.PublicKey, error) {
Expand Down Expand Up @@ -360,7 +362,14 @@ func (r *NotaryRepository) AddTarget(target *Target, roles ...data.RoleName) err
}
logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length)

meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes}
var customData *canonicaljson.RawMessage
if target.Custom != nil {
customData = new(canonicaljson.RawMessage)
if err := customData.UnmarshalJSON(target.Custom); err != nil {
return err
}
}
meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes, Custom: customData}
metaJSON, err := json.Marshal(meta)
if err != nil {
return err
Expand Down Expand Up @@ -412,11 +421,16 @@ func (r *NotaryRepository) ListTargets(roles ...data.RoleName) ([]*TargetWithRol
if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
continue
}
var custom canonicaljson.RawMessage
if targetMeta.Custom != nil {
custom = *targetMeta.Custom
}
targets[targetName] = &TargetWithRole{
Target: Target{
Name: targetName,
Hashes: targetMeta.Hashes,
Length: targetMeta.Length,
Custom: custom,
},
Role: validRole.Name,
}
Expand Down Expand Up @@ -472,7 +486,11 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...data.RoleName)
}
// Check that we didn't error, and that we assigned to our target
if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget {
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil
var custom canonicaljson.RawMessage
if resultMeta.Custom != nil {
custom = *resultMeta.Custom
}
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: custom}, Role: resultRoleName}, nil
}
}
return nil, fmt.Errorf("No trust data for %s", name)
Expand Down Expand Up @@ -514,9 +532,13 @@ func (r *NotaryRepository) GetAllTargetMetadataByName(name string) ([]TargetSign
}

for targetName, resultMeta := range targetMetaToAdd {
var custom canonicaljson.RawMessage
if resultMeta.Custom != nil {
custom = *resultMeta.Custom
}
targetInfo := TargetSignedStruct{
Role: validRole,
Target: Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length},
Target: Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: custom},
Signatures: tgt.Signatures,
}
targetInfoList = append(targetInfoList, targetInfo)
Expand Down
60 changes: 56 additions & 4 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,13 @@ func testInitRepoPasswordInvalid(t *testing.T, rootType string) {

func addTarget(t *testing.T, repo *NotaryRepository, targetName, targetFile string,
roles ...data.RoleName) *Target {
target, err := NewTarget(targetName, targetFile)
var targetCustom json.RawMessage
return addTargetWithCustom(t, repo, targetName, targetFile, targetCustom, roles...)
}

func addTargetWithCustom(t *testing.T, repo *NotaryRepository, targetName,
targetFile string, targetCustom json.RawMessage, roles ...data.RoleName) *Target {
target, err := NewTarget(targetName, targetFile, targetCustom)
require.NoError(t, err, "error creating target")
err = repo.AddTarget(target, roles...)
require.NoError(t, err, "error adding target")
Expand Down Expand Up @@ -815,7 +821,8 @@ func testAddTargetToSpecifiedInvalidRoles(t *testing.T, clearCache bool) {
}

for _, invalidRole := range invalidRoles {
target, err := NewTarget("latest", "../fixtures/intermediate-ca.crt")
var targetCustom json.RawMessage
target, err := NewTarget("latest", "../fixtures/intermediate-ca.crt", targetCustom)
require.NoError(t, err, "error creating target")

err = repo.AddTarget(target, data.CanonicalTargetsRole, invalidRole)
Expand Down Expand Up @@ -877,7 +884,8 @@ func TestAddTargetWithInvalidTarget(t *testing.T) {
repo, _ := initializeRepo(t, data.ECDSAKey, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo.baseDir)

target, err := NewTarget("latest", "../fixtures/intermediate-ca.crt")
var targetCustom json.RawMessage
target, err := NewTarget("latest", "../fixtures/intermediate-ca.crt", targetCustom)
require.NoError(t, err, "error creating target")

// Clear the hashes
Expand All @@ -889,7 +897,8 @@ func TestAddTargetWithInvalidTarget(t *testing.T) {
// to be propagated.
func TestAddTargetErrorWritingChanges(t *testing.T) {
testErrorWritingChangefiles(t, func(repo *NotaryRepository) error {
target, err := NewTarget("latest", "../fixtures/intermediate-ca.crt")
var targetCustom json.RawMessage
target, err := NewTarget("latest", "../fixtures/intermediate-ca.crt", targetCustom)
require.NoError(t, err, "error creating target")
return repo.AddTarget(target, data.CanonicalTargetsRole)
})
Expand Down Expand Up @@ -1019,6 +1028,7 @@ func TestRemoveTargetErrorWritingChanges(t *testing.T) {
func TestListTarget(t *testing.T) {
testListEmptyTargets(t, data.ECDSAKey)
testListTarget(t, data.ECDSAKey)
testListTargetWithCustom(t, data.ECDSAKey)
testListTargetWithDelegates(t, data.ECDSAKey)
if !testing.Short() {
testListEmptyTargets(t, data.RSAKey)
Expand Down Expand Up @@ -1238,6 +1248,48 @@ func testListTarget(t *testing.T, rootType string) {
require.True(t, reflect.DeepEqual(*currentTarget, newCurrentTarget.Target), "current target does not match")
}

func testListTargetWithCustom(t *testing.T, rootType string) {
ts, mux, keys := simpleTestServer(t)
defer ts.Close()

repo, _ := initializeRepo(t, rootType, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo.baseDir)

// tests need to manually bootstrap timestamp as client doesn't generate it
err := repo.tufRepo.InitTimestamp()
require.NoError(t, err, "error creating repository: %s", err)

var targetCustom json.RawMessage
err = json.Unmarshal([]byte("\"Lorem ipsum dolor sit\""), &targetCustom)
require.NoError(t, err)
latestTarget := addTargetWithCustom(t, repo, "latest", "../fixtures/intermediate-ca.crt", targetCustom)
require.Equal(t, targetCustom, latestTarget.Custom, "Target created does not contain the expected custom data")

// Apply the changelist. Normally, this would be done by Publish

// load the changelist for this repo
cl, err := changelist.NewFileChangelist(
filepath.Join(repo.baseDir, "tuf", filepath.FromSlash(repo.gun.String()), "changelist"))
require.NoError(t, err, "could not open changelist")

// apply the changelist to the repo
err = applyChangelist(repo.tufRepo, nil, cl)
require.NoError(t, err, "could not apply changelist")

fakeServerData(t, repo, mux, keys)

targets, err := repo.ListTargets(data.CanonicalTargetsRole)
require.NoError(t, err)

require.True(t, reflect.DeepEqual(*latestTarget, targets[0].Target), "latest target does not match")

// Also test GetTargetByName for a target with custom data
newLatestTarget, err := repo.GetTargetByName("latest")
require.NoError(t, err)
require.Equal(t, data.CanonicalTargetsRole, newLatestTarget.Role)
require.True(t, reflect.DeepEqual(*latestTarget, newLatestTarget.Target), "latest target does not match")
}

func testListTargetWithDelegates(t *testing.T, rootType string) {
ts, mux, keys := simpleTestServer(t)
defer ts.Close()
Expand Down
54 changes: 54 additions & 0 deletions cmd/notary/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import (
"testing"
"time"

"encoding/json"

"github.com/Sirupsen/logrus"
ctxu "github.com/docker/distribution/context"
canonicaljson "github.com/docker/go/canonical/json"
"github.com/docker/notary"
"github.com/docker/notary/client"
"github.com/docker/notary/cryptoservice"
Expand Down Expand Up @@ -422,6 +425,7 @@ func TestClientTUFAddByHashInteraction(t *testing.T) {
target1 = "sdgkadga"
target2 = "asdfasdf"
target3 = "qwerty"
target4 = "foobar"
)
// -- tests --

Expand Down Expand Up @@ -541,6 +545,56 @@ func TestClientTUFAddByHashInteraction(t *testing.T) {
// publish repo
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

tempFile, err := ioutil.TempFile("", "targetCustom")
require.NoError(t, err)
var customData canonicaljson.RawMessage
err = canonicaljson.Unmarshal([]byte("\"Lorem ipsum dolor sit amet, consectetur adipiscing elit\""), &customData)
require.NoError(t, err)
_, err = tempFile.Write(customData)
require.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())

// add a target by sha512 and custom data
_, err = runCommand(t, tempDir, "addhash", "gun", target4, "3", "--sha512", targetSha512Hex, "--custom", tempFile.Name())
require.NoError(t, err)

// check status - see target
output, err = runCommand(t, tempDir, "status", "gun")
require.NoError(t, err)
require.Contains(t, output, target4)

// publish repo
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

// check status - no targets
output, err = runCommand(t, tempDir, "status", "gun")
require.NoError(t, err)
require.False(t, strings.Contains(string(output), target4))

// list repo - see target
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun")
require.NoError(t, err)
require.Contains(t, output, target4)

// Check the file this was written to to inspect metadata
cache, err := nstorage.NewFileStore(
filepath.Join(tempDir, "tuf", filepath.FromSlash("gun"), "metadata"),
"json",
)
rawTargets, err := cache.Get("targets")
parsedTargets := data.SignedTargets{}
require.NoError(t, err)
err = json.Unmarshal(rawTargets, &parsedTargets)
require.NoError(t, err)
require.Equal(t, *parsedTargets.Signed.Targets[target4].Custom, customData)

// lookup target and repo - see target
output, err = runCommand(t, tempDir, "-s", server.URL, "lookup", "gun", target4)
require.NoError(t, err)
require.Contains(t, output, target4)
}

// Initialize repo and test delegations commands by adding, listing, and removing delegations
Expand Down
35 changes: 33 additions & 2 deletions cmd/notary/tuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ type tufCommander struct {
sha256 string
sha512 string
rootKey string
custom string

input string
output string
Expand Down Expand Up @@ -155,6 +156,7 @@ func (t *tufCommander) AddToCommand(cmd *cobra.Command) {
cmdTUFAdd := cmdTUFAddTemplate.ToCommand(t.tufAdd)
cmdTUFAdd.Flags().StringSliceVarP(&t.roles, "roles", "r", nil, "Delegation roles to add this target to")
cmdTUFAdd.Flags().BoolVarP(&t.autoPublish, "publish", "p", false, htAutoPublish)
cmdTUFAdd.Flags().StringVar(&t.custom, "custom", "", "Name of the file containing custom data for this target")
cmd.AddCommand(cmdTUFAdd)

cmdTUFRemove := cmdTUFRemoveTemplate.ToCommand(t.tufRemove)
Expand All @@ -167,6 +169,7 @@ func (t *tufCommander) AddToCommand(cmd *cobra.Command) {
cmdTUFAddHash.Flags().StringVar(&t.sha256, notary.SHA256, "", "hex encoded sha256 of the target to add")
cmdTUFAddHash.Flags().StringVar(&t.sha512, notary.SHA512, "", "hex encoded sha512 of the target to add")
cmdTUFAddHash.Flags().BoolVarP(&t.autoPublish, "publish", "p", false, htAutoPublish)
cmdTUFAddHash.Flags().StringVar(&t.custom, "custom", "", "Name of the file containing custom data for this target")
cmd.AddCommand(cmdTUFAddHash)

cmdTUFVerify := cmdTUFVerifyTemplate.ToCommand(t.tufVerify)
Expand Down Expand Up @@ -249,6 +252,20 @@ func getTargetHashes(t *tufCommander) (data.Hashes, error) {
return targetHash, nil
}

// Open and read a file containing the targetCustom data
func getTargetCustom(targetCustomFilename string) ([]byte, error) {
targetCustomFile, err := os.Open(targetCustomFilename)
if err != nil {
return []byte(nil), err
}
defer targetCustomFile.Close()
targetCustom, err := ioutil.ReadAll(targetCustomFile)
if err != nil {
return []byte(nil), err
}
return targetCustom, nil
}

func (t *tufCommander) tufAddByHash(cmd *cobra.Command, args []string) error {
if len(args) < 3 || t.sha256 == "" && t.sha512 == "" {
cmd.Usage()
Expand All @@ -262,6 +279,13 @@ func (t *tufCommander) tufAddByHash(cmd *cobra.Command, args []string) error {
gun := data.GUN(args[0])
targetName := args[1]
targetSize := args[2]
var targetCustom []byte
if t.custom != "" {
targetCustom, err = getTargetCustom(t.custom)
if err != nil {
return err
}
}

targetInt64Len, err := strconv.ParseInt(targetSize, 0, 64)
if err != nil {
Expand All @@ -287,7 +311,7 @@ func (t *tufCommander) tufAddByHash(cmd *cobra.Command, args []string) error {
}

// Manually construct the target with the given byte size and hashes
target := &notaryclient.Target{Name: targetName, Hashes: targetHashes, Length: targetInt64Len}
target := &notaryclient.Target{Name: targetName, Hashes: targetHashes, Length: targetInt64Len, Custom: targetCustom}

roleNames := data.NewRoleList(t.roles)

Expand Down Expand Up @@ -321,6 +345,13 @@ func (t *tufCommander) tufAdd(cmd *cobra.Command, args []string) error {
gun := data.GUN(args[0])
targetName := args[1]
targetPath := args[2]
var targetCustom []byte
if t.custom != "" {
targetCustom, err = getTargetCustom(t.custom)
if err != nil {
return err
}
}

trustPin, err := getTrustPinning(config)
if err != nil {
Expand All @@ -335,7 +366,7 @@ func (t *tufCommander) tufAdd(cmd *cobra.Command, args []string) error {
return err
}

target, err := notaryclient.NewTarget(targetName, targetPath)
target, err := notaryclient.NewTarget(targetName, targetPath, targetCustom)
if err != nil {
return err
}
Expand Down

0 comments on commit 8be2b1d

Please sign in to comment.