Browse code

Bump the notary version to one that fixes a bug with delegation path traversal

Signed-off-by: cyli <cyli@twistedmatrix.com>

cyli authored on 2016/02/20 07:34:34
Showing 9 changed files
... ...
@@ -88,7 +88,7 @@ RUN cd /usr/local/lvm2 \
88 88
 
89 89
 # Install Go
90 90
 # IMPORTANT: If the version of Go is updated, the Windows to Linux CI machines
91
-#            will need updating, to avoid errors. Ping #docker-maintainers on IRC 
91
+#            will need updating, to avoid errors. Ping #docker-maintainers on IRC
92 92
 #            with a heads-up.
93 93
 ENV GO_VERSION 1.5.3
94 94
 RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" \
... ...
@@ -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-5
171
+ENV NOTARY_VERSION docker-v1.10.2-1
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" \
... ...
@@ -145,7 +145,7 @@ RUN set -x \
145 145
 	&& rm -rf "$GOPATH"
146 146
 
147 147
 # Install notary server
148
-ENV NOTARY_VERSION docker-v1.10-5
148
+ENV NOTARY_VERSION docker-v1.10.2-1
149 149
 RUN set -x \
150 150
 	&& export GOPATH="$(mktemp -d)" \
151 151
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -116,7 +116,7 @@ RUN set -x \
116 116
 	&& rm -rf "$GOPATH"
117 117
 
118 118
 # Install notary server
119
-#ENV NOTARY_VERSION docker-v1.10-5
119
+#ENV NOTARY_VERSION docker-v1.10.2-1
120 120
 #RUN set -x \
121 121
 #	&& export GOPATH="$(mktemp -d)" \
122 122
 #	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -116,7 +116,7 @@ RUN set -x \
116 116
 	&& rm -rf "$GOPATH"
117 117
 
118 118
 # Install notary server
119
-ENV NOTARY_VERSION docker-v1.10-5
119
+ENV NOTARY_VERSION docker-v1.10.2-1
120 120
 RUN set -x \
121 121
 	&& export GOPATH="$(mktemp -d)" \
122 122
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -50,7 +50,7 @@ clone git github.com/docker/distribution 0f2d99b13ae0cfbcf118eff103e6e680b726b47
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-5
53
+clone git github.com/docker/notary docker-v1.10.2-1
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
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"net/url"
10 10
 	"os"
11 11
 	"path/filepath"
12
+	"strings"
12 13
 	"time"
13 14
 
14 15
 	"github.com/Sirupsen/logrus"
... ...
@@ -451,11 +452,48 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
451 451
 		roles = []string{data.CanonicalTargetsRole}
452 452
 	}
453 453
 	targets := make(map[string]*TargetWithRole)
454
+
454 455
 	for _, role := range roles {
456
+		var foundRole *data.Role
457
+		walkRoles := []*data.Role{}
458
+		if role == data.CanonicalTargetsRole {
459
+			foundRole = &data.Role{
460
+				Name:             data.CanonicalTargetsRole,
461
+				Paths:            []string{""},
462
+				PathHashPrefixes: []string{""},
463
+			}
464
+		}
465
+
466
+		walkRoles = append(walkRoles, r.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles...)
467
+		for len(walkRoles) > 0 && foundRole == nil {
468
+			currRole := walkRoles[0]
469
+			walkRoles = walkRoles[1:]
470
+			if currRole.Name == role {
471
+				foundRole = currRole
472
+				break
473
+			}
474
+			if strings.HasPrefix(role, currRole.Name+"/") {
475
+				targetMeta, ok := r.tufRepo.Targets[currRole.Name]
476
+				if !ok {
477
+					continue
478
+				}
479
+				for _, childRole := range targetMeta.Signed.Delegations.Roles {
480
+					restricted, err := data.Restrict(*currRole, *childRole)
481
+					if err == nil {
482
+						walkRoles = append(walkRoles, restricted)
483
+					}
484
+				}
485
+			}
486
+		}
487
+
488
+		if foundRole == nil {
489
+			continue
490
+		}
491
+
455 492
 		// we don't need to do anything special with removing role from
456 493
 		// roles because listSubtree always processes role and only excludes
457 494
 		// descendant delegations that appear in roles.
458
-		r.listSubtree(targets, role, roles...)
495
+		r.listSubtree(targets, foundRole, roles...)
459 496
 	}
