1
0

ssh.go 29 KB

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