Merge "services/mgmt/device/impl: fix leaking processes"
diff --git a/profiles/internal/naming/namespace/glob.go b/profiles/internal/naming/namespace/glob.go
index 451dc5b..e81d052 100644
--- a/profiles/internal/naming/namespace/glob.go
+++ b/profiles/internal/naming/namespace/glob.go
@@ -146,7 +146,7 @@
 	return strings.Count(name, "/") + 1
 }
 
-// globLoop fires off a go routine for each server and read backs replies.
+// globLoop fires off a go routine for each server and reads backs replies.
 func (ns *namespace) globLoop(ctx *context.T, e *naming.MountEntry, prefix string, pattern *glob.Glob, reply chan naming.GlobReply, tr *tracks, opts []rpc.CallOpt) {
 	defer close(reply)
 
@@ -159,77 +159,72 @@
 	// root of the search and the full pattern.  It will be the first task fired off in the for
 	// loop that follows.
 	replies <- &task{me: e, pattern: pattern}
-	inFlight := 0
+	replies <- nil
+	inFlight := 1
 
 	// Perform a parallel search of the name graph.  Each task will send what it learns
 	// on the replies channel.  If the reply is a mount point and the pattern is not completely
 	// fulfilled, a new task will be fired off to handle it.
-	for {
-		select {
-		case t := <-replies:
-			// A nil reply represents a successfully terminated task.
-			// If no tasks are running, return.
-			if t == nil {
-				if inFlight--; inFlight <= 0 {
-					return
-				}
-				continue
-			}
-
-			// We want to output this entry if there was a real error other than
-			// "not a mount table".
-			//
-			// An error reply is also a terminated task.
-			// If no tasks are running, return.
-			if t.error != nil {
-				if !notAnMT(t.error) {
-					reply <- naming.GlobReplyError{naming.GlobError{Name: naming.Join(prefix, t.me.Name), Error: t.error}}
-				}
-				if inFlight--; inFlight <= 0 {
-					return
-				}
-				continue
-			}
-
-			// If this is just an error from the mount table, pass it on.
-			if t.er != nil {
-				x := *t.er
-				x.Name = naming.Join(prefix, x.Name)
-				reply <- &naming.GlobReplyError{x}
-				continue
-			}
-
-			// Get the pattern elements below the current path.
-			suffix := pattern.Split(depth(t.me.Name))
-
-			// If we've satisfied the request and this isn't the root,
-			// reply to the caller.
-			if suffix.Len() == 0 && t.depth != 0 {
-				x := *t.me
-				x.Name = naming.Join(prefix, x.Name)
-				reply <- &naming.GlobReplyEntry{x}
-			}
-
-			// If the pattern is finished (so we're only querying about the root on the
-			// remote server) and the server is not another MT, then we needn't send the
-			// query on since we know the server will not supply a new address for the
-			// current name.
-			if suffix.Finished() {
-				if !t.me.ServesMountTable {
-					continue
-				}
-			}
-
-			// If this is restricted recursive and not a mount table, don't descend into it.
-			if suffix.Restricted() && suffix.Len() == 0 && !t.me.ServesMountTable {
-				continue
-			}
-
-			// Perform a glob at the next server.
-			inFlight++
-			t.pattern = suffix
-			go ns.globAtServer(ctx, t, replies, tr, opts)
+	for inFlight != 0 {
+		t := <-replies
+		// A nil reply represents a successfully terminated task.
+		// If no tasks are running, return.
+		if t == nil {
+			inFlight--
+			continue
 		}
+
+		// We want to output this entry if there was a real error other than
+		// "not a mount table".
+		//
+		// An error reply is also a terminated task.
+		// If no tasks are running, return.
+		if t.error != nil {
+			if !notAnMT(t.error) {
+				reply <- naming.GlobReplyError{naming.GlobError{Name: naming.Join(prefix, t.me.Name), Error: t.error}}
+			}
+			inFlight--
+			continue
+		}
+
+		// If this is just an error from the mount table, pass it on.
+		if t.er != nil {
+			x := *t.er
+			x.Name = naming.Join(prefix, x.Name)
+			reply <- &naming.GlobReplyError{x}
+			continue
+		}
+
+		// Get the pattern elements below the current path.
+		suffix := pattern.Split(depth(t.me.Name))
+
+		// If we've satisfied the request and this isn't the root,
+		// reply to the caller.
+		if suffix.Len() == 0 && t.depth != 0 {
+			x := *t.me
+			x.Name = naming.Join(prefix, x.Name)
+			reply <- &naming.GlobReplyEntry{x}
+		}
+
+		// If the pattern is finished (so we're only querying about the root on the
+		// remote server) and the server is not another MT, then we needn't send the
+		// query on since we know the server will not supply a new address for the
+		// current name.
+		if suffix.Finished() {
+			if !t.me.ServesMountTable {
+				continue
+			}
+		}
+
+		// If this is restricted recursive and not a mount table, don't descend into it.
+		if suffix.Restricted() && suffix.Len() == 0 && !t.me.ServesMountTable {
+			continue
+		}
+
+		// Perform a glob at the next server.
+		inFlight++
+		t.pattern = suffix
+		go ns.globAtServer(ctx, t, replies, tr, opts)
 	}
 }
 
diff --git a/services/device/internal/impl/app_service.go b/services/device/internal/impl/app_service.go
index 322b8d9..546a445 100644
--- a/services/device/internal/impl/app_service.go
+++ b/services/device/internal/impl/app_service.go
@@ -668,6 +668,7 @@
 // initializeSubAccessLists updates the provided acl for instance-specific ACLs
 func (i *appService) initializeSubAccessLists(instanceDir string, blessings []string, acl access.Permissions) error {
 	for _, b := range blessings {
+		b = b + string(security.ChainSeparator) + string(security.NoExtension)
 		for _, tag := range access.AllTypicalTags() {
 			acl.Add(security.BlessingPattern(b), string(tag))
 		}
diff --git a/services/device/internal/impl/impl_test.go b/services/device/internal/impl/impl_test.go
index 3e8e858..326ab3e 100644
--- a/services/device/internal/impl/impl_test.go
+++ b/services/device/internal/impl/impl_test.go
@@ -1527,6 +1527,19 @@
 	// Install and start the app as root/self.
 	appID := installApp(t, selfCtx)
 
+	vlog.VI(2).Infof("Validate that the created app has the right permission lists.")
+	acl, _, err := appStub(appID).GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions on appID: %v failed %v", appID, err)
+	}
+	expected := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		expected[string(tag)] = access.AccessList{In: []security.BlessingPattern{"root/self/$"}}
+	}
+	if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v", got, want)
+	}
+
 	// Start an instance of the app but this time it should fail: we do not
 	// have an associated uname for the invoking identity.
 	startAppExpectError(t, selfCtx, appID, verror.ErrNoAccess.ID)
@@ -1578,6 +1591,21 @@
 	vlog.VI(2).Infof("other attempting to run an app with access. Should succeed.")
 	instance2ID := startApp(t, otherCtx, appID)
 	verifyPingArgs(t, pingCh, testUserName, "flag-val-envelope", "env-var") // Wait until the app pings us that it's ready.
+
+	vlog.VI(2).Infof("Validate that created instance has the right permissions.")
+	expected = make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		expected[string(tag)] = access.AccessList{In: []security.BlessingPattern{"root/other/$"}}
+	}
+	acl, _, err = appStub(appID, instance2ID).GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions on instance %v/%v failed: %v", appID, instance2ID, err)
+	}
+	if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	// Shutdown the app.
 	suspendApp(t, otherCtx, appID, instance2ID)
 
 	vlog.VI(2).Infof("Verify that Resume with the same systemName works.")
