commands.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. package nebula
  2. import (
  3. "encoding/json"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "maps"
  9. "net/netip"
  10. "sort"
  11. "strings"
  12. "github.com/slackhq/nebula/cert"
  13. "github.com/slackhq/nebula/commands"
  14. "github.com/slackhq/nebula/config"
  15. "github.com/slackhq/nebula/header"
  16. "github.com/slackhq/nebula/logging"
  17. )
  18. // The commands nebula serves. attachCommands builds the one registry every surface
  19. // reads; the ssh debug server is a consumer of it, not its owner, which is why this
  20. // is not ssh.go. See the commands package for what a Command is.
  21. type listHostMapFlags struct {
  22. ByIndex bool
  23. }
  24. type printCertFlags struct {
  25. Raw bool
  26. }
  27. type changeRemoteFlags struct {
  28. Address string
  29. }
  30. type closeTunnelFlags struct {
  31. LocalOnly bool
  32. }
  33. type createTunnelFlags struct {
  34. Address string
  35. }
  36. // lighthouseInfo is one entry of list-lighthouse-addrmap.
  37. type lighthouseInfo struct {
  38. VpnAddr string `json:"vpnAddr"`
  39. Addrs *CacheMap `json:"addrs"`
  40. }
  41. // deviceInfo is the result of device-info.
  42. type deviceInfo struct {
  43. Name string `json:"name"`
  44. Cidr []netip.Prefix `json:"cidr"`
  45. }
  46. // certPEM is the result of print-cert -raw. The PEM form is a different thing to emit,
  47. // not a different formatting of the certificate, so it is its own shape.
  48. type certPEM struct {
  49. PEM string `json:"pem"`
  50. }
  51. // Result shapes for print-relays. Field names are the JSON keys and predate this
  52. // refactor; changing them would change the output.
  53. type relayFor struct {
  54. Error string `json:",omitempty"`
  55. Type string
  56. State string
  57. PeerAddr netip.Addr
  58. LocalIndex uint32
  59. RemoteIndex uint32
  60. RelayedThrough []netip.Addr
  61. }
  62. type relayOutput struct {
  63. NebulaAddr netip.Addr
  64. RelayForAddrs []relayFor
  65. }
  66. type relaysOutput struct {
  67. Relays []*relayOutput
  68. }
  69. type versionInfo struct {
  70. Version string `json:"version"`
  71. }
  72. type logLevelInfo struct {
  73. Level string `json:"level"`
  74. }
  75. type logFormatInfo struct {
  76. Format string `json:"format"`
  77. }
  78. // defineListHostMapFlags declares the flags list-hostmap and list-pending-hostmap share.
  79. func defineListHostMapFlags(fl *flag.FlagSet, s *listHostMapFlags) {
  80. fl.BoolVar(&s.ByIndex, "by-index", false, "gets all hosts in the hostmap from the index table")
  81. }
  82. func attachCommands(l *slog.Logger, c *config.C, reg *commands.Registry, f *Interface) {
  83. reg.Register(commands.Flagged(&commands.Command{
  84. Name: commands.ListHostmap,
  85. ShortDescription: "List all known previously connected hosts",
  86. Text: commands.Typed(textListHostMap),
  87. }, defineListHostMapFlags, func(s *listHostMapFlags, a []string) (commands.Result, error) {
  88. return runListHostMap(f.hostMap, s)
  89. }))
  90. reg.Register(commands.Flagged(&commands.Command{
  91. Name: commands.ListPendingHostmap,
  92. ShortDescription: "List all handshaking hosts",
  93. Text: commands.Typed(textListHostMap),
  94. }, defineListHostMapFlags, func(s *listHostMapFlags, a []string) (commands.Result, error) {
  95. return runListHostMap(f.handshakeManager, s)
  96. }))
  97. reg.Register(&commands.Command{
  98. Name: commands.ListLighthouseAddrmap,
  99. ShortDescription: "List all lighthouse map entries",
  100. Run: func(fs any, a []string) (commands.Result, error) {
  101. return runListLighthouseMap(f.lightHouse)
  102. },
  103. Text: commands.Typed(textListLighthouseMap),
  104. })
  105. reg.Register(&commands.Command{
  106. Name: commands.Reload,
  107. ShortDescription: "Reloads configuration from disk, same as sending HUP to the process",
  108. Run: func(fs any, a []string) (commands.Result, error) {
  109. return runReload(l, c)
  110. },
  111. })
  112. reg.Register(&commands.Command{
  113. Name: commands.LogLevel,
  114. ShortDescription: "Gets or sets the current log level",
  115. Run: func(fs any, a []string) (commands.Result, error) {
  116. return runLogLevel(l, a)
  117. },
  118. Text: commands.Typed(textLogLevel),
  119. })
  120. reg.Register(&commands.Command{
  121. Name: commands.LogFormat,
  122. ShortDescription: "Gets or sets the current log format",
  123. Run: func(fs any, a []string) (commands.Result, error) {
  124. return runLogFormat(l, a)
  125. },
  126. Text: commands.Typed(textLogFormat),
  127. })
  128. reg.Register(&commands.Command{
  129. Name: commands.Version,
  130. ShortDescription: "Prints the currently running version of nebula",
  131. Run: func(fs any, a []string) (commands.Result, error) {
  132. return commands.Value(versionInfo{Version: f.version}), nil
  133. },
  134. Text: commands.Typed(textVersion),
  135. })
  136. reg.Register(&commands.Command{
  137. Name: commands.DeviceInfo,
  138. ShortDescription: "Prints information about the network device.",
  139. Run: func(fs any, a []string) (commands.Result, error) {
  140. return commands.Value(runDeviceInfo(f)), nil
  141. },
  142. Text: commands.Typed(textDeviceInfo),
  143. })
  144. reg.Register(commands.Flagged(&commands.Command{
  145. Name: commands.PrintCert,
  146. ShortDescription: "Prints the current certificate being used or the certificate for the provided vpn addr",
  147. Text: textPrintCert,
  148. }, func(fl *flag.FlagSet, s *printCertFlags) {
  149. fl.BoolVar(&s.Raw, "raw", false, "prints the PEM encoded certificate; with -json, an object holding it")
  150. }, func(s *printCertFlags, a []string) (commands.Result, error) {
  151. return runPrintCert(f, s, a)
  152. }))
  153. reg.Register(&commands.Command{
  154. Name: commands.PrintTunnel,
  155. ShortDescription: "Prints json details about a tunnel for the provided vpn addr",
  156. Run: func(fs any, a []string) (commands.Result, error) {
  157. return runPrintTunnel(f, a)
  158. },
  159. // No Text on purpose: nothing human readable exists yet, and shipping an interim
  160. // form would invite scripts against it. ssh defaults these to JSON; ctl asks.
  161. JSONOnly: true,
  162. })
  163. reg.Register(&commands.Command{
  164. Name: commands.PrintRelays,
  165. ShortDescription: "Prints json details about all relay info",
  166. Run: func(fs any, a []string) (commands.Result, error) {
  167. return commands.Value(runPrintRelays(f)), nil
  168. },
  169. // No Text on purpose; see print-tunnel.
  170. JSONOnly: true,
  171. })
  172. reg.Register(commands.Flagged(&commands.Command{
  173. Name: commands.ChangeRemote,
  174. ShortDescription: "Changes the remote address used in the tunnel for the provided vpn addr",
  175. }, func(fl *flag.FlagSet, s *changeRemoteFlags) {
  176. fl.StringVar(&s.Address, "address", "", "The new remote address, ip:port")
  177. }, func(s *changeRemoteFlags, a []string) (commands.Result, error) {
  178. return runChangeRemote(f, s, a)
  179. }))
  180. reg.Register(commands.Flagged(&commands.Command{
  181. Name: commands.CloseTunnel,
  182. ShortDescription: "Closes a tunnel for the provided vpn addr",
  183. }, func(fl *flag.FlagSet, s *closeTunnelFlags) {
  184. fl.BoolVar(&s.LocalOnly, "local-only", false, "Disables notifying the remote that the tunnel is shutting down")
  185. }, func(s *closeTunnelFlags, a []string) (commands.Result, error) {
  186. return runCloseTunnel(f, s, a)
  187. }))
  188. reg.Register(commands.Flagged(&commands.Command{
  189. Name: commands.CreateTunnel,
  190. ShortDescription: "Creates a tunnel for the provided vpn address",
  191. Help: "The lighthouses will be queried for real addresses but you can provide one as well.",
  192. }, func(fl *flag.FlagSet, s *createTunnelFlags) {
  193. fl.StringVar(&s.Address, "address", "", "Optionally provide a real remote address, ip:port ")
  194. }, func(s *createTunnelFlags, a []string) (commands.Result, error) {
  195. return runCreateTunnel(f, s, a)
  196. }))
  197. reg.Register(&commands.Command{
  198. Name: commands.QueryLighthouse,
  199. ShortDescription: "Query the lighthouses for the provided vpn address",
  200. Help: "This command is asynchronous. Only currently known udp addresses will be printed.",
  201. Run: func(fs any, a []string) (commands.Result, error) {
  202. return runQueryLighthouse(f, a)
  203. },
  204. // No Text on purpose; see print-tunnel.
  205. JSONOnly: true,
  206. })
  207. }
  208. // runListHostMap computes the hostmap listing. Presentation is Render's job.
  209. func runListHostMap(hl controlHostLister, fs *listHostMapFlags) (commands.Result, error) {
  210. var hm []ControlHostInfo
  211. if fs.ByIndex {
  212. hm = listHostMapIndexes(hl)
  213. } else {
  214. hm = listHostMapHosts(hl)
  215. }
  216. sort.Slice(hm, func(i, j int) bool {
  217. return hm[i].VpnAddrs[0].Compare(hm[j].VpnAddrs[0]) < 0
  218. })
  219. return commands.Value(hm), nil
  220. }
  221. // textListHostMap is the human rendering, shared by every surface.
  222. func textListHostMap(hm []ControlHostInfo, w io.Writer) error {
  223. for _, h := range hm {
  224. if _, err := fmt.Fprintf(w, "%s: %s\n", h.VpnAddrs, h.RemoteAddrs); err != nil {
  225. return err
  226. }
  227. }
  228. return nil
  229. }
  230. func runListLighthouseMap(lightHouse *LightHouse) (commands.Result, error) {
  231. lightHouse.RLock()
  232. addrMap := make([]lighthouseInfo, len(lightHouse.addrMap))
  233. x := 0
  234. for k, v := range lightHouse.addrMap {
  235. addrMap[x] = lighthouseInfo{
  236. VpnAddr: k.String(),
  237. Addrs: v.CopyCache(),
  238. }
  239. x++
  240. }
  241. lightHouse.RUnlock()
  242. sort.Slice(addrMap, func(i, j int) bool {
  243. return strings.Compare(addrMap[i].VpnAddr, addrMap[j].VpnAddr) < 0
  244. })
  245. return commands.Value(addrMap), nil
  246. }
  247. func textListLighthouseMap(addrMap []lighthouseInfo, w io.Writer) error {
  248. for _, e := range addrMap {
  249. b, err := json.Marshal(e.Addrs)
  250. if err != nil {
  251. return err
  252. }
  253. if _, err := fmt.Fprintf(w, "%s: %s\n", e.VpnAddr, string(b)); err != nil {
  254. return err
  255. }
  256. }
  257. return nil
  258. }
  259. func textVersion(vi versionInfo, w io.Writer) error {
  260. _, err := fmt.Fprintf(w, "%s\n", vi.Version)
  261. return err
  262. }
  263. func runQueryLighthouse(ifce *Interface, a []string) (commands.Result, error) {
  264. if len(a) == 0 {
  265. return commands.Failed("No vpn address was provided"), nil
  266. }
  267. vpnAddr, err := netip.ParseAddr(a[0])
  268. if err != nil || !vpnAddr.IsValid() {
  269. return commands.Failed("The provided vpn address could not be parsed: %s", a[0]), nil
  270. }
  271. var cm *CacheMap
  272. rl := ifce.lightHouse.Query(vpnAddr)
  273. if rl != nil {
  274. cm = rl.CopyCache()
  275. }
  276. return commands.Value(cm), nil
  277. }
  278. func runCloseTunnel(ifce *Interface, flags *closeTunnelFlags, a []string) (commands.Result, error) {
  279. if len(a) == 0 {
  280. return commands.Failed("No vpn address was provided"), nil
  281. }
  282. vpnAddr, err := netip.ParseAddr(a[0])
  283. if err != nil || !vpnAddr.IsValid() {
  284. return commands.Failed("The provided vpn address could not be parsed: %s", a[0]), nil
  285. }
  286. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  287. if hostInfo == nil {
  288. return commands.Failed("Could not find tunnel for vpn address: %v", a[0]), nil
  289. }
  290. if !flags.LocalOnly {
  291. ifce.send(
  292. header.CloseTunnel,
  293. 0,
  294. hostInfo.ConnectionState,
  295. hostInfo,
  296. []byte{},
  297. make([]byte, 12, 12),
  298. make([]byte, mtu),
  299. )
  300. }
  301. ifce.closeTunnel(hostInfo)
  302. return commands.Did("Closed"), nil
  303. }
  304. func runCreateTunnel(ifce *Interface, flags *createTunnelFlags, a []string) (commands.Result, error) {
  305. if len(a) == 0 {
  306. return commands.Failed("No vpn address was provided"), nil
  307. }
  308. vpnAddr, err := netip.ParseAddr(a[0])
  309. if err != nil || !vpnAddr.IsValid() {
  310. return commands.Failed("The provided vpn address could not be parsed: %s", a[0]), nil
  311. }
  312. if hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr); hostInfo != nil {
  313. return commands.Failed("Tunnel already exists"), nil
  314. }
  315. if hostInfo := ifce.handshakeManager.QueryVpnAddr(vpnAddr); hostInfo != nil {
  316. return commands.Failed("Tunnel already handshaking"), nil
  317. }
  318. var addr netip.AddrPort
  319. if flags.Address != "" {
  320. addr, err = netip.ParseAddrPort(flags.Address)
  321. if err != nil {
  322. return commands.Failed("Address could not be parsed"), nil
  323. }
  324. }
  325. // Set the remote from the callback, which StartHandshake runs under the handshake manager lock
  326. // before the hostinfo reaches the handshake loop or the static-host trigger, so the address is
  327. // in the RemoteList before the first handshake is sent.
  328. ifce.handshakeManager.StartHandshake(vpnAddr, func(hh *HandshakeHostInfo) {
  329. if addr.IsValid() {
  330. hh.hostinfo.SetRemote(addr)
  331. }
  332. })
  333. return commands.Did("Created"), nil
  334. }
  335. func runChangeRemote(ifce *Interface, flags *changeRemoteFlags, a []string) (commands.Result, error) {
  336. if len(a) == 0 {
  337. return commands.Failed("No vpn address was provided"), nil
  338. }
  339. if flags.Address == "" {
  340. return commands.Failed("No address was provided"), nil
  341. }
  342. addr, err := netip.ParseAddrPort(flags.Address)
  343. if err != nil {
  344. return commands.Failed("Address could not be parsed"), nil
  345. }
  346. vpnAddr, err := netip.ParseAddr(a[0])
  347. if err != nil || !vpnAddr.IsValid() {
  348. return commands.Failed("The provided vpn address could not be parsed: %s", a[0]), nil
  349. }
  350. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  351. if hostInfo == nil {
  352. return commands.Failed("Could not find tunnel for vpn address: %v", a[0]), nil
  353. }
  354. hostInfo.SetRemote(addr)
  355. return commands.Did("Changed"), nil
  356. }
  357. func runLogLevel(l *slog.Logger, a []string) (commands.Result, error) {
  358. ctrl, ok := l.Handler().(interface {
  359. GetLevel() slog.Level
  360. SetLevel(slog.Level)
  361. })
  362. if !ok {
  363. return commands.Failed("Log level is not reconfigurable on this logger"), nil
  364. }
  365. if len(a) > 0 {
  366. level, err := logging.ParseLevel(strings.ToLower(a[0]))
  367. if err != nil {
  368. return commands.Failed("Unknown log level %s. Possible log levels: trace, debug, info, warn, error", a), nil
  369. }
  370. ctrl.SetLevel(level)
  371. }
  372. return commands.Value(logLevelInfo{Level: logging.LevelName(ctrl.GetLevel())}), nil
  373. }
  374. func textLogLevel(li logLevelInfo, w io.Writer) error {
  375. _, err := fmt.Fprintf(w, "Log level is: %s\n", li.Level)
  376. return err
  377. }
  378. func runLogFormat(l *slog.Logger, a []string) (commands.Result, error) {
  379. ctrl, ok := l.Handler().(interface {
  380. GetFormat() string
  381. SetFormat(string) error
  382. })
  383. if !ok {
  384. return commands.Failed("Log format is not reconfigurable on this logger"), nil
  385. }
  386. if len(a) > 0 {
  387. if err := ctrl.SetFormat(strings.ToLower(a[0])); err != nil {
  388. return commands.Failed("Unable to set log format: %s", err), nil
  389. }
  390. }
  391. return commands.Value(logFormatInfo{Format: ctrl.GetFormat()}), nil
  392. }
  393. func textLogFormat(fi logFormatInfo, w io.Writer) error {
  394. _, err := fmt.Fprintf(w, "Log format is: %s\n", fi.Format)
  395. return err
  396. }
  397. func runPrintCert(ifce *Interface, args *printCertFlags, a []string) (commands.Result, error) {
  398. c := ifce.pki.getCertState().GetDefaultCertificate()
  399. if len(a) > 0 {
  400. vpnAddr, err := netip.ParseAddr(a[0])
  401. if err != nil || !vpnAddr.IsValid() {
  402. return commands.Failed("The provided vpn addr could not be parsed: %s", a[0]), nil
  403. }
  404. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  405. if hostInfo == nil {
  406. return commands.Failed("Could not find tunnel for vpn addr: %v", a[0]), nil
  407. }
  408. c = hostInfo.GetCert().Certificate
  409. }
  410. if args.Raw {
  411. b, err := c.MarshalPEM()
  412. if err != nil {
  413. return commands.Result{}, err
  414. }
  415. return commands.Value(certPEM{PEM: string(b)}), nil
  416. }
  417. return commands.Value(c), nil
  418. }
  419. // textPrintCert takes the value untyped: print-cert returns a certificate or, with -raw,
  420. // its PEM, and those are two things to emit rather than two renderings of one.
  421. func textPrintCert(v any, w io.Writer) error {
  422. switch c := v.(type) {
  423. case certPEM:
  424. _, err := io.WriteString(w, c.PEM)
  425. return err
  426. case cert.Certificate:
  427. _, err := io.WriteString(w, c.String()+"\n")
  428. return err
  429. default:
  430. return fmt.Errorf("expected a certificate, got %T", v)
  431. }
  432. }
  433. func runPrintRelays(ifce *Interface) any {
  434. relays := map[uint32]*HostInfo{}
  435. ifce.hostMap.Lock()
  436. maps.Copy(relays, ifce.hostMap.Relays)
  437. ifce.hostMap.Unlock()
  438. co := relaysOutput{}
  439. for k, v := range relays {
  440. ro := relayOutput{NebulaAddr: v.vpnAddrs[0]}
  441. co.Relays = append(co.Relays, &ro)
  442. relayHI := ifce.hostMap.QueryVpnAddr(v.vpnAddrs[0])
  443. if relayHI == nil {
  444. ro.RelayForAddrs = append(ro.RelayForAddrs, relayFor{Error: "could not find hostinfo"})
  445. continue
  446. }
  447. for _, vpnAddr := range relayHI.relayState.CopyRelayForIps() {
  448. rf := relayFor{}
  449. r, ok := relayHI.relayState.GetRelayForByAddr(vpnAddr)
  450. if ok {
  451. t := ""
  452. switch r.Type {
  453. case ForwardingType:
  454. t = "forwarding"
  455. case TerminalType:
  456. t = "terminal"
  457. default:
  458. t = "unknown"
  459. }
  460. st := ""
  461. switch r.State {
  462. case Requested:
  463. st = "requested"
  464. case Established:
  465. st = "established"
  466. default:
  467. st = "unknown"
  468. }
  469. rf.LocalIndex = r.LocalIndex
  470. rf.RemoteIndex = r.RemoteIndex
  471. rf.PeerAddr = r.PeerAddr
  472. rf.Type = t
  473. rf.State = st
  474. if rf.LocalIndex != k {
  475. rf.Error = fmt.Sprintf("hostmap LocalIndex '%v' does not match RelayState LocalIndex", k)
  476. }
  477. }
  478. relayedHI := ifce.hostMap.QueryVpnAddr(vpnAddr)
  479. if relayedHI != nil {
  480. rf.RelayedThrough = append(rf.RelayedThrough, relayedHI.relayState.CopyRelayIps()...)
  481. }
  482. ro.RelayForAddrs = append(ro.RelayForAddrs, rf)
  483. }
  484. }
  485. return co
  486. }
  487. func runPrintTunnel(ifce *Interface, a []string) (commands.Result, error) {
  488. if len(a) == 0 {
  489. return commands.Failed("No vpn address was provided"), nil
  490. }
  491. vpnAddr, err := netip.ParseAddr(a[0])
  492. if err != nil || !vpnAddr.IsValid() {
  493. return commands.Failed("The provided vpn addr could not be parsed: %s", a[0]), nil
  494. }
  495. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  496. if hostInfo == nil {
  497. return commands.Failed("Could not find tunnel for vpn addr: %v", a[0]), nil
  498. }
  499. return commands.Value(copyHostInfo(hostInfo, ifce.hostMap.GetPreferredRanges())), nil
  500. }
  501. func runDeviceInfo(ifce *Interface) deviceInfo {
  502. d := deviceInfo{
  503. Name: ifce.inside.Name(),
  504. Cidr: make([]netip.Prefix, len(ifce.inside.Networks())),
  505. }
  506. copy(d.Cidr, ifce.inside.Networks())
  507. return d
  508. }
  509. func textDeviceInfo(d deviceInfo, w io.Writer) error {
  510. _, err := fmt.Fprintf(w, "name=%v cidr=%v\n", d.Name, d.Cidr)
  511. return err
  512. }
  513. // runReload reloads the config and reports how it went, under the "Reloading config"
  514. // line the command has always printed first. A reload that failed is a refusal, exit 1
  515. // over ssh; one that merely needs a restart is not.
  516. func runReload(l *slog.Logger, c *config.C) (commands.Result, error) {
  517. res := c.ReloadConfig()
  518. res.Log(l)
  519. if res.Err != nil || res.Failed {
  520. return commands.Failed("Reloading config\n%s", res.String()), nil
  521. }
  522. return commands.Did("Reloading config\n%s", res.String()), nil
  523. }