veyron/services/identity: MacaroonBlesser supports the new security model.
The OAuthBlesser does not yet, that will have to be done in a separate CL.
Also, listblessings will have to be updated to handle audit entries with
the new model.
Change-Id: Ia5539f936ad07a6d5d922ff6a955edc0e36efa74
diff --git a/services/identity/blesser/macaroon.go b/services/identity/blesser/macaroon.go
index 3cee53a..5233788 100644
--- a/services/identity/blesser/macaroon.go
+++ b/services/identity/blesser/macaroon.go
@@ -32,6 +32,8 @@
//
// Blessings generated by this server expire after duration. If domain is non-empty, then blessings
// are generated only for email addresses from that domain.
+//
+// TODO(ashankar): Remove the veyron2.Runtime parameter once the old security model is ripped out.
func NewMacaroonBlesserServer(r veyron2.Runtime, key []byte) interface{} {
return identity.NewServerMacaroonBlesser(&macaroonBlesser{
rt: r,
@@ -49,17 +51,20 @@
return nil, err
}
if time.Now().After(m.Creation.Add(time.Minute * 5)) {
- return nil, fmt.Errorf("bless failed: macaroon has expired")
+ return nil, fmt.Errorf("macaroon has expired")
}
- return b.bless(ctx, m.Name, m.Caveats)
-}
-
-func (b *macaroonBlesser) bless(ctx ipc.ServerContext, name string, caveats []security.Caveat) (vdlutil.Any, error) {
- self := b.rt.Identity()
- var err error
- // Use the blessing that was used to authenticate with the client to bless it.
- if self, err = self.Derive(ctx.LocalID()); err != nil {
- return nil, err
+ if ctx.LocalPrincipal() == nil || ctx.RemoteBlessings() == nil {
+ // TODO(ashankar): Old security model, remove this block.
+ self := b.rt.Identity()
+ var err error
+ // Use the blessing that was used to authenticate with the client to bless it.
+ if self, err = self.Derive(ctx.LocalID()); err != nil {
+ return nil, err
+ }
+ return self.Bless(ctx.RemoteID(), m.Name, time.Hour*24*365, m.Caveats)
}
- return self.Bless(ctx.RemoteID(), name, time.Hour*24*365, caveats)
+ if len(m.Caveats) == 0 {
+ m.Caveats = []security.Caveat{security.UnconstrainedUse()}
+ }
+ return ctx.LocalPrincipal().Bless(ctx.RemoteBlessings().PublicKey(), ctx.LocalBlessings(), m.Name, m.Caveats[0], m.Caveats[1:]...)
}
diff --git a/services/identity/blesser/macaroon_test.go b/services/identity/blesser/macaroon_test.go
new file mode 100644
index 0000000..4c6a7d2
--- /dev/null
+++ b/services/identity/blesser/macaroon_test.go
@@ -0,0 +1,123 @@
+package blesser
+
+import (
+ "bytes"
+ "crypto/rand"
+ "reflect"
+ "testing"
+ "time"
+
+ "veyron.io/veyron/veyron/services/identity"
+ "veyron.io/veyron/veyron/services/identity/util"
+
+ "veyron.io/veyron/veyron2/ipc"
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/veyron2/security/sectest"
+ "veyron.io/veyron/veyron2/vom"
+)
+
+func TestMacaroonBlesser(t *testing.T) {
+ var (
+ key = make([]byte, 16)
+ provider, user = newPrincipal(t), newPrincipal(t)
+ cOnlyMethodFoo = newCaveat(security.MethodCaveat("Foo"))
+ context = &serverCall{
+ p: provider,
+ local: blessSelf(t, provider, "provider"),
+ remote: blessSelf(t, user, "self-signed-user"),
+ }
+ )
+ if _, err := rand.Read(key); err != nil {
+ t.Fatal(err)
+ }
+ blesser := NewMacaroonBlesserServer(nil, key).(*identity.ServerStubMacaroonBlesser)
+
+ m := BlessingMacaroon{Creation: time.Now().Add(-1 * time.Hour), Name: "foo"}
+ if got, err := blesser.Bless(context, newMacaroon(t, key, m)); got != nil || err == nil || err.Error() != "macaroon has expired" {
+ t.Errorf("Got (%v, %v)", got, err)
+ }
+ m = BlessingMacaroon{Creation: time.Now(), Name: "user", Caveats: []security.Caveat{cOnlyMethodFoo}}
+ if result, err := blesser.Bless(context, newMacaroon(t, key, m)); err != nil || result == nil {
+ t.Errorf("Got (%v, %v)", result, err)
+ } else if _, ok := result.(security.Blessings); !ok {
+ t.Errorf("Got %T, want security.Blessings", result)
+ } else {
+ b := result.(security.Blessings)
+ if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+ t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+ }
+ // Context at a server to which the user will present her blessings.
+ server := newPrincipal(t)
+ serverCtxNoMethod := &serverCall{
+ p: server,
+ remote: b,
+ }
+ serverCtxFoo := &serverCall{
+ p: server,
+ remote: b,
+ method: "Foo",
+ }
+ // When the server does not recognize the provider, it should not see any strings for the client's blessings.
+ if got := b.ForContext(serverCtxNoMethod); len(got) > 0 {
+ t.Errorf("Got blessing that returned %v for an empty security.Context (%v)", got, b)
+ }
+ if got := b.ForContext(serverCtxFoo); len(got) > 0 {
+ t.Errorf("Got blessing that returned %v for an empty security.Context (%v)", got, b)
+ }
+ // But once it recognizes the provider, serverCtxFoo should see the "provider/user" name.
+ server.AddToRoots(b)
+ if got := b.ForContext(serverCtxNoMethod); len(got) > 0 {
+ t.Errorf("Got blessing that returned %v for an empty security.Context (%v)", got, b)
+ }
+ if got, want := b.ForContext(serverCtxFoo), []string{"provider/user"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+ }
+}
+
+type serverCall struct {
+ ipc.ServerCall
+ method string
+ p security.Principal
+ local, remote security.Blessings
+}
+
+func (c *serverCall) Method() string { return c.method }
+func (c *serverCall) LocalPrincipal() security.Principal { return c.p }
+func (c *serverCall) LocalBlessings() security.Blessings { return c.local }
+func (c *serverCall) RemoteBlessings() security.Blessings { return c.remote }
+
+func newPrincipal(t *testing.T) security.Principal {
+ _, key, err := sectest.NewKey()
+ if err != nil {
+ t.Fatal(err)
+ }
+ p, err := security.CreatePrincipal(security.NewInMemoryECDSASigner(key), nil, sectest.NewBlessingRoots())
+ if err != nil {
+ t.Fatal(err)
+ }
+ return p
+}
+
+func blessSelf(t *testing.T, p security.Principal, name string) security.Blessings {
+ b, err := p.BlessSelf(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return b
+}
+
+func newCaveat(c security.Caveat, err error) security.Caveat {
+ if err != nil {
+ panic(err)
+ }
+ return c
+}
+
+func newMacaroon(t *testing.T, key []byte, m BlessingMacaroon) string {
+ buf := new(bytes.Buffer)
+ if err := vom.NewEncoder(buf).Encode(m); err != nil {
+ t.Fatal(err)
+ }
+ return string(util.NewMacaroon(key, buf.Bytes()))
+}