You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

151 lines
5.4 KiB

package directstream
import (
"bytes"
"context"
"errors"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"github.com/gorilla/websocket"
)
func sharedTestSocket(t *testing.T) string {
t.Helper()
dir, err := os.MkdirTemp("", "shared-")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.RemoveAll(dir) })
return filepath.Join(dir, "s.sock")
}
func TestBrowserAndTerminalSessionsShareOneUARTAndCombinedLimit(t *testing.T) {
factory := newFakeTransportFactory()
broker, _ := NewBroker(factory, BrokerOptions{})
defer broker.Close()
path := sharedTestSocket(t)
listener, err := ListenTicketSocket(path)
if err != nil {
t.Fatal(err)
}
defer listener.Close()
go ServeSharedSocket(listener, broker)
client := SharedSocketClient{Path: path}
authority := &websocketAuthority{store: NewTicketStore(TicketStoreOptions{})}
server := httptest.NewServer(NewWebSocketHandler(broker, authority, authority, WebSocketOptions{}))
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var browsers []*websocket.Conn
for _, id := range []string{"browser-1", "browser-2"} {
claims := TicketClaims{SessionID: id, Channel: "CH20", Actor: id, Access: AccessWriter, MaxAttachments: 4}
connection := dialDirectWebSocket(t, server.URL, claims, server.URL, true)
defer connection.Close()
authenticateWebSocket(t, connection, issueWebSocketTicket(t, authority.store, claims))
status := readStatusFrame(t, connection)
if !status.Writer || status.QueuePosition != 0 {
t.Fatalf("browser waiting: %+v", status)
}
_ = readWelcomeFrame(t, connection)
browsers = append(browsers, connection)
}
var terminals []SharedAttachment
for _, id := range []string{"ssh-operation-1", "telnet-operation-2"} {
attachment, err := client.Attach(ctx, AttachRequest{SessionID: id, Channel: "CH20", Actor: id, Access: AccessWriter, MaxAttachments: 4})
if err != nil {
t.Fatal(err)
}
defer attachment.Close()
terminals = append(terminals, attachment)
}
if _, err := client.Attach(ctx, AttachRequest{SessionID: "fifth", Channel: "CH20", Actor: "fifth", Access: AccessWriter, MaxAttachments: 4}); !errors.Is(err, ErrAttachmentLimit) {
t.Fatalf("fifth terminal: %v", err)
}
claims := TicketClaims{SessionID: "fifth-browser", Channel: "CH20", Actor: "fifth", Access: AccessWriter, MaxAttachments: 4}
fifth := dialDirectWebSocket(t, server.URL, claims, server.URL, true)
defer fifth.Close()
authenticateWebSocket(t, fifth, issueWebSocketTicket(t, authority.store, claims))
_ = fifth.SetReadDeadline(time.Now().Add(time.Second))
if _, _, err := fifth.ReadMessage(); err == nil {
t.Fatal("fifth browser accepted")
}
transport := factory.transport("CH20")
for _, browser := range browsers {
if err := browser.WriteMessage(websocket.BinaryMessage, []byte("web\r")); err != nil {
t.Fatal(err)
}
}
for _, terminal := range terminals {
if err := terminal.Write(ctx, []byte("terminal\r")); err != nil {
t.Fatal(err)
}
}
if !waitFor(time.Second, func() bool { return len(transport.written()) == 26 }) {
t.Fatalf("UART writes: %q", transport.written())
}
payload := []byte{0, 0xff, 'o', 'k', '\r', '\n'}
transport.emit(payload)
for _, browser := range browsers {
_ = browser.SetReadDeadline(time.Now().Add(time.Second))
kind, actual, err := browser.ReadMessage()
if err != nil || kind != websocket.BinaryMessage || !bytes.Equal(actual, payload) {
t.Fatalf("browser RX: %v %v", actual, err)
}
}
for _, terminal := range terminals {
actual, err := terminal.Receive(ctx)
if err != nil || !bytes.Equal(actual, payload) {
t.Fatalf("terminal RX: %v %v", actual, err)
}
}
if err := terminals[0].SendBreak(ctx); err != nil || transport.breakCount() != 1 {
t.Fatalf("break: %v", err)
}
_ = terminals[0].Close()
if !waitFor(time.Second, func() bool { return broker.Snapshot().TotalAttachments == 3 }) {
t.Fatal("closed terminal slot leaked")
}
replacement, err := client.Attach(ctx, AttachRequest{SessionID: "replacement", Channel: "CH20", Actor: "replacement", Access: AccessWriter, MaxAttachments: 4})
if err != nil {
t.Fatal(err)
}
defer replacement.Close()
if err := terminals[1].Write(ctx, []byte("still-connected\r")); err != nil {
t.Fatal(err)
}
if snapshot := broker.Snapshot(); snapshot.TransportOpens != 1 || snapshot.TotalAttachments != 4 {
t.Fatalf("snapshot: %+v", snapshot)
}
}
func TestSharedSocketHonorsLowerConfiguredLimitAndViewerAccess(t *testing.T) {
factory := newFakeTransportFactory()
broker, _ := NewBroker(factory, BrokerOptions{})
defer broker.Close()
path := sharedTestSocket(t)
listener, err := ListenTicketSocket(path)
if err != nil {
t.Fatal(err)
}
defer listener.Close()
go ServeSharedSocket(listener, broker)
client := SharedSocketClient{Path: path}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
viewer, err := client.Attach(ctx, AttachRequest{SessionID: "viewer", Channel: "CH01", Actor: "viewer", Access: AccessViewer, SharedWrite: true, MaxAttachments: 1})
if err != nil {
t.Fatal(err)
}
defer viewer.Close()
if err := viewer.Write(ctx, []byte("denied")); err == nil {
t.Fatal("viewer wrote data")
}
if _, err := client.Attach(ctx, AttachRequest{SessionID: "writer", Channel: "CH01", Actor: "writer", Access: AccessWriter, MaxAttachments: 4}); !errors.Is(err, ErrAttachmentLimit) {
t.Fatalf("configured limit bypassed: %v", err)
}
}