add tcp daemon listener with token auth
This commit is contained in:
@@ -3,6 +3,7 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -85,6 +86,80 @@ func TestDaemonDetachReattachPreservesProcess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonTCPTokenAuthAndUnixExemption(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(root, "config"))
|
||||
t.Setenv("XDG_DATA_HOME", filepath.Join(root, "data"))
|
||||
t.Setenv("XDG_RUNTIME_DIR", filepath.Join(root, "runtime"))
|
||||
projectDir := filepath.Join(root, "project")
|
||||
if err := os.MkdirAll(projectDir, 0o700); err != nil {
|
||||
t.Fatalf("mkdir project: %v", err)
|
||||
}
|
||||
socket := filepath.Join(root, "runtime", "patterm", "daemon.sock")
|
||||
pid := filepath.Join(root, "runtime", "patterm", "daemon.pid")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
errCh := make(chan error, 1)
|
||||
ready := make(chan string, 1)
|
||||
go func() {
|
||||
errCh <- RunDaemon(ctx, DaemonOptions{
|
||||
ProjectDir: projectDir,
|
||||
SocketPath: socket,
|
||||
PidPath: pid,
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
Token: "secret-token",
|
||||
TokenOut: io.Discard,
|
||||
ListenReady: ready,
|
||||
Cols: 80,
|
||||
Rows: 24,
|
||||
})
|
||||
}()
|
||||
waitForSocket(t, socket, errCh)
|
||||
tcpAddr := waitForTCPAddr(t, ready, errCh)
|
||||
|
||||
assertTCPAttachDenied(t, tcpAddr, "")
|
||||
assertTCPAttachDenied(t, tcpAddr, "wrong-token")
|
||||
|
||||
tcpClient := dialTCPDaemon(t, tcpAddr)
|
||||
defer tcpClient.Close()
|
||||
sendFrame(t, tcpClient, protocol.FrameAttach, protocol.Attach{
|
||||
Token: "secret-token",
|
||||
ProjectPath: projectDir,
|
||||
TermSize: protocol.Size{Cols: 80, Rows: 24},
|
||||
})
|
||||
expectFrame(t, tcpClient, protocol.FrameHello)
|
||||
expectFrame(t, tcpClient, protocol.FrameProjectList)
|
||||
expectFrame(t, tcpClient, protocol.FrameChrome)
|
||||
data, _ := json.Marshal(map[string]any{
|
||||
"argv": []string{"sh", "-c", "trap 'exit 0' TERM; echo TCP-SNAPSHOT; sleep 30"},
|
||||
"name": "tcp-survivor",
|
||||
})
|
||||
sendFrame(t, tcpClient, protocol.FramePaletteCommand, protocol.PaletteCommand{
|
||||
Kind: "spawn_command",
|
||||
Data: data,
|
||||
})
|
||||
expectFrame(t, tcpClient, protocol.FramePaneSnapshot)
|
||||
|
||||
unixClient := dialDaemon(t, socket)
|
||||
defer unixClient.Close()
|
||||
sendFrame(t, unixClient, protocol.FrameAttach, protocol.Attach{
|
||||
ProjectPath: projectDir,
|
||||
TermSize: protocol.Size{Cols: 80, Rows: 24},
|
||||
})
|
||||
expectFrame(t, unixClient, protocol.FrameHello)
|
||||
|
||||
cancel()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
t.Fatalf("daemon returned error: %v", err)
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("daemon did not stop")
|
||||
}
|
||||
}
|
||||
|
||||
func waitForSocket(t *testing.T, socket string, errCh <-chan error) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
@@ -114,6 +189,49 @@ func dialDaemon(t *testing.T, socket string) protocol.Transport {
|
||||
return protocol.NewConnTransport(conn)
|
||||
}
|
||||
|
||||
func dialTCPDaemon(t *testing.T, addr string) protocol.Transport {
|
||||
t.Helper()
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("dial tcp daemon: %v", err)
|
||||
}
|
||||
return protocol.NewConnTransport(conn)
|
||||
}
|
||||
|
||||
func waitForTCPAddr(t *testing.T, ready <-chan string, errCh <-chan error) string {
|
||||
t.Helper()
|
||||
select {
|
||||
case addr := <-ready:
|
||||
return addr
|
||||
case err := <-errCh:
|
||||
if err != nil && strings.Contains(err.Error(), "operation not permitted") {
|
||||
t.Skipf("tcp sockets unavailable in this sandbox: %v", err)
|
||||
}
|
||||
t.Fatalf("daemon exited before TCP listener was ready: %v", err)
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("tcp listener was not ready")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func assertTCPAttachDenied(t *testing.T, addr, token string) {
|
||||
t.Helper()
|
||||
client := dialTCPDaemon(t, addr)
|
||||
defer client.Close()
|
||||
sendFrame(t, client, protocol.FrameAttach, protocol.Attach{
|
||||
Token: token,
|
||||
TermSize: protocol.Size{Cols: 80, Rows: 24},
|
||||
})
|
||||
f := expectFrame(t, client, protocol.FrameError)
|
||||
msg, err := protocol.Decode[protocol.Error](f)
|
||||
if err != nil {
|
||||
t.Fatalf("decode error frame: %v", err)
|
||||
}
|
||||
if !strings.Contains(msg.Message, "auth denied") {
|
||||
t.Fatalf("error message = %q, want auth denied", msg.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func sendFrame[T any](t *testing.T, tr protocol.Transport, typ protocol.FrameType, payload T) {
|
||||
t.Helper()
|
||||
f, err := protocol.NewFrame(typ, payload)
|
||||
|
||||
Reference in New Issue
Block a user