Browse code

revendor notary and wrap friendlier error messages

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>

Riyaz Faizullabhoy authored on 2016/01/15 05:21:55
Showing 24 changed files
... ...
@@ -165,7 +165,7 @@ RUN set -x \
165 165
 	&& rm -rf "$GOPATH"
166 166
 
167 167
 # Install notary server
168
-ENV NOTARY_VERSION docker-v1.10-2
168
+ENV NOTARY_VERSION docker-v1.10-3
169 169
 RUN set -x \
170 170
 	&& export GOPATH="$(mktemp -d)" \
171 171
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -284,13 +284,15 @@ func notaryError(repoName string, err error) error {
284 284
 	case signed.ErrInvalidKeyType:
285 285
 		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
286 286
 	case signed.ErrNoKeys:
287
-		return fmt.Errorf("Error: could not find signing keys for remote repository %s: %v", repoName, err)
287
+		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
288 288
 	case signed.ErrLowVersion:
289 289
 		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
290
-	case signed.ErrInsufficientSignatures:
290
+	case signed.ErrRoleThreshold:
291 291
 		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
292 292
 	case client.ErrRepositoryNotExist:
293
-		return fmt.Errorf("Error: remote trust data repository not initialized for %s: %v", repoName, err)
293
+		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
294
+	case signed.ErrInsufficientSignatures:
295
+		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
294 296
 	}
295 297
 
296 298
 	return err
... ...
@@ -49,7 +49,7 @@ clone git github.com/docker/distribution a7ae88da459b98b481a245e5b1750134724ac67
49 49
 clone git github.com/vbatts/tar-split v0.9.11
50 50
 
51 51
 # get desired notary commit, might also need to be updated in Dockerfile
52
-clone git github.com/docker/notary docker-v1.10-2
52
+clone git github.com/docker/notary docker-v1.10-3
53 53
 
54 54
 clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git
55 55
 clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
... ...
@@ -312,7 +312,7 @@ func (s *DockerTrustSuite) TestUntrustedCreate(c *check.C) {
312 312
 	s.trustedCmd(createCmd)
313 313
 	out, _, err := runCommandWithOutput(createCmd)
314 314
 	c.Assert(err, check.Not(check.IsNil))
315
-	c.Assert(string(out), checker.Contains, "does not have trust data for", check.Commentf("Missing expected output on trusted create:\n%s", out))
315
+	c.Assert(string(out), checker.Contains, "trust data unavailable.  Has a notary repository been initialized?", check.Commentf("Missing expected output on trusted create:\n%s", out))
316 316
 
317 317
 }
318 318
 
... ...
@@ -402,7 +402,7 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
402 402
 	s.trustedCmd(createCmd)
403 403
 	out, _, err = runCommandWithOutput(createCmd)
404 404
 	c.Assert(err, check.Not(check.IsNil))
405
-	c.Assert(string(out), checker.Contains, "failed to validate data with current trusted certificates", check.Commentf("Missing expected output on trusted push:\n%s", out))
405
+	c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf("Missing expected output on trusted push:\n%s", out))
406 406
 
407 407
 }
408 408
 
... ...
@@ -59,7 +59,7 @@ func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) {
59 59
 	out, _, err := runCommandWithOutput(pullCmd)
60 60
 
61 61
 	c.Assert(err, check.NotNil, check.Commentf(out))
62
-	c.Assert(string(out), checker.Contains, "Error: remote trust data repository not initialized", check.Commentf(out))
62
+	c.Assert(string(out), checker.Contains, "Error: remote trust data does not exist", check.Commentf(out))
63 63
 }
64 64
 
65 65
 func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) {
... ...
@@ -141,7 +141,7 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) {
141 141
 	out, _, err = runCommandWithOutput(pullCmd)
142 142
 
143 143
 	c.Assert(err, check.NotNil, check.Commentf(out))
144
-	c.Assert(string(out), checker.Contains, "failed to validate data with current trusted certificates", check.Commentf(out))
144
+	c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf(out))
145 145
 }
146 146
 
147 147
 func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
... ...
@@ -3303,7 +3303,7 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
3303 3303
 		c.Fatalf("Expected to fail on this run due to different remote data: %s\n%s", err, out)
