blob: 59de734987c0af0dccfafec2e9ae00bf83d148bf [file] [log] [blame]
// 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)
}
}