ssh.go 27 KB

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