server.go 14 KB

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