ssh.go 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  1. package nebula
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "flag"
  7. "fmt"
  8. "log/slog"
  9. "maps"
  10. "net"
  11. "net/netip"
  12. "os"
  13. "path/filepath"
  14. "runtime"
  15. "runtime/pprof"
  16. "sort"
  17. "strconv"
  18. "strings"
  19. "github.com/slackhq/nebula/config"
  20. "github.com/slackhq/nebula/header"
  21. "github.com/slackhq/nebula/logging"
  22. "github.com/slackhq/nebula/sshd"
  23. )
  24. type sshListHostMapFlags struct {
  25. Json bool
  26. Pretty bool
  27. ByIndex bool
  28. }
  29. type sshPrintCertFlags struct {
  30. Json bool
  31. Pretty bool
  32. Raw bool
  33. }
  34. type sshPrintTunnelFlags struct {
  35. Pretty bool
  36. }
  37. type sshChangeRemoteFlags struct {
  38. Address string
  39. }
  40. type sshCloseTunnelFlags struct {
  41. LocalOnly bool
  42. }
  43. type sshCreateTunnelFlags struct {
  44. Address string
  45. }
  46. type sshDeviceInfoFlags struct {
  47. Json bool
  48. Pretty bool
  49. }
  50. func wireSSHReload(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) {
  51. c.RegisterReloadCallback(func(c *config.C) {
  52. if c.GetBool("sshd.enabled", false) {
  53. // Restarting the server drops every open session, the one that asked for this reload included. Only restart
  54. // when it isn't running or its config changed. The host key file can change under the same config, new
  55. // handshakes use the new key without a restart
  56. if ssh.Running() && !c.HasChanged("sshd") {
  57. key, err := loadSSHHostKey(c)
  58. if err == nil {
  59. err = ssh.SetHostKey(key)
  60. }
  61. if err != nil {
  62. l.Error("Failed to reconfigure the sshd", "error", err)
  63. ssh.Stop()
  64. }
  65. return
  66. }
  67. sshRun, err := configSSH(l, ssh, c)
  68. if err != nil {
  69. l.Error("Failed to reconfigure the sshd", "error", err)
  70. ssh.Stop()
  71. }
  72. if sshRun != nil {
  73. go sshRun()
  74. }
  75. } else {
  76. ssh.Stop()
  77. }
  78. })
  79. }
  80. // loadSSHHostKey returns sshd.host_key, read from its file when it names one.
  81. func loadSSHHostKey(c *config.C) ([]byte, error) {
  82. hostKeyPathOrKey := c.GetString("sshd.host_key", "")
  83. if hostKeyPathOrKey == "" {
  84. return nil, fmt.Errorf("sshd.host_key must be provided")
  85. }
  86. if strings.Contains(hostKeyPathOrKey, "-----BEGIN") {
  87. return []byte(hostKeyPathOrKey), nil
  88. }
  89. b, err := os.ReadFile(hostKeyPathOrKey)
  90. if err != nil {
  91. return nil, fmt.Errorf("error while loading sshd.host_key file: %s", err)
  92. }
  93. return b, nil
  94. }
  95. // configSSH reads the ssh info out of the passed-in Config and
  96. // updates the passed-in SSHServer. On success, it returns a function
  97. // that callers may invoke to run the configured ssh server. On
  98. // failure, it returns nil, error.
  99. func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error) {
  100. listen := c.GetString("sshd.listen", "")
  101. if listen == "" {
  102. return nil, fmt.Errorf("sshd.listen must be provided")
  103. }
  104. _, port, err := net.SplitHostPort(listen)
  105. if err != nil {
  106. return nil, fmt.Errorf("invalid sshd.listen address: %s", err)
  107. }
  108. if port == "22" {
  109. return nil, fmt.Errorf("sshd.listen can not use port 22")
  110. }
  111. hostKeyBytes, err := loadSSHHostKey(c)
  112. if err != nil {
  113. return nil, err
  114. }
  115. err = ssh.SetHostKey(hostKeyBytes)
  116. if err != nil {
  117. return nil, fmt.Errorf("error while adding sshd.host_key: %s", err)
  118. }
  119. // Clear existing trusted CAs and authorized keys
  120. ssh.ClearTrustedCAs()
  121. ssh.ClearAuthorizedKeys()
  122. rawCAs := c.GetStringSlice("sshd.trusted_cas", []string{})
  123. for _, caAuthorizedKey := range rawCAs {
  124. err := ssh.AddTrustedCA(caAuthorizedKey)
  125. if err != nil {
  126. l.Warn("SSH CA had an error, ignoring", "error", err, "sshCA", caAuthorizedKey)
  127. continue
  128. }
  129. }
  130. rawKeys := c.Get("sshd.authorized_users")
  131. keys, ok := rawKeys.([]any)
  132. if ok {
  133. for _, rk := range keys {
  134. kDef, ok := rk.(map[string]any)
  135. if !ok {
  136. l.Warn("Authorized user had an error, ignoring", "sshKeyConfig", rk)
  137. continue
  138. }
  139. user, ok := kDef["user"].(string)
  140. if !ok {
  141. l.Warn("Authorized user is missing the user field", "sshKeyConfig", rk)
  142. continue
  143. }
  144. k := kDef["keys"]
  145. switch v := k.(type) {
  146. case string:
  147. err := ssh.AddAuthorizedKey(user, v)
  148. if err != nil {
  149. l.Warn("Failed to authorize key",
  150. "error", err,
  151. "sshKeyConfig", rk,
  152. "sshKey", v,
  153. )
  154. continue
  155. }
  156. case []any:
  157. for _, subK := range v {
  158. sk, ok := subK.(string)
  159. if !ok {
  160. l.Warn("Did not understand ssh key",
  161. "sshKeyConfig", rk,
  162. "sshKey", subK,
  163. )
  164. continue
  165. }
  166. err := ssh.AddAuthorizedKey(user, sk)
  167. if err != nil {
  168. l.Warn("Failed to authorize key",
  169. "error", err,
  170. "sshKeyConfig", sk,
  171. )
  172. continue
  173. }
  174. }
  175. default:
  176. l.Warn("Authorized user is missing the keys field or was not understood", "sshKeyConfig", rk)
  177. }
  178. }
  179. } else {
  180. l.Info("no ssh users to authorize")
  181. }
  182. var runner func()
  183. if c.GetBool("sshd.enabled", false) {
  184. ssh.Stop()
  185. runner = func() {
  186. if err := ssh.Run(listen); err != nil {
  187. l.Warn("Failed to run the SSH server", "error", err)
  188. }
  189. }
  190. } else {
  191. ssh.Stop()
  192. }
  193. return runner, nil
  194. }
  195. func attachCommands(l *slog.Logger, c *config.C, ssh *sshd.SSHServer, f *Interface) {
  196. // sandboxDir defaults to a dir in temp. The intention is that end user will
  197. // create this dir as needed. Overriding this config value to "" allows
  198. // writing to anywhere in the system.
  199. defaultDir := filepath.Join(os.TempDir(), "nebula-debug")
  200. sandboxDir := c.GetString("sshd.sandbox_dir", defaultDir)
  201. ssh.RegisterCommand(&sshd.Command{
  202. Name: "list-hostmap",
  203. ShortDescription: "List all known previously connected hosts",
  204. Flags: func() (*flag.FlagSet, any) {
  205. fl := flag.NewFlagSet("", flag.ContinueOnError)
  206. s := sshListHostMapFlags{}
  207. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  208. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  209. fl.BoolVar(&s.ByIndex, "by-index", false, "gets all hosts in the hostmap from the index table")
  210. return fl, &s
  211. },
  212. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  213. return sshListHostMap(f.hostMap, fs, w)
  214. },
  215. })
  216. ssh.RegisterCommand(&sshd.Command{
  217. Name: "list-pending-hostmap",
  218. ShortDescription: "List all handshaking hosts",
  219. Flags: func() (*flag.FlagSet, any) {
  220. fl := flag.NewFlagSet("", flag.ContinueOnError)
  221. s := sshListHostMapFlags{}
  222. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  223. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  224. fl.BoolVar(&s.ByIndex, "by-index", false, "gets all hosts in the hostmap from the index table")
  225. return fl, &s
  226. },
  227. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  228. return sshListHostMap(f.handshakeManager, fs, w)
  229. },
  230. })
  231. ssh.RegisterCommand(&sshd.Command{
  232. Name: "list-lighthouse-addrmap",
  233. ShortDescription: "List all lighthouse map entries",
  234. Flags: func() (*flag.FlagSet, any) {
  235. fl := flag.NewFlagSet("", flag.ContinueOnError)
  236. s := sshListHostMapFlags{}
  237. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  238. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  239. return fl, &s
  240. },
  241. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  242. return sshListLighthouseMap(f.lightHouse, fs, w)
  243. },
  244. })
  245. ssh.RegisterCommand(&sshd.Command{
  246. Name: "reload",
  247. ShortDescription: "Reloads configuration from disk, same as sending HUP to the process",
  248. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  249. return sshReload(c, w)
  250. },
  251. })
  252. ssh.RegisterCommand(&sshd.Command{
  253. Name: "start-cpu-profile",
  254. ShortDescription: "Starts a cpu profile and write output to the provided file, ex: `cpu-profile.pb.gz`",
  255. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  256. return sshStartCpuProfile(sandboxDir, fs, a, w)
  257. },
  258. })
  259. ssh.RegisterCommand(&sshd.Command{
  260. Name: "stop-cpu-profile",
  261. ShortDescription: "Stops a cpu profile and writes output to the previously provided file",
  262. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  263. pprof.StopCPUProfile()
  264. return w.WriteLine("If a CPU profile was running it is now stopped")
  265. },
  266. })
  267. ssh.RegisterCommand(&sshd.Command{
  268. Name: "save-heap-profile",
  269. ShortDescription: "Saves a heap profile to the provided path, ex: `heap-profile.pb.gz`",
  270. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  271. return sshGetHeapProfile(sandboxDir, fs, a, w)
  272. },
  273. })
  274. ssh.RegisterCommand(&sshd.Command{
  275. Name: "mutex-profile-fraction",
  276. ShortDescription: "Gets or sets runtime.SetMutexProfileFraction",
  277. Callback: sshMutexProfileFraction,
  278. })
  279. ssh.RegisterCommand(&sshd.Command{
  280. Name: "save-mutex-profile",
  281. ShortDescription: "Saves a mutex profile to the provided path, ex: `mutex-profile.pb.gz`",
  282. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  283. return sshGetMutexProfile(sandboxDir, fs, a, w)
  284. },
  285. })
  286. ssh.RegisterCommand(&sshd.Command{
  287. Name: "log-level",
  288. ShortDescription: "Gets or sets the current log level",
  289. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  290. return sshLogLevel(l, fs, a, w)
  291. },
  292. })
  293. ssh.RegisterCommand(&sshd.Command{
  294. Name: "log-format",
  295. ShortDescription: "Gets or sets the current log format",
  296. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  297. return sshLogFormat(l, fs, a, w)
  298. },
  299. })
  300. ssh.RegisterCommand(&sshd.Command{
  301. Name: "version",
  302. ShortDescription: "Prints the currently running version of nebula",
  303. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  304. return sshVersion(f, fs, a, w)
  305. },
  306. })
  307. ssh.RegisterCommand(&sshd.Command{
  308. Name: "device-info",
  309. ShortDescription: "Prints information about the network device.",
  310. Flags: func() (*flag.FlagSet, any) {
  311. fl := flag.NewFlagSet("", flag.ContinueOnError)
  312. s := sshDeviceInfoFlags{}
  313. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  314. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  315. return fl, &s
  316. },
  317. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  318. return sshDeviceInfo(f, fs, w)
  319. },
  320. })
  321. ssh.RegisterCommand(&sshd.Command{
  322. Name: "print-cert",
  323. ShortDescription: "Prints the current certificate being used or the certificate for the provided vpn addr",
  324. Flags: func() (*flag.FlagSet, any) {
  325. fl := flag.NewFlagSet("", flag.ContinueOnError)
  326. s := sshPrintCertFlags{}
  327. fl.BoolVar(&s.Json, "json", false, "outputs as json")
  328. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  329. fl.BoolVar(&s.Raw, "raw", false, "raw prints the PEM encoded certificate, not compatible with -json or -pretty")
  330. return fl, &s
  331. },
  332. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  333. return sshPrintCert(f, fs, a, w)
  334. },
  335. })
  336. ssh.RegisterCommand(&sshd.Command{
  337. Name: "print-tunnel",
  338. ShortDescription: "Prints json details about a tunnel for the provided vpn addr",
  339. Flags: func() (*flag.FlagSet, any) {
  340. fl := flag.NewFlagSet("", flag.ContinueOnError)
  341. s := sshPrintTunnelFlags{}
  342. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json")
  343. return fl, &s
  344. },
  345. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  346. return sshPrintTunnel(f, fs, a, w)
  347. },
  348. })
  349. ssh.RegisterCommand(&sshd.Command{
  350. Name: "print-relays",
  351. ShortDescription: "Prints json details about all relay info",
  352. Flags: func() (*flag.FlagSet, any) {
  353. fl := flag.NewFlagSet("", flag.ContinueOnError)
  354. s := sshPrintTunnelFlags{}
  355. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json")
  356. return fl, &s
  357. },
  358. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  359. return sshPrintRelays(f, fs, a, w)
  360. },
  361. })
  362. ssh.RegisterCommand(&sshd.Command{
  363. Name: "change-remote",
  364. ShortDescription: "Changes the remote address used in the tunnel for the provided vpn addr",
  365. Flags: func() (*flag.FlagSet, any) {
  366. fl := flag.NewFlagSet("", flag.ContinueOnError)
  367. s := sshChangeRemoteFlags{}
  368. fl.StringVar(&s.Address, "address", "", "The new remote address, ip:port")
  369. return fl, &s
  370. },
  371. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  372. return sshChangeRemote(f, fs, a, w)
  373. },
  374. })
  375. ssh.RegisterCommand(&sshd.Command{
  376. Name: "close-tunnel",
  377. ShortDescription: "Closes a tunnel for the provided vpn addr",
  378. Flags: func() (*flag.FlagSet, any) {
  379. fl := flag.NewFlagSet("", flag.ContinueOnError)
  380. s := sshCloseTunnelFlags{}
  381. fl.BoolVar(&s.LocalOnly, "local-only", false, "Disables notifying the remote that the tunnel is shutting down")
  382. return fl, &s
  383. },
  384. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  385. return sshCloseTunnel(f, fs, a, w)
  386. },
  387. })
  388. ssh.RegisterCommand(&sshd.Command{
  389. Name: "create-tunnel",
  390. ShortDescription: "Creates a tunnel for the provided vpn address",
  391. Help: "The lighthouses will be queried for real addresses but you can provide one as well.",
  392. Flags: func() (*flag.FlagSet, any) {
  393. fl := flag.NewFlagSet("", flag.ContinueOnError)
  394. s := sshCreateTunnelFlags{}
  395. fl.StringVar(&s.Address, "address", "", "Optionally provide a real remote address, ip:port ")
  396. return fl, &s
  397. },
  398. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  399. return sshCreateTunnel(f, fs, a, w)
  400. },
  401. })
  402. ssh.RegisterCommand(&sshd.Command{
  403. Name: "query-lighthouse",
  404. ShortDescription: "Query the lighthouses for the provided vpn address",
  405. Help: "This command is asynchronous. Only currently known udp addresses will be printed.",
  406. Callback: func(fs any, a []string, w sshd.StringWriter) error {
  407. return sshQueryLighthouse(f, fs, a, w)
  408. },
  409. })
  410. }
  411. func sshListHostMap(hl controlHostLister, a any, w sshd.StringWriter) error {
  412. fs, ok := a.(*sshListHostMapFlags)
  413. if !ok {
  414. return nil
  415. }
  416. var hm []ControlHostInfo
  417. if fs.ByIndex {
  418. hm = listHostMapIndexes(hl)
  419. } else {
  420. hm = listHostMapHosts(hl)
  421. }
  422. sort.Slice(hm, func(i, j int) bool {
  423. return hm[i].VpnAddrs[0].Compare(hm[j].VpnAddrs[0]) < 0
  424. })
  425. if fs.Json || fs.Pretty {
  426. js := json.NewEncoder(w.GetWriter())
  427. if fs.Pretty {
  428. js.SetIndent("", " ")
  429. }
  430. err := js.Encode(hm)
  431. if err != nil {
  432. return nil
  433. }
  434. } else {
  435. for _, v := range hm {
  436. err := w.WriteLine(fmt.Sprintf("%s: %s", v.VpnAddrs, v.RemoteAddrs))
  437. if err != nil {
  438. return err
  439. }
  440. }
  441. }
  442. return nil
  443. }
  444. func sshListLighthouseMap(lightHouse *LightHouse, a any, w sshd.StringWriter) error {
  445. fs, ok := a.(*sshListHostMapFlags)
  446. if !ok {
  447. return nil
  448. }
  449. type lighthouseInfo struct {
  450. VpnAddr string `json:"vpnAddr"`
  451. Addrs *CacheMap `json:"addrs"`
  452. }
  453. lightHouse.RLock()
  454. addrMap := make([]lighthouseInfo, len(lightHouse.addrMap))
  455. x := 0
  456. for k, v := range lightHouse.addrMap {
  457. addrMap[x] = lighthouseInfo{
  458. VpnAddr: k.String(),
  459. Addrs: v.CopyCache(),
  460. }
  461. x++
  462. }
  463. lightHouse.RUnlock()
  464. sort.Slice(addrMap, func(i, j int) bool {
  465. return strings.Compare(addrMap[i].VpnAddr, addrMap[j].VpnAddr) < 0
  466. })
  467. if fs.Json || fs.Pretty {
  468. js := json.NewEncoder(w.GetWriter())
  469. if fs.Pretty {
  470. js.SetIndent("", " ")
  471. }
  472. err := js.Encode(addrMap)
  473. if err != nil {
  474. return nil
  475. }
  476. } else {
  477. for _, v := range addrMap {
  478. b, err := json.Marshal(v.Addrs)
  479. if err != nil {
  480. return err
  481. }
  482. err = w.WriteLine(fmt.Sprintf("%s: %s", v.VpnAddr, string(b)))
  483. if err != nil {
  484. return err
  485. }
  486. }
  487. }
  488. return nil
  489. }
  490. // sshSanitizeFilePath validates that the given file path is within the sandbox directory.
  491. // If sandboxDir is empty, the path is returned as-is for backwards compatibility.
  492. func sshSanitizeFilePath(sandboxDir, filePath string) (string, error) {
  493. if sandboxDir == "" {
  494. return filePath, nil
  495. }
  496. // Clean and resolve the path relative to the sandbox directory
  497. if !filepath.IsAbs(filePath) {
  498. filePath = filepath.Join(sandboxDir, filePath)
  499. }
  500. cleaned := filepath.Clean(filePath)
  501. // Ensure the resolved path is within the sandbox directory
  502. cleanedSandbox := filepath.Clean(sandboxDir)
  503. if cleaned == cleanedSandbox {
  504. return "", fmt.Errorf("path %q resolves to the sandbox directory itself %q", filePath, sandboxDir)
  505. }
  506. if !strings.HasPrefix(cleaned, cleanedSandbox+string(filepath.Separator)) {
  507. return "", fmt.Errorf("path %q is outside the sandbox directory %q", filePath, sandboxDir)
  508. }
  509. return cleaned, nil
  510. }
  511. func sshStartCpuProfile(sandboxDir string, fs any, a []string, w sshd.StringWriter) error {
  512. if len(a) == 0 {
  513. err := w.WriteLine("No path to write profile provided")
  514. return err
  515. }
  516. filePath, err := sshSanitizeFilePath(sandboxDir, a[0])
  517. if err != nil {
  518. return w.WriteLine(err.Error())
  519. }
  520. file, err := os.Create(filePath)
  521. if err != nil {
  522. err = w.WriteLine(fmt.Sprintf("Unable to create profile file: %s", err))
  523. return err
  524. }
  525. err = pprof.StartCPUProfile(file)
  526. if err != nil {
  527. err = w.WriteLine(fmt.Sprintf("Unable to start cpu profile: %s", err))
  528. return err
  529. }
  530. err = w.WriteLine(fmt.Sprintf("Started cpu profile, issue stop-cpu-profile to write the output to %s", a))
  531. return err
  532. }
  533. func sshVersion(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  534. return w.WriteLine(fmt.Sprintf("%s", ifce.version))
  535. }
  536. func sshQueryLighthouse(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  537. if len(a) == 0 {
  538. return w.WriteLine("No vpn address was provided")
  539. }
  540. vpnAddr, err := netip.ParseAddr(a[0])
  541. if err != nil {
  542. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  543. }
  544. if !vpnAddr.IsValid() {
  545. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  546. }
  547. var cm *CacheMap
  548. rl := ifce.lightHouse.Query(vpnAddr)
  549. if rl != nil {
  550. cm = rl.CopyCache()
  551. }
  552. return json.NewEncoder(w.GetWriter()).Encode(cm)
  553. }
  554. func sshCloseTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  555. flags, ok := fs.(*sshCloseTunnelFlags)
  556. if !ok {
  557. return nil
  558. }
  559. if len(a) == 0 {
  560. return w.WriteLine("No vpn address was provided")
  561. }
  562. vpnAddr, err := netip.ParseAddr(a[0])
  563. if err != nil {
  564. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  565. }
  566. if !vpnAddr.IsValid() {
  567. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  568. }
  569. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  570. if hostInfo == nil {
  571. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn address: %v", a[0]))
  572. }
  573. if !flags.LocalOnly {
  574. ifce.send(
  575. header.CloseTunnel,
  576. 0,
  577. hostInfo.ConnectionState,
  578. hostInfo,
  579. []byte{},
  580. make([]byte, 12, 12),
  581. make([]byte, mtu),
  582. )
  583. }
  584. ifce.closeTunnel(hostInfo)
  585. return w.WriteLine("Closed")
  586. }
  587. func sshCreateTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  588. flags, ok := fs.(*sshCreateTunnelFlags)
  589. if !ok {
  590. return nil
  591. }
  592. if len(a) == 0 {
  593. return w.WriteLine("No vpn address was provided")
  594. }
  595. vpnAddr, err := netip.ParseAddr(a[0])
  596. if err != nil {
  597. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  598. }
  599. if !vpnAddr.IsValid() {
  600. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  601. }
  602. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  603. if hostInfo != nil {
  604. return w.WriteLine(fmt.Sprintf("Tunnel already exists"))
  605. }
  606. hostInfo = ifce.handshakeManager.QueryVpnAddr(vpnAddr)
  607. if hostInfo != nil {
  608. return w.WriteLine(fmt.Sprintf("Tunnel already handshaking"))
  609. }
  610. var addr netip.AddrPort
  611. if flags.Address != "" {
  612. addr, err = netip.ParseAddrPort(flags.Address)
  613. if err != nil {
  614. return w.WriteLine("Address could not be parsed")
  615. }
  616. }
  617. // Set the remote from the callback, which StartHandshake runs under the handshake manager lock
  618. // before the hostinfo reaches the handshake loop or the static-host trigger, so the address is
  619. // in the RemoteList before the first handshake is sent.
  620. ifce.handshakeManager.StartHandshake(vpnAddr, func(hh *HandshakeHostInfo) {
  621. if addr.IsValid() {
  622. hh.hostinfo.SetRemote(addr)
  623. }
  624. })
  625. return w.WriteLine("Created")
  626. }
  627. func sshChangeRemote(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  628. flags, ok := fs.(*sshChangeRemoteFlags)
  629. if !ok {
  630. return nil
  631. }
  632. if len(a) == 0 {
  633. return w.WriteLine("No vpn address was provided")
  634. }
  635. if flags.Address == "" {
  636. return w.WriteLine("No address was provided")
  637. }
  638. addr, err := netip.ParseAddrPort(flags.Address)
  639. if err != nil {
  640. return w.WriteLine("Address could not be parsed")
  641. }
  642. vpnAddr, err := netip.ParseAddr(a[0])
  643. if err != nil {
  644. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  645. }
  646. if !vpnAddr.IsValid() {
  647. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  648. }
  649. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  650. if hostInfo == nil {
  651. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn address: %v", a[0]))
  652. }
  653. hostInfo.SetRemote(addr)
  654. return w.WriteLine("Changed")
  655. }
  656. func sshGetHeapProfile(sandboxDir string, fs any, a []string, w sshd.StringWriter) error {
  657. if len(a) == 0 {
  658. return w.WriteLine("No path to write profile provided")
  659. }
  660. filePath, err := sshSanitizeFilePath(sandboxDir, a[0])
  661. if err != nil {
  662. return w.WriteLine(err.Error())
  663. }
  664. file, err := os.Create(filePath)
  665. if err != nil {
  666. err = w.WriteLine(fmt.Sprintf("Unable to create profile file: %s", err))
  667. return err
  668. }
  669. err = pprof.WriteHeapProfile(file)
  670. if err != nil {
  671. err = w.WriteLine(fmt.Sprintf("Unable to write profile: %s", err))
  672. return err
  673. }
  674. err = w.WriteLine(fmt.Sprintf("Mem profile created at %s", a))
  675. return err
  676. }
  677. func sshMutexProfileFraction(fs any, a []string, w sshd.StringWriter) error {
  678. if len(a) == 0 {
  679. rate := runtime.SetMutexProfileFraction(-1)
  680. return w.WriteLine(fmt.Sprintf("Current value: %d", rate))
  681. }
  682. newRate, err := strconv.Atoi(a[0])
  683. if err != nil {
  684. return w.WriteLine(fmt.Sprintf("Invalid argument: %s", a[0]))
  685. }
  686. oldRate := runtime.SetMutexProfileFraction(newRate)
  687. return w.WriteLine(fmt.Sprintf("New value: %d. Old value: %d", newRate, oldRate))
  688. }
  689. func sshGetMutexProfile(sandboxDir string, fs any, a []string, w sshd.StringWriter) error {
  690. if len(a) == 0 {
  691. return w.WriteLine("No path to write profile provided")
  692. }
  693. filePath, err := sshSanitizeFilePath(sandboxDir, a[0])
  694. if err != nil {
  695. return w.WriteLine(err.Error())
  696. }
  697. file, err := os.Create(filePath)
  698. if err != nil {
  699. return w.WriteLine(fmt.Sprintf("Unable to create profile file: %s", err))
  700. }
  701. defer file.Close()
  702. mutexProfile := pprof.Lookup("mutex")
  703. if mutexProfile == nil {
  704. return w.WriteLine("Unable to get pprof.Lookup(\"mutex\")")
  705. }
  706. err = mutexProfile.WriteTo(file, 0)
  707. if err != nil {
  708. return w.WriteLine(fmt.Sprintf("Unable to write profile: %s", err))
  709. }
  710. return w.WriteLine(fmt.Sprintf("Mutex profile created at %s", a))
  711. }
  712. func sshLogLevel(l *slog.Logger, fs any, a []string, w sshd.StringWriter) error {
  713. ctrl, ok := l.Handler().(interface {
  714. GetLevel() slog.Level
  715. SetLevel(slog.Level)
  716. })
  717. if !ok {
  718. return w.WriteLine("Log level is not reconfigurable on this logger")
  719. }
  720. if len(a) == 0 {
  721. return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
  722. }
  723. level, err := logging.ParseLevel(strings.ToLower(a[0]))
  724. if err != nil {
  725. return w.WriteLine(fmt.Sprintf("Unknown log level %s. Possible log levels: trace, debug, info, warn, error", a))
  726. }
  727. ctrl.SetLevel(level)
  728. return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
  729. }
  730. func sshLogFormat(l *slog.Logger, fs any, a []string, w sshd.StringWriter) error {
  731. ctrl, ok := l.Handler().(interface {
  732. GetFormat() string
  733. SetFormat(string) error
  734. })
  735. if !ok {
  736. return w.WriteLine("Log format is not reconfigurable on this logger")
  737. }
  738. if len(a) == 0 {
  739. return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
  740. }
  741. if err := ctrl.SetFormat(strings.ToLower(a[0])); err != nil {
  742. return err
  743. }
  744. return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
  745. }
  746. func sshPrintCert(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  747. args, ok := fs.(*sshPrintCertFlags)
  748. if !ok {
  749. return nil
  750. }
  751. cert := ifce.pki.getCertState().GetDefaultCertificate()
  752. if len(a) > 0 {
  753. vpnAddr, err := netip.ParseAddr(a[0])
  754. if err != nil {
  755. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  756. }
  757. if !vpnAddr.IsValid() {
  758. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  759. }
  760. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  761. if hostInfo == nil {
  762. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn addr: %v", a[0]))
  763. }
  764. cert = hostInfo.GetCert().Certificate
  765. }
  766. if args.Json || args.Pretty {
  767. b, err := cert.MarshalJSON()
  768. if err != nil {
  769. return nil
  770. }
  771. if args.Pretty {
  772. buf := new(bytes.Buffer)
  773. err := json.Indent(buf, b, "", " ")
  774. b = buf.Bytes()
  775. if err != nil {
  776. return nil
  777. }
  778. }
  779. return w.WriteBytes(b)
  780. }
  781. if args.Raw {
  782. b, err := cert.MarshalPEM()
  783. if err != nil {
  784. return nil
  785. }
  786. return w.WriteBytes(b)
  787. }
  788. return w.WriteLine(cert.String())
  789. }
  790. func sshPrintRelays(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  791. args, ok := fs.(*sshPrintTunnelFlags)
  792. if !ok {
  793. w.WriteLine(fmt.Sprintf("sshPrintRelays failed to convert args type"))
  794. return nil
  795. }
  796. relays := map[uint32]*HostInfo{}
  797. ifce.hostMap.Lock()
  798. maps.Copy(relays, ifce.hostMap.Relays)
  799. ifce.hostMap.Unlock()
  800. type RelayFor struct {
  801. Error error
  802. Type string
  803. State string
  804. PeerAddr netip.Addr
  805. LocalIndex uint32
  806. RemoteIndex uint32
  807. RelayedThrough []netip.Addr
  808. }
  809. type RelayOutput struct {
  810. NebulaAddr netip.Addr
  811. RelayForAddrs []RelayFor
  812. }
  813. type CmdOutput struct {
  814. Relays []*RelayOutput
  815. }
  816. co := CmdOutput{}
  817. enc := json.NewEncoder(w.GetWriter())
  818. if args.Pretty {
  819. enc.SetIndent("", " ")
  820. }
  821. for k, v := range relays {
  822. ro := RelayOutput{NebulaAddr: v.vpnAddrs[0]}
  823. co.Relays = append(co.Relays, &ro)
  824. relayHI := ifce.hostMap.QueryVpnAddr(v.vpnAddrs[0])
  825. if relayHI == nil {
  826. ro.RelayForAddrs = append(ro.RelayForAddrs, RelayFor{Error: errors.New("could not find hostinfo")})
  827. continue
  828. }
  829. for _, vpnAddr := range relayHI.relayState.CopyRelayForIps() {
  830. rf := RelayFor{Error: nil}
  831. r, ok := relayHI.relayState.GetRelayForByAddr(vpnAddr)
  832. if ok {
  833. t := ""
  834. switch r.Type {
  835. case ForwardingType:
  836. t = "forwarding"
  837. case TerminalType:
  838. t = "terminal"
  839. default:
  840. t = "unknown"
  841. }
  842. s := ""
  843. switch r.State {
  844. case Requested:
  845. s = "requested"
  846. case Established:
  847. s = "established"
  848. default:
  849. s = "unknown"
  850. }
  851. rf.LocalIndex = r.LocalIndex
  852. rf.RemoteIndex = r.RemoteIndex
  853. rf.PeerAddr = r.PeerAddr
  854. rf.Type = t
  855. rf.State = s
  856. if rf.LocalIndex != k {
  857. rf.Error = fmt.Errorf("hostmap LocalIndex '%v' does not match RelayState LocalIndex", k)
  858. }
  859. }
  860. relayedHI := ifce.hostMap.QueryVpnAddr(vpnAddr)
  861. if relayedHI != nil {
  862. rf.RelayedThrough = append(rf.RelayedThrough, relayedHI.relayState.CopyRelayIps()...)
  863. }
  864. ro.RelayForAddrs = append(ro.RelayForAddrs, rf)
  865. }
  866. }
  867. err := enc.Encode(co)
  868. if err != nil {
  869. return err
  870. }
  871. return nil
  872. }
  873. func sshPrintTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  874. args, ok := fs.(*sshPrintTunnelFlags)
  875. if !ok {
  876. return nil
  877. }
  878. if len(a) == 0 {
  879. return w.WriteLine("No vpn address was provided")
  880. }
  881. vpnAddr, err := netip.ParseAddr(a[0])
  882. if err != nil {
  883. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  884. }
  885. if !vpnAddr.IsValid() {
  886. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  887. }
  888. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  889. if hostInfo == nil {
  890. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn addr: %v", a[0]))
  891. }
  892. enc := json.NewEncoder(w.GetWriter())
  893. if args.Pretty {
  894. enc.SetIndent("", " ")
  895. }
  896. return enc.Encode(copyHostInfo(hostInfo, ifce.hostMap.GetPreferredRanges()))
  897. }
  898. func sshDeviceInfo(ifce *Interface, fs any, w sshd.StringWriter) error {
  899. data := struct {
  900. Name string `json:"name"`
  901. Cidr []netip.Prefix `json:"cidr"`
  902. }{
  903. Name: ifce.inside.Name(),
  904. Cidr: make([]netip.Prefix, len(ifce.inside.Networks())),
  905. }
  906. copy(data.Cidr, ifce.inside.Networks())
  907. flags, ok := fs.(*sshDeviceInfoFlags)
  908. if !ok {
  909. return fmt.Errorf("internal error: expected flags to be sshDeviceInfoFlags but was %+v", fs)
  910. }
  911. if flags.Json || flags.Pretty {
  912. js := json.NewEncoder(w.GetWriter())
  913. if flags.Pretty {
  914. js.SetIndent("", " ")
  915. }
  916. return js.Encode(data)
  917. } else {
  918. return w.WriteLine(fmt.Sprintf("name=%v cidr=%v", data.Name, data.Cidr))
  919. }
  920. }
  921. func sshReload(c *config.C, w sshd.StringWriter) error {
  922. err := w.WriteLine("Reloading config")
  923. c.ReloadConfig()
  924. return err
  925. }