configssh.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package configssh // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sshcheckreceiver/internal/configssh"
  4. import (
  5. "errors"
  6. "fmt"
  7. "os"
  8. "time"
  9. "github.com/pkg/sftp"
  10. "go.opentelemetry.io/collector/component"
  11. "golang.org/x/crypto/ssh"
  12. "golang.org/x/crypto/ssh/knownhosts"
  13. )
  14. const (
  15. defaultClientVersion = "SSH-2.0-OTelClient"
  16. )
  17. var errMissingKnownHosts = errors.New(`known_hosts file is missing`)
  18. type SSHClientSettings struct {
  19. // Endpoint is always required
  20. Endpoint string `mapstructure:"endpoint"`
  21. Timeout time.Duration `mapstructure:"timeout"`
  22. // authentication requires a Username and either a Password or KeyFile
  23. Username string `mapstructure:"username"`
  24. Password string `mapstructure:"password"`
  25. KeyFile string `mapstructure:"key_file"`
  26. // file path to the known_hosts
  27. KnownHosts string `mapstructure:"known_hosts"`
  28. // IgnoreHostKey provides an insecure path to quickstarts and testing
  29. IgnoreHostKey bool `mapstructure:"ignore_host_key"`
  30. }
  31. type Client struct {
  32. *ssh.Client
  33. *ssh.ClientConfig
  34. DialFunc func(network, address string, config *ssh.ClientConfig) (*ssh.Client, error)
  35. }
  36. // Dial starts an SSH session.
  37. func (c *Client) Dial(endpoint string) (err error) {
  38. c.Client, err = c.DialFunc("tcp", endpoint, c.ClientConfig)
  39. if err != nil {
  40. return err
  41. }
  42. return nil
  43. }
  44. func (c *Client) SFTPClient() (*SFTPClient, error) {
  45. if c.Client == nil || c.Client.Conn == nil {
  46. return nil, fmt.Errorf("SSH client not initialized")
  47. }
  48. client, err := sftp.NewClient(c.Client)
  49. if err != nil {
  50. return nil, err
  51. }
  52. return &SFTPClient{
  53. Client: client,
  54. ClientConfig: c.ClientConfig,
  55. }, nil
  56. }
  57. type SFTPClient struct {
  58. *sftp.Client
  59. *ssh.ClientConfig
  60. }
  61. // ToClient creates an SSHClient.
  62. func (scs *SSHClientSettings) ToClient(_ component.Host, _ component.TelemetrySettings) (*Client, error) {
  63. var (
  64. auth ssh.AuthMethod
  65. hkc ssh.HostKeyCallback
  66. )
  67. if len(scs.KeyFile) > 0 {
  68. key, err := os.ReadFile(scs.KeyFile)
  69. if err != nil {
  70. return nil, fmt.Errorf("unable to read private key: %w", err)
  71. }
  72. if len(scs.Password) > 0 {
  73. sgn, err := ssh.ParsePrivateKeyWithPassphrase(key, []byte(scs.Password))
  74. if err != nil {
  75. return nil, fmt.Errorf("unable to parse private key with passphrase: %w", err)
  76. }
  77. auth = ssh.PublicKeys(sgn)
  78. } else {
  79. sgn, err := ssh.ParsePrivateKey(key)
  80. if err != nil {
  81. return nil, fmt.Errorf("unable to parse private key with passphrase: %w", err)
  82. }
  83. auth = ssh.PublicKeys(sgn)
  84. }
  85. } else {
  86. auth = ssh.Password(scs.Password)
  87. }
  88. switch {
  89. case scs.IgnoreHostKey:
  90. // nolint G106
  91. hkc = ssh.InsecureIgnoreHostKey() //#nosec G106
  92. case scs.KnownHosts != "":
  93. fn, err := knownhosts.New(scs.KnownHosts)
  94. if err != nil {
  95. return nil, err
  96. }
  97. hkc = fn
  98. default:
  99. fn, err := defaultKnownHostsCallback()
  100. if err != nil {
  101. return nil, err
  102. }
  103. hkc = fn
  104. }
  105. return &Client{
  106. ClientConfig: &ssh.ClientConfig{
  107. User: scs.Username,
  108. Auth: []ssh.AuthMethod{auth},
  109. HostKeyCallback: hkc,
  110. ClientVersion: defaultClientVersion,
  111. },
  112. DialFunc: ssh.Dial,
  113. }, nil
  114. }
  115. func defaultKnownHostsPath() (string, error) {
  116. home, err := os.UserHomeDir()
  117. if err != nil {
  118. return "", err
  119. }
  120. path := fmt.Sprintf("%s/.ssh/known_hosts", home)
  121. if _, err := os.Stat(path); err != nil {
  122. return "", errMissingKnownHosts
  123. }
  124. return path, nil
  125. }
  126. func defaultKnownHostsCallback() (hkc ssh.HostKeyCallback, err error) {
  127. var knownHosts []string
  128. if homeKH, err := defaultKnownHostsPath(); err == nil {
  129. knownHosts = append(knownHosts, homeKH)
  130. }
  131. if _, err := os.Stat("/etc/ssh/known_hosts"); err == nil {
  132. knownHosts = append(knownHosts, "/etc/ssh/known_hosts")
  133. }
  134. return knownhosts.New(knownHosts...)
  135. }