blob: b0495fee3bf8db5ea429bfb71113fe8eb2a3f9e4 [file] [log] [blame]
// Copyright 2016 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 (
"bufio"
"bytes"
"html/template"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"testing"
"v.io/v23/security"
"v.io/x/ref"
vsecurity "v.io/x/ref/lib/security"
_ "v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/services/agent/internal/constants"
"v.io/x/ref/test/v23test"
)
func upComesAgentd(t *testing.T, sh *v23test.Shell, credsDir, password string) {
agentd := v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/v23agentd")
agentC := sh.Cmd(agentd, "--daemon=false", "--credentials="+credsDir)
if len(password) > 0 {
agentC.SetStdinReader(strings.NewReader(password))
}
agentRead, agentWrite, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create pipe: %v", err)
}
agentC.ExtraFiles = append(agentC.ExtraFiles, agentWrite)
pipeFD := 3 + len(agentC.ExtraFiles) - 1
agentC.Vars[constants.EnvAgentParentPipeFD] = strconv.Itoa(pipeFD)
agentC.Vars["PATH"] = os.Getenv("PATH")
agentC.Start()
agentWrite.Close()
scanner := bufio.NewScanner(agentRead)
if !scanner.Scan() || scanner.Text() != constants.ServingMsg {
t.Fatalf("Failed to receive \"%s\" from agent", constants.ServingMsg)
}
if err := scanner.Err(); err != nil {
t.Fatalf("reading standard input: %v", err)
}
agentRead.Close()
}
func TestV23UnencryptedPrincipal(t *testing.T) {
v23test.SkipUnlessRunningIntegrationTests(t)
sh := v23test.NewShell(t, nil)
sh.PropagateChildOutput = true
defer sh.Cleanup()
testbin := v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/internal/test_principal")
credsDir := sh.MakeTempDir()
principal, err := vsecurity.CreatePersistentPrincipal(credsDir, nil)
if err != nil {
t.Fatal(err)
}
if err := vsecurity.InitDefaultBlessings(principal, "happy"); err != nil {
t.Fatal(err)
}
const noPassword = ""
upComesAgentd(t, sh, credsDir, noPassword)
testC := sh.Cmd(testbin, "--expect-blessing=happy")
testC.Vars[ref.EnvAgentPath] = constants.SocketPath(credsDir)
testC.Run()
}
func TestV23EncryptedPrincipal(t *testing.T) {
v23test.SkipUnlessRunningIntegrationTests(t)
sh := v23test.NewShell(t, nil)
sh.PropagateChildOutput = true
defer sh.Cleanup()
testbin := v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/internal/test_principal")
credsDir := sh.MakeTempDir()
principal, err := vsecurity.CreatePersistentPrincipal(credsDir, []byte("PASSWORD"))
if err != nil {
t.Fatal(err)
}
if err := vsecurity.InitDefaultBlessings(principal, "sneezy"); err != nil {
t.Fatal(err)
}
upComesAgentd(t, sh, credsDir, "PASSWORD")
testC := sh.Cmd(testbin, "--expect-blessing=sneezy")
testC.Vars[ref.EnvAgentPath] = constants.SocketPath(credsDir)
testC.Run()
}
func TestMain(m *testing.M) {
v23test.TestMain(m)
}
func upComesAgentdDaemon(t *testing.T, sh *v23test.Shell, credsDir, password string) {
agentd := v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/v23agentd")
agentC := sh.Cmd(agentd, "--credentials="+credsDir)
if len(password) > 0 {
agentC.SetStdinReader(strings.NewReader(password))
}
agentC.Run()
}
func downComesAgentd(t *testing.T, sh *v23test.Shell, credsDir string) {
agentd := v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/v23agentd")
agentC := sh.Cmd(agentd, "stop", "--credentials="+credsDir)
agentC.Run()
}
func TestV23Daemon(t *testing.T) {
v23test.SkipUnlessRunningIntegrationTests(t)
sh := v23test.NewShell(t, nil)
sh.PropagateChildOutput = true
defer sh.Cleanup()
testbin := v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/internal/test_principal")
credsDir := sh.MakeTempDir()
principal, err := vsecurity.CreatePersistentPrincipal(credsDir, []byte("P@SsW0rd"))
if err != nil {
t.Fatal(err)
}
if err := vsecurity.InitDefaultBlessings(principal, "grumpy"); err != nil {
t.Fatal(err)
}
upComesAgentdDaemon(t, sh, credsDir, "P@SsW0rd")
testC := sh.Cmd(testbin, "--expect-blessing=grumpy")
testC.Vars[ref.EnvAgentPath] = constants.SocketPath(credsDir)
testC.Run()
// Since we started the agent as a daemon, we need to explicitly stop it
// (since the Shell won't do it for us).
downComesAgentd(t, sh, credsDir)
}
func TestV23PingPong(t *testing.T) {
v23test.SkipUnlessRunningIntegrationTests(t)
sh := v23test.NewShell(t, nil)
defer sh.Cleanup()
// Setup two principals: One for the agent that runs the pingpong
// server, one for the client. Since the server uses the default
// authorization policy, the client must have a blessing delegated from
// the server.
var (
clientDir, serverDir = createClientAndServerCredentials(t, sh)
pingpong = v23test.BuildGoPkg(sh, "v.io/x/ref/services/agent/internal/pingpong")
)
upComesAgentd(t, sh, serverDir, "")
server := sh.Cmd(pingpong)
server.Vars[ref.EnvAgentPath] = constants.SocketPath(serverDir)
server.Start()
serverName := server.S.ExpectVar("NAME")
// Run the client via an agent once.
upComesAgentd(t, sh, clientDir, "")
client := sh.Cmd(pingpong, serverName)
client.Vars[ref.EnvAgentPath] = constants.SocketPath(clientDir)
client.Start()
client.S.Expect("Pinging...")
client.S.Expect("pong (client:[pingpongd:client] server:[pingpongd])")
client.Wait()
// Run it through a shell to test that the agent can pass credentials
// to subprocess of a shell (making things like "agentd bash" provide
// useful terminals).
// This only works with shells that propagate open file descriptors to
// children. POSIX-compliant shells do this as to many other commonly
// used ones like bash.
script := filepath.Join(sh.MakeTempDir(), "test.sh")
if err := writeScript(
script,
`#!/bin/bash
echo "Running client"
{{.Bin}} {{.Server}} || exit 101
echo "Running client again"
{{.Bin}} {{.Server}} || exit 102
`,
struct{ Bin, Server string }{pingpong, serverName}); err != nil {
t.Fatal(err)
}
client = sh.Cmd("bash", script)
client.Vars[ref.EnvAgentPath] = constants.SocketPath(clientDir)
client.Start()
client.S.Expect("Running client")
client.S.Expect("Pinging...")
client.S.Expect("pong (client:[pingpongd:client] server:[pingpongd])")
client.S.Expect("Running client again")
client.S.Expect("Pinging...")
client.S.Expect("pong (client:[pingpongd:client] server:[pingpongd])")
client.Wait()
downComesAgentd(t, sh, clientDir)
server.Terminate(syscall.SIGINT)
downComesAgentd(t, sh, serverDir)
}
func writeScript(dstfile, tmpl string, args interface{}) error {
t, err := template.New(dstfile).Parse(tmpl)
if err != nil {
return err
}
var buf bytes.Buffer
if err := t.Execute(&buf, args); err != nil {
return err
}
if err := ioutil.WriteFile(dstfile, buf.Bytes(), 0700); err != nil {
return err
}
return nil
}
// createClientAndServerCredentials creates two principals, sets up their
// blessings and returns the created credentials directories.
//
// The server will have a single blessing "pingpongd".
// The client will have a single blessing "pingpongd:client", blessed by the
// server.
func createClientAndServerCredentials(t *testing.T, sh *v23test.Shell) (clientDir, serverDir string) {
clientDir = sh.MakeTempDir()
serverDir = sh.MakeTempDir()
pserver, err := vsecurity.CreatePersistentPrincipal(serverDir, nil)
if err != nil {
t.Fatal(err)
}
pclient, err := vsecurity.CreatePersistentPrincipal(clientDir, nil)
if err != nil {
t.Fatal(err)
}
// Server will only serve, not make any client requests, so only needs a
// default blessing.
bserver, err := pserver.BlessSelf("pingpongd")
if err != nil {
t.Fatal(err)
}
if err := pserver.BlessingStore().SetDefault(bserver); err != nil {
t.Fatal(err)
}
// Clients need not have a default blessing as they will only make a request
// to the server.
bclient, err := pserver.Bless(pclient.PublicKey(), bserver, "client", security.UnconstrainedUse())
if err != nil {
t.Fatal(err)
}
if _, err := pclient.BlessingStore().Set(bclient, "pingpongd"); err != nil {
t.Fatal(err)
}
// The client and server must both recognize bserver and its delegates.
if err := security.AddToRoots(pserver, bserver); err != nil {
t.Fatal(err)
}
if err := security.AddToRoots(pclient, bserver); err != nil {
t.Fatal(err)
}
return
}