ssh.go 29 KB

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