3304 3304
 	}
3305 3305
 
3306
-	if !strings.Contains(string(out), "failed to validate data with current trusted certificates") {
3306
+	if !strings.Contains(string(out), "valid signatures did not meet threshold") {
3307 3307
 		c.Fatalf("Missing expected output on trusted push:\n%s", out)
3308 3308
 	}
3309 3309
 }
... ...
@@ -19,7 +19,6 @@ Then please do not report your issue here - you should instead report it to [htt
19 19
 Then please do not open an issue here yet - you should first try one of the following support forums:
20 20
 
21 21
  - irc: #docker-trust on freenode
22
- - mailing-list: <trust@dockerproject.org> or https://groups.google.com/a/dockerproject.org/forum/#!forum/trust
23 22
 
24 23
 ## Reporting an issue properly
25 24
 
26 25
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-FROM golang:1.5.1
2
-
3
-RUN apt-get update && apt-get install -y \
4
-    libltdl-dev \
5
-    --no-install-recommends \
6
-    && rm -rf /var/lib/apt/lists/*
7
-
8
-EXPOSE 4443
9
-
10
-ENV NOTARYPKG github.com/docker/notary
11
-ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
12
-
13
-COPY . /go/src/github.com/docker/notary
14
-
15
-WORKDIR /go/src/${NOTARYPKG}
16
-
17
-RUN go install \
18
-    -tags pkcs11 \
19
-    -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
20
-    ${NOTARYPKG}/cmd/notary-server
21
-
22
-ENTRYPOINT [ "notary-server" ]
23
-CMD [ "-config=fixtures/server-config-local.json" ]
24 1
deleted file mode 100644
... ...
@@ -1,41 +0,0 @@
1
-FROM dockersecurity/golang-softhsm2
2
-MAINTAINER Diogo Monica "diogo@docker.com"
3
-
4
-# CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token
5
-ENV NOTARY_SIGNER_PIN="1234"
6
-ENV SOPIN="1234"
7
-ENV LIBDIR="/usr/local/lib/softhsm/"
8
-ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
9
-ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
10
-
11
-# Install openSC and dependencies
12
-RUN apt-get update && apt-get install -y \
13
-    libltdl-dev \
14
-    libpcsclite-dev \
15
-    opensc \
16
-    usbutils \
17
-    --no-install-recommends \
18
-    && rm -rf /var/lib/apt/lists/*
19
-
20
-# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles
21
-RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $NOTARY_SIGNER_PIN --so-pin $SOPIN
22
-
23
-ENV NOTARYPKG github.com/docker/notary
24
-ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
25
-
26
-EXPOSE 4444
27
-
28
-# Copy the local repo to the expected go path
29
-COPY . /go/src/github.com/docker/notary
30
-
31
-WORKDIR /go/src/${NOTARYPKG}
32
-
33
-# Install notary-signer
34
-RUN go install \
35
-    -tags pkcs11 \
36
-    -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
37
-    ${NOTARYPKG}/cmd/notary-signer
38
-
39
-
40
-ENTRYPOINT [ "notary-signer" ]
41
-CMD [ "-config=fixtures/signer-config-local.json" ]
... ...
@@ -16,6 +16,7 @@
16 16
 			"dmcgowan",
17 17
 			"endophage",
18 18
 			"nathanmccauley",
19
+			"riyazdf",
19 20
 		]
20 21
 
21 22
 [people]
... ...
@@ -50,3 +51,8 @@
50 50
 	Name = "Nathan McCauley"
51 51
 	Email = "nathan.mccauley@docker.com"
52 52
 	GitHub = "nathanmccauley"
53
+
54
+	[people.riyazdf]
55
+	Name = "Riyaz Faizullabhoy"
56
+	Email = "riyaz@docker.com"
57
+	GitHub = "riyazdf"
... ...
@@ -92,14 +92,21 @@ build: go_version
92 92
 	@echo "+ $@"
93 93
 	@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./...
94 94
 
95
+# When running `go test ./...`, it runs all the suites in parallel, which causes
96
+# problems when running with a yubikey
95 97
 test: TESTOPTS =
96 98
 test: go_version
99
+	@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
97 100
 	@echo "+ $@ $(TESTOPTS)"
101
+	@echo
98 102
 	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) ./...
99 103
 
104
+test-full: TESTOPTS =
100 105
 test-full: vet lint
106
+	@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
101 107
 	@echo "+ $@"
102
-	go test -tags "${NOTARY_BUILDTAGS}" -v ./...
108
+	@echo
109
+	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v ./...
103 110
 
104 111
 protos:
105 112
 	@protoc --go_out=plugins=grpc:. proto/*.proto
... ...
@@ -118,14 +125,18 @@ gen-cover: go_version
118 118
 	@mkdir -p "$(COVERDIR)"
119 119
 	$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
120 120
 
121
+# Generates the cover binaries and runs them all in serial, so this can be used
122
+# run all tests with a yubikey without any problems
121 123
 cover: GO_EXC := go
122 124
        OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
123 125
 cover: gen-cover covmerge
124 126
 	@go tool cover -html="$(COVERPROFILE)"
125 127
 
126
-# Codecov knows how to merge multiple coverage files
128
+# Generates the cover binaries and runs them all in serial, so this can be used
129
+# run all tests with a yubikey without any problems
127 130
 ci: OPTS = -tags "${NOTARY_BUILDTAGS}" -race -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
128 131
     GO_EXC := godep go
132
+# Codecov knows how to merge multiple coverage files, so covmerge is not needed
129 133
 ci: gen-cover
130 134
 
131 135
 covmerge:
... ...
@@ -151,10 +162,10 @@ notary-dockerfile:
151 151
 	@docker build --rm --force-rm -t notary .
152 152
 
153 153
 server-dockerfile:
154
-	@docker build --rm --force-rm -f Dockerfile.server -t notary-server .
154
+	@docker build --rm --force-rm -f server.Dockerfile -t notary-server .
155 155
 
156 156
 signer-dockerfile:
157
-	@docker build --rm --force-rm -f Dockerfile.signer -t notary-signer .
157
+	@docker build --rm --force-rm -f signer.Dockerfile -t notary-signer .
158 158
 
159 159
 docker-images: notary-dockerfile server-dockerfile signer-dockerfile
160 160
 
... ...
@@ -419,7 +419,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
419 419
 // subtree and also the "targets/x" subtree, as we will defer parsing it until
420 420
 // we explicitly reach it in our iteration of the provided list of roles.
421 421
 func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, error) {
422
-	_, err := r.Update()
422
+	_, err := r.Update(false)
423 423
 	if err != nil {
424 424
 		return nil, err
425 425
 	}
... ...
@@ -479,7 +479,7 @@ func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role
479 479
 // will be returned
480 480
 // See the IMPORTANT section on ListTargets above. Those roles also apply here.
481 481
 func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
482
-	c, err := r.Update()
482
+	c, err := r.Update(false)
483 483
 	if err != nil {
484 484
 		return nil, err
485 485
 	}
... ...
@@ -514,7 +514,7 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
514 514
 func (r *NotaryRepository) Publish() error {
515 515
 	var initialPublish bool
516 516
 	// update first before publishing
517
-	_, err := r.Update()
517
+	_, err := r.Update(true)
518 518
 	if err != nil {
519 519
 		// If the remote is not aware of the repo, then this is being published
520 520
 		// for the first time.  Try to load from disk instead for publishing.
... ...
@@ -555,13 +555,21 @@ func (r *NotaryRepository) Publish() error {
555 555
 	// we send anything to remote
556 556
 	updatedFiles := make(map[string][]byte)
557 557
 
558
-	// check if our root file is nearing expiry. Resign if it is.
559
-	if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty || initialPublish {
558
+	// check if our root file is nearing expiry or dirty. Resign if it is.  If
559
+	// root is not dirty but we are publishing for the first time, then just
560
+	// publish the existing root we have.
561
+	if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty {
560 562
 		rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole)
561 563
 		if err != nil {
562 564
 			return err
563 565
 		}
564 566
 		updatedFiles[data.CanonicalRootRole] = rootJSON
567
+	} else if initialPublish {
568
+		rootJSON, err := r.tufRepo.Root.MarshalJSON()
569
+		if err != nil {
570
+			return err
571
+		}
572
+		updatedFiles[data.CanonicalRootRole] = rootJSON
565 573
 	}
566 574
 
567 575
 	// iterate through all the targets files - if they are dirty, sign and update
... ...
@@ -714,75 +722,94 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
714 714
 	return r.fileStore.SetMeta(data.CanonicalSnapshotRole, snapshotJSON)
715 715
 }
716 716
 
717
+// returns a properly constructed ErrRepositoryNotExist error based on this
718
+// repo's information
719
+func (r *NotaryRepository) errRepositoryNotExist() error {
720
+	host := r.baseURL
721
+	parsed, err := url.Parse(r.baseURL)
722
+	if err == nil {
723
+		host = parsed.Host // try to exclude the scheme and any paths
724
+	}
725
+	return ErrRepositoryNotExist{remote: host, gun: r.gun}
726
+}
727
+
717 728
 // Update bootstraps a trust anchor (root.json) before updating all the
718 729
 // metadata from the repo.
719
-func (r *NotaryRepository) Update() (*tufclient.Client, error) {
720
-	c, err := r.bootstrapClient()
730
+func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
731
+	c, err := r.bootstrapClient(forWrite)
721 732
 	if err != nil {
722 733
 		if _, ok := err.(store.ErrMetaNotFound); ok {
723
-			host := r.baseURL
724
-			parsed, err := url.Parse(r.baseURL)
725
-			if err == nil {
726
-				host = parsed.Host // try to exclude the scheme and any paths
727
-			}
728
-			return nil, ErrRepositoryNotExist{remote: host, gun: r.gun}
734
+			return nil, r.errRepositoryNotExist()
729 735
 		}
730 736
 		return nil, err
731 737
 	}
732 738
 	err = c.Update()
733 739
 	if err != nil {
740
+		if notFound, ok := err.(store.ErrMetaNotFound); ok && notFound.Resource == data.CanonicalRootRole {
741
+			return nil, r.errRepositoryNotExist()
742
+		}
734 743
 		return nil, err
735 744
 	}
736 745
 	return c, nil
737 746
 }
738 747
 
739
-func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
740
-	var rootJSON []byte
741
-	remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
742
-	if err == nil {
743
-		// if remote store successfully set up, try and get root from remote
744
-		rootJSON, err = remote.GetMeta("root", maxSize)
748
+// bootstrapClient attempts to bootstrap a root.json to be used as the trust
749
+// anchor for a repository. The checkInitialized argument indicates whether
750
+// we should always attempt to contact the server to determine if the repository
751
+// is initialized or not. If set to true, we will always attempt to download
752
+// and return an error if the remote repository errors.
753
+func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) {
754
+	var (
755
+		rootJSON   []byte
756
+		err        error
757
+		signedRoot *data.SignedRoot
758
+	)
759
+	// try to read root from cache first. We will trust this root
760
+	// until we detect a problem during update which will cause
761
+	// us to download a new root and perform a rotation.
762
+	rootJSON, cachedRootErr := r.fileStore.GetMeta("root", maxSize)
763
+
764
+	if cachedRootErr == nil {
765
+		signedRoot, cachedRootErr = r.validateRoot(rootJSON)
745 766
 	}
746 767
 
747
-	// if remote store couldn't be setup, or we failed to get a root from it
748
-	// load the root from cache (offline operation)
749
-	if err != nil {
750
-		if err, ok := err.(store.ErrMetaNotFound); ok {
751
-			// if the error was MetaNotFound then we successfully contacted
752
-			// the store and it doesn't know about the repo.
753
-			return nil, err
754
-		}
755
-		result, cacheErr := r.fileStore.GetMeta("root", maxSize)
756
-		if cacheErr != nil {
757
-			// if cache didn't return a root, we cannot proceed - just return
758
-			// the original error.
768
+	remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
769
+	if remoteErr != nil {
770
+		logrus.Error(remoteErr)
771
+	} else if cachedRootErr != nil || checkInitialized {
772
+		// remoteErr was nil and we had a cachedRootErr (or are specifically
773
+		// checking for initialization of the repo).
774
+
775
+		// if remote store successfully set up, try and get root from remote
776
+		tmpJSON, err := remote.GetMeta("root", maxSize)
777
+		if err != nil {
778
+			// we didn't have a root in cache and were unable to load one from
779
+			// the server. Nothing we can do but error.
759 780
 			return nil, err
760 781
 		}
761
-		rootJSON = result
762
-		logrus.Debugf(
763
-			"Using local cache instead of remote due to failure: %s", err.Error())
764
-	}
765
-	// can't just unmarshal into SignedRoot because validate root
766
-	// needs the root.Signed field to still be []byte for signature
767
-	// validation
768
-	root := &data.Signed{}
769
-	err = json.Unmarshal(rootJSON, root)
770
-	if err != nil {
771
-		return nil, err
772
-	}
782
+		if cachedRootErr != nil {
783
+			// we always want to use the downloaded root if there was a cache
784
+			// error.
785
+			signedRoot, err = r.validateRoot(tmpJSON)
786
+			if err != nil {
787
+				return nil, err
788
+			}
773 789
 
774
-	err = r.CertManager.ValidateRoot(root, r.gun)
775
-	if err != nil {
776
-		return nil, err
790
+			err = r.fileStore.SetMeta("root", tmpJSON)
791
+			if err != nil {
792
+				// if we can't write cache we should still continue, just log error
793
+				logrus.Errorf("could not save root to cache: %s", err.Error())
794
+			}
795
+		}
777 796
 	}
778 797
 
779 798
 	kdb := keys.NewDB()
780 799
 	r.tufRepo = tuf.NewRepo(kdb, r.CryptoService)
781 800
 
782
-	signedRoot, err := data.RootFromSigned(root)
783
-	if err != nil {
784
-		return nil, err
801
+	if signedRoot == nil {
802
+		return nil, ErrRepoNotInitialized{}
785 803
 	}
804
+
786 805
 	err = r.tufRepo.SetRoot(signedRoot)
787 806
 	if err != nil {
788 807
 		return nil, err
... ...
@@ -796,6 +823,28 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
796 796
 	), nil
797 797
 }
798 798
 
799
+// validateRoot MUST only be used during bootstrapping. It will only validate
800
+// signatures of the root based on known keys, not expiry or other metadata.
801
+// This is so that an out of date root can be loaded to be used in a rotation
802
+// should the TUF update process detect a problem.
803
+func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, error) {
804
+	// can't just unmarshal into SignedRoot because validate root
805
+	// needs the root.Signed field to still be []byte for signature
806
+	// validation
807
+	root := &data.Signed{}
808
+	err := json.Unmarshal(rootJSON, root)
809
+	if err != nil {
810
+		return nil, err
811
+	}
812
+
813
+	err = r.CertManager.ValidateRoot(root, r.gun)
814
+	if err != nil {
815
+		return nil, err
816
+	}
817
+
818
+	return data.RootFromSigned(root)
819
+}
820
+
799 821
 // RotateKey removes all existing keys associated with the role, and either
800 822
 // creates and adds one new key or delegates managing the key to the server.
801 823
 // These changes are staged in a changelist until publish is called.
... ...
@@ -17,7 +17,7 @@ import (
17 17
 
18 18
 // Use this to initialize remote HTTPStores from the config settings
19 19
 func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStore, error) {
20
-	return store.NewHTTPStore(
20
+	s, err := store.NewHTTPStore(
21 21
 		baseURL+"/v2/"+gun+"/_trust/tuf/",
22 22
 		"",
23 23
 		"json",
... ...
@@ -25,6 +25,10 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor
25 25
 		"key",
26 26
 		rt,
27 27
 	)
28
+	if err != nil {
29
+		return store.OfflineStore{}, err
30
+	}
31
+	return s, err
28 32
 }
29 33
 
30 34
 func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error {
... ...
@@ -26,7 +26,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
26 26
 	keyStores := []trustmanager.KeyStore{fileKeyStore}
27 27
 	yubiKeyStore, _ := yubikey.NewYubiKeyStore(fileKeyStore, retriever)
28 28
 	if yubiKeyStore != nil {
29
-		keyStores = append(keyStores, yubiKeyStore)
29
+		keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
30 30
 	}
31 31
 
32 32
 	return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores)
... ...
@@ -73,9 +73,9 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
73 73
 
74 74
 }
75 75
 
76
-// GetPrivateKey returns a private key by ID. It tries to get the key first
77
-// without a GUN (in which case it's a root key).  If that fails, try to get
78
-// the key with the GUN (non-root key).
76
+// GetPrivateKey returns a private key and role if present by ID.
77
+// It tries to get the key first without a GUN (in which case it's a root key).
78
+// If that fails, try to get the key with the GUN (non-root key).
79 79
 // If that fails, then we don't have the key.
80 80
 func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) {
81 81
 	keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
... ...
@@ -1,6 +1,6 @@
1 1
 notaryserver:
2 2
   build: .
3
-  dockerfile: Dockerfile.server
3
+  dockerfile: server.Dockerfile
4 4
   links:
5 5
    - notarymysql
6 6
    - notarysigner
... ...
@@ -15,7 +15,7 @@ notarysigner:
15 15
    - /dev/bus/usb/003/010:/dev/bus/usb/002/010
16 16
    - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm
17 17
   build: .
18
-  dockerfile: Dockerfile.signer
18
+  dockerfile: signer.Dockerfile
19 19
   links:
20 20
    - notarymysql
21 21
   command: -config=fixtures/signer-config.json
22 22
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+FROM golang:1.5.1
1
+
2
+RUN apt-get update && apt-get install -y \
3
+    libltdl-dev \
4
+    --no-install-recommends \
5
+    && rm -rf /var/lib/apt/lists/*
6
+
7
+EXPOSE 4443
8
+
9
+ENV NOTARYPKG github.com/docker/notary
10
+ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
11
+
12
+COPY . /go/src/github.com/docker/notary
13
+
14
+WORKDIR /go/src/${NOTARYPKG}
15
+
16
+RUN go install \
17
+    -tags pkcs11 \
18
+    -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
19
+    ${NOTARYPKG}/cmd/notary-server
20
+
21
+ENTRYPOINT [ "notary-server" ]
22
+CMD [ "-config=fixtures/server-config-local.json" ]
0 23
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+FROM dockersecurity/golang-softhsm2
1
+MAINTAINER Diogo Monica "diogo@docker.com"
2
+
3
+# CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token
4
+ENV NOTARY_SIGNER_PIN="1234"
5
+ENV SOPIN="1234"
6
+ENV LIBDIR="/usr/local/lib/softhsm/"
7
+ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
8
+ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
9
+
10
+# Install openSC and dependencies
11
+RUN apt-get update && apt-get install -y \
12
+    libltdl-dev \
13
+    libpcsclite-dev \
14
+    opensc \
15
+    usbutils \
16
+    --no-install-recommends \
17
+    && rm -rf /var/lib/apt/lists/*
18
+
19
+# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles
20
+RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $NOTARY_SIGNER_PIN --so-pin $SOPIN
21
+
22
+ENV NOTARYPKG github.com/docker/notary
23
+ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
24
+
25
+EXPOSE 4444
26
+
27
+# Copy the local repo to the expected go path
28
+COPY . /go/src/github.com/docker/notary
29
+
30
+WORKDIR /go/src/${NOTARYPKG}
31
+
32
+# Install notary-signer
33
+RUN go install \
34
+    -tags pkcs11 \
35
+    -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
36
+    ${NOTARYPKG}/cmd/notary-signer
37
+
38
+
39
+ENTRYPOINT [ "notary-signer" ]
40
+CMD [ "-config=fixtures/signer-config-local.json" ]
... ...
@@ -129,6 +129,7 @@ func (c Client) checkRoot() error {
129 129
 
130 130
 // downloadRoot is responsible for downloading the root.json
131 131
 func (c *Client) downloadRoot() error {
132
+	logrus.Debug("Downloading Root...")
132 133
 	role := data.CanonicalRootRole
133 134
 	size := maxSize
134 135
 	var expectedSha256 []byte
... ...
@@ -240,7 +241,7 @@ func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error {
240 240
 // Timestamps are special in that we ALWAYS attempt to download and only
241 241
 // use cache if the download fails (and the cache is still valid).
242 242
 func (c *Client) downloadTimestamp() error {
243
-	logrus.Debug("downloadTimestamp")
243
+	logrus.Debug("Downloading Timestamp...")
244 244
 	role := data.CanonicalTimestampRole
245 245
 
246 246
 	// We may not have a cached timestamp if this is the first time
... ...
@@ -299,7 +300,7 @@ func (c *Client) downloadTimestamp() error {
299 299
 
300 300
 // downloadSnapshot is responsible for downloading the snapshot.json
301 301
 func (c *Client) downloadSnapshot() error {
302
-	logrus.Debug("downloadSnapshot")
302
+	logrus.Debug("Downloading Snapshot...")
303 303
 	role := data.CanonicalSnapshotRole
304 304
 	if c.local.Timestamp == nil {
305 305
 		return ErrMissingMeta{role: "snapshot"}
... ...
@@ -372,6 +373,7 @@ func (c *Client) downloadSnapshot() error {
372 372
 // It uses a pre-order tree traversal as it's necessary to download parents first
373 373
 // to obtain the keys to validate children.
374 374
 func (c *Client) downloadTargets(role string) error {
375
+	logrus.Debug("Downloading Targets...")
375 376
 	stack := utils.NewStack()
376 377
 	stack.Push(role)
377 378
 	for !stack.Empty() {
... ...
@@ -43,10 +43,11 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
43 43
 
44 44
 // ToSigned partially serializes a SignedRoot for further signing
45 45
 func (r SignedRoot) ToSigned() (*Signed, error) {
46
-	s, err := json.MarshalCanonical(r.Signed)
46
+	s, err := defaultSerializer.MarshalCanonical(r.Signed)
47 47
 	if err != nil {
48 48
 		return nil, err
49 49
 	}
50
+	// cast into a json.RawMessage
50 51
 	signed := json.RawMessage{}
51 52
 	err = signed.UnmarshalJSON(s)
52 53
 	if err != nil {
... ...
@@ -60,6 +61,15 @@ func (r SignedRoot) ToSigned() (*Signed, error) {
60 60
 	}, nil
61 61
 }
62 62
 
63
+// MarshalJSON returns the serialized form of SignedRoot as bytes
64
+func (r SignedRoot) MarshalJSON() ([]byte, error) {
65
+	signed, err := r.ToSigned()
66
+	if err != nil {
67
+		return nil, err
68
+	}
69
+	return defaultSerializer.Marshal(signed)
70
+}
71
+
63 72
 // RootFromSigned fully unpacks a Signed object into a SignedRoot
64 73
 func RootFromSigned(s *Signed) (*SignedRoot, error) {
65 74
 	r := Root{}
66 75
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package data
1
+
2
+import "github.com/jfrazelle/go/canonical/json"
3
+
4
+// Serializer is an interface that can marshal and unmarshal TUF data.  This
5
+// is expected to be a canonical JSON marshaller
6
+type serializer interface {
7
+	MarshalCanonical(from interface{}) ([]byte, error)
8
+	Marshal(from interface{}) ([]byte, error)
9
+	Unmarshal(from []byte, to interface{}) error
10
+}
11
+
12
+// CanonicalJSON marshals to and from canonical JSON
13
+type canonicalJSON struct{}
14
+
15
+// MarshalCanonical returns the canonical JSON form of a thing
16
+func (c canonicalJSON) MarshalCanonical(from interface{}) ([]byte, error) {
17
+	return json.MarshalCanonical(from)
18
+}
19
+
20
+// Marshal returns the regular non-canonical JSON form of a thing
21
+func (c canonicalJSON) Marshal(from interface{}) ([]byte, error) {
22
+	return json.Marshal(from)
23
+}
24
+
25
+// Unmarshal unmarshals some JSON bytes
26
+func (c canonicalJSON) Unmarshal(from []byte, to interface{}) error {
27
+	return json.Unmarshal(from, to)
28
+}
29
+
30
+// defaultSerializer is a canonical JSON serializer
31
+var defaultSerializer serializer = canonicalJSON{}
32
+
33
+func setDefaultSerializer(s serializer) {
34
+	defaultSerializer = s
35
+}
... ...
@@ -95,7 +95,7 @@ func (e *Ed25519) GetKey(keyID string) data.PublicKey {
95 95
 	return data.PublicKeyFromPrivate(e.keys[keyID].privKey)
96 96
 }
97 97
 
98
-// GetPrivateKey returns a single private key based on the ID
98
+// GetPrivateKey returns a single private key and role if present, based on the ID
99 99
 func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
100 100
 	if k, ok := e.keys[keyID]; ok {
101 101
 		return k.privKey, k.role, nil
... ...
@@ -118,12 +118,12 @@ func tryUnmarshalError(resp *http.Response, defaultError error) error {
118 118
 	return err
119 119
 }
120 120
 
121
-func translateStatusToError(resp *http.Response) error {
121
+func translateStatusToError(resp *http.Response, resource string) error {
122 122
 	switch resp.StatusCode {
123 123
 	case http.StatusOK:
124 124
 		return nil
125 125
 	case http.StatusNotFound:
126
-		return ErrMetaNotFound{}
126
+		return ErrMetaNotFound{Resource: resource}
127 127
 	case http.StatusBadRequest:
128 128
 		return tryUnmarshalError(resp, ErrInvalidOperation{})
129 129
 	default:
... ...
@@ -148,7 +148,7 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
148 148
 		return nil, err
149 149
 	}
150 150
 	defer resp.Body.Close()
151
-	if err := translateStatusToError(resp); err != nil {
151
+	if err := translateStatusToError(resp, name); err != nil {
152 152
 		logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name)
153 153
 		return nil, err
154 154
 	}
... ...
@@ -179,7 +179,7 @@ func (s HTTPStore) SetMeta(name string, blob []byte) error {
179 179
 		return err
180 180
 	}
181 181
 	defer resp.Body.Close()
182
-	return translateStatusToError(resp)
182
+	return translateStatusToError(resp, "POST "+name)
183 183
 }
184 184
 
185 185
 // NewMultiPartMetaRequest builds a request with the provided metadata updates
... ...
@@ -223,7 +223,8 @@ func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error {
223 223
 		return err
224 224
 	}
225 225
 	defer resp.Body.Close()
226
-	return translateStatusToError(resp)
226
+	// if this 404's something is pretty wrong
227
+	return translateStatusToError(resp, "POST metadata endpoint")
227 228
 }
228 229
 
229 230
 func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
... ...
@@ -271,7 +272,7 @@ func (s HTTPStore) GetTarget(path string) (io.ReadCloser, error) {
271 271
 		return nil, err
272 272
 	}
273 273
 	defer resp.Body.Close()
274
-	if err := translateStatusToError(resp); err != nil {
274
+	if err := translateStatusToError(resp, path); err != nil {
275 275
 		return nil, err
276 276
 	}
277 277
 	return resp.Body, nil
... ...
@@ -292,7 +293,7 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) {
292 292
 		return nil, err
293 293
 	}
294 294
 	defer resp.Body.Close()
295
-	if err := translateStatusToError(resp); err != nil {
295
+	if err := translateStatusToError(resp, role+" key"); err != nil {
296 296
 		return nil, err
297 297
 	}
298 298
 	body, err := ioutil.ReadAll(resp.Body)
299 299
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package store
1
+
2
+import (
3
+	"io"
4
+)
5
+
6
+// ErrOffline is used to indicate we are operating offline
7
+type ErrOffline struct{}
8
+
9
+func (e ErrOffline) Error() string {
10
+	return "client is offline"
11
+}
12
+
13
+var err = ErrOffline{}
14
+
15
+// OfflineStore is to be used as a placeholder for a nil store. It simply
16
+// return ErrOffline for every operation
17
+type OfflineStore struct{}
18
+
19
+// GetMeta return ErrOffline
20
+func (es OfflineStore) GetMeta(name string, size int64) ([]byte, error) {
21
+	return nil, err
22
+}
23
+
24
+// SetMeta return ErrOffline
25
+func (es OfflineStore) SetMeta(name string, blob []byte) error {
26
+	return err
27
+}
28
+
29
+// SetMultiMeta return ErrOffline
30
+func (es OfflineStore) SetMultiMeta(map[string][]byte) error {
31
+	return err
32
+}
33
+
34
+// GetKey return ErrOffline
35
+func (es OfflineStore) GetKey(role string) ([]byte, error) {
36
+	return nil, err
37
+}
38
+
39
+// GetTarget return ErrOffline
40
+func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) {
41
+	return nil, err
42
+}