blob: 8b847872f0e13bbb7788f0ea8542be9623608f55 [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 (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"v.io/v23/security"
"v.io/x/ref"
vsecurity "v.io/x/ref/lib/security"
"v.io/x/ref/test/v23tests"
)
//go:generate v23 test generate
func V23TestTestPassPhraseUse(i *v23tests.T) {
bin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithEnv(ref.EnvCredentials + "=" + i.NewTempDir(""))
// Create the passphrase
agent := bin.Start("echo", "Hello")
fmt.Fprintln(agent.Stdin(), "PASSWORD")
agent.ReadLine() // Skip over ...creating new key... message
agent.Expect("Hello")
agent.ExpectEOF()
if err := agent.Error(); err != nil {
i.Fatal(err)
}
// Use it successfully
agent = bin.Start("echo", "Hello")
fmt.Fprintln(agent.Stdin(), "PASSWORD")
agent.Expect("Hello")
agent.ExpectEOF()
if err := agent.Error(); err != nil {
i.Fatal(err)
}
// Provide a bad password
agent = bin.Start("echo", "Hello")
fmt.Fprintln(agent.Stdin(), "BADPASSWORD")
agent.ExpectEOF()
var stdout, stderr bytes.Buffer
err := agent.Wait(&stdout, &stderr)
if err == nil {
i.Fatalf("expected an error.STDOUT:%v\nSTDERR:%v\n", stdout.String(), stderr.String())
}
if got, want := err.Error(), "exit status 1"; got != want {
i.Errorf("Got %q, want %q", got, want)
}
if got, want := stderr.String(), "passphrase incorrect for decrypting private key"; !strings.Contains(got, want) {
i.Errorf("Got %q, wanted it to contain %q", got, want)
}
if err := agent.Error(); err != nil {
i.Error(err)
}
}
func V23TestAllPrincipalMethods(i *v23tests.T) {
// Test all methods of the principal interface.
// (Errors are printed to STDERR)
testbin := i.BuildGoPkg("v.io/x/ref/services/agent/internal/test_principal").Path()
i.BuildGoPkg("v.io/x/ref/services/agent/agentd").
WithEnv(ref.EnvCredentials+"="+i.NewTempDir("")).
Start(testbin).
WaitOrDie(nil, os.Stderr)
}
func V23TestAgentProcesses(i *v23tests.T) {
// 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 (
clientAgent, serverAgent = createClientAndServerAgents(i)
pingpong = i.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
serverName = serverAgent.Start(pingpong).ExpectVar("NAME")
)
// Run the client via an agent once.
client := clientAgent.Start(pingpong, serverName)
client.Expect("Pinging...")
client.Expect("pong (client:[pingpongd/client] server:[pingpongd])")
client.WaitOrDie(os.Stdout, os.Stderr)
if err := client.Error(); err != nil { // Check expectations
i.Fatal(err)
}
// 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(i.NewTempDir(""), "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 {
i.Fatal(err)
}
client = clientAgent.Start("bash", script)
client.Expect("Running client")
client.Expect("Pinging...")
client.Expect("pong (client:[pingpongd/client] server:[pingpongd])")
client.Expect("Running client again")
client.Expect("Pinging...")
client.Expect("pong (client:[pingpongd/client] server:[pingpongd])")
client.WaitOrDie(os.Stdout, os.Stderr)
if err := client.Error(); err != nil { // Check expectations
i.Fatal(err)
}
}
func V23TestAgentRestartExitCode(i *v23tests.T) {
var (
clientAgent, serverAgent = createClientAndServerAgents(i)
pingpong = i.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
serverName = serverAgent.Start(pingpong).ExpectVar("NAME")
scriptDir = i.NewTempDir("")
counter = filepath.Join(scriptDir, "counter")
script = filepath.Join(scriptDir, "test.sh")
)
if err := writeScript(
script,
`#!/bin/bash
# Execute the client ping once
{{.Bin}} {{.Server}} || exit 101
# Increment the contents of the counter file
readonly COUNT=$(expr $(<"{{.Counter}}") + 1)
echo -n $COUNT >{{.Counter}}
# Exit code is 0 if the counter is less than 5
[[ $COUNT -lt 5 ]]; exit $?
`,
struct{ Bin, Server, Counter string }{
Bin: pingpong,
Server: serverName,
Counter: counter,
}); err != nil {
i.Fatal(err)
}
tests := []struct {
RestartExitCode string
WantError string
WantCounter string
}{
{
// With --restart-exit-code=0, the script should be kicked off
// 5 times till the counter reaches 5 and the last iteration of
// the script will exit.
RestartExitCode: "0",
WantError: "exit status 1",
WantCounter: "5",
},
{
// With --restart-exit-code=!0, the script will be executed only once.
RestartExitCode: "!0",
WantError: "",
WantCounter: "1",
},
{
// --restart-exit-code=!1, should be the same
// as --restart-exit-code=0 for this
// particular script only exits with 0 or 1
RestartExitCode: "!1",
WantError: "exit status 1",
WantCounter: "5",
},
}
for _, test := range tests {
// Clear out the counter file.
if err := ioutil.WriteFile(counter, []byte("0"), 0644); err != nil {
i.Fatalf("%q: %v", counter, err)
}
// Run the script under the agent
var gotError string
if err := clientAgent.Start(
"--restart-exit-code="+test.RestartExitCode,
"bash", "-c", script).
Wait(os.Stdout, os.Stderr); err != nil {
gotError = err.Error()
}
if got, want := gotError, test.WantError; got != want {
i.Errorf("%+v: Got %q, want %q", test, got, want)
}
if buf, err := ioutil.ReadFile(counter); err != nil {
i.Errorf("ioutil.ReadFile(%q) failed: %v", counter, err)
} else if got, want := string(buf), test.WantCounter; got != want {
i.Errorf("%+v: Got %q, want %q", test, got, want)
}
}
}
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
}
// createClientAndServerAgents creates two principals, sets up their
// blessings and returns the agent binaries that will use the created credentials.
//
// The server will have a single blessing "pingpongd".
// The client will have a single blessing "pingpongd/client", blessed by the server.
func createClientAndServerAgents(i *v23tests.T) (client, server *v23tests.Binary) {
var (
agentd = i.BuildGoPkg("v.io/x/ref/services/agent/agentd")
clientDir = i.NewTempDir("")
serverDir = i.NewTempDir("")
)
pserver, err := vsecurity.CreatePersistentPrincipal(serverDir, nil)
if err != nil {
i.Fatal(err)
}
pclient, err := vsecurity.CreatePersistentPrincipal(clientDir, nil)
if err != nil {
i.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 {
i.Fatal(err)
}
if err := pserver.BlessingStore().SetDefault(bserver); err != nil {
i.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 {
i.Fatal(err)
}
if _, err := pclient.BlessingStore().Set(bclient, "pingpongd"); err != nil {
i.Fatal(err)
}
// The client and server must both recognize bserver and its delegates.
if err := pserver.AddToRoots(bserver); err != nil {
i.Fatal(err)
}
if err := pclient.AddToRoots(bserver); err != nil {
i.Fatal(err)
}
return agentd.WithEnv(ref.EnvCredentials + "=" + clientDir), agentd.WithEnv(ref.EnvCredentials + "=" + serverDir)
}