blob: 5b4b6a9c053151e7fa261dafe9779dcea3bb15df [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 agentlib_test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"v.io/v23"
"v.io/v23/security"
"v.io/x/lib/gosh"
_ "v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/services/agent/agentlib"
"v.io/x/ref/services/agent/internal/ipc"
"v.io/x/ref/services/agent/internal/server"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
"v.io/x/ref/test/v23test"
)
// As of November 17, 2015, the benchmarks for serving a principal with and
// without the agent are as follows:
// (on an 3.1 GHz Intel Core i7)
//
// BenchmarkSignNoAgent-4 2000 625858 ns/op
// BenchmarkSignCachedAgent-4 2000 805983 ns/op
// BenchmarkSignUncachedAgent-4 2000 789841 ns/op
// BenchmarkDefaultNoAgent-4 20000000 109 ns/op
// BenchmarkDefaultCachedAgent-4 30000000 44.9 ns/op
// BenchmarkDefaultUncachedAgent-4 10000 155312 ns/op
// BenchmarkRecognizedNegativeNoAgent-4 100000 20091 ns/op
// BenchmarkRecognizedNegativeCachedAgent-4 100000 21940 ns/op
// BenchmarkRecognizedNegativeUncachedAgent-4 10000 209605 ns/op
// BenchmarkRecognizedNoAgent-4 2000000 748 ns/op
// BenchmarkRecognizedCachedAgent-4 3000000 774 ns/op
// BenchmarkRecognizedUncachedAgent-4 20000 99022 ns/op
var getPrincipalAndHang = gosh.RegisterFunc("getPrincipalAndHang", func() {
ctx, shutdown := test.V23Init()
defer shutdown()
b, _ := v23.GetPrincipal(ctx).BlessingStore().Default()
fmt.Printf("DEFAULT_BLESSING=%s\n", b)
ioutil.ReadAll(os.Stdin)
})
func newAgent(path string, cached bool) (security.Principal, error) {
if cached {
return agentlib.NewAgentPrincipalX(path)
} else {
return agentlib.NewUncachedPrincipalX(path)
}
}
func setupAgentPair(t *testing.T, p security.Principal) (security.Principal, security.Principal, func()) {
i := ipc.NewIPC()
if err := server.ServeAgent(i, p); err != nil {
t.Fatal(err)
}
dir, err := ioutil.TempDir("", "agent")
if err != nil {
t.Fatal(err)
}
sock := filepath.Join(dir, "sock")
defer os.RemoveAll(dir)
if err := i.Listen(sock); err != nil {
t.Fatal(err)
}
agent1, err := newAgent(sock, true)
if err != nil {
t.Fatal(err)
}
agent2, err := newAgent(sock, true)
if err != nil {
t.Fatal(err)
}
return agent1, agent2, i.Close
}
func setupAgent(caching bool) (security.Principal, func()) {
p := testutil.NewPrincipal("agentTest")
i := ipc.NewIPC()
if err := server.ServeAgent(i, p); err != nil {
panic(err)
}
dir, err := ioutil.TempDir("", "agent")
if err != nil {
panic(err)
}
sock := filepath.Join(dir, "sock")
defer os.RemoveAll(dir)
if err := i.Listen(sock); err != nil {
panic(err)
}
agent, err := newAgent(sock, caching)
if err != nil {
panic(err)
}
return agent, i.Close
}
func TestAgent(t *testing.T) {
var (
p = testutil.NewPrincipal("agentTest")
agent1, agent2, cleanup = setupAgentPair(t, p)
def = func(p security.Principal) security.Blessings {
b, _ := p.BlessingStore().Default()
return b
}
defP = def(p)
def1 = def(agent1)
def2 = def(agent2)
)
defer cleanup()
if !reflect.DeepEqual(defP, def1) {
t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def1)
}
if !reflect.DeepEqual(defP, def2) {
t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def2)
}
// Check that we're caching:
// Modify the principal directly and the client's shouldn't notic.
blessing, err := p.BlessSelf("alice")
if err != nil {
t.Fatal(err)
}
if err = p.BlessingStore().SetDefault(blessing); err != nil {
t.Fatal(err)
}
def1, def2 = def(agent1), def(agent2)
if !reflect.DeepEqual(defP, def1) {
t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def1)
}
if !reflect.DeepEqual(defP, def2) {
t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def2)
}
// Now make a change through the client.
blessing, err = agent1.BlessSelf("john")
if err != nil {
t.Fatal(err)
}
if err = agent2.BlessingStore().SetDefault(blessing); err != nil {
t.Fatal(err)
}
// The principal and the other client should both reflect the change.
if newDefault := def(p); !reflect.DeepEqual(newDefault, blessing) {
t.Errorf("Default blessing mismatch. Wanted %v, got %v", blessing, newDefault)
}
// There's no synchronization, so keep fetching from the other client.
// Eventually it should get notified of the new value.
for i := 0; i < 10000 && !reflect.DeepEqual(blessing, def(agent1)); i += 1 {
time.Sleep(100 * time.Millisecond)
}
if got := def(agent1); !reflect.DeepEqual(got, blessing) {
t.Errorf("Default blessing mismatch. Wanted %v, got %v", blessing, got)
}
}
func TestAgentDischargeCache(t *testing.T) {
agent, cleanup := setupAgent(true)
defer cleanup()
expcav, err := security.NewExpiryCaveat(time.Now().Add(time.Hour))
if err != nil {
t.Fatal(err)
}
tpcav, err := security.NewPublicKeyCaveat(
agent.PublicKey(),
"discharger",
security.ThirdPartyRequirements{},
expcav)
d, err := agent.MintDischarge(tpcav, expcav)
if err != nil {
t.Fatal(err)
}
before := time.Now()
agent.BlessingStore().CacheDischarge(d, tpcav, security.DischargeImpetus{})
after := time.Now()
got, cacheTime := agent.BlessingStore().Discharge(tpcav, security.DischargeImpetus{})
if d.ID() != got.ID() {
t.Errorf("got: %v, wanted %v", got.ID(), d.ID())
}
if cacheTime.Before(before) || cacheTime.After(after) {
t.Errorf("got %v, wanted value in (%v, %v)", cacheTime, before, after)
}
}
// NOTE(sadovsky): Whether v23test.Shell uses the agent to manage credentials is
// an internal implementation detail of the Shell. Currently, it happens to use
// the agent. We could potentially expose an option to allow clients to
// configure this behavior.
func TestAgentShutdown(t *testing.T) {
sh := v23test.NewShell(t, nil)
defer func() {
fmt.Fprintf(os.Stderr, "cleanup...\n")
sh.Cleanup()
fmt.Fprintf(os.Stderr, "cleanup done\n")
}()
// The child process will connect to the agent.
c := sh.FuncCmd(getPrincipalAndHang)
c.Start()
fmt.Fprintf(os.Stderr, "reading var...\n")
c.S.ExpectVar("DEFAULT_BLESSING")
fmt.Fprintf(os.Stderr, "read\n")
if err := c.S.Error(); err != nil {
t.Fatalf("failed to read input: %s", err)
}
}
var message = []byte("bugs bunny")
func runSignBenchmark(b *testing.B, p security.Principal) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := p.Sign(message); err != nil {
b.Fatal(err)
}
}
}
func runDefaultBenchmark(b *testing.B, p security.Principal) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
if d, _ := p.BlessingStore().Default(); d.IsZero() {
b.Fatal("empty blessings")
}
}
}
func runRecognizedNegativeBenchmark(b *testing.B, p security.Principal) {
key, err := p.PublicKey().MarshalBinary()
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if d := p.Roots().Recognized(key, "foobar"); d == nil {
b.Fatal("nil")
}
}
}
func runRecognizedBenchmark(b *testing.B, p security.Principal) {
key, err := p.PublicKey().MarshalBinary()
if err != nil {
b.Fatal(err)
}
blessing, err := p.BlessSelf("foobar")
if err != nil {
b.Fatal(err)
}
if err = security.AddToRoots(p, blessing); err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if d := p.Roots().Recognized(key, "foobar"); d != nil {
b.Fatal(d)
}
}
}
func BenchmarkSignNoAgent(b *testing.B) {
p := testutil.NewPrincipal("agentTest")
runSignBenchmark(b, p)
}
func BenchmarkSignCachedAgent(b *testing.B) {
p, cleanup := setupAgent(true)
defer cleanup()
runSignBenchmark(b, p)
}
func BenchmarkSignUncachedAgent(b *testing.B) {
p, cleanup := setupAgent(false)
defer cleanup()
runSignBenchmark(b, p)
}
func BenchmarkDefaultNoAgent(b *testing.B) {
p := testutil.NewPrincipal("agentTest")
runDefaultBenchmark(b, p)
}
func BenchmarkDefaultCachedAgent(b *testing.B) {
p, cleanup := setupAgent(true)
defer cleanup()
runDefaultBenchmark(b, p)
}
func BenchmarkDefaultUncachedAgent(b *testing.B) {
p, cleanup := setupAgent(false)
defer cleanup()
runDefaultBenchmark(b, p)
}
func BenchmarkRecognizedNegativeNoAgent(b *testing.B) {
p := testutil.NewPrincipal("agentTest")
runRecognizedNegativeBenchmark(b, p)
}
func BenchmarkRecognizedNegativeCachedAgent(b *testing.B) {
p, cleanup := setupAgent(true)
defer cleanup()
runRecognizedNegativeBenchmark(b, p)
}
func BenchmarkRecognizedNegativeUncachedAgent(b *testing.B) {
p, cleanup := setupAgent(false)
defer cleanup()
runRecognizedNegativeBenchmark(b, p)
}
func BenchmarkRecognizedNoAgent(b *testing.B) {
p := testutil.NewPrincipal("agentTest")
runRecognizedBenchmark(b, p)
}
func BenchmarkRecognizedCachedAgent(b *testing.B) {
p, cleanup := setupAgent(true)
defer cleanup()
runRecognizedBenchmark(b, p)
}
func BenchmarkRecognizedUncachedAgent(b *testing.B) {
p, cleanup := setupAgent(false)
defer cleanup()
runRecognizedBenchmark(b, p)
}