auth.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package plugin
  2. import (
  3. "crypto/sha256"
  4. "errors"
  5. "fmt"
  6. "os/exec"
  7. "github.com/hashicorp/go-hclog"
  8. "github.com/hashicorp/go-plugin"
  9. "github.com/sftpgo/sdk/plugin/auth"
  10. "github.com/drakkan/sftpgo/v2/logger"
  11. )
  12. // Supported auth scopes
  13. const (
  14. AuthScopePassword = 1
  15. AuthScopePublicKey = 2
  16. AuthScopeKeyboardInteractive = 4
  17. AuthScopeTLSCertificate = 8
  18. )
  19. // KeyboardAuthRequest defines the request for a keyboard interactive authentication step
  20. type KeyboardAuthRequest struct {
  21. RequestID string `json:"request_id"`
  22. Step int `json:"step"`
  23. Username string `json:"username,omitempty"`
  24. IP string `json:"ip,omitempty"`
  25. Password string `json:"password,omitempty"`
  26. Answers []string `json:"answers,omitempty"`
  27. Questions []string `json:"questions,omitempty"`
  28. }
  29. // KeyboardAuthResponse defines the response for a keyboard interactive authentication step
  30. type KeyboardAuthResponse struct {
  31. Instruction string `json:"instruction"`
  32. Questions []string `json:"questions"`
  33. Echos []bool `json:"echos"`
  34. AuthResult int `json:"auth_result"`
  35. CheckPwd int `json:"check_password"`
  36. }
  37. // Validate returns an error if the KeyboardAuthResponse is invalid
  38. func (r *KeyboardAuthResponse) Validate() error {
  39. if len(r.Questions) == 0 {
  40. err := errors.New("interactive auth error: response does not contain questions")
  41. return err
  42. }
  43. if len(r.Questions) != len(r.Echos) {
  44. err := fmt.Errorf("interactive auth error: response questions don't match echos: %v %v",
  45. len(r.Questions), len(r.Echos))
  46. return err
  47. }
  48. return nil
  49. }
  50. // AuthConfig defines configuration parameters for auth plugins
  51. type AuthConfig struct {
  52. // Scope defines the scope for the authentication plugin.
  53. // - 1 means passwords only
  54. // - 2 means public keys only
  55. // - 4 means keyboard interactive only
  56. // - 8 means TLS certificates only
  57. // you can combine the scopes, for example 3 means password and public key, 5 password and keyboard
  58. // interactive and so on
  59. Scope int `json:"scope" mapstructure:"scope"`
  60. }
  61. func (c *AuthConfig) validate() error {
  62. authScopeMax := AuthScopePassword + AuthScopePublicKey + AuthScopeKeyboardInteractive + AuthScopeTLSCertificate
  63. if c.Scope == 0 || c.Scope > authScopeMax {
  64. return fmt.Errorf("invalid auth scope: %v", c.Scope)
  65. }
  66. return nil
  67. }
  68. type authPlugin struct {
  69. config Config
  70. service auth.Authenticator
  71. client *plugin.Client
  72. }
  73. func newAuthPlugin(config Config) (*authPlugin, error) {
  74. p := &authPlugin{
  75. config: config,
  76. }
  77. if err := p.initialize(); err != nil {
  78. logger.Warn(logSender, "", "unable to create auth plugin: %v, config %+v", err, config)
  79. return nil, err
  80. }
  81. return p, nil
  82. }
  83. func (p *authPlugin) initialize() error {
  84. killProcess(p.config.Cmd)
  85. logger.Debug(logSender, "", "create new auth plugin %#v", p.config.Cmd)
  86. if err := p.config.AuthOptions.validate(); err != nil {
  87. return fmt.Errorf("invalid options for auth plugin %#v: %v", p.config.Cmd, err)
  88. }
  89. var secureConfig *plugin.SecureConfig
  90. if p.config.SHA256Sum != "" {
  91. secureConfig.Checksum = []byte(p.config.SHA256Sum)
  92. secureConfig.Hash = sha256.New()
  93. }
  94. client := plugin.NewClient(&plugin.ClientConfig{
  95. HandshakeConfig: auth.Handshake,
  96. Plugins: auth.PluginMap,
  97. Cmd: exec.Command(p.config.Cmd, p.config.Args...),
  98. AllowedProtocols: []plugin.Protocol{
  99. plugin.ProtocolGRPC,
  100. },
  101. AutoMTLS: p.config.AutoMTLS,
  102. SecureConfig: secureConfig,
  103. Managed: false,
  104. Logger: &logger.HCLogAdapter{
  105. Logger: hclog.New(&hclog.LoggerOptions{
  106. Name: fmt.Sprintf("%v.%v", logSender, auth.PluginName),
  107. Level: pluginsLogLevel,
  108. DisableTime: true,
  109. }),
  110. },
  111. })
  112. rpcClient, err := client.Client()
  113. if err != nil {
  114. logger.Debug(logSender, "", "unable to get rpc client for kms plugin %#v: %v", p.config.Cmd, err)
  115. return err
  116. }
  117. raw, err := rpcClient.Dispense(auth.PluginName)
  118. if err != nil {
  119. logger.Debug(logSender, "", "unable to get plugin %v from rpc client for command %#v: %v",
  120. auth.PluginName, p.config.Cmd, err)
  121. return err
  122. }
  123. p.service = raw.(auth.Authenticator)
  124. p.client = client
  125. return nil
  126. }
  127. func (p *authPlugin) exited() bool {
  128. return p.client.Exited()
  129. }
  130. func (p *authPlugin) cleanup() {
  131. p.client.Kill()
  132. }
  133. func (p *authPlugin) checkUserAndPass(username, password, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  134. return p.service.CheckUserAndPass(username, password, ip, protocol, userAsJSON)
  135. }
  136. func (p *authPlugin) checkUserAndTLSCertificate(username, tlsCert, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  137. return p.service.CheckUserAndTLSCert(username, tlsCert, ip, protocol, userAsJSON)
  138. }
  139. func (p *authPlugin) checkUserAndPublicKey(username, pubKey, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  140. return p.service.CheckUserAndPublicKey(username, pubKey, ip, protocol, userAsJSON)
  141. }
  142. func (p *authPlugin) checkUserAndKeyboardInteractive(username, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  143. return p.service.CheckUserAndKeyboardInteractive(username, ip, protocol, userAsJSON)
  144. }
  145. func (p *authPlugin) sendKeyboardIteractiveRequest(req *KeyboardAuthRequest) (*KeyboardAuthResponse, error) {
  146. instructions, questions, echos, authResult, checkPassword, err := p.service.SendKeyboardAuthRequest(
  147. req.RequestID, req.Username, req.Password, req.IP, req.Answers, req.Questions, int32(req.Step))
  148. if err != nil {
  149. return nil, err
  150. }
  151. return &KeyboardAuthResponse{
  152. Instruction: instructions,
  153. Questions: questions,
  154. Echos: echos,
  155. AuthResult: authResult,
  156. CheckPwd: checkPassword,
  157. }, nil
  158. }