ssh.go 27 KB

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