server.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. package localapi
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "log/slog"
  10. "math"
  11. "net"
  12. "os"
  13. "runtime/debug"
  14. "strconv"
  15. "sync/atomic"
  16. "time"
  17. "github.com/slackhq/nebula/commands"
  18. )
  19. const (
  20. // defaultMaxConns caps concurrent connections. Exceeding it is rejected rather than
  21. // queued: a queue turns a load problem into a latency problem and hides it.
  22. defaultMaxConns = 8
  23. // readTimeout bounds how long a client may take to send its request line.
  24. readTimeout = 10 * time.Second
  25. // writeTimeout bounds a single write. A client that stops reading is dropped rather
  26. // than waited on, so a paused terminal cannot pin daemon resources.
  27. writeTimeout = 30 * time.Second
  28. // defaultSlowAfter is how long a command may run before the daemon says so. A
  29. // command holds a connection slot until it returns, and one that never returns
  30. // holds it for good; after enough of those the socket answers only "busy". The
  31. // warning is what tells an operator that from load, and names the command.
  32. defaultSlowAfter = 30 * time.Second
  33. )
  34. // ctlSocketMode is the control socket's mode when no group shares it: the user who ran
  35. // nebula, and nobody else. The mode itself is not configurable; what an operator can
  36. // choose is a group, CtlGroupKey, which makes it groupSocketMode.
  37. const ctlSocketMode os.FileMode = 0600
  38. // Server serves a command registry over a local socket to the user who ran nebula.
  39. type Server struct {
  40. endpoint
  41. src *commands.Registry
  42. maxConns int
  43. slowAfter time.Duration
  44. sem chan struct{}
  45. euid uint32
  46. // trustSocket is set while a group shares the socket. The kernel admitted the peer
  47. // through the socket's mode and group, which is the whole access control then, and
  48. // the peer check below stands aside: on unix it sees the peer's uid and primary gid
  49. // only, so it could not tell a member of the group from anyone else.
  50. trustSocket atomic.Bool
  51. }
  52. // servedHere decides what this surface will run.
  53. //
  54. // Everything except the profiling commands, which stay on the ssh debug server. Profile
  55. // output is written on the host running nebula, under sshd.sandbox_dir, so a path typed
  56. // here would not mean what the person typing it expects; and the profiling commands are
  57. // one workflow, so splitting them would leave someone able to start a CPU profile in one
  58. // place and stop it only in another.
  59. func servedHere(c *commands.Command) bool {
  60. return !c.ForProfiling
  61. }
  62. // notServedHere explains a refusal in terms an operator can act on, naming the surface
  63. // that will run the command instead.
  64. const notServedHere = "profiling is driven from the ssh debug server, where the sshd.sandbox_dir " +
  65. "setting governs where profile output is written on the host running nebula"
  66. // NewServer returns a Server that will serve the commands in reg that this surface
  67. // accepts.
  68. //
  69. // reg is kept rather than filtered here, so a command registered on it after the server
  70. // is built is served too. Filtering happens per request, which is how the ssh server
  71. // treats the same registry, cloning it per session.
  72. func NewServer(l *slog.Logger, reg *commands.Registry) *Server {
  73. return &Server{
  74. endpoint: endpoint{l: l, name: "local control socket"},
  75. src: reg,
  76. maxConns: defaultMaxConns,
  77. slowAfter: defaultSlowAfter,
  78. // The same identity the client's owner check is made for, so the peer check
  79. // cannot disagree with it, and so a test can stand in for it the same way.
  80. euid: currentUID(),
  81. }
  82. }
  83. // Rebind brings the server up at path, moves a running server there, and serves it until
  84. // Stop or ctx cancellation; endpoint.rebindLocked says what that promises. An error here
  85. // is never fatal to the caller: nebula keeps forwarding packets without a control
  86. // endpoint, and the caller is expected to log and carry on.
  87. func (s *Server) Rebind(ctx context.Context, path string, acc Access) error {
  88. s.mu.Lock()
  89. defer s.mu.Unlock()
  90. err := s.rebindLocked(ctx, path, acc, func(ln net.Listener) io.Closer {
  91. logListening(s.l, path, acc)
  92. // Closing the listener, when it is given up, ends the accept loop on the
  93. // closed-listener error; in flight commands keep their connections, which stay
  94. // usable until they finish.
  95. go s.accept(ln, s.semaphore())
  96. return ln
  97. })
  98. if err == nil {
  99. s.trustSocket.Store(acc.Group != nil)
  100. }
  101. return err
  102. }
  103. // registry returns the commands this surface serves, filtered from the source at the
  104. // time of the call. It is a few allocations per request, which a control socket that
  105. // sees a request every few seconds at most never notices.
  106. func (s *Server) registry() *commands.Registry {
  107. return s.src.Filter(servedHere, notServedHere)
  108. }
  109. // semaphore returns the connection limiter, creating it on first use. It is made once
  110. // and never replaced: a handler releases into the channel it acquired from, so swapping
  111. // in a fresh one across a Stop and Rebind would leave any handler still in flight
  112. // blocked forever on a channel nothing had filled. Callers hold s.mu.
  113. func (s *Server) semaphore() chan struct{} {
  114. if s.sem == nil {
  115. s.sem = make(chan struct{}, s.maxConns)
  116. }
  117. return s.sem
  118. }
  119. // Accept failures that are not the listener closing are retried after a pause that
  120. // doubles from acceptRetryMin up to acceptRetryMax, the same shape as net/http.Serve.
  121. const (
  122. acceptRetryMin = 5 * time.Millisecond
  123. acceptRetryMax = time.Second
  124. )
  125. // accept serves connections from ln until it is closed. sem is the limiter to acquire
  126. // from and release into; it is passed rather than read from s so the handlers spawned
  127. // here never touch s.sem without the lock.
  128. //
  129. // Only a closed listener ends the loop; Stop and Rebind are the two things that close
  130. // it. Every other error is retried: the process being out of descriptors (EMFILE,
  131. // ENFILE) or a client that hung up between the kernel's queue and us (ECONNABORTED)
  132. // are passing conditions, and giving up on them would leave a socket on disk that
  133. // nothing answers, with the reason buried in a log line from minutes or days earlier.
  134. func (s *Server) accept(ln net.Listener, sem chan struct{}) {
  135. var delay time.Duration
  136. for {
  137. conn, err := ln.Accept()
  138. if err != nil {
  139. if errors.Is(err, net.ErrClosed) {
  140. return
  141. }
  142. if ne, ok := err.(net.Error); ok && ne.Timeout() {
  143. continue
  144. }
  145. if delay == 0 {
  146. delay = acceptRetryMin
  147. } else {
  148. delay = min(delay*2, acceptRetryMax)
  149. }
  150. s.l.Warn("Local control socket could not accept a connection, retrying", "error", err, "retry_in", delay)
  151. time.Sleep(delay)
  152. continue
  153. }
  154. delay = 0
  155. select {
  156. case sem <- struct{}{}:
  157. default:
  158. // At capacity. Say so and hang up rather than holding the connection open.
  159. // Logged too: a slot is held for as long as a command runs, and the slow
  160. // command warning in serve says which ones are still running.
  161. s.l.Warn("Local control socket is at capacity, refusing a connection", "max", s.maxConns)
  162. s.reply(conn, &Response{Version: ProtocolVersion, Error: "local control socket is busy, try again"})
  163. _ = conn.Close()
  164. continue
  165. }
  166. go func() {
  167. defer func() { <-sem }()
  168. s.handle(conn)
  169. }()
  170. }
  171. }
  172. // handle serves one request. It recovers from panics in command callbacks: this
  173. // surface is present on every install and reachable by any local process running as
  174. // the daemon's user, so a malformed argument must cost a connection rather than the
  175. // overlay.
  176. func (s *Server) handle(conn net.Conn) {
  177. defer conn.Close()
  178. var (
  179. args []string
  180. replied bool
  181. )
  182. defer func() {
  183. if r := recover(); r != nil {
  184. s.l.Error("Recovered from panic serving a local control command",
  185. "panic", r,
  186. "args", args,
  187. "stack", string(debug.Stack()),
  188. )
  189. // A panic in a command's AfterReply arrives here after the answer has gone
  190. // out; the client has its reply, so it is not sent a second one.
  191. if !replied {
  192. s.reply(conn, &Response{Version: ProtocolVersion, Error: "command failed"})
  193. }
  194. }
  195. }()
  196. // Where the platform can identify the caller, check it. Where it cannot, or where a
  197. // group shares the socket, the endpoint is protected by the socket's mode and group
  198. // or the pipe's security descriptor, which listen() set before serving.
  199. if peerCheckSupported && !s.trustSocket.Load() {
  200. uid, err := peerUID(conn)
  201. if err != nil {
  202. s.l.Warn("Refusing local control connection with unreadable credentials", "error", err)
  203. return
  204. }
  205. // Access is restricted to the user who ran nebula. Root is allowed through
  206. // because it can reach the daemon's memory regardless of what this check says.
  207. if uid != s.euid && uid != 0 {
  208. s.l.Warn("Refusing local control connection from another user", "peerUid", uid)
  209. s.reply(conn, &Response{Version: ProtocolVersion, Error: "not permitted"})
  210. return
  211. }
  212. }
  213. _ = conn.SetReadDeadline(time.Now().Add(readTimeout))
  214. req, err := readRequest(conn)
  215. if err != nil {
  216. s.reply(conn, &Response{Version: ProtocolVersion, Error: err.Error()})
  217. return
  218. }
  219. args = req.Args
  220. // No deadline while a command runs; the write deadline below is what bounds a client
  221. // that stops reading.
  222. _ = conn.SetReadDeadline(time.Time{})
  223. _ = conn.SetWriteDeadline(time.Now().Add(writeTimeout))
  224. res, after := s.serve(req)
  225. s.reply(conn, res)
  226. replied = true
  227. // A command that would cut off its own reply acts only now, once the answer is out.
  228. if after != nil {
  229. after()
  230. }
  231. }
  232. // serve runs one request and describes the outcome. after, when not nil, is the
  233. // command's AfterReply, which the caller runs once the response has been written.
  234. //
  235. // Nothing bounds a command's run; a context would have to reach every Run. What is
  236. // bounded is the silence: a command still running at slowAfter is named in the log, with
  237. // the slot it is holding, so a socket that has gone "busy" can be traced to the commands
  238. // that wedged it. The timer is stopped however the command ends, a panic included, so
  239. // the line is only ever written about a command that is still running.
  240. func (s *Server) serve(req *Request) (res *Response, after func()) {
  241. slow := time.AfterFunc(s.slowAfter, func() {
  242. s.l.Warn("Local control command is still running and holding a connection slot",
  243. "command", req.Command,
  244. "args", req.Args,
  245. "after", s.slowAfter,
  246. "max", s.maxConns,
  247. )
  248. })
  249. defer slow.Stop()
  250. res = &Response{Version: ProtocolVersion}
  251. reg := s.registry()
  252. if req.Describe {
  253. schema, err := json.Marshal(reg.Schema())
  254. if err != nil {
  255. res.Error = err.Error()
  256. return res, nil
  257. }
  258. res.Result = schema
  259. return res, nil
  260. }
  261. if req.Command == "" {
  262. res.Error = "no command given"
  263. return res, nil
  264. }
  265. params, err := stringParams(req.Params)
  266. if err != nil {
  267. res.Error = err.Error()
  268. return res, nil
  269. }
  270. format, err := parseFormat(req.Format)
  271. if err != nil {
  272. res.Error = err.Error()
  273. return res, nil
  274. }
  275. cmd, err := reg.Resolve(commands.Name(req.Command))
  276. if err != nil {
  277. s.l.Debug("Local control command refused", "command", req.Command, "error", err)
  278. res.Error = err.Error()
  279. return res, nil
  280. }
  281. r, err := commands.Invoke(commands.Request{Command: cmd, Params: params, Args: req.Args})
  282. if err != nil {
  283. s.l.Debug("Local control command failed", "command", req.Command, "error", err)
  284. res.Error = err.Error()
  285. return res, nil
  286. }
  287. after = cmd.AfterReply
  288. // A command with nothing to say gets an empty, successful reply.
  289. if r.Empty() {
  290. return res, after
  291. }
  292. return render(res, cmd, r, format), after
  293. }
  294. // render turns a command's Result into the response, in the format the caller asked for.
  295. // Both formats come from commands.Render, which is where ssh and the control socket
  296. // meet: the JSON a program reads here is byte for byte what -json prints at an ssh
  297. // prompt, and the text is the command's own rendering, which only the daemon can run.
  298. func render(res *Response, cmd *commands.Command, r commands.Result, format commands.Format) *Response {
  299. buf := &bytes.Buffer{}
  300. if err := commands.Render(cmd, r, format, buf); err != nil {
  301. res.Error = err.Error()
  302. return res
  303. }
  304. if format != commands.FormatText {
  305. // The caller is a program. Carry the payload as JSON rather than as a string that
  306. // happens to contain some; the newline Render ends a line with is not the value's.
  307. res.Result = bytes.TrimSuffix(buf.Bytes(), []byte("\n"))
  308. // A command that ran and refused is a failure to whoever asked, in either format.
  309. // The structured outcome still travels in Result for a program to read; Error
  310. // carries the reason so the one rule, Error set means failed, holds here too and
  311. // a client never has to guess from the payload.
  312. if !r.OK {
  313. res.Error = r.Reason
  314. }
  315. return res
  316. }
  317. // A command that ran and refused reads as an error to whoever asked for text: they
  318. // are a person or a script, and both expect a refusal on the error stream.
  319. if !r.OK {
  320. res.Error = buf.String()
  321. return res
  322. }
  323. // The bytes the command wrote, as they are; see Response.Text.
  324. res.Text = buf.Bytes()
  325. return res
  326. }
  327. // parseFormat turns the request's format into the one the registry understands. An
  328. // unknown value is an error rather than a silent default, so a client that asks for
  329. // something this daemon cannot do hears about it.
  330. func parseFormat(s string) (commands.Format, error) {
  331. switch s {
  332. case "", "json":
  333. return commands.FormatJSON, nil
  334. case "text":
  335. return commands.FormatText, nil
  336. default:
  337. return commands.FormatJSON, fmt.Errorf("unknown format %q, expected \"json\" or \"text\"", s)
  338. }
  339. }
  340. // reply writes one response frame.
  341. func (s *Server) reply(conn net.Conn, res *Response) {
  342. _ = conn.SetWriteDeadline(time.Now().Add(writeTimeout))
  343. if err := writeFrame(conn, res); err != nil {
  344. s.l.Debug("Could not send a local control response", "error", err)
  345. }
  346. }
  347. // stringParams converts JSON's types into the strings flag.Value.Set expects, so a
  348. // caller writes true and 3 rather than "true" and "3".
  349. func stringParams(in map[string]any) (map[string]string, error) {
  350. if len(in) == 0 {
  351. return nil, nil
  352. }
  353. out := make(map[string]string, len(in))
  354. for k, v := range in {
  355. switch t := v.(type) {
  356. case bool:
  357. out[k] = strconv.FormatBool(t)
  358. case string:
  359. out[k] = t
  360. case float64:
  361. // encoding/json gives every number as a float64; render whole ones without
  362. // a decimal point so an int flag accepts them.
  363. if t != math.Trunc(t) {
  364. out[k] = strconv.FormatFloat(t, 'f', -1, 64)
  365. continue
  366. }
  367. // A whole float64 can be far outside int64, and Go's conversion of one that is
  368. // gives an unspecified value rather than an error, so the bounds are checked
  369. // first. MinInt64 is exactly representable and is in range. MaxInt64 is not:
  370. // as a float64 it rounds up to 2^63, which is the first value out of range,
  371. // hence the >= on that side.
  372. if t < math.MinInt64 || t >= math.MaxInt64 {
  373. return nil, fmt.Errorf("parameter %q is %s, outside the range of a 64-bit integer",
  374. k, strconv.FormatFloat(t, 'g', -1, 64))
  375. }
  376. out[k] = strconv.FormatInt(int64(t), 10)
  377. case nil:
  378. return nil, fmt.Errorf("parameter %q has no value", k)
  379. default:
  380. return nil, fmt.Errorf("parameter %q is not a string, number or boolean", k)
  381. }
  382. }
  383. return out, nil
  384. }
  385. func readRequest(conn net.Conn) (*Request, error) {
  386. req := &Request{}
  387. if err := readFrame(conn, MaxRequestBytes, "request", req, true); err != nil {
  388. return nil, err
  389. }
  390. if req.Version != ProtocolVersion {
  391. return nil, fmt.Errorf(
  392. "this nebula speaks local control protocol v%d, the client sent v%d; the two binaries are different versions",
  393. ProtocolVersion, req.Version,
  394. )
  395. }
  396. return req, nil
  397. }