server.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. package localapi
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "log/slog"
  9. "math"
  10. "net"
  11. "os"
  12. "runtime/debug"
  13. "strconv"
  14. "sync"
  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 the read only portion of a command registry over a local socket.
  39. type Server struct {
  40. l *slog.Logger
  41. src *commands.Registry
  42. maxConns int
  43. slowAfter time.Duration
  44. mu sync.Mutex
  45. ln net.Listener
  46. path string
  47. running bool
  48. unwatch func() bool
  49. sem chan struct{}
  50. euid uint32
  51. // access is what the socket at path was bound with, so a reload that changes only
  52. // the group or the mode is applied without moving the socket.
  53. access Access
  54. // trustSocket is set while a group shares the socket. The kernel admitted the peer
  55. // through the socket's mode and group, which is the whole access control then, and
  56. // the peer check in serve stands aside: on unix it sees the peer's uid and primary
  57. // gid only, so it could not tell a member of the group from anyone else.
  58. trustSocket atomic.Bool
  59. }
  60. // servedHere decides what this surface will run.
  61. //
  62. // Everything except the profiling commands, which stay on the ssh debug server. Profile
  63. // output is written on the host running nebula, under sshd.sandbox_dir, so a path typed
  64. // here would not mean what the person typing it expects; and the profiling commands are
  65. // one workflow, so splitting them would leave someone able to start a CPU profile in one
  66. // place and stop it only in another.
  67. func servedHere(c *commands.Command) bool {
  68. return !c.ForProfiling
  69. }
  70. // notServedHere explains a refusal in terms an operator can act on, naming the surface
  71. // that will run the command instead.
  72. const notServedHere = "profiling is driven from the ssh debug server, where the sshd.sandbox_dir " +
  73. "setting governs where profile output is written on the host running nebula"
  74. // NewServer returns a Server that will serve the commands in reg that this surface
  75. // accepts.
  76. //
  77. // reg is kept rather than filtered here, so a command registered on it after the server
  78. // is built is served too. Filtering happens per request, which is how the ssh server
  79. // treats the same registry, cloning it per session.
  80. func NewServer(l *slog.Logger, reg *commands.Registry) *Server {
  81. return &Server{
  82. l: l,
  83. src: reg,
  84. maxConns: defaultMaxConns,
  85. slowAfter: defaultSlowAfter,
  86. // The same identity the client's owner check is made for, so the peer check
  87. // cannot disagree with it, and so a test can stand in for it the same way.
  88. euid: currentUID(),
  89. }
  90. }
  91. // Rebind brings the server up at path, moves a running server there, and serves it until
  92. // Stop or ctx cancellation. It is the one way the server comes up, so Wire's first
  93. // attempt and its reload callback are the same call, and a server already serving path
  94. // is left as it is: Wire registers the callback before the first attempt, so a HUP that
  95. // lands between the two has the callback do the first bind, and the first attempt then
  96. // finds the socket it was about to create already up.
  97. //
  98. // The new socket is bound before the old one is given up: a bad path in a reloaded
  99. // config leaves the operator with the endpoint they already had rather than none.
  100. //
  101. // s.mu is held for the whole operation, bind included. Releasing it around listen would
  102. // let a Stop land in between, and this would then install a fresh listener on a server
  103. // that had just shut down, leaving a live socket on disk until the process exits. listen
  104. // does not take s.mu, so holding it costs the other methods one bind's wait, at most the
  105. // stale socket probe, once per reload.
  106. //
  107. // An error here is never fatal to the caller: nebula keeps forwarding packets without a
  108. // control endpoint, and the caller is expected to log and carry on.
  109. func (s *Server) Rebind(ctx context.Context, path string, acc Access) error {
  110. s.mu.Lock()
  111. defer s.mu.Unlock()
  112. reopening := false
  113. if s.running && s.path == path {
  114. if s.access.equal(acc) {
  115. return nil
  116. }
  117. // Only the access changed. On unix it is set on the socket in place; on windows
  118. // the descriptor is fixed when the pipe is created, so the pipe is created again.
  119. // The name cannot be held twice, so the old one goes first, and a bind that then
  120. // fails leaves the server down rather than on the access the operator asked to
  121. // change; that is reported.
  122. err := applyAccess(path, acc)
  123. if err == nil {
  124. s.access = acc
  125. s.trustSocket.Store(acc.Group != nil)
  126. s.l.Info("Changed the local control socket's access", "path", path, "access", acc.String())
  127. return nil
  128. }
  129. if !errors.Is(err, errReopenForAccess) {
  130. return fmt.Errorf("could not change the access of %s: %w", path, err)
  131. }
  132. reopening = true
  133. release(s.ln, s.path)
  134. s.ln, s.path, s.running, s.access = nil, "", false, Access{}
  135. }
  136. ln, err := listen(path, acc)
  137. if err != nil {
  138. if reopening {
  139. return fmt.Errorf("the local control socket was closed to change its access and could not be created again: %w", err)
  140. }
  141. return err
  142. }
  143. // A reload that arrives during shutdown must not bring the endpoint back. Stop is
  144. // not terminal (Rebind after Stop is allowed, and is how a server whose first bind
  145. // failed comes up), so running is no guide; the context is what shutdown cancels,
  146. // and it is checked here, while nothing else can change the server's state.
  147. if err := ctx.Err(); err != nil {
  148. release(ln, path)
  149. return fmt.Errorf("not starting the local control socket, nebula is shutting down: %w", err)
  150. }
  151. oldLn, oldPath := s.ln, s.path
  152. s.ln = ln
  153. s.path = path
  154. s.access = acc
  155. s.running = true
  156. s.trustSocket.Store(acc.Group != nil)
  157. sem := s.semaphore()
  158. s.watchLocked(ctx)
  159. // Stop accepting on the old listener. In flight commands keep their connections,
  160. // which stay usable until they finish. Its accept loop exits on the closed-listener
  161. // error, so it is gone as soon as this returns. oldPath differs from path whenever
  162. // oldLn is set: a running server on the same path returned above.
  163. if oldLn != nil {
  164. release(oldLn, oldPath)
  165. }
  166. logListening(s.l, path, acc)
  167. go func() {
  168. s.accept(ln, sem)
  169. }()
  170. return nil
  171. }
  172. // watchLocked stops the server when ctx is cancelled. One watcher covers a run of the
  173. // server, from the first bind that succeeds until Stop: a daemon whose initial listen
  174. // failed and which later Rebinds onto a good path from a reloaded config still needs its
  175. // socket removed on shutdown, and a Rebind on a running server must not stack a second
  176. // watcher on the same ctx. Stop detaches the watcher, so a server started again
  177. // afterwards is watched on the ctx that started it, and the ctx of an earlier run no
  178. // longer reaches it whenever that one ends. Callers hold s.mu.
  179. func (s *Server) watchLocked(ctx context.Context) {
  180. if s.unwatch != nil {
  181. return
  182. }
  183. s.unwatch = context.AfterFunc(ctx, s.Stop)
  184. }
  185. // registry returns the commands this surface serves, filtered from the source at the
  186. // time of the call. It is a few allocations per request, which a control socket that
  187. // sees a request every few seconds at most never notices.
  188. func (s *Server) registry() *commands.Registry {
  189. return s.src.Filter(servedHere, notServedHere)
  190. }
  191. // Path returns the socket path currently being served, or "" when stopped.
  192. func (s *Server) Path() string {
  193. s.mu.Lock()
  194. defer s.mu.Unlock()
  195. return s.path
  196. }
  197. // Stop closes the listener and removes the socket. In flight commands are allowed to
  198. // finish; Stop does not wait for them.
  199. //
  200. // The whole of it runs under s.mu, listener and socket included, so a Rebind cannot land
  201. // between the state change and the close and find a path that is still bound by the
  202. // listener it is replacing.
  203. func (s *Server) Stop() {
  204. s.mu.Lock()
  205. defer s.mu.Unlock()
  206. if !s.running {
  207. return
  208. }
  209. release(s.ln, s.path)
  210. s.running = false
  211. s.access = Access{}
  212. s.ln = nil
  213. s.path = ""
  214. // Set whenever running is. When this Stop is the watcher's own, stopping a watcher
  215. // that has already fired is a no-op.
  216. s.unwatch()
  217. s.unwatch = nil
  218. }
  219. // release gives up a listener and the socket node at its path. The node goes first,
  220. // while the listener is still open: for as long as it is, the node is this server's,
  221. // since a starting daemon's stale check refuses to remove a socket that answers. The
  222. // other order opens a window between the close and the unlink in which a new daemon can
  223. // find the node stale, clear it and bind its own at the same path, which the unlink
  224. // then takes from under it, leaving that daemon serving a socket nothing can reach.
  225. // listen turns off the listener's own unlink on close so this is the only unlink.
  226. func release(ln net.Listener, path string) {
  227. removeSocket(path)
  228. _ = ln.Close()
  229. }
  230. // semaphore returns the connection limiter, creating it on first use. It is made once
  231. // and never replaced: a handler releases into the channel it acquired from, so swapping
  232. // in a fresh one across a Stop and Start would leave any handler still in flight
  233. // blocked forever on a channel nothing had filled. Callers hold s.mu.
  234. func (s *Server) semaphore() chan struct{} {
  235. if s.sem == nil {
  236. s.sem = make(chan struct{}, s.maxConns)
  237. }
  238. return s.sem
  239. }
  240. // Accept failures that are not the listener closing are retried after a pause that
  241. // doubles from acceptRetryMin up to acceptRetryMax, the same shape as net/http.Serve.
  242. const (
  243. acceptRetryMin = 5 * time.Millisecond
  244. acceptRetryMax = time.Second
  245. )
  246. // accept serves connections from ln until it is closed. sem is the limiter to acquire
  247. // from and release into; it is passed rather than read from s so the handlers spawned
  248. // here never touch s.sem without the lock.
  249. //
  250. // Only a closed listener ends the loop; Stop and Rebind are the two things that close
  251. // it. Every other error is retried: the process being out of descriptors (EMFILE,
  252. // ENFILE) or a client that hung up between the kernel's queue and us (ECONNABORTED)
  253. // are passing conditions, and giving up on them would leave a socket on disk that
  254. // nothing answers, with the reason buried in a log line from minutes or days earlier.
  255. func (s *Server) accept(ln net.Listener, sem chan struct{}) {
  256. var delay time.Duration
  257. for {
  258. conn, err := ln.Accept()
  259. if err != nil {
  260. if errors.Is(err, net.ErrClosed) {
  261. return
  262. }
  263. if ne, ok := err.(net.Error); ok && ne.Timeout() {
  264. continue
  265. }
  266. if delay == 0 {
  267. delay = acceptRetryMin
  268. } else {
  269. delay = min(delay*2, acceptRetryMax)
  270. }
  271. s.l.Warn("Local control socket could not accept a connection, retrying", "error", err, "retry_in", delay)
  272. time.Sleep(delay)
  273. continue
  274. }
  275. delay = 0
  276. select {
  277. case sem <- struct{}{}:
  278. default:
  279. // At capacity. Say so and hang up rather than holding the connection open.
  280. // Logged too: a slot is held for as long as a command runs, and the slow
  281. // command warning in serve says which ones are still running.
  282. s.l.Warn("Local control socket is at capacity, refusing a connection", "max", s.maxConns)
  283. s.reply(conn, &Response{Version: ProtocolVersion, Error: "local control socket is busy, try again"})
  284. _ = conn.Close()
  285. continue
  286. }
  287. go func() {
  288. defer func() { <-sem }()
  289. s.handle(conn)
  290. }()
  291. }
  292. }
  293. // handle serves one request. It recovers from panics in command callbacks: this
  294. // surface is present on every install and reachable by any local process running as
  295. // the daemon's user, so a malformed argument must cost a connection rather than the
  296. // overlay.
  297. func (s *Server) handle(conn net.Conn) {
  298. defer conn.Close()
  299. var (
  300. args []string
  301. replied bool
  302. )
  303. defer func() {
  304. if r := recover(); r != nil {
  305. s.l.Error("Recovered from panic serving a local control command",
  306. "panic", r,
  307. "args", args,
  308. "stack", string(debug.Stack()),
  309. )
  310. // A panic in a command's AfterReply arrives here after the answer has gone
  311. // out; the client has its reply, so it is not sent a second one.
  312. if !replied {
  313. s.reply(conn, &Response{Version: ProtocolVersion, Error: "command failed"})
  314. }
  315. }
  316. }()
  317. // Where the platform can identify the caller, check it. Where it cannot, or where a
  318. // group shares the socket, the endpoint is protected by the socket's mode and group
  319. // or the pipe's security descriptor, which listen() set before serving.
  320. if peerCheckSupported && !s.trustSocket.Load() {
  321. uid, err := peerUID(conn)
  322. if err != nil {
  323. s.l.Warn("Refusing local control connection with unreadable credentials", "error", err)
  324. return
  325. }
  326. // Access is restricted to the user who ran nebula. Root is allowed through
  327. // because it can reach the daemon's memory regardless of what this check says.
  328. if uid != s.euid && uid != 0 {
  329. s.l.Warn("Refusing local control connection from another user", "peerUid", uid)
  330. s.reply(conn, &Response{Version: ProtocolVersion, Error: "not permitted"})
  331. return
  332. }
  333. }
  334. _ = conn.SetReadDeadline(time.Now().Add(readTimeout))
  335. req, err := readRequest(conn)
  336. if err != nil {
  337. s.reply(conn, &Response{Version: ProtocolVersion, Error: err.Error()})
  338. return
  339. }
  340. args = req.Args
  341. // No deadline while a command runs; the write deadline below is what bounds a client
  342. // that stops reading.
  343. _ = conn.SetReadDeadline(time.Time{})
  344. _ = conn.SetWriteDeadline(time.Now().Add(writeTimeout))
  345. res, after := s.serve(req)
  346. s.reply(conn, res)
  347. replied = true
  348. // A command that would cut off its own reply acts only now, once the answer is out.
  349. if after != nil {
  350. after()
  351. }
  352. }
  353. // serve runs one request and describes the outcome. after, when not nil, is the
  354. // command's AfterReply, which the caller runs once the response has been written.
  355. //
  356. // Nothing bounds a command's run; a context would have to reach every Run. What is
  357. // bounded is the silence: a command still running at slowAfter is named in the log, with
  358. // the slot it is holding, so a socket that has gone "busy" can be traced to the commands
  359. // that wedged it. The timer is stopped however the command ends, a panic included, so
  360. // the line is only ever written about a command that is still running.
  361. func (s *Server) serve(req *Request) (res *Response, after func()) {
  362. slow := time.AfterFunc(s.slowAfter, func() {
  363. s.l.Warn("Local control command is still running and holding a connection slot",
  364. "command", req.Command,
  365. "args", req.Args,
  366. "after", s.slowAfter,
  367. "max", s.maxConns,
  368. )
  369. })
  370. defer slow.Stop()
  371. res = &Response{Version: ProtocolVersion}
  372. reg := s.registry()
  373. if req.Describe {
  374. schema, err := json.Marshal(reg.Schema())
  375. if err != nil {
  376. res.Error = err.Error()
  377. return res, nil
  378. }
  379. res.Result = schema
  380. return res, nil
  381. }
  382. if req.Command == "" {
  383. res.Error = "no command given"
  384. return res, nil
  385. }
  386. params, err := stringParams(req.Params)
  387. if err != nil {
  388. res.Error = err.Error()
  389. return res, nil
  390. }
  391. format, err := parseFormat(req.Format)
  392. if err != nil {
  393. res.Error = err.Error()
  394. return res, nil
  395. }
  396. cmd, err := reg.Resolve(commands.Name(req.Command))
  397. if err != nil {
  398. s.l.Debug("Local control command refused", "command", req.Command, "error", err)
  399. res.Error = err.Error()
  400. return res, nil
  401. }
  402. r, err := commands.Invoke(commands.Request{Command: cmd, Params: params, Args: req.Args})
  403. if err != nil {
  404. s.l.Debug("Local control command failed", "command", req.Command, "error", err)
  405. res.Error = err.Error()
  406. return res, nil
  407. }
  408. after = cmd.AfterReply
  409. // A command with nothing to say gets an empty, successful reply.
  410. if r.Empty() {
  411. return res, after
  412. }
  413. return render(res, cmd, r, format), after
  414. }
  415. // render turns a command's Result into the response, in the format the caller asked for.
  416. // Both formats come from commands.Render, which is where ssh and the control socket
  417. // meet: the JSON a program reads here is byte for byte what -json prints at an ssh
  418. // prompt, and the text is the command's own rendering, which only the daemon can run.
  419. func render(res *Response, cmd *commands.Command, r commands.Result, format commands.Format) *Response {
  420. buf := &bytes.Buffer{}
  421. if err := commands.Render(cmd, r, format, buf); err != nil {
  422. res.Error = err.Error()
  423. return res
  424. }
  425. if format != commands.FormatText {
  426. // The caller is a program. Carry the payload as JSON rather than as a string that
  427. // happens to contain some; the newline Render ends a line with is not the value's.
  428. res.Result = bytes.TrimSuffix(buf.Bytes(), []byte("\n"))
  429. // A command that ran and refused is a failure to whoever asked, in either format.
  430. // The structured outcome still travels in Result for a program to read; Error
  431. // carries the reason so the one rule, Error set means failed, holds here too and
  432. // a client never has to guess from the payload.
  433. if !r.OK {
  434. res.Error = r.Reason
  435. }
  436. return res
  437. }
  438. // A command that ran and refused reads as an error to whoever asked for text: they
  439. // are a person or a script, and both expect a refusal on the error stream.
  440. if !r.OK {
  441. res.Error = buf.String()
  442. return res
  443. }
  444. quoted, err := json.Marshal(buf.String())
  445. if err != nil {
  446. res.Error = err.Error()
  447. return res
  448. }
  449. res.Result = quoted
  450. return res
  451. }
  452. // parseFormat turns the request's format into the one the registry understands. An
  453. // unknown value is an error rather than a silent default, so a client that asks for
  454. // something this daemon cannot do hears about it.
  455. func parseFormat(s string) (commands.Format, error) {
  456. switch s {
  457. case "", "json":
  458. return commands.FormatJSON, nil
  459. case "text":
  460. return commands.FormatText, nil
  461. default:
  462. return commands.FormatJSON, fmt.Errorf("unknown format %q, expected \"json\" or \"text\"", s)
  463. }
  464. }
  465. // reply writes one response frame.
  466. func (s *Server) reply(conn net.Conn, res *Response) {
  467. _ = conn.SetWriteDeadline(time.Now().Add(writeTimeout))
  468. if err := writeFrame(conn, res); err != nil {
  469. s.l.Debug("Could not send a local control response", "error", err)
  470. }
  471. }
  472. // stringParams converts JSON's types into the strings flag.Value.Set expects, so a
  473. // caller writes true and 3 rather than "true" and "3".
  474. func stringParams(in map[string]any) (map[string]string, error) {
  475. if len(in) == 0 {
  476. return nil, nil
  477. }
  478. out := make(map[string]string, len(in))
  479. for k, v := range in {
  480. switch t := v.(type) {
  481. case bool:
  482. out[k] = strconv.FormatBool(t)
  483. case string:
  484. out[k] = t
  485. case float64:
  486. // encoding/json gives every number as a float64; render whole ones without
  487. // a decimal point so an int flag accepts them.
  488. if t != math.Trunc(t) {
  489. out[k] = strconv.FormatFloat(t, 'f', -1, 64)
  490. continue
  491. }
  492. // A whole float64 can be far outside int64, and Go's conversion of one that is
  493. // gives an unspecified value rather than an error, so the bounds are checked
  494. // first. MinInt64 is exactly representable and is in range. MaxInt64 is not:
  495. // as a float64 it rounds up to 2^63, which is the first value out of range,
  496. // hence the >= on that side.
  497. if t < math.MinInt64 || t >= math.MaxInt64 {
  498. return nil, fmt.Errorf("parameter %q is %s, outside the range of a 64-bit integer",
  499. k, strconv.FormatFloat(t, 'g', -1, 64))
  500. }
  501. out[k] = strconv.FormatInt(int64(t), 10)
  502. case nil:
  503. return nil, fmt.Errorf("parameter %q has no value", k)
  504. default:
  505. return nil, fmt.Errorf("parameter %q is not a string, number or boolean", k)
  506. }
  507. }
  508. return out, nil
  509. }
  510. func readRequest(conn net.Conn) (*Request, error) {
  511. req := &Request{}
  512. if err := readFrame(conn, MaxRequestBytes, "request", req); err != nil {
  513. return nil, err
  514. }
  515. if req.Version != ProtocolVersion {
  516. return nil, fmt.Errorf(
  517. "this nebula speaks local control protocol v%d, the client sent v%d; the two binaries are different versions",
  518. ProtocolVersion, req.Version,
  519. )
  520. }
  521. return req, nil
  522. }