| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package binarylib_test |
| |
| import ( |
| "fmt" |
| "io" |
| "os" |
| "reflect" |
| "syscall" |
| "testing" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/naming" |
| "v.io/v23/security" |
| "v.io/v23/security/access" |
| "v.io/v23/services/repository" |
| "v.io/v23/verror" |
| "v.io/x/lib/vlog" |
| vsecurity "v.io/x/ref/lib/security" |
| "v.io/x/ref/lib/signals" |
| "v.io/x/ref/services/internal/binarylib" |
| "v.io/x/ref/services/internal/servicetest" |
| "v.io/x/ref/test" |
| "v.io/x/ref/test/testutil" |
| ) |
| |
| //go:generate v23 test generate |
| |
| const ( |
| binaryCmd = "binaryd" |
| ) |
| |
| func binaryd(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| if len(args) < 2 { |
| vlog.Fatalf("binaryd expected at least name and store arguments and optionally AccessList flags per PermissionsFromFlag") |
| } |
| publishName := args[0] |
| storedir := args[1] |
| |
| ctx, shutdown := test.InitForTest() |
| |
| defer fmt.Fprintf(stdout, "%v terminating\n", publishName) |
| defer vlog.VI(1).Infof("%v terminating", publishName) |
| defer shutdown() |
| |
| server, endpoint := servicetest.NewServer(ctx) |
| name := naming.JoinAddressName(endpoint, "") |
| vlog.VI(1).Infof("binaryd name: %v", name) |
| |
| depth := 2 |
| state, err := binarylib.NewState(storedir, "", depth) |
| if err != nil { |
| vlog.Fatalf("NewState(%v, %v, %v) failed: %v", storedir, "", depth, err) |
| } |
| dispatcher, err := binarylib.NewDispatcher(v23.GetPrincipal(ctx), state) |
| if err != nil { |
| vlog.Fatalf("Failed to create binaryd 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(ctx) |
| |
| return nil |
| } |
| |
| func b(name string) repository.BinaryClientStub { |
| return repository.BinaryClient(name) |
| } |
| |
| func ctxWithBlessedPrincipal(ctx *context.T, childExtension string) (*context.T, error) { |
| parent := v23.GetPrincipal(ctx) |
| child := testutil.NewPrincipal() |
| b, err := parent.Bless(child.PublicKey(), parent.BlessingStore().Default(), childExtension, security.UnconstrainedUse()) |
| if err != nil { |
| return nil, err |
| } |
| if err := vsecurity.SetDefaultBlessings(child, b); err != nil { |
| return nil, err |
| } |
| return v23.WithPrincipal(ctx, child) |
| } |
| |
| func TestBinaryCreateAccessList(t *testing.T) { |
| ctx, shutdown := test.InitForTest() |
| defer shutdown() |
| v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true)) |
| |
| selfCtx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("self")) |
| if err != nil { |
| t.Fatalf("WithPrincipal failed: %v", err) |
| } |
| childCtx, err := ctxWithBlessedPrincipal(selfCtx, "child") |
| if err != nil { |
| t.Fatalf("WithPrincipal failed: %v", err) |
| } |
| |
| sh, deferFn := servicetest.CreateShellAndMountTable(t, childCtx, v23.GetPrincipal(childCtx)) |
| defer deferFn() |
| // make selfCtx and childCtx have the same Namespace Roots as set by |
| // CreateShellAndMountTable |
| v23.GetNamespace(selfCtx).SetRoots(v23.GetNamespace(childCtx).Roots()...) |
| |
| // setup mock up directory to put state in |
| storedir, cleanup := servicetest.SetupRootDir(t, "bindir") |
| defer cleanup() |
| prepDirectory(t, storedir) |
| |
| nmh := servicetest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir) |
| pid := servicetest.ReadPID(t, nmh) |
| defer syscall.Kill(pid, syscall.SIGINT) |
| |
| vlog.VI(2).Infof("Self uploads a shared and private binary.") |
| if err := b("bini/private").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, b("bini/private"), fakeDataPrivate, 0); streamErr != nil || err != nil { |
| t.Fatalf("invokeUpload() failed %v, %v", err, streamErr) |
| } |
| |
| vlog.VI(2).Infof("Validate that the AccessList also allows Self") |
| perms, _, err := b("bini/private").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %v", err) |
| } |
| expected := access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}}, |
| } |
| if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| } |
| |
| func TestBinaryRootAccessList(t *testing.T) { |
| ctx, shutdown := test.InitForTest() |
| defer shutdown() |
| v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true)) |
| |
| selfPrincipal := testutil.NewPrincipal("self") |
| selfCtx, err := v23.WithPrincipal(ctx, selfPrincipal) |
| if err != nil { |
| t.Fatalf("WithPrincipal failed: %v", err) |
| } |
| sh, deferFn := servicetest.CreateShellAndMountTable(t, selfCtx, v23.GetPrincipal(selfCtx)) |
| defer deferFn() |
| |
| // setup mock up directory to put state in |
| storedir, cleanup := servicetest.SetupRootDir(t, "bindir") |
| defer cleanup() |
| prepDirectory(t, storedir) |
| |
| otherPrincipal := testutil.NewPrincipal("other") |
| if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil { |
| t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err) |
| } |
| otherCtx, err := v23.WithPrincipal(selfCtx, otherPrincipal) |
| if err != nil { |
| t.Fatalf("WithPrincipal() failed: %v", err) |
| } |
| |
| nmh := servicetest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir) |
| pid := servicetest.ReadPID(t, nmh) |
| defer syscall.Kill(pid, syscall.SIGINT) |
| |
| vlog.VI(2).Infof("Self uploads a shared and private binary.") |
| if err := b("bini/private").Create(selfCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil { |
| t.Fatalf("Create() failed %v", err) |
| } |
| fakeDataPrivate := testData() |
| if streamErr, err := invokeUpload(t, selfCtx, b("bini/private"), fakeDataPrivate, 0); streamErr != nil || err != nil { |
| t.Fatalf("invokeUpload() failed %v, %v", err, streamErr) |
| } |
| |
| if err := b("bini/shared").Create(selfCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil { |
| t.Fatalf("Create() failed %v", err) |
| } |
| fakeDataShared := testData() |
| if streamErr, err := invokeUpload(t, selfCtx, b("bini/shared"), fakeDataShared, 0); streamErr != nil || err != nil { |
| t.Fatalf("invokeUpload() failed %v, %v", err, streamErr) |
| } |
| |
| vlog.VI(2).Infof("Verify that in the beginning other can't access bini/private or bini/shared") |
| if _, _, err := b("bini/private").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("Stat() should have failed but didn't: %v", err) |
| } |
| if _, _, err := b("bini/shared").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("Stat() should have failed but didn't: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Validate the AccessList file on bini/private.") |
| perms, _, err := b("bini/private").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %v", err) |
| } |
| expected := access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self"}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self"}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self"}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self"}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self"}}, |
| } |
| if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| vlog.VI(2).Infof("Validate the AccessList file on bini/private.") |
| perms, version, err := b("bini/private").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %v", err) |
| } |
| if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| vlog.VI(2).Infof("self blesses other as self/other and locks the bini/private binary to itself.") |
| selfBlessing := selfPrincipal.BlessingStore().Default() |
| otherBlessing, err := selfPrincipal.Bless(otherPrincipal.PublicKey(), selfBlessing, "other", security.UnconstrainedUse()) |
| if err != nil { |
| t.Fatalf("selfPrincipal.Bless() failed: %v", err) |
| } |
| if _, err := otherPrincipal.BlessingStore().Set(otherBlessing, security.AllPrincipals); err != nil { |
| t.Fatalf("otherPrincipal.BlessingStore() failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Self modifies the AccessList file on bini/private.") |
| for _, tag := range access.AllTypicalTags() { |
| perms.Clear("self", string(tag)) |
| perms.Add("self/$", string(tag)) |
| } |
| if err := b("bini/private").SetPermissions(selfCtx, perms, version); err != nil { |
| t.Fatalf("SetPermissions failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof(" Verify that bini/private's perms are updated.") |
| updated := access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self/$"}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self/$"}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self/$"}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self/$"}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self/$"}}, |
| } |
| perms, _, err = b("bini/private").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %v", err) |
| } |
| if got, want := perms.Normalize(), updated.Normalize(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| // Other still can't access bini/shared because there's no AccessList file at the |
| // root level. Self has to set one explicitly to enable sharing. This way, self |
| // can't accidentally expose the server without setting a root AccessList. |
| vlog.VI(2).Infof(" Verify that other still can't access bini/shared.") |
| if _, _, err := b("bini/shared").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("Stat() should have failed but didn't: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Self sets a root AccessList.") |
| newRootAccessList := make(access.Permissions) |
| for _, tag := range access.AllTypicalTags() { |
| newRootAccessList.Add("self/$", string(tag)) |
| } |
| if err := b("bini").SetPermissions(selfCtx, newRootAccessList, ""); err != nil { |
| t.Fatalf("SetPermissions failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Verify that other can access bini/shared now but not access bini/private.") |
| if _, _, err := b("bini/shared").Stat(otherCtx); err != nil { |
| t.Fatalf("Stat() shouldn't have failed: %v", err) |
| } |
| if _, _, err := b("bini/private").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("Stat() should have failed but didn't: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Other still can't create so Self gives Other right to Create.") |
| perms, tag, err := b("bini").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions() failed: %v", err) |
| } |
| |
| // More than one AccessList change will result in the same functional result in |
| // this test: that self/other acquires the right to invoke Create at the |
| // root. In particular: |
| // |
| // a. perms.Add("self", "Write ") |
| // b. perms.Add("self/other", "Write") |
| // c. perms.Add("self/other/$", "Write") |
| // |
| // will all give self/other the right to invoke Create but in the case of |
| // (a) it will also extend this right to self's delegates (because of the |
| // absence of the $) including other and in (b) will also extend the |
| // Create right to all of other's delegates. Since (c) is the minimum |
| // case, use that. |
| perms.Add("self/other/$", string("Write")) |
| err = b("bini").SetPermissions(selfCtx, perms, tag) |
| if err != nil { |
| t.Fatalf("SetPermissions() failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Other creates bini/otherbinary") |
| if err := b("bini/otherbinary").Create(otherCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil { |
| t.Fatalf("Create() failed %v", err) |
| } |
| fakeDataOther := testData() |
| if streamErr, err := invokeUpload(t, otherCtx, b("bini/otherbinary"), fakeDataOther, 0); streamErr != nil || err != nil { |
| t.FailNow() |
| } |
| |
| vlog.VI(2).Infof("Other can read perms for bini/otherbinary.") |
| updated = access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}}, |
| } |
| perms, _, err = b("bini/otherbinary").GetPermissions(otherCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %v", err) |
| } |
| if got, want := perms.Normalize(), updated.Normalize(); !reflect.DeepEqual(want, got) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| vlog.VI(2).Infof("Other tries to exclude self by removing self from the AccessList set") |
| perms, tag, err = b("bini/otherbinary").GetPermissions(otherCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions() failed: %v", err) |
| } |
| perms.Clear("self/$") |
| err = b("bini/otherbinary").SetPermissions(otherCtx, perms, tag) |
| if err != nil { |
| t.Fatalf("SetPermissions() failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Verify that other can make this change.") |
| updated = access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self/other"}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self/other"}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self/other"}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self/other"}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self/other"}}, |
| } |
| perms, _, err = b("bini/otherbinary").GetPermissions(otherCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %v", err) |
| } |
| if got, want := perms.Normalize(), updated.Normalize(); !reflect.DeepEqual(want, got) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| vlog.VI(2).Infof("But self's rights are inherited from root so self can still access despite this.") |
| if _, _, err := b("bini/otherbinary").Stat(selfCtx); err != nil { |
| t.Fatalf("Stat() shouldn't have failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Self petulantly blacklists other back.") |
| perms, tag, err = b("bini").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions() failed: %v", err) |
| } |
| for _, tag := range access.AllTypicalTags() { |
| perms.Blacklist("self/other", string(tag)) |
| } |
| err = b("bini").SetPermissions(selfCtx, perms, tag) |
| if err != nil { |
| t.Fatalf("SetPermissions() failed: %v", err) |
| } |
| |
| vlog.VI(2).Infof("And now other can do nothing at affecting the root. Other should be penitent.") |
| if err := b("bini/nototherbinary").Create(otherCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("Create() should have failed %v", err) |
| } |
| |
| vlog.VI(2).Infof("But other can still access shared.") |
| if _, _, err := b("bini/shared").Stat(otherCtx); err != nil { |
| t.Fatalf("Stat() should not have failed but did: %v", err) |
| } |
| |
| vlog.VI(2).Infof("Self petulantly blacklists other's binary too.") |
| perms, tag, err = b("bini/shared").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions() failed: %v", err) |
| } |
| for _, tag := range access.AllTypicalTags() { |
| perms.Blacklist("self/other", string(tag)) |
| } |
| err = b("bini/shared").SetPermissions(selfCtx, perms, tag) |
| if err != nil { |
| t.Fatalf("SetPermissions() failed: %v", err) |
| } |
| vlog.VI(2).Infof("And now other can't access shared either.") |
| if _, _, err := b("bini/shared").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("Stat() should have failed but didn't: %v", err) |
| } |
| // TODO(rjkroege): Extend the test with a third principal and verify that |
| // new principals can be given Admin perimission at the root. |
| |
| vlog.VI(2).Infof("Self feels guilty for petulance and disempowers itself") |
| // TODO(rjkroege,caprita): This is a one-way transition for self. Perhaps it |
| // should not be. Consider adding a factory-reset facility. |
| perms, tag, err = b("bini").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions() failed: %v", err) |
| } |
| perms.Clear("self/$", "Admin") |
| err = b("bini").SetPermissions(selfCtx, perms, tag) |
| if err != nil { |
| t.Fatalf("SetPermissions() failed: %v", err) |
| } |
| |
| vlog.VI(2).Info("Self can't access other's binary now") |
| if _, _, err := b("bini/otherbinary").Stat(selfCtx); err == nil { |
| t.Fatalf("Stat() should have failed but didn't") |
| } |
| } |
| |
| func TestBinaryRationalStartingValueForGetPermissions(t *testing.T) { |
| ctx, shutdown := test.InitForTest() |
| defer shutdown() |
| v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true)) |
| |
| selfPrincipal := testutil.NewPrincipal("self") |
| selfCtx, err := v23.WithPrincipal(ctx, selfPrincipal) |
| if err != nil { |
| t.Fatalf("WithPrincipal failed: %v", err) |
| } |
| sh, deferFn := servicetest.CreateShellAndMountTable(t, selfCtx, v23.GetPrincipal(selfCtx)) |
| defer deferFn() |
| |
| // setup mock up directory to put state in |
| storedir, cleanup := servicetest.SetupRootDir(t, "bindir") |
| defer cleanup() |
| prepDirectory(t, storedir) |
| |
| otherPrincipal := testutil.NewPrincipal("other") |
| if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil { |
| t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err) |
| } |
| |
| nmh := servicetest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir) |
| pid := servicetest.ReadPID(t, nmh) |
| defer syscall.Kill(pid, syscall.SIGINT) |
| |
| perms, tag, err := b("bini").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %#v", err) |
| } |
| expected := access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| } |
| if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| perms.Blacklist("self", string("Read")) |
| err = b("bini").SetPermissions(selfCtx, perms, tag) |
| if err != nil { |
| t.Fatalf("SetPermissions() failed: %v", err) |
| } |
| |
| perms, tag, err = b("bini").GetPermissions(selfCtx) |
| if err != nil { |
| t.Fatalf("GetPermissions failed: %#v", err) |
| } |
| expected = access.Permissions{ |
| "Admin": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Read": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{"self"}}, |
| "Write": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Debug": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| "Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}}, |
| } |
| if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v, expected %#v ", got, want) |
| } |
| |
| } |