"core": Get rid of prefix-matching on BlessingPatterns
Martin Abadi (@abadi) recently made a very convincing argument
that default prefix matching on BlessingPatterns leads
to surprising (and undesirable) properties on semantics of
'Deny' clauses and groups in ACLS.
The argument can be found here:
https://docs.google.com/a/google.com/document/d/1pvENXMkfKHMok1GNU8dy6jPPACzsJ9EjVyG642Pk1CI/edit
This CL gets rid of prefix-matching on BlessingPatterns
and fixes all relevant tests in the core repo. Fixes for other repos
will follow soon.
After this CL, a pattern "a/b/c" wil only be matched by
"a/b/c", "a/b/c/x", "a/b/c/y", "a/b/c/x/y", etc., and a
pattern "a/b/c/$" will only be matched by "a/b/c".
Change-Id: I8bbfe77479283a1b8cf6fd2cd8391e6aa8e2c2c9
diff --git a/security/blessingroots_test.go b/security/blessingroots_test.go
index a3bae58..c27f86d 100644
--- a/security/blessingroots_test.go
+++ b/security/blessingroots_test.go
@@ -52,8 +52,8 @@
},
{
root: t[1],
- recognized: []string{"google", "google/foo", "google/foo/bar"},
- notRecognized: []string{"google/bar", "veyron", "veyron/foo", "foo", "foo/bar"},
+ recognized: []string{"google/foo", "google/foo/bar"},
+ notRecognized: []string{"google", "google/bar", "veyron", "veyron/foo", "foo", "foo/bar"},
},
{
root: t[2],
diff --git a/services/mgmt/binary/impl/acl_test.go b/services/mgmt/binary/impl/acl_test.go
index 704a3fa..6a2767b 100644
--- a/services/mgmt/binary/impl/acl_test.go
+++ b/services/mgmt/binary/impl/acl_test.go
@@ -75,6 +75,65 @@
return nil
}
+func TestBinaryCreateACL(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+ veyron2.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
+
+ selfPrincipal := tsecurity.NewPrincipal("self")
+ selfCtx, err := veyron2.SetPrincipal(ctx, selfPrincipal)
+ if err != nil {
+ t.Fatalf("SetPrincipal failed: %v", err)
+ }
+ dir, childPrincipal := tsecurity.ForkCredentials(selfPrincipal, "child")
+ defer os.RemoveAll(dir)
+ childCtx, err := veyron2.SetPrincipal(ctx, childPrincipal)
+ if err != nil {
+ t.Fatalf("SetPrincipal failed: %v", err)
+ }
+
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, childCtx, veyron2.GetPrincipal(childCtx))
+ defer deferFn()
+ // make selfCtx and childCtx have the same Namespace Roots as set by
+ // CreateShellAndMountTable
+ veyron2.GetNamespace(selfCtx).SetRoots(veyron2.GetNamespace(childCtx).Roots()...)
+
+ // setup mock up directory to put state in
+ storedir, cleanup := mgmttest.SetupRootDir(t, "bindir")
+ defer cleanup()
+ prepDirectory(t, storedir)
+
+ _, nms := mgmttest.RunShellCommand(t, sh, nil, binaryCmd, "bini", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ vlog.VI(2).Infof("Self uploads a shared and private binary.")
+ binary := repository.BinaryClient("bini/private")
+ if err := binary.Create(childCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+ t.Fatalf("Create() failed %v", err)
+ }
+ fakeDataPrivate := testData()
+ if streamErr, err := invokeUpload(t, childCtx, binary, fakeDataPrivate, 0); streamErr != nil || err != nil {
+ t.Fatalf("invokeUpload() failed %v, %v", err, streamErr)
+ }
+
+ vlog.VI(2).Infof("Validate that the ACL also allows Self")
+ acl, _, err := binary.GetACL(selfCtx)
+ if err != nil {
+ t.Fatalf("GetACL failed: %v", err)
+ }
+ expected := access.TaggedACLMap{
+ "Admin": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}},
+ "Read": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}},
+ "Write": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}},
+ }
+ if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+ t.Errorf("got %#v, expected %#v ", got, want)
+ }
+}
+
func TestBinaryRootACL(t *testing.T) {
ctx, shutdown := testutil.InitForTest()
defer shutdown()
@@ -141,7 +200,13 @@
if err != nil {
t.Fatalf("GetACL failed: %v", err)
}
- expected := access.TaggedACLMap{"Admin": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Read": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Write": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Debug": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}, "Resolve": access.ACL{In: []security.BlessingPattern{"self"}, NotIn: []string{}}}
+ expected := access.TaggedACLMap{
+ "Admin": access.ACL{In: []security.BlessingPattern{"self"}},
+ "Read": access.ACL{In: []security.BlessingPattern{"self"}},
+ "Write": access.ACL{In: []security.BlessingPattern{"self"}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"self"}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"self"}},
+ }
if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
t.Errorf("got %#v, expected %#v ", got, want)
}
@@ -177,7 +242,13 @@
vlog.VI(2).Infof(" Verify that bini/private's acls are updated.")
binary = repository.BinaryClient("bini/private")
- updated := access.TaggedACLMap{"Admin": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Read": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Write": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Debug": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}, "Resolve": access.ACL{In: []security.BlessingPattern{"self/$"}, NotIn: []string{}}}
+ updated := access.TaggedACLMap{
+ "Admin": access.ACL{In: []security.BlessingPattern{"self/$"}},
+ "Read": access.ACL{In: []security.BlessingPattern{"self/$"}},
+ "Write": access.ACL{In: []security.BlessingPattern{"self/$"}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"self/$"}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"self/$"}},
+ }
acl, _, err = binary.GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
@@ -239,19 +310,12 @@
vlog.VI(2).Infof("Other can read acls for bini/otherbinary.")
updated = access.TaggedACLMap{
- "Admin": access.ACL{
- In: []security.BlessingPattern{"self/other"},
- NotIn: []string{}},
- "Read": access.ACL{
- In: []security.BlessingPattern{"self/other"},
- NotIn: []string{}},
- "Write": access.ACL{
- In: []security.BlessingPattern{"self/other"},
- NotIn: []string{}},
- "Debug": access.ACL{In: []security.BlessingPattern{"self/other"},
- NotIn: []string{}},
- "Resolve": access.ACL{In: []security.BlessingPattern{"self/other"},
- NotIn: []string{}}}
+ "Admin": access.ACL{In: []security.BlessingPattern{"self/$", "self/other"}},
+ "Read": access.ACL{In: []security.BlessingPattern{"self/$", "self/other"}},
+ "Write": access.ACL{In: []security.BlessingPattern{"self/$", "self/other"}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"self/$", "self/other"}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"self/$", "self/other"}},
+ }
acl, _, err = binary.GetACL(otherCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
@@ -342,11 +406,11 @@
t.Fatalf("GetACL failed: %#v", err)
}
expected := access.TaggedACLMap{
- "Admin": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Read": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Write": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Debug": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Resolve": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
+ "Admin": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Read": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Write": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
}
if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
t.Errorf("got %#v, expected %#v ", got, want)
@@ -363,11 +427,11 @@
t.Fatalf("GetACL failed: %#v", err)
}
expected = access.TaggedACLMap{
- "Admin": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Read": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{"self"}},
- "Write": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Debug": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
- "Resolve": access.ACL{In: []security.BlessingPattern{"self/child"}, NotIn: []string{}},
+ "Admin": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Read": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{"self"}},
+ "Write": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
}
if got, want := acl.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
t.Errorf("got %#v, expected %#v ", got, want)
diff --git a/services/mgmt/binary/impl/service.go b/services/mgmt/binary/impl/service.go
index f8b7586..ccc92b6 100644
--- a/services/mgmt/binary/impl/service.go
+++ b/services/mgmt/binary/impl/service.go
@@ -66,6 +66,7 @@
ErrInvalidParts = verror.Register(pkgPath+".errInvalidParts", verror.NoRetry, "{1:}{2:} invalid number of binary parts{:_}")
ErrInvalidPart = verror.Register(pkgPath+".errInvalidPart", verror.NoRetry, "{1:}{2:} invalid binary part number{:_}")
ErrOperationFailed = verror.Register(pkgPath+".errOperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+ ErrNotAuthorized = verror.Register(pkgPath+".errNotAuthorized", verror.NoRetry, "{1:}{2:} none of the client's blessings are valid {:_}")
)
// TODO(jsimsa): When VDL supports composite literal constants, remove
@@ -87,15 +88,23 @@
const BufferLength = 4096
+func prefixPatterns(blessings []string) []security.BlessingPattern {
+ var patterns []security.BlessingPattern
+ for _, b := range blessings {
+ patterns = append(patterns, security.BlessingPattern(b).PrefixPatterns()...)
+ }
+ return patterns
+}
+
// insertACLs configures the starting ACL set for a newly "Create"-d binary based
// on the caller's blessings.
func insertACLs(dir string, principal security.Principal, locks *acls.Locks, blessings []string) error {
tam := make(access.TaggedACLMap)
- // Add the invoker's blessings.
- for _, b := range blessings {
+ // Add the invoker's blessings and all its prefixes.
+ for _, p := range prefixPatterns(blessings) {
for _, tag := range access.AllTypicalTags() {
- tam.Add(security.BlessingPattern(b), string(tag))
+ tam.Add(p, string(tag))
}
}
return locks.SetPathACL(principal, dir, tam, "")
@@ -125,6 +134,10 @@
lp := context.LocalPrincipal()
rb, _ := context.RemoteBlessings().ForContext(context)
+ if len(rb) == 0 {
+ // None of the client's blessings are valid.
+ return verror.New(ErrNotAuthorized, context.Context())
+ }
if err := insertACLs(aclPath(i.state.rootDir, i.suffix), lp, i.locks, rb); err != nil {
vlog.Errorf("insertACLs(%v, %v) failed: %v", lp, rb, err)
return verror.New(ErrOperationFailed, context.Context())
@@ -382,12 +395,16 @@
ctx.LocalPrincipal(), aclPath(i.state.rootDir, i.suffix))
if os.IsNotExist(err) {
- // No ACL file which implies a nil authorizer.
+ // No ACL file found which implies a nil authorizer. This results in default authorization.
+ // Therefore we return an ACL that mimics the default authorization policy (i.e., the ACL
+ // is matched by all blessings that are either extensions of one of the local blessings or
+ // can be extended to form one of the local blessings.)
tam := make(access.TaggedACLMap)
+
lb, _ := ctx.LocalBlessings().ForContext(ctx)
- for _, b := range lb {
+ for _, p := range prefixPatterns(lb) {
for _, tag := range access.AllTypicalTags() {
- tam.Add(security.BlessingPattern(b), string(tag))
+ tam.Add(p, string(tag))
}
}
return tam, "", nil
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
index 37e0a55..6674f3e 100644
--- a/services/mgmt/device/impl/dispatcher.go
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -170,10 +170,13 @@
// Create ACLs to transfer devicemanager permissions to the provided identity.
acl := make(access.TaggedACLMap)
for _, n := range names {
- for _, tag := range access.AllTypicalTags() {
- // TODO(caprita, ataly, ashankar): Do we really need the NonExtendable restriction
- // below?
- acl.Add(security.BlessingPattern(n).MakeNonExtendable(), string(tag))
+ // TODO(caprita, ataly, ashankar): Do we really need the NonExtendable restriction
+ // below?
+ patterns := security.BlessingPattern(n).MakeNonExtendable().PrefixPatterns()
+ for _, p := range patterns {
+ for _, tag := range access.AllTypicalTags() {
+ acl.Add(p, string(tag))
+ }
}
}
if err := d.locks.SetPathACL(principal, d.getACLDir(), acl, ""); err != nil {
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index 4ddc44c..d429120 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -945,7 +945,7 @@
}
expectedACL := make(access.TaggedACLMap)
for _, tag := range access.AllTypicalTags() {
- expectedACL[string(tag)] = access.ACL{In: []security.BlessingPattern{"root/self/mydevice/$"}}
+ expectedACL[string(tag)] = access.ACL{In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/mydevice/$"}}
}
var b bytes.Buffer
if err := expectedACL.WriteTo(&b); err != nil {