veyron/runtimes/google/ipc: Explicitly implement default authorization.
The default authorization can be expressed in terms of ACLs, however
some folks had expressed the desire to explicitly pull out the default
policy so that:
(a) It can be more succinctly expressed
(the language used in documentation is clearly visible in a 5 line
code snippet)
(b) Error messages are more meaningful (does wonders in the tutorial).
This comes at the cost of the server revealing the authorization
policy it is using, but that seems okay.
Furthermore, there seems to be growing dissent when it comes to
using Labels in ACLs, so decoupling the default policy from ACLs
seems like the right thing to do.
Change-Id: Idec4fa9d6d9ce932bd61f8c718b67c294135f7b1
diff --git a/runtimes/google/ipc/default_authorizer.go b/runtimes/google/ipc/default_authorizer.go
new file mode 100644
index 0000000..bbaa7c9
--- /dev/null
+++ b/runtimes/google/ipc/default_authorizer.go
@@ -0,0 +1,34 @@
+package ipc
+
+import (
+ "fmt"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+// defaultAuthorizer implements a security.Authorizer with an authorization
+// policy that requires one end of the RPC to have a blessing that makes it a
+// delegate of the other.
+type defaultAuthorizer struct{}
+
+func (defaultAuthorizer) Authorize(ctx security.Context) error {
+ var (
+ local = ctx.LocalBlessings().ForContext(ctx)
+ remote = ctx.RemoteBlessings().ForContext(ctx)
+ )
+ // Authorize if any element in local is a "delegate of" (i.e., has been
+ // blessed by) any element in remote, OR vice-versa.
+ for _, l := range local {
+ if security.BlessingPattern(l).MatchedBy(remote...) {
+ // l is a delegate of an element in remote.
+ return nil
+ }
+ }
+ for _, r := range remote {
+ if security.BlessingPattern(r).MatchedBy(local...) {
+ // r is a delegate of an element in local.
+ return nil
+ }
+ }
+ return fmt.Errorf("policy disallows %v", remote)
+}
diff --git a/runtimes/google/ipc/default_authorizer_test.go b/runtimes/google/ipc/default_authorizer_test.go
new file mode 100644
index 0000000..852fbab
--- /dev/null
+++ b/runtimes/google/ipc/default_authorizer_test.go
@@ -0,0 +1,96 @@
+package ipc
+
+import (
+ "testing"
+
+ vsecurity "veyron.io/veyron/veyron/security"
+ "veyron.io/veyron/veyron2/security"
+)
+
+func TestDefaultAuthorizer(t *testing.T) {
+ var (
+ pali, _ = vsecurity.NewPrincipal()
+ pbob, _ = vsecurity.NewPrincipal()
+ pche, _ = vsecurity.NewPrincipal()
+
+ che, _ = pche.BlessSelf("che")
+ ali, _ = pali.BlessSelf("ali")
+ bob, _ = pbob.BlessSelf("bob")
+
+ // bless(ali, bob, "friend") will generate a blessing for ali, calling him "bob/friend".
+ bless = func(target, extend security.Blessings, extension string) security.Blessings {
+ var p security.Principal
+ switch extend {
+ case ali:
+ p = pali
+ case bob:
+ p = pbob
+ case che:
+ p = pche
+ default:
+ panic(extend)
+ }
+ ret, err := p.Bless(target.PublicKey(), extend, extension, security.UnconstrainedUse())
+ if err != nil {
+ panic(err)
+ }
+ return ret
+ }
+
+ U = func(blessings ...security.Blessings) security.Blessings {
+ u, err := security.UnionOfBlessings(blessings...)
+ if err != nil {
+ panic(err)
+ }
+ return u
+ }
+
+ // Shorthands for getting blessings for Ali and Bob.
+ A = func(as security.Blessings, extension string) security.Blessings { return bless(ali, as, extension) }
+ B = func(as security.Blessings, extension string) security.Blessings { return bless(bob, as, extension) }
+
+ authorizer defaultAuthorizer
+ )
+ // Make ali, bob (the two ends) recognize all three blessings
+ for ip, p := range []security.Principal{pali, pbob} {
+ for _, b := range []security.Blessings{ali, bob, che} {
+ if err := p.AddToRoots(b); err != nil {
+ t.Fatalf("%d: %v - %v", ip, b, err)
+ }
+ }
+ }
+ // All tests are run as if "ali" is the local end and "bob" is the remote.
+ tests := []struct {
+ local, remote security.Blessings
+ authorized bool
+ }{
+ {ali, ali, true},
+ {ali, bob, false},
+ {ali, B(ali, "friend"), true}, // ali talking to ali/friend
+ {A(bob, "friend"), bob, true}, // bob/friend talking to bob
+ {A(che, "friend"), B(che, "family"), false}, // che/friend talking to che/family
+ {U(ali, A(bob, "friend"), A(che, "friend")),
+ U(bob, B(che, "family")),
+ true}, // {ali, bob/friend, che/friend} talking to {bob, che/family}
+ }
+ for _, test := range tests {
+ err := authorizer.Authorize(&mockSecurityContext{
+ p: pali,
+ l: test.local,
+ r: test.remote,
+ })
+ if (err == nil) != test.authorized {
+ t.Errorf("Local:%v Remote:%v. Got %v", test.local, test.remote, err)
+ }
+ }
+}
+
+type mockSecurityContext struct {
+ security.Context
+ p security.Principal
+ l, r security.Blessings
+}
+
+func (c *mockSecurityContext) LocalPrincipal() security.Principal { return c.p }
+func (c *mockSecurityContext) LocalBlessings() security.Blessings { return c.l }
+func (c *mockSecurityContext) RemoteBlessings() security.Blessings { return c.r }
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 002a233..08fb74a 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -28,7 +28,6 @@
"veyron.io/veyron/veyron/runtimes/google/lib/publisher"
inaming "veyron.io/veyron/veyron/runtimes/google/naming"
ivtrace "veyron.io/veyron/veyron/runtimes/google/vtrace"
- vsecurity "veyron.io/veyron/veyron/security"
"veyron.io/veyron/veyron/services/mgmt/debug"
)
@@ -688,15 +687,6 @@
return v
}
-func defaultAuthorizer(ctx security.Context) security.Authorizer {
- blessings := ctx.LocalBlessings().ForContext(ctx)
- acl := security.ACL{In: make(map[security.BlessingPattern]security.LabelSet)}
- for _, b := range blessings {
- acl.In[security.BlessingPattern(b).MakeGlob()] = security.AllLabels
- }
- return vsecurity.NewACLAuthorizer(acl)
-}
-
func (fs *flowServer) serve() error {
defer fs.flow.Close()
@@ -984,11 +974,11 @@
func (fs *flowServer) authorize(auth security.Authorizer) verror.E {
if auth == nil {
- auth = defaultAuthorizer(fs)
+ auth = defaultAuthorizer{}
}
if err := auth.Authorize(fs); err != nil {
// TODO(ataly, ashankar): For privacy reasons, should we hide the authorizer error?
- return verror.NoAccessf("ipc: %v not authorized to call %q.%q (%v)", fs.RemoteBlessings(), fs.Name(), fs.Method(), err)
+ return verror.NoAccessf("ipc: not authorized to call %q.%q (%v)", fs.Name(), fs.Method(), err)
}
return nil
}
@@ -1005,7 +995,7 @@
func (fs *flowServer) authorizeForDebug(auth security.Authorizer) error {
dc := debugContext{fs}
if auth == nil {
- auth = defaultAuthorizer(dc)
+ auth = defaultAuthorizer{}
}
return auth.Authorize(dc)
}