1
0

ssh.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  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. // Set the remote from the callback, which StartHandshake runs under the handshake manager lock
  598. // before the hostinfo reaches the handshake loop or the static-host trigger, so the address is
  599. // in the RemoteList before the first handshake is sent.
  600. ifce.handshakeManager.StartHandshake(vpnAddr, func(hh *HandshakeHostInfo) {
  601. if addr.IsValid() {
  602. hh.hostinfo.SetRemote(addr)
  603. }
  604. })
  605. return w.WriteLine("Created")
  606. }
  607. func sshChangeRemote(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  608. flags, ok := fs.(*sshChangeRemoteFlags)
  609. if !ok {
  610. return nil
  611. }
  612. if len(a) == 0 {
  613. return w.WriteLine("No vpn address was provided")
  614. }
  615. if flags.Address == "" {
  616. return w.WriteLine("No address was provided")
  617. }
  618. addr, err := netip.ParseAddrPort(flags.Address)
  619. if err != nil {
  620. return w.WriteLine("Address could not be parsed")
  621. }
  622. vpnAddr, err := netip.ParseAddr(a[0])
  623. if err != nil {
  624. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  625. }
  626. if !vpnAddr.IsValid() {
  627. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  628. }
  629. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  630. if hostInfo == nil {
  631. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn address: %v", a[0]))
  632. }
  633. hostInfo.SetRemote(addr)
  634. return w.WriteLine("Changed")
  635. }
  636. func sshGetHeapProfile(sandboxDir string, fs any, a []string, w sshd.StringWriter) error {
  637. if len(a) == 0 {
  638. return w.WriteLine("No path to write profile provided")
  639. }
  640. filePath, err := sshSanitizeFilePath(sandboxDir, a[0])
  641. if err != nil {
  642. return w.WriteLine(err.Error())
  643. }
  644. file, err := os.Create(filePath)
  645. if err != nil {
  646. err = w.WriteLine(fmt.Sprintf("Unable to create profile file: %s", err))
  647. return err
  648. }
  649. err = pprof.WriteHeapProfile(file)
  650. if err != nil {
  651. err = w.WriteLine(fmt.Sprintf("Unable to write profile: %s", err))
  652. return err
  653. }
  654. err = w.WriteLine(fmt.Sprintf("Mem profile created at %s", a))
  655. return err
  656. }
  657. func sshMutexProfileFraction(fs any, a []string, w sshd.StringWriter) error {
  658. if len(a) == 0 {
  659. rate := runtime.SetMutexProfileFraction(-1)
  660. return w.WriteLine(fmt.Sprintf("Current value: %d", rate))
  661. }
  662. newRate, err := strconv.Atoi(a[0])
  663. if err != nil {
  664. return w.WriteLine(fmt.Sprintf("Invalid argument: %s", a[0]))
  665. }
  666. oldRate := runtime.SetMutexProfileFraction(newRate)
  667. return w.WriteLine(fmt.Sprintf("New value: %d. Old value: %d", newRate, oldRate))
  668. }
  669. func sshGetMutexProfile(sandboxDir string, fs any, a []string, w sshd.StringWriter) error {
  670. if len(a) == 0 {
  671. return w.WriteLine("No path to write profile provided")
  672. }
  673. filePath, err := sshSanitizeFilePath(sandboxDir, a[0])
  674. if err != nil {
  675. return w.WriteLine(err.Error())
  676. }
  677. file, err := os.Create(filePath)
  678. if err != nil {
  679. return w.WriteLine(fmt.Sprintf("Unable to create profile file: %s", err))
  680. }
  681. defer file.Close()
  682. mutexProfile := pprof.Lookup("mutex")
  683. if mutexProfile == nil {
  684. return w.WriteLine("Unable to get pprof.Lookup(\"mutex\")")
  685. }
  686. err = mutexProfile.WriteTo(file, 0)
  687. if err != nil {
  688. return w.WriteLine(fmt.Sprintf("Unable to write profile: %s", err))
  689. }
  690. return w.WriteLine(fmt.Sprintf("Mutex profile created at %s", a))
  691. }
  692. func sshLogLevel(l *slog.Logger, fs any, a []string, w sshd.StringWriter) error {
  693. ctrl, ok := l.Handler().(interface {
  694. GetLevel() slog.Level
  695. SetLevel(slog.Level)
  696. })
  697. if !ok {
  698. return w.WriteLine("Log level is not reconfigurable on this logger")
  699. }
  700. if len(a) == 0 {
  701. return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
  702. }
  703. level, err := logging.ParseLevel(strings.ToLower(a[0]))
  704. if err != nil {
  705. return w.WriteLine(fmt.Sprintf("Unknown log level %s. Possible log levels: trace, debug, info, warn, error", a))
  706. }
  707. ctrl.SetLevel(level)
  708. return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
  709. }
  710. func sshLogFormat(l *slog.Logger, fs any, a []string, w sshd.StringWriter) error {
  711. ctrl, ok := l.Handler().(interface {
  712. GetFormat() string
  713. SetFormat(string) error
  714. })
  715. if !ok {
  716. return w.WriteLine("Log format is not reconfigurable on this logger")
  717. }
  718. if len(a) == 0 {
  719. return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
  720. }
  721. if err := ctrl.SetFormat(strings.ToLower(a[0])); err != nil {
  722. return err
  723. }
  724. return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
  725. }
  726. func sshPrintCert(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  727. args, ok := fs.(*sshPrintCertFlags)
  728. if !ok {
  729. return nil
  730. }
  731. cert := ifce.pki.getCertState().GetDefaultCertificate()
  732. if len(a) > 0 {
  733. vpnAddr, err := netip.ParseAddr(a[0])
  734. if err != nil {
  735. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  736. }
  737. if !vpnAddr.IsValid() {
  738. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  739. }
  740. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  741. if hostInfo == nil {
  742. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn addr: %v", a[0]))
  743. }
  744. cert = hostInfo.GetCert().Certificate
  745. }
  746. if args.Json || args.Pretty {
  747. b, err := cert.MarshalJSON()
  748. if err != nil {
  749. return nil
  750. }
  751. if args.Pretty {
  752. buf := new(bytes.Buffer)
  753. err := json.Indent(buf, b, "", " ")
  754. b = buf.Bytes()
  755. if err != nil {
  756. return nil
  757. }
  758. }
  759. return w.WriteBytes(b)
  760. }
  761. if args.Raw {
  762. b, err := cert.MarshalPEM()
  763. if err != nil {
  764. return nil
  765. }
  766. return w.WriteBytes(b)
  767. }
  768. return w.WriteLine(cert.String())
  769. }
  770. func sshPrintRelays(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  771. args, ok := fs.(*sshPrintTunnelFlags)
  772. if !ok {
  773. w.WriteLine(fmt.Sprintf("sshPrintRelays failed to convert args type"))
  774. return nil
  775. }
  776. relays := map[uint32]*HostInfo{}
  777. ifce.hostMap.Lock()
  778. maps.Copy(relays, ifce.hostMap.Relays)
  779. ifce.hostMap.Unlock()
  780. type RelayFor struct {
  781. Error error
  782. Type string
  783. State string
  784. PeerAddr netip.Addr
  785. LocalIndex uint32
  786. RemoteIndex uint32
  787. RelayedThrough []netip.Addr
  788. }
  789. type RelayOutput struct {
  790. NebulaAddr netip.Addr
  791. RelayForAddrs []RelayFor
  792. }
  793. type CmdOutput struct {
  794. Relays []*RelayOutput
  795. }
  796. co := CmdOutput{}
  797. enc := json.NewEncoder(w.GetWriter())
  798. if args.Pretty {
  799. enc.SetIndent("", " ")
  800. }
  801. for k, v := range relays {
  802. ro := RelayOutput{NebulaAddr: v.vpnAddrs[0]}
  803. co.Relays = append(co.Relays, &ro)
  804. relayHI := ifce.hostMap.QueryVpnAddr(v.vpnAddrs[0])
  805. if relayHI == nil {
  806. ro.RelayForAddrs = append(ro.RelayForAddrs, RelayFor{Error: errors.New("could not find hostinfo")})
  807. continue
  808. }
  809. for _, vpnAddr := range relayHI.relayState.CopyRelayForIps() {
  810. rf := RelayFor{Error: nil}
  811. r, ok := relayHI.relayState.GetRelayForByAddr(vpnAddr)
  812. if ok {
  813. t := ""
  814. switch r.Type {
  815. case ForwardingType:
  816. t = "forwarding"
  817. case TerminalType:
  818. t = "terminal"
  819. default:
  820. t = "unknown"
  821. }
  822. s := ""
  823. switch r.State {
  824. case Requested:
  825. s = "requested"
  826. case Established:
  827. s = "established"
  828. default:
  829. s = "unknown"
  830. }
  831. rf.LocalIndex = r.LocalIndex
  832. rf.RemoteIndex = r.RemoteIndex
  833. rf.PeerAddr = r.PeerAddr
  834. rf.Type = t
  835. rf.State = s
  836. if rf.LocalIndex != k {
  837. rf.Error = fmt.Errorf("hostmap LocalIndex '%v' does not match RelayState LocalIndex", k)
  838. }
  839. }
  840. relayedHI := ifce.hostMap.QueryVpnAddr(vpnAddr)
  841. if relayedHI != nil {
  842. rf.RelayedThrough = append(rf.RelayedThrough, relayedHI.relayState.CopyRelayIps()...)
  843. }
  844. ro.RelayForAddrs = append(ro.RelayForAddrs, rf)
  845. }
  846. }
  847. err := enc.Encode(co)
  848. if err != nil {
  849. return err
  850. }
  851. return nil
  852. }
  853. func sshPrintTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
  854. args, ok := fs.(*sshPrintTunnelFlags)
  855. if !ok {
  856. return nil
  857. }
  858. if len(a) == 0 {
  859. return w.WriteLine("No vpn address was provided")
  860. }
  861. vpnAddr, err := netip.ParseAddr(a[0])
  862. if err != nil {
  863. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  864. }
  865. if !vpnAddr.IsValid() {
  866. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  867. }
  868. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  869. if hostInfo == nil {
  870. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn addr: %v", a[0]))
  871. }
  872. enc := json.NewEncoder(w.GetWriter())
  873. if args.Pretty {
  874. enc.SetIndent("", " ")
  875. }
  876. return enc.Encode(copyHostInfo(hostInfo, ifce.hostMap.GetPreferredRanges()))
  877. }
  878. func sshDeviceInfo(ifce *Interface, fs any, w sshd.StringWriter) error {
  879. data := struct {
  880. Name string `json:"name"`
  881. Cidr []netip.Prefix `json:"cidr"`
  882. }{
  883. Name: ifce.inside.Name(),
  884. Cidr: make([]netip.Prefix, len(ifce.inside.Networks())),
  885. }
  886. copy(data.Cidr, ifce.inside.Networks())
  887. flags, ok := fs.(*sshDeviceInfoFlags)
  888. if !ok {
  889. return fmt.Errorf("internal error: expected flags to be sshDeviceInfoFlags but was %+v", fs)
  890. }
  891. if flags.Json || flags.Pretty {
  892. js := json.NewEncoder(w.GetWriter())
  893. if flags.Pretty {
  894. js.SetIndent("", " ")
  895. }
  896. return js.Encode(data)
  897. } else {
  898. return w.WriteLine(fmt.Sprintf("name=%v cidr=%v", data.Name, data.Cidr))
  899. }
  900. }
  901. func sshReload(c *config.C, w sshd.StringWriter) error {
  902. err := w.WriteLine("Reloading config")
  903. c.ReloadConfig()
  904. return err
  905. }