| ... | ... |
@@ -168,7 +168,7 @@ RUN set -x \ |
| 168 | 168 |
&& rm -rf "$GOPATH" |
| 169 | 169 |
|
| 170 | 170 |
# Install notary server |
| 171 |
-ENV NOTARY_VERSION docker-v1.10-2 |
|
| 171 |
+ENV NOTARY_VERSION docker-v1.10-3 |
|
| 172 | 172 |
RUN set -x \ |
| 173 | 173 |
&& export GOPATH="$(mktemp -d)" \ |
| 174 | 174 |
&& 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 |
| ... | ... |
@@ -50,7 +50,7 @@ clone git github.com/docker/distribution cb08de17d74bef86ce6c5abe8b240e282f5750b |
| 50 | 50 |
clone git github.com/vbatts/tar-split v0.9.11 |
| 51 | 51 |
|
| 52 | 52 |
# get desired notary commit, might also need to be updated in Dockerfile |
| 53 |
-clone git github.com/docker/notary docker-v1.10-2 |
|
| 53 |
+clone git github.com/docker/notary docker-v1.10-3 |
|
| 54 | 54 |
|
| 55 | 55 |
clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git |
| 56 | 56 |
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 |
+} |