diff --git a/services/role/roled/internal/config.vdl b/services/role/roled/internal/config.vdl
index 734ca95..e940903 100644
--- a/services/role/roled/internal/config.vdl
+++ b/services/role/roled/internal/config.vdl
@@ -28,4 +28,8 @@
 	// string representation of a time.Duration, e.g. "24h". An empty string
 	// indicates that the role blessing will not expire.
 	Expiry string
+	// The blessings issued for this role will only be valid for
+	// communicating with peers that match at least one of these patterns.
+	// If the list is empty, all peers are allowed.
+	Peers []security.BlessingPattern
 }
diff --git a/services/role/roled/internal/config.vdl.go b/services/role/roled/internal/config.vdl.go
index 0928eb7..7e23661 100644
--- a/services/role/roled/internal/config.vdl.go
+++ b/services/role/roled/internal/config.vdl.go
@@ -37,6 +37,10 @@
 	// string representation of a time.Duration, e.g. "24h". An empty string
 	// indicates that the role blessing will not expire.
 	Expiry string
+	// The blessings issued for this role will only be valid for
+	// communicating with peers that match at least one of these patterns.
+	// If the list is empty, all peers are allowed.
+	Peers []security.BlessingPattern
 }
 
 func (Config) __VDLReflect(struct {
diff --git a/services/role/roled/internal/role.go b/services/role/roled/internal/role.go
index ee578d4..ad1ea41 100644
--- a/services/role/roled/internal/role.go
+++ b/services/role/roled/internal/role.go
@@ -85,18 +85,26 @@
 }
 
 func caveats(ctx *context.T, config *Config) ([]security.Caveat, error) {
-	if config.Expiry == "" {
-		return nil, nil
+	var caveats []security.Caveat
+	if config.Expiry != "" {
+		d, err := time.ParseDuration(config.Expiry)
+		if err != nil {
+			return nil, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		expiry, err := security.ExpiryCaveat(time.Now().Add(d))
+		if err != nil {
+			return nil, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		caveats = append(caveats, expiry)
 	}
-	d, err := time.ParseDuration(config.Expiry)
-	if err != nil {
-		return nil, verror.Convert(verror.ErrInternal, ctx, err)
+	if len(config.Peers) != 0 {
+		peer, err := security.NewCaveat(security.PeerBlessingsCaveat, config.Peers)
+		if err != nil {
+			return nil, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		caveats = append(caveats, peer)
 	}
-	expiry, err := security.ExpiryCaveat(time.Now().Add(d))
-	if err != nil {
-		return nil, verror.Convert(verror.ErrInternal, ctx, err)
-	}
-	return []security.Caveat{expiry}, nil
+	return caveats, nil
 }
 
 func createBlessings(ctx *context.T, config *Config, principal security.Principal, extensions []string, caveats []security.Caveat, dischargerLocation string) (security.Blessings, error) {
diff --git a/services/role/roled/internal/role_test.go b/services/role/roled/internal/role_test.go
index 483279f..30d927d 100644
--- a/services/role/roled/internal/role_test.go
+++ b/services/role/roled/internal/role_test.go
@@ -109,16 +109,91 @@
 		if verror.ErrorID(err) != tc.errID {
 			t.Errorf("unexpected error ID for (%q, %q). Got %#v, expected %#v", user, tc.role, verror.ErrorID(err), tc.errID)
 		}
-		if err == nil {
-			previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
-			blessingNames, rejected := callTest(t, tc.ctx, testAddr)
-			if !reflect.DeepEqual(blessingNames, tc.blessings) {
-				t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
-			}
-			if len(rejected) != 0 {
-				t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
-			}
-			v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
+		if err != nil {
+			continue
+		}
+		previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
+		blessingNames, rejected := callTest(t, tc.ctx, testAddr)
+		if !reflect.DeepEqual(blessingNames, tc.blessings) {
+			t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
+		}
+		if len(rejected) != 0 {
+			t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
+		}
+		v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
+	}
+}
+
+func TestPeerBlessingCaveats(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "test-role-server-")
+	if err != nil {
+		t.Fatal("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+
+	roleConf := irole.Config{
+		Members: []security.BlessingPattern{"root/users/user/_role"},
+		Peers: []security.BlessingPattern{
+			security.BlessingPattern("root/peer1"),
+			security.BlessingPattern("root/peer3"),
+		},
+	}
+	irole.WriteConfig(t, roleConf, filepath.Join(workdir, "role.conf"))
+
+	var (
+		root  = testutil.NewIDProvider("root")
+		user  = newPrincipalContext(t, ctx, root, "users/user/_role")
+		peer1 = newPrincipalContext(t, ctx, root, "peer1")
+		peer2 = newPrincipalContext(t, ctx, root, "peer2")
+		peer3 = newPrincipalContext(t, ctx, root, "peer3")
+	)
+
+	roleAddr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
+
+	tDisp := &testDispatcher{}
+	server1, testPeer1 := newServer(t, peer1)
+	if err := server1.ServeDispatcher("", tDisp); err != nil {
+		t.Fatalf("server.ServeDispatcher failed: %v", err)
+	}
+	server2, testPeer2 := newServer(t, peer2)
+	if err := server2.ServeDispatcher("", tDisp); err != nil {
+		t.Fatalf("server.ServeDispatcher failed: %v", err)
+	}
+	server3, testPeer3 := newServer(t, peer3)
+	if err := server3.ServeDispatcher("", tDisp); err != nil {
+		t.Fatalf("server.ServeDispatcher failed: %v", err)
+	}
+
+	c := role.RoleClient(naming.Join(roleAddr, "role"))
+	blessings, err := c.SeekBlessings(user)
+	if err != nil {
+		t.Errorf("unexpected erro:", err)
+	}
+	v23.GetPrincipal(user).BlessingStore().Set(blessings, security.AllPrincipals)
+
+	testcases := []struct {
+		peer          string
+		blessingNames []string
+		rejectedNames []string
+	}{
+		{testPeer1, []string{"root/roles/role"}, nil},
+		{testPeer2, nil, []string{"root/roles/role"}},
+		{testPeer3, []string{"root/roles/role"}, nil},
+	}
+	for i, tc := range testcases {
+		blessingNames, rejected := callTest(t, user, tc.peer)
+		var rejectedNames []string
+		for _, r := range rejected {
+			rejectedNames = append(rejectedNames, r.Blessing)
+		}
+		if !reflect.DeepEqual(blessingNames, tc.blessingNames) {
+			t.Errorf("Unexpected blessing names for #%d. Got %q, expected %q", i, blessingNames, tc.blessingNames)
+		}
+		if !reflect.DeepEqual(rejectedNames, tc.rejectedNames) {
+			t.Errorf("Unexpected rejected names for #%d. Got %q, expected %q", i, rejectedNames, tc.rejectedNames)
 		}
 	}
 }