mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 06:46:52 +01:00
Add support for accessing the previously ssh-only debug commands via `nebula ctl <command>.` - unix socket (named pipe on windows) for communication - sockets scoped to the user + an optional group - in cmd/nebula and cmd/nebula-service, but not nebula.Main Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
103 lines
3.8 KiB
Go
103 lines
3.8 KiB
Go
package localapi
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestReadFrameLimitIsOnTheBody pins the framing rule at its edge: the limit bounds the
|
|
// JSON body, so the newline that terminates the frame must not count against it.
|
|
func TestReadFrameLimitIsOnTheBody(t *testing.T) {
|
|
// A JSON string whose encoded form is exactly n bytes, with or without a terminator.
|
|
body := func(n int) string {
|
|
return fmt.Sprintf(`"%s"`, strings.Repeat("a", n-2))
|
|
}
|
|
|
|
var v string
|
|
|
|
// Exactly at the limit, terminated: accepted.
|
|
v = ""
|
|
require.NoError(t, readFrame(bytes.NewReader([]byte(body(64)+"\n")), 64, "request", &v, true))
|
|
assert.Len(t, v, 62)
|
|
|
|
// Exactly at the limit, EOF instead of a newline: still accepted.
|
|
v = ""
|
|
require.NoError(t, readFrame(bytes.NewReader([]byte(body(64))), 64, "request", &v, true))
|
|
assert.Len(t, v, 62)
|
|
|
|
// One byte over: refused as oversized, not as a parse failure.
|
|
err := readFrame(bytes.NewReader([]byte(body(65)+"\n")), 64, "request", &v, true)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "exceeded 64 bytes")
|
|
}
|
|
|
|
// stallingReader hands out its bytes and then fails with err, the shape of a peer that
|
|
// sends part of a frame and then stops, until the deadline on the connection lands.
|
|
type stallingReader struct {
|
|
data []byte
|
|
err error
|
|
}
|
|
|
|
func (r *stallingReader) Read(p []byte) (int, error) {
|
|
if len(r.data) == 0 {
|
|
return 0, r.err
|
|
}
|
|
n := copy(p, r.data)
|
|
r.data = r.data[n:]
|
|
return n, nil
|
|
}
|
|
|
|
type timeoutError struct{}
|
|
|
|
func (timeoutError) Error() string { return "i/o timeout" }
|
|
func (timeoutError) Timeout() bool { return true }
|
|
func (timeoutError) Temporary() bool { return true }
|
|
|
|
// TestReadFramePartialLineThenReadErrorIsTheError: a read that fails after part of a
|
|
// frame has arrived is a failed read, and the caller sees that error. The old behaviour
|
|
// treated the fragment as the whole frame and reported a parse failure, so a client
|
|
// that stalled mid request was told its JSON was malformed, and a daemon that stalled
|
|
// mid response hid the timeout the client's error path looks for.
|
|
func TestReadFramePartialLineThenReadErrorIsTheError(t *testing.T) {
|
|
var v map[string]any
|
|
|
|
err := readFrame(&stallingReader{data: []byte(`{"command": "hel`), err: timeoutError{}}, 1024, "request", &v, true)
|
|
require.Error(t, err)
|
|
var ne net.Error
|
|
require.ErrorAs(t, err, &ne, "the timeout is still visible through the wrapping: %v", err)
|
|
assert.True(t, ne.Timeout())
|
|
assert.NotContains(t, err.Error(), "parse", "a stalled peer is not a parse failure")
|
|
|
|
// EOF after an unterminated line is the one exception: a final line with no newline
|
|
// is still a frame.
|
|
v = nil
|
|
require.NoError(t, readFrame(&stallingReader{data: []byte(`{"command": "help"}`), err: io.EOF}, 1024, "request", &v, true))
|
|
assert.Equal(t, "help", v["command"])
|
|
|
|
// And an unterminated line that is EOF only because the limit cut it off is still
|
|
// reported as oversized.
|
|
err = readFrame(bytes.NewReader([]byte(strings.Repeat("a", 70)+"\n")), 64, "request", &v, true)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "exceeded 64 bytes")
|
|
}
|
|
|
|
// TestReadFrameUnknownFieldIsRefusedWhenStrict: the daemon refuses a request field it
|
|
// does not know, as it refuses an unknown parameter, so a misspelling is reported rather
|
|
// than ignored; the client, reading leniently, passes over a field a newer daemon sends.
|
|
func TestReadFrameUnknownFieldIsRefusedWhenStrict(t *testing.T) {
|
|
var req Request
|
|
err := readFrame(bytes.NewReader([]byte(`{"version":1,"comand":"help"}`+"\n")), 1024, "request", &req, true)
|
|
require.ErrorContains(t, err, `unknown field "comand"`)
|
|
|
|
var res Response
|
|
require.NoError(t, readFrame(bytes.NewReader([]byte(`{"version":1,"later":true}`+"\n")), 1024, "response", &res, false))
|
|
assert.Equal(t, 1, res.Version)
|
|
}
|