Vouch for the account that runs mesh-cli, and ask the controller its line (hq ADR 0272)
The records send the operator to "the controller's terminal", and nothing reaches it (hq issue 343). mesh-cli holds no credential: it asks the engine on its own machine over a socket. The engine reads the asking account from the kernel (SO_PEERCRED), never from the program, and asks the controller on mesh.control.<node>.cli, its own node's subject, so the bus server says which node asked. The controller decides what the line may do; nothing received on the socket is run or applied here, and the socket has no network address (ADR 0004 stands). The local protocol is the SDK's go/cli package, at a pseudo-version of its branch until that merges and is tagged go/v0.1.8.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package meshcli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/cli"
|
||||
)
|
||||
|
||||
// serving starts Serve on a socket of the test's own, and stops it when the test ends.
|
||||
func serving(t *testing.T, forward Forward) string {
|
||||
t.Helper()
|
||||
socket := filepath.Join(t.TempDir(), "run", "cli.sock")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- Serve(ctx, socket, forward, func(string) {}) }()
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
if err := <-done; err != nil {
|
||||
t.Errorf("Serve ended with %v", err)
|
||||
}
|
||||
})
|
||||
for i := 0; i < 200; i++ {
|
||||
if _, err := os.Stat(socket); err == nil {
|
||||
return socket
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
t.Fatal("the socket never appeared")
|
||||
return ""
|
||||
}
|
||||
|
||||
// The account sent is the one the kernel gives for the connecting process, and the answer comes back as it was.
|
||||
func TestTheAccountAskingIsTheKernelsAndTheAnswerComesBack(t *testing.T) {
|
||||
var got Asked
|
||||
socket := serving(t, func(_ context.Context, asked Asked) (cli.Answer, error) {
|
||||
got = asked
|
||||
return cli.Answer{Stdout: []byte("done\n"), Exit: 0, Terminal: true, Why: "the terminal"}, nil
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"settings", "show", "zsh"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(a.Stdout) != "done\n" || !a.Terminal || a.Refused != "" {
|
||||
t.Fatalf("the answer came back as %+v", a)
|
||||
}
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
t.Skipf("this machine cannot name the test's account: %v", err)
|
||||
}
|
||||
if int(got.UID) != os.Getuid() || got.Account != me.Username {
|
||||
t.Fatalf("asked as %d %q, and the kernel says %d %q", got.UID, got.Account, os.Getuid(), me.Username)
|
||||
}
|
||||
if strings.Join(got.Line, " ") != "settings show zsh" {
|
||||
t.Fatalf("the line was sent as %q", got.Line)
|
||||
}
|
||||
if info, err := os.Stat(socket); err != nil || info.Mode().Perm() != 0o666 {
|
||||
t.Fatalf("the socket is not open to every account: %v %v", info.Mode(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Credentials the kernel did not give are no account: refused, and nothing is asked.
|
||||
func TestAConnectionWhoseAccountCannotBeReadIsNeverAskedAsAnyone(t *testing.T) {
|
||||
before := peerOf
|
||||
peerOf = func(*net.UnixConn) (uint32, error) { return 0, errors.New("no credentials") }
|
||||
t.Cleanup(func() { peerOf = before })
|
||||
asked := false
|
||||
socket := serving(t, func(context.Context, Asked) (cli.Answer, error) {
|
||||
asked = true
|
||||
return cli.Answer{}, nil
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if asked || a.Refused == "" || a.Exit == 0 {
|
||||
t.Fatalf("a connection with no account was asked (%v) or not refused: %+v", asked, a)
|
||||
}
|
||||
}
|
||||
|
||||
// A controller out of reach is said: nothing ran, and why.
|
||||
func TestAControllerOutOfReachIsSaidAsNothingRan(t *testing.T) {
|
||||
socket := serving(t, func(context.Context, Asked) (cli.Answer, error) {
|
||||
return cli.Answer{}, errors.New("this machine has no link to the bus open now")
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(a.Refused, "nothing ran") || !strings.Contains(a.Refused, "no link") || a.Exit != 1 {
|
||||
t.Fatalf("an unreached controller was not said: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// A request over the bound is refused and never asked.
|
||||
func TestARequestOverTheBoundIsRefused(t *testing.T) {
|
||||
asked := false
|
||||
socket := serving(t, func(context.Context, Asked) (cli.Answer, error) {
|
||||
asked = true
|
||||
return cli.Answer{}, nil
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{strings.Repeat("x", cli.MaxRequest)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if asked || a.Refused == "" {
|
||||
t.Fatalf("a request over the bound was asked (%v): %+v", asked, a)
|
||||
}
|
||||
}
|
||||
|
||||
// A file at the socket's path that is not a socket is never removed.
|
||||
func TestAFileThatIsNotASocketIsKept(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
socket := filepath.Join(dir, "cli.sock")
|
||||
if err := os.WriteFile(socket, []byte("mine"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := Serve(context.Background(), socket, nil, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not a socket") {
|
||||
t.Fatalf("a file at the socket's path was not refused: %v", err)
|
||||
}
|
||||
if body, _ := os.ReadFile(socket); string(body) != "mine" {
|
||||
t.Fatal("the file at the socket's path was touched")
|
||||
}
|
||||
}
|
||||
|
||||
// The request the controller reads holds these field names; the controller's side holds the same list
|
||||
// (mesh-controller, the mesh-cli request's test).
|
||||
func TestTheRequestToTheControllerKeepsItsFieldNames(t *testing.T) {
|
||||
body, err := json.Marshal(Asked{Line: []string{"status"}, Account: "a", UID: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := keysOf(t, body); got != "account line uid" {
|
||||
t.Fatalf("the request's fields are %q", got)
|
||||
}
|
||||
body, err = json.Marshal(cli.Answer{Stdout: []byte("o"), Stderr: []byte("e"), Exit: 1, Terminal: true, Why: "w",
|
||||
Refused: "r", Cut: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := keysOf(t, body); got != "cut exit refused stderr stdout terminal why" {
|
||||
t.Fatalf("the answer's fields are %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func keysOf(t *testing.T, body []byte) string {
|
||||
t.Helper()
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var keys []string
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return strings.Join(keys, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user