blob: cc319f06f5076a7f003106c3570a2c1b0c50d66b [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 main_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"reflect"
"syscall"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/v23/services/groups"
"v.io/v23/verror"
"v.io/x/lib/set"
"v.io/x/ref/services/groups/groupsd/testdata/kvstore"
"v.io/x/ref/test/modules"
"v.io/x/ref/test/v23tests"
)
//go:generate jiri test generate
type relateResult struct {
Remainder map[string]struct{}
Approximations []groups.Approximation
Version string
}
// V23TestGroupServerIntegration tests the integration between the
// "groups" command-line client and the "groupsd" server.
func V23TestGroupServerIntegration(t *v23tests.T) {
v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
// Build binaries for the client and server.
var (
clientBin = t.BuildV23Pkg("v.io/x/ref/services/groups/groups")
serverBin = t.BuildV23Pkg("v.io/x/ref/services/groups/groupsd", "-tags=leveldb")
serverName = "groups-server"
groupA = naming.Join(serverName, "groupA")
groupB = naming.Join(serverName, "groupB")
)
// Start the groups server.
serverBin.Start("-name="+serverName, "-v23.tcp.address=127.0.0.1:0")
// Create a couple of groups.
clientBin.Start("create", groupA).WaitOrDie(os.Stdout, os.Stderr)
clientBin.Start("create", groupB, "a", "a/b").WaitOrDie(os.Stdout, os.Stderr)
// Add a couple of blessing patterns.
clientBin.Start("add", groupA, "<grp:groups-server/groupB>").WaitOrDie(os.Stdout, os.Stderr)
clientBin.Start("add", groupA, "a").WaitOrDie(os.Stdout, os.Stderr)
clientBin.Start("add", groupB, "a/b/c").WaitOrDie(os.Stdout, os.Stderr)
// Remove a blessing pattern.
clientBin.Start("remove", groupB, "a").WaitOrDie(os.Stdout, os.Stderr)
// Test simple group resolution.
{
var buffer, stderrBuf bytes.Buffer
clientBin.Start("relate", groupB, "a/b/c/d").WaitOrDie(&buffer, &stderrBuf)
var got relateResult
if err := json.Unmarshal(buffer.Bytes(), &got); err != nil {
t.Fatalf("Unmarshal(%v) failed: %v", buffer.String(), err)
}
want := relateResult{
Remainder: set.String.FromSlice([]string{"c/d", "d"}),
Approximations: nil,
Version: "2",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if got, want := stderrBuf.Len(), 0; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Test recursive group resolution.
{
var buffer, stderrBuf bytes.Buffer
clientBin.Start("relate", groupA, "a/b/c/d").WaitOrDie(&buffer, &stderrBuf)
var got relateResult
if err := json.Unmarshal(buffer.Bytes(), &got); err != nil {
t.Fatalf("Unmarshal(%v) failed: %v", buffer.String(), err)
}
want := relateResult{
Remainder: set.String.FromSlice([]string{"b/c/d", "c/d", "d"}),
Approximations: nil,
Version: "2",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if got, want := stderrBuf.Len(), 0; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Test group resolution failure. Note that under-approximation is
// used as the default to handle resolution failures.
{
clientBin.Start("add", groupB, "<grp:groups-server/groupC>").WaitOrDie(os.Stdout, os.Stderr)
var buffer, stderrBuf bytes.Buffer
clientBin.Start("relate", groupB, "a/b/c/d").WaitOrDie(&buffer, &stderrBuf)
var got relateResult
if err := json.Unmarshal(buffer.Bytes(), &got); err != nil {
t.Fatalf("Unmarshal(%v) failed: %v", buffer.String(), err)
}
want := relateResult{
Remainder: set.String.FromSlice([]string{"c/d", "d"}),
Approximations: []groups.Approximation{
groups.Approximation{
Reason: "v.io/v23/verror.NoExist",
Details: `groupsd:"groupC".Relate: Does not exist: groupC`,
},
},
Version: "3",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
if got, want := stderrBuf.Len(), 0; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Delete the groups.
clientBin.Start("delete", groupA).WaitOrDie(os.Stdout, os.Stderr)
clientBin.Start("delete", groupB).WaitOrDie(os.Stdout, os.Stderr)
}
// store implements the kvstore.Store interface.
type store map[string]string
func (s store) Get(ctx *context.T, call rpc.ServerCall, key string) (string, error) {
return s[key], nil
}
func (s store) Set(ctx *context.T, call rpc.ServerCall, key string, value string) error {
s[key] = value
return nil
}
const (
kvServerName = "key-value-store"
getFailed = "GET FAILED"
getOK = "GET OK"
setFailed = "SET FAILED"
setOK = "SET OK"
)
var runServer = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := v23.Init()
defer shutdown()
// Use a shorter timeout to reduce the test overall runtime as the
// permission authorizer will attempt to connect to a non-existing
// groups server at some point in the test.
ctx, _ = context.WithTimeout(ctx, 2*time.Second)
authorizer, err := groups.PermissionsAuthorizer(access.Permissions{
"Read": access.AccessList{In: []security.BlessingPattern{"<grp:groups-server/readers>"}},
"Write": access.AccessList{In: []security.BlessingPattern{"<grp:groups-server/writers>"}},
}, access.TypicalTagType())
if err != nil {
return err
}
if _, _, err := v23.WithNewServer(ctx, kvServerName, kvstore.StoreServer(&store{}), authorizer); err != nil {
return err
}
modules.WaitForEOF(env.Stdin)
return nil
}, "server")
var runClient = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := v23.Init()
defer shutdown()
if got, want := len(args), 1; got < want {
return fmt.Errorf("unexpected number of arguments: got %v, want at least %v", got, want)
}
command := args[0]
client := kvstore.StoreClient("key-value-store")
switch command {
case "get":
if got, want := len(args), 2; got != want {
return fmt.Errorf("unexpected number of arguments: got %v, want %v", got, want)
}
key := args[1]
value, err := client.Get(ctx, key)
if err != nil {
fmt.Fprintf(env.Stdout, "%v %v\n", getFailed, verror.ErrorID(err))
} else {
fmt.Fprintf(env.Stdout, "%v %v\n", getOK, value)
}
case "set":
if got, want := len(args), 3; got != want {
return fmt.Errorf("unexpected number of arguments: got %v, want %v", got, want)
}
key, value := args[1], args[2]
if err := client.Set(ctx, key, value); err != nil {
fmt.Fprintf(env.Stdout, "%v %v\n", setFailed, verror.ErrorID(err))
} else {
fmt.Fprintf(env.Stdout, "%v\n", setOK)
}
}
return nil
}, "client")
func startClient(t *v23tests.T, name string, args ...string) modules.Handle {
creds, err := t.Shell().NewChildCredentials(name)
if err != nil {
t.Fatalf("NewChildCredentials(%v) failed: %v", name, err)
}
handle, err := t.Shell().StartWithOpts(
t.Shell().DefaultStartOpts().WithCustomCredentials(creds).WithSessions(t, time.Minute),
nil,
runClient,
args...,
)
if err != nil {
t.Fatalf("StartWithOpts() failed: %v", err)
}
return handle
}
// V23TestGroupServerAuthorization uses an instance of the
// KeyValueStore server with an groups-based authorizer to test the
// group server implementation.
func V23TestGroupServerAuthorization(t *v23tests.T) {
v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
// Build binaries for the groups client and server.
var (
clientBin = t.BuildV23Pkg("v.io/x/ref/services/groups/groups")
serverBin = t.BuildV23Pkg("v.io/x/ref/services/groups/groupsd")
serverName = "groups-server"
readers = naming.Join(serverName, "readers")
writers = naming.Join(serverName, "writers")
)
// Start the groups server.
server := serverBin.Start("-name="+serverName, "-v23.tcp.address=127.0.0.1:0")
// Create a couple of groups. The <readers> and <writers> groups
// identify blessings that can be used to read from and write to the
// key value store server respectively.
clientBin.Start("create", readers, "root/alice", "root/bob").WaitOrDie(os.Stdout, os.Stderr)
clientBin.Start("create", writers, "root/alice").WaitOrDie(os.Stdout, os.Stderr)
// Start an instance of the key value store server.
if _, err := t.Shell().Start(nil, runServer); err != nil {
t.Fatalf("Start() failed: %v", err)
}
// Test that alice can write.
startClient(t, "alice", "set", "foo", "bar").Expect(setOK)
// Test that alice can read.
startClient(t, "alice", "get", "foo").Expectf("%v %v", getOK, "bar")
// Test that bob can read.
startClient(t, "bob", "get", "foo").Expectf("%v %v", getOK, "bar")
// Test that bob cannot write.
startClient(t, "bob", "set", "foo", "bar").Expectf("%v %v", setFailed, verror.ErrNoAccess.ID)
// Stop the groups server and check that as a consequence "alice"
// cannot read from the key value store server anymore.
if err := server.Kill(syscall.SIGTERM); err != nil {
t.Fatalf("Kill() failed: %v", err)
}
if err := server.Wait(os.Stdout, os.Stderr); err != nil {
t.Fatalf("Wait() failed: %v", err)
}
startClient(t, "alice", "get", "foo").Expectf("%v %v", getFailed, verror.ErrNoAccess.ID)
}