auth.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package plugin
  15. import (
  16. "errors"
  17. "fmt"
  18. "github.com/hashicorp/go-hclog"
  19. "github.com/hashicorp/go-plugin"
  20. "github.com/sftpgo/sdk/plugin/auth"
  21. "github.com/drakkan/sftpgo/v2/internal/logger"
  22. )
  23. // Supported auth scopes
  24. const (
  25. AuthScopePassword = 1
  26. AuthScopePublicKey = 2
  27. AuthScopeKeyboardInteractive = 4
  28. AuthScopeTLSCertificate = 8
  29. )
  30. // KeyboardAuthRequest defines the request for a keyboard interactive authentication step
  31. type KeyboardAuthRequest struct {
  32. RequestID string `json:"request_id"`
  33. Step int `json:"step"`
  34. Username string `json:"username,omitempty"`
  35. IP string `json:"ip,omitempty"`
  36. Password string `json:"password,omitempty"`
  37. Answers []string `json:"answers,omitempty"`
  38. Questions []string `json:"questions,omitempty"`
  39. }
  40. // KeyboardAuthResponse defines the response for a keyboard interactive authentication step
  41. type KeyboardAuthResponse struct {
  42. Instruction string `json:"instruction"`
  43. Questions []string `json:"questions"`
  44. Echos []bool `json:"echos"`
  45. AuthResult int `json:"auth_result"`
  46. CheckPwd int `json:"check_password"`
  47. }
  48. // Validate returns an error if the KeyboardAuthResponse is invalid
  49. func (r *KeyboardAuthResponse) Validate() error {
  50. if len(r.Questions) == 0 {
  51. err := errors.New("interactive auth error: response does not contain questions")
  52. return err
  53. }
  54. if len(r.Questions) != len(r.Echos) {
  55. err := fmt.Errorf("interactive auth error: response questions don't match echos: %v %v",
  56. len(r.Questions), len(r.Echos))
  57. return err
  58. }
  59. return nil
  60. }
  61. // AuthConfig defines configuration parameters for auth plugins
  62. type AuthConfig struct {
  63. // Scope defines the scope for the authentication plugin.
  64. // - 1 means passwords only
  65. // - 2 means public keys only
  66. // - 4 means keyboard interactive only
  67. // - 8 means TLS certificates only
  68. // you can combine the scopes, for example 3 means password and public key, 5 password and keyboard
  69. // interactive and so on
  70. Scope int `json:"scope" mapstructure:"scope"`
  71. }
  72. func (c *AuthConfig) validate() error {
  73. authScopeMax := AuthScopePassword + AuthScopePublicKey + AuthScopeKeyboardInteractive + AuthScopeTLSCertificate
  74. if c.Scope == 0 || c.Scope > authScopeMax {
  75. return fmt.Errorf("invalid auth scope: %v", c.Scope)
  76. }
  77. return nil
  78. }
  79. type authPlugin struct {
  80. config Config
  81. service auth.Authenticator
  82. client *plugin.Client
  83. }
  84. func newAuthPlugin(config Config) (*authPlugin, error) {
  85. p := &authPlugin{
  86. config: config,
  87. }
  88. if err := p.initialize(); err != nil {
  89. logger.Warn(logSender, "", "unable to create auth plugin: %v, config %+v", err, config)
  90. return nil, err
  91. }
  92. return p, nil
  93. }
  94. func (p *authPlugin) initialize() error {
  95. killProcess(p.config.Cmd)
  96. logger.Debug(logSender, "", "create new auth plugin %q", p.config.Cmd)
  97. if err := p.config.AuthOptions.validate(); err != nil {
  98. return fmt.Errorf("invalid options for auth plugin %q: %v", p.config.Cmd, err)
  99. }
  100. secureConfig, err := p.config.getSecureConfig()
  101. if err != nil {
  102. return err
  103. }
  104. client := plugin.NewClient(&plugin.ClientConfig{
  105. HandshakeConfig: auth.Handshake,
  106. Plugins: auth.PluginMap,
  107. Cmd: p.config.getCommand(),
  108. SkipHostEnv: true,
  109. AllowedProtocols: []plugin.Protocol{
  110. plugin.ProtocolGRPC,
  111. },
  112. AutoMTLS: p.config.AutoMTLS,
  113. SecureConfig: secureConfig,
  114. Managed: false,
  115. Logger: &logger.HCLogAdapter{
  116. Logger: hclog.New(&hclog.LoggerOptions{
  117. Name: fmt.Sprintf("%v.%v", logSender, auth.PluginName),
  118. Level: pluginsLogLevel,
  119. DisableTime: true,
  120. }),
  121. },
  122. })
  123. rpcClient, err := client.Client()
  124. if err != nil {
  125. logger.Debug(logSender, "", "unable to get rpc client for auth plugin %q: %v", p.config.Cmd, err)
  126. return err
  127. }
  128. raw, err := rpcClient.Dispense(auth.PluginName)
  129. if err != nil {
  130. logger.Debug(logSender, "", "unable to get plugin %v from rpc client for command %q: %v",
  131. auth.PluginName, p.config.Cmd, err)
  132. return err
  133. }
  134. p.service = raw.(auth.Authenticator)
  135. p.client = client
  136. return nil
  137. }
  138. func (p *authPlugin) exited() bool {
  139. return p.client.Exited()
  140. }
  141. func (p *authPlugin) cleanup() {
  142. p.client.Kill()
  143. }
  144. func (p *authPlugin) checkUserAndPass(username, password, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  145. return p.service.CheckUserAndPass(username, password, ip, protocol, userAsJSON)
  146. }
  147. func (p *authPlugin) checkUserAndTLSCertificate(username, tlsCert, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  148. return p.service.CheckUserAndTLSCert(username, tlsCert, ip, protocol, userAsJSON)
  149. }
  150. func (p *authPlugin) checkUserAndPublicKey(username, pubKey, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  151. return p.service.CheckUserAndPublicKey(username, pubKey, ip, protocol, userAsJSON)
  152. }
  153. func (p *authPlugin) checkUserAndKeyboardInteractive(username, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  154. return p.service.CheckUserAndKeyboardInteractive(username, ip, protocol, userAsJSON)
  155. }
  156. func (p *authPlugin) sendKeyboardIteractiveRequest(req *KeyboardAuthRequest) (*KeyboardAuthResponse, error) {
  157. instructions, questions, echos, authResult, checkPassword, err := p.service.SendKeyboardAuthRequest(
  158. req.RequestID, req.Username, req.Password, req.IP, req.Answers, req.Questions, int32(req.Step))
  159. if err != nil {
  160. return nil, err
  161. }
  162. return &KeyboardAuthResponse{
  163. Instruction: instructions,
  164. Questions: questions,
  165. Echos: echos,
  166. AuthResult: authResult,
  167. CheckPwd: checkPassword,
  168. }, nil
  169. }