veyron/services/mgmt/application: add support for per envelope acls
This change extends the applicationd daemon with per envelope ACLs.
Change-Id: Ib0ab7efa39f82723418e3f959877d05e6e22eb08
diff --git a/services/mgmt/application/impl/acl_test.go b/services/mgmt/application/impl/acl_test.go
new file mode 100644
index 0000000..9c0d6ab
--- /dev/null
+++ b/services/mgmt/application/impl/acl_test.go
@@ -0,0 +1,413 @@
+package impl_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "syscall"
+ "testing"
+
+ "v.io/core/veyron2"
+ "v.io/core/veyron2/context"
+ "v.io/core/veyron2/naming"
+ "v.io/core/veyron2/rt"
+ "v.io/core/veyron2/security"
+ "v.io/core/veyron2/services/mgmt/application"
+ "v.io/core/veyron2/services/security/access"
+ "v.io/core/veyron2/vdl/vdlutil"
+ "v.io/core/veyron2/verror"
+ "v.io/core/veyron2/vlog"
+
+ "v.io/core/veyron/lib/modules"
+ "v.io/core/veyron/lib/signals"
+ "v.io/core/veyron/lib/testutil"
+ tsecurity "v.io/core/veyron/lib/testutil/security"
+ "v.io/core/veyron/services/mgmt/application/impl"
+ mgmttest "v.io/core/veyron/services/mgmt/lib/testutil"
+ "v.io/core/veyron/services/mgmt/repository"
+)
+
+const (
+ repoCmd = "repository"
+)
+
+var globalRT veyron2.Runtime
+var globalCtx *context.T
+
+// This is also a modules world.
+// Insert necessary code here to be a modules test.
+func init() {
+ // TODO(rjkroege): Remove when vom2 is ready.
+ vdlutil.Register(&naming.VDLMountedServer{})
+
+ modules.RegisterChild(repoCmd, "", appRepository)
+ testutil.Init()
+
+ var err error
+ if globalRT, err = rt.New(); err != nil {
+ panic(err)
+ }
+ globalCtx = globalRT.NewContext()
+ globalRT.Namespace().CacheCtl(naming.DisableCache(true))
+}
+
+// TestHelperProcess is the entrypoint for the modules commands in a
+// a test subprocess.
+func TestHelperProcess(t *testing.T) {
+ modules.DispatchInTest()
+}
+
+func appRepository(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ args = args[1:]
+ if len(args) < 2 {
+ vlog.Fatalf("repository expected at least name and store arguments and optionally ACL flags per TaggedACLMapFromFlag")
+ }
+ publishName := args[0]
+ storedir := args[1]
+
+ defer fmt.Fprintf(stdout, "%v terminating\n", publishName)
+ defer vlog.VI(1).Infof("%v terminating", publishName)
+ defer globalRT.Cleanup()
+ server, endpoint := mgmttest.NewServer(globalRT)
+ defer server.Stop()
+
+ name := naming.JoinAddressName(endpoint, "")
+ vlog.VI(1).Infof("applicationd name: %v", name)
+
+ dispatcher, err := impl.NewDispatcher(storedir)
+ if err != nil {
+ vlog.Fatalf("Failed to create repository dispatcher: %v", err)
+ }
+ if err := server.ServeDispatcher(publishName, dispatcher); err != nil {
+ vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
+ }
+
+ fmt.Fprintf(stdout, "ready:%d\n", os.Getpid())
+ <-signals.ShutdownOnSignals(globalCtx)
+
+ return nil
+}
+
+func TestApplicationUpdateACL(t *testing.T) {
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
+ defer deferFn()
+
+ // setup mock up directory to put state in
+ storedir, cleanup := mgmttest.SetupRootDir(t, "application")
+ defer cleanup()
+
+ selfRT := globalRT
+ otherRT := mgmttest.NewRuntime(t, globalRT)
+ defer otherRT.Cleanup()
+ idp := tsecurity.NewIDProvider("root")
+
+ // By default, selfRT and otherRT will have blessings generated based on the
+ // username/machine name running this process. Since these blessings will appear
+ // in ACLs, give them recognizable names.
+ if err := idp.Bless(selfRT.Principal(), "self"); err != nil {
+ t.Fatal(err)
+ }
+ if err := idp.Bless(otherRT.Principal(), "other"); err != nil {
+ t.Fatal(err)
+ }
+
+ crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "repo")
+ defer os.RemoveAll(crDir)
+
+ // Make server credentials derived from the test harness.
+ _, nms := mgmttest.RunShellCommand(t, sh, crEnv, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ otherStub := repository.ApplicationClient("repo/search/v1", otherRT.Client())
+
+ // Create example envelopes.
+ envelopeV1 := application.Envelope{
+ Args: []string{"--help"},
+ Env: []string{"DEBUG=1"},
+ Binary: "/veyron/name/of/binary",
+ }
+
+ // Envelope putting as other should fail.
+ // TODO(rjkroege): Validate that it is failed with permission denied.
+ if err := otherStub.Put(otherRT.NewContext(), []string{"base"}, envelopeV1); err == nil {
+ t.Fatalf("Put() wrongly didn't fail")
+ }
+
+ // Envelope putting as self should succeed.
+ selfStub := repository.ApplicationClient("repo/search/v1", selfRT.Client())
+ if err := selfStub.Put(selfRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+
+ selfStub = repository.ApplicationClient("repo", selfRT.Client())
+ acl, etag, err := selfStub.GetACL(selfRT.NewContext())
+ if !verror.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("GetACL should have failed with ErrNotFound but was: %v", err)
+ }
+ if etag != "default" {
+ t.Fatalf("getACL expected:default, got:%v(%v)", etag, acl)
+ }
+ if acl != nil {
+ t.Fatalf("GetACL got %v, expected %v", acl, nil)
+ }
+
+ vlog.VI(2).Infof("self attempting to give other permission to update application")
+ newACL := make(access.TaggedACLMap)
+ for _, tag := range access.AllTypicalTags() {
+ newACL.Add("root/self", string(tag))
+ newACL.Add("root/other", string(tag))
+ }
+ if err := selfStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ acl, etag, err = selfStub.GetACL(selfRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ expected := newACL
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+
+ // Envelope putting as other should now succeed.
+ if err := otherStub.Put(otherRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() wrongly failed: %v", err)
+ }
+
+ // Other takes control.
+ otherStub = repository.ApplicationClient("repo/", otherRT.Client())
+ acl, etag, err = otherStub.GetACL(otherRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL 2 should not have failed: %v", err)
+ }
+ acl["Admin"] = access.ACL{
+ In: []security.BlessingPattern{"root/other"},
+ NotIn: []string{}}
+ if err = otherStub.SetACL(otherRT.NewContext(), acl, etag); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ // Self is now locked out but other isn't.
+ if _, _, err = selfStub.GetACL(selfRT.NewContext()); err == nil {
+ t.Fatalf("GetACL should not have succeeded")
+ }
+ acl, _, err = otherStub.GetACL(otherRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ expected = access.TaggedACLMap{
+ "Admin": access.ACL{
+ In: []security.BlessingPattern{"root/other"},
+ NotIn: []string{}},
+ "Read": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Write": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}}}
+
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+}
+
+func TestPerAppACL(t *testing.T) {
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
+ defer deferFn()
+
+ // setup mock up directory to put state in
+ storedir, cleanup := mgmttest.SetupRootDir(t, "application")
+ defer cleanup()
+
+ selfRT := globalRT
+ otherRT := mgmttest.NewRuntime(t, globalRT)
+ defer otherRT.Cleanup()
+ idp := tsecurity.NewIDProvider("root")
+
+ // By default, selfRT and otherRT will have blessings generated based on the
+ // username/machine name running this process. Since these blessings will appear
+ // in ACLs, give them recognizable names.
+ if err := idp.Bless(selfRT.Principal(), "self"); err != nil {
+ t.Fatal(err)
+ }
+ if err := idp.Bless(otherRT.Principal(), "other"); err != nil {
+ t.Fatal(err)
+ }
+
+ crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "repo")
+ defer os.RemoveAll(crDir)
+
+ // Make a server with the same credential as test harness.
+ _, nms := mgmttest.RunShellCommand(t, sh, crEnv, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ // Create example envelope.
+ envelopeV1 := application.Envelope{
+ Args: []string{"--help"},
+ Env: []string{"DEBUG=1"},
+ Binary: "/veyron/name/of/binary",
+ }
+
+ // Upload the envelope at two different names.
+ selfStub := repository.ApplicationClient("repo/search/v1", selfRT.Client())
+ if err := selfStub.Put(selfRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+ selfStub = repository.ApplicationClient("repo/search/v2", selfRT.Client())
+ if err := selfStub.Put(selfRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+
+ // Self can access ACLs but other can't.
+ for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+ selfStub = repository.ApplicationClient(path, selfRT.Client())
+ acl, etag, err := selfStub.GetACL(selfRT.NewContext())
+ if !verror.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("GetACL should have failed with ErrNotFound but was: %v", err)
+ }
+ if etag != "default" {
+ t.Fatalf("GetACL expected:default, got:%v(%v)", etag, acl)
+ }
+ if acl != nil {
+ t.Fatalf("GetACL got %v, expected %v", acl, nil)
+ }
+ otherStub := repository.ApplicationClient(path, otherRT.Client())
+ if _, _, err := otherStub.GetACL(otherRT.NewContext()); err == nil {
+ t.Fatalf("GetACL didn't fail for other when it should have.")
+ }
+ }
+
+ // Self gives other full access only to repo/search/v1.
+ selfStub = repository.ApplicationClient("repo/search/v1", selfRT.Client())
+ newACL := make(access.TaggedACLMap)
+ for _, tag := range access.AllTypicalTags() {
+ newACL.Add("root/self", string(tag))
+ newACL.Add("root/other", string(tag))
+ }
+ if err := selfStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ // Other can now access this location.
+ otherStub := repository.ApplicationClient("repo/search/v1", otherRT.Client())
+ acl, _, err := otherStub.GetACL(otherRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ expected := access.TaggedACLMap{
+ "Admin": access.ACL{
+ In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Read": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Write": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}}}
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+
+ // But other locations should be unaffected and other cannot access.
+ for _, path := range []string{"repo/search", "repo/search/v2"} {
+ otherStub := repository.ApplicationClient(path, otherRT.Client())
+ if _, _, err := otherStub.GetACL(otherRT.NewContext()); err == nil {
+ t.Fatalf("GetACL didn't fail for other when it should have.")
+ }
+ }
+
+ // Self gives other write perms on base.
+ selfStub = repository.ApplicationClient("repo/", selfRT.Client())
+ newACL = make(access.TaggedACLMap)
+ for _, tag := range access.AllTypicalTags() {
+ newACL.Add("root/self", string(tag))
+ }
+ newACL["Write"] = access.ACL{In: []security.BlessingPattern{"root/other", "root/self"}}
+ if err := selfStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ // Other can now upload an envelope at both locations.
+ for _, path := range []string{"repo/search/v1", "repo/search/v2"} {
+ otherStub = repository.ApplicationClient(path, otherRT.Client())
+ if err := otherStub.Put(otherRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+ }
+
+ // But self didn't give other ACL modification permissions.
+ for _, path := range []string{"repo/search", "repo/search/v2"} {
+ otherStub := repository.ApplicationClient(path, otherRT.Client())
+ if _, _, err := otherStub.GetACL(otherRT.NewContext()); err == nil {
+ t.Fatalf("GetACL didn't fail for other when it should have.")
+ }
+ }
+}
+
+func TestInitialACLSet(t *testing.T) {
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
+ defer deferFn()
+
+ // Setup mock up directory to put state in.
+ storedir, cleanup := mgmttest.SetupRootDir(t, "application")
+ defer cleanup()
+
+ selfRT := globalRT
+ otherRT := mgmttest.NewRuntime(t, globalRT)
+ defer otherRT.Cleanup()
+ idp := tsecurity.NewIDProvider("root")
+
+ // Make a recognizable principal name.
+ if err := idp.Bless(selfRT.Principal(), "self"); err != nil {
+ t.Fatal(err)
+ }
+ crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "repo")
+ defer os.RemoveAll(crDir)
+
+ // Make an TAM for use on the command line.
+ expected := access.TaggedACLMap{
+ "Admin": access.ACL{
+ In: []security.BlessingPattern{"root/rubberchicken",
+ "root/self"},
+ NotIn: []string{},
+ },
+ }
+
+ b := new(bytes.Buffer)
+ if err := expected.WriteTo(b); err != nil {
+ t.Fatal(err)
+ }
+
+ // Start a server with the same credential as test harness.
+ _, nms := mgmttest.RunShellCommand(t, sh, crEnv, repoCmd, "--veyron.acl.literal", b.String(), "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ // It should have the correct starting ACLs from the command line.
+ selfStub := repository.ApplicationClient("repo", selfRT.Client())
+ acl, _, err := selfStub.GetACL(selfRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+}