139 lines
3.9 KiB
Go
139 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The socket answers "status" with the kernel's name for the caller, so a
|
|
// real connection reads the caller's own ask and nobody else's.
|
|
func TestTheAskSocketAnswersStatusOnlyForTheAsker(t *testing.T) {
|
|
askFlowWorld(t)
|
|
me, err := user.Current()
|
|
if err != nil {
|
|
t.Skip("no account name for this process:", err)
|
|
}
|
|
mine := fileAsk(agentAskRequest{Op: "stop", PID: 101}, me.Username, 0)
|
|
theirs := fileAsk(agentAskRequest{Op: "stop", PID: 102}, me.Username+"-other", 0)
|
|
if !mine.OK || !theirs.OK {
|
|
t.Fatalf("asks: %+v %+v", mine, theirs)
|
|
}
|
|
sock := filepath.Join(t.TempDir(), "ask.sock")
|
|
ln, err := net.Listen("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go askSockServe(c)
|
|
}
|
|
}()
|
|
ask := func(id string) agentAskReply {
|
|
t.Helper()
|
|
c, err := net.DialTimeout("unix", sock, 3*time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
_ = json.NewEncoder(c).Encode(agentAskRequest{Op: "status", Request: id})
|
|
var r agentAskReply
|
|
if err := json.NewDecoder(c).Decode(&r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return r
|
|
}
|
|
if r := ask(mine.Request); !r.OK || r.State != askWaiting || r.User != me.Username {
|
|
t.Errorf("my own ask over the socket: %+v", r)
|
|
}
|
|
if r := ask(theirs.Request); r.OK || r.State != askUnknown {
|
|
t.Errorf("another account's ask over the socket: %+v", r)
|
|
}
|
|
}
|
|
|
|
// `stop` run by an agent (no terminal) files the ask and prints it as JSON;
|
|
// `request` follows it to the outcome, with the exit status to branch on.
|
|
func TestStopAndRequestFromAnAgent(t *testing.T) {
|
|
d := askFlowWorld(t)
|
|
prevAuth := machineAuthorityHeld
|
|
t.Cleanup(func() { machineAuthorityHeld = prevAuth })
|
|
machineAuthorityHeld = func() bool { return true } // holds the state: the direct path
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prevIn := os.Stdin
|
|
os.Stdin = r // a pipe, not a terminal: an agent
|
|
t.Cleanup(func() { os.Stdin = prevIn; _ = w.Close(); _ = r.Close() })
|
|
|
|
out := captureStdout(t)
|
|
if err := cmdStop([]string{"101", "--json"}); err != nil {
|
|
t.Fatalf("stop: %v", err)
|
|
}
|
|
var v askView
|
|
if err := json.Unmarshal([]byte(printedSince(out, 0, "}\n")), &v); err != nil || v.State != askWaiting ||
|
|
v.Request == "" {
|
|
t.Fatalf("stop --json printed %q (%v)", out(), err)
|
|
}
|
|
rows := auditRowsOf(t, d, localStopAskOp)
|
|
if len(rows) != 1 {
|
|
t.Fatalf("filing rows: %v", rows)
|
|
}
|
|
|
|
id := fullRequestID(t, d, v.Request)
|
|
if _, err := stepupApproveRequest(d, id, codeFor(t, machineTestSecret)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
localStopOnce(d, time.Now())
|
|
before := len(out())
|
|
if err := cmdRequest([]string{v.Request}); err != nil {
|
|
t.Fatalf("request after the stop: %v", err)
|
|
}
|
|
if got := printedSince(out, before, "done."); !strings.Contains(got, "approved by the machine's owner and done") {
|
|
t.Errorf("request printed %q", got)
|
|
}
|
|
|
|
// A second ask, denied: `request` exits 1 without an error line.
|
|
second := mustAsk(t, 102)
|
|
if err := stepupDenyRequest(d, fullRequestID(t, d, second)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var q quietExit
|
|
if err := cmdRequest([]string{second, "--json"}); !errors.As(err, &q) || q.code != 1 {
|
|
t.Errorf("a denied ask: %v", err)
|
|
}
|
|
}
|
|
|
|
// printedSince waits (briefly) for captured output past from to hold want:
|
|
// captureStdout copies the pipe on its own goroutine.
|
|
func printedSince(out func() string, from int, want string) string {
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for {
|
|
got := out()[from:]
|
|
if strings.Contains(got, want) || time.Now().After(deadline) {
|
|
return got
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func mustAsk(t *testing.T, pid int) string {
|
|
t.Helper()
|
|
r := fileAsk(agentAskRequest{Op: "stop", PID: pid}, procUser(os.Getpid()), 0)
|
|
if !r.OK {
|
|
t.Fatal(r.Why)
|
|
}
|
|
return r.Request
|
|
}
|