460 497
 
461 498
 	var targetList []*TargetWithRole
... ...
@@ -466,29 +504,32 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
466 466
 	return targetList, nil
467 467
 }
468 468
 
469
-func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role string, exclude ...string) {
469
+func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role *data.Role, exclude ...string) {
470 470
 	excl := make(map[string]bool)
471 471
 	for _, r := range exclude {
472 472
 		excl[r] = true
473 473
 	}
474
-	roles := []string{role}
474
+	roles := []*data.Role{role}
475 475
 	for len(roles) > 0 {
476 476
 		role = roles[0]
477 477
 		roles = roles[1:]
478
-		tgts, ok := r.tufRepo.Targets[role]
478
+		tgts, ok := r.tufRepo.Targets[role.Name]
479 479
 		if !ok {
480 480
 			// not every role has to exist
481 481
 			continue
482 482
 		}
483 483
 		for name, meta := range tgts.Signed.Targets {
484
-			if _, ok := targets[name]; !ok {
484
+			if _, ok := targets[name]; !ok && role.CheckPaths(name) {
485 485
 				targets[name] = &TargetWithRole{
486
-					Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: role}
486
+					Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: role.Name}
487 487
 			}
488 488
 		}
489
-		for _, d := range tgts.Signed.Delegations.Roles {
490
-			if !excl[d.Name] {
491
-				roles = append(roles, d.Name)
489
+		for _, child := range tgts.Signed.Delegations.Roles {
490
+			if !excl[child.Name] {
491
+				child, err := data.Restrict(*role, *child)
492
+				if err == nil {
493
+					roles = append(roles, child)
494
+				}
492 495
 			}
493 496
 		}
494 497
 	}
... ...
@@ -511,10 +552,47 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
511 511
 		roles = append(roles, data.CanonicalTargetsRole)
512 512
 	}
513 513
 	for _, role := range roles {
514
-		meta, foundRole := c.TargetMeta(role, name, roles...)
514
+
515
+		var foundRole *data.Role
516
+		walkRoles := []*data.Role{}
517
+		if role == data.CanonicalTargetsRole {
518
+			foundRole = &data.Role{
519
+				Name:             data.CanonicalTargetsRole,
520
+				Paths:            []string{""},
521
+				PathHashPrefixes: []string{""},
522
+			}
523
+		}
524
+
525
+		walkRoles = append(walkRoles, r.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles...)
526
+		for len(walkRoles) > 0 && foundRole == nil {
527
+			currRole := walkRoles[0]
528
+			walkRoles = walkRoles[1:]
529
+			if currRole.Name == role {
530
+				foundRole = currRole
531
+				break
532
+			}
533
+			if strings.HasPrefix(role, currRole.Name+"/") {
534
+				targetMeta, ok := r.tufRepo.Targets[currRole.Name]
535
+				if !ok {
536
+					continue
537
+				}
538
+				for _, childRole := range targetMeta.Signed.Delegations.Roles {
539
+					restricted, err := data.Restrict(*currRole, *childRole)
540
+					if err == nil && restricted.CheckPaths(name) {
541
+						walkRoles = append(walkRoles, restricted)
542
+					}
543
+				}
544
+			}
545
+		}
546
+
547
+		if foundRole == nil {
548
+			continue
549
+		}
550
+
551
+		meta, ownerName := c.TargetMeta(foundRole, name, roles...)
515 552
 		if meta != nil {
516 553
 			return &TargetWithRole{
517
-				Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: foundRole}, nil
554
+				Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: ownerName}, nil
518 555
 		}
519 556
 	}
520 557
 	return nil, fmt.Errorf("No trust data for %s", name)
... ...
@@ -526,39 +526,44 @@ func (c Client) RoleTargetsPath(role string, hashSha256 string, consistent bool)
526 526
 
527 527
 // TargetMeta ensures the repo is up to date. It assumes downloadTargets
528 528
 // has already downloaded all delegated roles
529
-func (c Client) TargetMeta(role, path string, excludeRoles ...string) (*data.FileMeta, string) {
529
+func (c Client) TargetMeta(role *data.Role, path string, excludeRoles ...string) (*data.FileMeta, string) {
530 530
 	excl := make(map[string]bool)
531 531
 	for _, r := range excludeRoles {
532 532
 		excl[r] = true
533 533
 	}
534 534
 
535
-	pathDigest := sha256.Sum256([]byte(path))
536
-	pathHex := hex.EncodeToString(pathDigest[:])
537
-
538 535
 	// FIFO list of targets delegations to inspect for target
539
-	roles := []string{role}
536
+	roles := []*data.Role{role}
540 537
 	var (
541 538
 		meta *data.FileMeta
542
-		curr string
539
+		curr *data.Role
543 540
 	)
544 541
 	for len(roles) > 0 {
545 542
 		// have to do these lines here because of order of execution in for statement
546 543
 		curr = roles[0]
547 544
 		roles = roles[1:]
548 545
 
549
-		meta = c.local.TargetMeta(curr, path)
546
+		meta = c.local.TargetMeta(curr.Name, path)
550 547
 		if meta != nil {
551 548
 			// we found the target!
552
-			return meta, curr
549
+			return meta, curr.Name
550
+		}
551
+		tgts, ok := c.local.Targets[curr.Name]
552
+		if !ok {
553
+			// not every role has to exist
554
+			continue
553 555
 		}
554
-		delegations := c.local.TargetDelegations(curr, path, pathHex)
555
-		for _, d := range delegations {
556
-			if !excl[d.Name] {
557
-				roles = append(roles, d.Name)
556
+
557
+		for _, child := range tgts.Signed.Delegations.Roles {
558
+			if !excl[child.Name] {
559
+				child, err := data.Restrict(*curr, *child)
560
+				if err == nil && child.CheckPaths(path) {
561
+					roles = append(roles, child)
562
+				}
558 563
 			}
559 564
 		}
560 565
 	}
561
-	return meta, ""
566
+	return nil, ""
562 567
 }
563 568
 
564 569
 // DownloadTarget downloads the target to dst from the remote
... ...
@@ -2,10 +2,11 @@ package data
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"github.com/Sirupsen/logrus"
6 5
 	"path"
7 6
 	"regexp"
8 7
 	"strings"
8
+
9
+	"github.com/Sirupsen/logrus"
9 10
 )
10 11
 
11 12
 // Canonical base role names
... ...
@@ -244,3 +245,40 @@ func subtractStrSlices(orig, remove []string) []string {
244 244
 	}
245 245
 	return keep
246 246
 }
247
+
248
+// Restrict restricts the paths and path hash prefixes for the passed in delegation role,
249
+// returning a copy of the role with validated paths as if it was a direct child
250
+func Restrict(parent, child Role) (*Role, error) {
251
+	if path.Dir(child.Name) != parent.Name {
252
+		return nil, fmt.Errorf("%s is not a parent of %s", parent.Name, child.Name)
253
+	}
254
+	return &Role{
255
+		RootRole: child.RootRole,
256
+		Name:     child.Name,
257
+		Paths:    RestrictDelegationPathPrefixes(parent.Paths, child.Paths),
258
+	}, nil
259
+}
260
+
261
+// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
262
+func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
263
+	validPaths := []string{}
264
+	if len(delegationPaths) == 0 {
265
+		return validPaths
266
+	}
267
+
268
+	// Validate each individual delegation path
269
+	for _, delgPath := range delegationPaths {
270
+		isPrefixed := false
271
+		for _, parentPath := range parentPaths {
272
+			if strings.HasPrefix(delgPath, parentPath) {
273
+				isPrefixed = true
274
+				break
275
+			}
276
+		}
277
+		// If the delegation path did not match prefix against any parent path, it is not valid
278
+		if isPrefixed {
279
+			validPaths = append(validPaths, delgPath)
280
+		}
281
+	}
282
+	return validPaths
283
+}
... ...
@@ -459,6 +459,9 @@ func (tr *Repo) SetTargets(role string, s *data.SignedTargets) error {
459 459
 		tr.keysDB.AddKey(k)
460 460
 	}
461 461
 	for _, r := range s.Signed.Delegations.Roles {
462
+		if path.Dir(r.Name) != role || tr.keysDB.GetRole(r.Name) != nil {
463
+			continue
464
+		}
462 465
 		tr.keysDB.AddRole(r)
463 466
 	}
464 467
 	tr.Targets[role] = s