blob: 281a5943bb58440db23aec85c528b52428131589 [file] [log] [blame]
package impl_test
import (
"fmt"
"io"
"os"
"reflect"
"syscall"
"testing"
"v.io/v23"
"v.io/v23/naming"
"v.io/v23/security"
"v.io/v23/services/mgmt/repository"
"v.io/v23/services/security/access"
"v.io/v23/verror"
"v.io/x/lib/vlog"
"v.io/x/ref/lib/signals"
"v.io/x/ref/lib/testutil"
tsecurity "v.io/x/ref/lib/testutil/security"
"v.io/x/ref/services/mgmt/binary/impl"
mgmttest "v.io/x/ref/services/mgmt/lib/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 ACL flags per TaggedACLMapFromFlag")
}
publishName := args[0]
storedir := args[1]
ctx, shutdown := testutil.InitForTest()
defer fmt.Fprintf(stdout, "%v terminating\n", publishName)
defer vlog.VI(1).Infof("%v terminating", publishName)
defer shutdown()
server, endpoint := mgmttest.NewServer(ctx)
name := naming.JoinAddressName(endpoint, "")
vlog.VI(1).Infof("binaryd name: %v", name)
depth := 2
state, err := impl.NewState(storedir, "", depth)
if err != nil {
vlog.Fatalf("NewState(%v, %v, %v) failed: %v", storedir, "", depth, err)
}
dispatcher, err := impl.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 TestBinaryCreateACL(t *testing.T) {
ctx, shutdown := testutil.InitForTest()
defer shutdown()
v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
selfPrincipal := tsecurity.NewPrincipal("self")
selfCtx, err := v23.SetPrincipal(ctx, selfPrincipal)
if err != nil {
t.Fatalf("SetPrincipal failed: %v", err)
}
dir, childPrincipal := tsecurity.ForkCredentials(selfPrincipal, "child")
defer os.RemoveAll(dir)
childCtx, err := v23.SetPrincipal(ctx, childPrincipal)
if err != nil {
t.Fatalf("SetPrincipal failed: %v", err)
}
sh, deferFn := mgmttest.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 := 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.")
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 ACL also allows Self")
acl, _, err := b("bini/private").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()
v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
selfPrincipal := tsecurity.NewPrincipal("self")
selfCtx, err := v23.SetPrincipal(ctx, selfPrincipal)
if err != nil {
t.Fatalf("SetPrincipal failed: %v", err)
}
sh, deferFn := mgmttest.CreateShellAndMountTable(t, selfCtx, v23.GetPrincipal(selfCtx))
defer deferFn()
// setup mock up directory to put state in
storedir, cleanup := mgmttest.SetupRootDir(t, "bindir")
defer cleanup()
prepDirectory(t, storedir)
otherPrincipal := tsecurity.NewPrincipal("other")
if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil {
t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
}
otherCtx, err := v23.SetPrincipal(selfCtx, otherPrincipal)
if err != nil {
t.Fatalf("SetPrincipal() failed: %v", err)
}
_, 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.")
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.Is(err, verror.ErrNoAccess.ID) {
t.Fatalf("Stat() should have failed but didn't: %v", err)
}
if _, _, err := b("bini/shared").Stat(otherCtx); !verror.Is(err, verror.ErrNoAccess.ID) {
t.Fatalf("Stat() should have failed but didn't: %v", err)
}
vlog.VI(2).Infof("Validate the ACL file on bini/private.")
acl, _, err := b("bini/private").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
}
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)
}
vlog.VI(2).Infof("Validate the ACL file on bini/private.")
acl, etag, err := b("bini/private").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
}
if got, want := acl.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 ACL file on bini/private.")
for _, tag := range access.AllTypicalTags() {
acl.Clear("self", string(tag))
acl.Add("self/$", string(tag))
}
if err := b("bini/private").SetACL(selfCtx, acl, etag); err != nil {
t.Fatalf("SetACL failed: %v", err)
}
vlog.VI(2).Infof(" Verify that bini/private's acls are updated.")
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 = b("bini/private").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
}
if got, want := acl.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 ACL 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 ACL.
vlog.VI(2).Infof(" Verify that other still can't access bini/shared.")
if _, _, err := b("bini/shared").Stat(otherCtx); !verror.Is(err, verror.ErrNoAccess.ID) {
t.Fatalf("Stat() should have failed but didn't: %v", err)
}
vlog.VI(2).Infof("Self sets a root ACL.")
newRootACL := make(access.TaggedACLMap)
for _, tag := range access.AllTypicalTags() {
newRootACL.Add("self/$", string(tag))
}
if err := b("bini").SetACL(selfCtx, newRootACL, ""); err != nil {
t.Fatalf("SetACL 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.Is(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.")
acl, tag, err := b("bini").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL() failed: %v", err)
}
// More than one ACL 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. acl.Add("self", "Write ")
// b. acl.Add("self/other", "Write")
// c. acl.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.
acl.Add("self/other/$", string("Write"))
err = b("bini").SetACL(selfCtx, acl, tag)
if err != nil {
t.Fatalf("SetACL() 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 acls for bini/otherbinary.")
updated = access.TaggedACLMap{
"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 = b("bini/otherbinary").GetACL(otherCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
}
if got, want := acl.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 ACL set")
acl, tag, err = b("bini/otherbinary").GetACL(otherCtx)
if err != nil {
t.Fatalf("GetACL() failed: %v", err)
}
acl.Clear("self/$")
err = b("bini/otherbinary").SetACL(otherCtx, acl, tag)
if err != nil {
t.Fatalf("SetACL() failed: %v", err)
}
vlog.VI(2).Infof("Verify that other can make this change.")
updated = access.TaggedACLMap{
"Admin": access.ACL{In: []security.BlessingPattern{"self/other"}},
"Read": access.ACL{In: []security.BlessingPattern{"self/other"}},
"Write": access.ACL{In: []security.BlessingPattern{"self/other"}},
"Debug": access.ACL{In: []security.BlessingPattern{"self/other"}},
"Resolve": access.ACL{In: []security.BlessingPattern{"self/other"}},
}
acl, _, err = b("bini/otherbinary").GetACL(otherCtx)
if err != nil {
t.Fatalf("GetACL failed: %v", err)
}
if got, want := acl.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.")
acl, tag, err = b("bini").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL() failed: %v", err)
}
for _, tag := range access.AllTypicalTags() {
acl.Blacklist("self/other", string(tag))
}
err = b("bini").SetACL(selfCtx, acl, tag)
if err != nil {
t.Fatalf("SetACL() 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.Is(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.")
acl, tag, err = b("bini/shared").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL() failed: %v", err)
}
for _, tag := range access.AllTypicalTags() {
acl.Blacklist("self/other", string(tag))
}
err = b("bini/shared").SetACL(selfCtx, acl, tag)
if err != nil {
t.Fatalf("SetACL() failed: %v", err)
}
vlog.VI(2).Infof("And now other can't access shared either.")
if _, _, err := b("bini/shared").Stat(otherCtx); !verror.Is(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.
acl, tag, err = b("bini").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL() failed: %v", err)
}
acl.Clear("self/$", "Admin")
err = b("bini").SetACL(selfCtx, acl, tag)
if err != nil {
t.Fatalf("SetACL() 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 TestBinaryRationalStartingValueForGetACL(t *testing.T) {
ctx, shutdown := testutil.InitForTest()
defer shutdown()
v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
selfPrincipal := tsecurity.NewPrincipal("self")
selfCtx, err := v23.SetPrincipal(ctx, selfPrincipal)
if err != nil {
t.Fatalf("SetPrincipal failed: %v", err)
}
sh, deferFn := mgmttest.CreateShellAndMountTable(t, selfCtx, v23.GetPrincipal(selfCtx))
defer deferFn()
// setup mock up directory to put state in
storedir, cleanup := mgmttest.SetupRootDir(t, "bindir")
defer cleanup()
prepDirectory(t, storedir)
otherPrincipal := tsecurity.NewPrincipal("other")
if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil {
t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
}
_, nms := mgmttest.RunShellCommand(t, sh, nil, binaryCmd, "bini", storedir)
pid := mgmttest.ReadPID(t, nms)
defer syscall.Kill(pid, syscall.SIGINT)
acl, tag, err := b("bini").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL failed: %#v", err)
}
expected := access.TaggedACLMap{
"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)
}
acl.Blacklist("self", string("Read"))
err = b("bini").SetACL(selfCtx, acl, tag)
if err != nil {
t.Fatalf("SetACL() failed: %v", err)
}
acl, tag, err = b("bini").GetACL(selfCtx)
if err != nil {
t.Fatalf("GetACL failed: %#v", err)
}
expected = access.TaggedACLMap{
"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)
}
}