services/role/roled/internal: Add Peer Caveats

Add an option to set peer caveats on blessings issued by the role
server.

Change-Id: Ic7fc583f5a096b27a00139a6e7d12ea608a70eb1
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)
 		}
 	}
 }