Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Riyaz Faizullabhoy authored on 2016/01/15 05:21:55... | ... |
@@ -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 |
+} |