auth.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright (C) 2019-2023 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. "os/exec"
  19. "github.com/hashicorp/go-hclog"
  20. "github.com/hashicorp/go-plugin"
  21. "github.com/sftpgo/sdk/plugin/auth"
  22. "github.com/drakkan/sftpgo/v2/internal/logger"
  23. )
  24. // Supported auth scopes
  25. const (
  26. AuthScopePassword = 1
  27. AuthScopePublicKey = 2
  28. AuthScopeKeyboardInteractive = 4
  29. AuthScopeTLSCertificate = 8
  30. )
  31. // KeyboardAuthRequest defines the request for a keyboard interactive authentication step
  32. type KeyboardAuthRequest struct {
  33. RequestID string `json:"request_id"`
  34. Step int `json:"step"`
  35. Username string `json:"username,omitempty"`
  36. IP string `json:"ip,omitempty"`
  37. Password string `json:"password,omitempty"`
  38. Answers []string `json:"answers,omitempty"`
  39. Questions []string `json:"questions,omitempty"`
  40. }
  41. // KeyboardAuthResponse defines the response for a keyboard interactive authentication step
  42. type KeyboardAuthResponse struct {
  43. Instruction string `json:"instruction"`
  44. Questions []string `json:"questions"`
  45. Echos []bool `json:"echos"`
  46. AuthResult int `json:"auth_result"`
  47. CheckPwd int `json:"check_password"`
  48. }
  49. // Validate returns an error if the KeyboardAuthResponse is invalid
  50. func (r *KeyboardAuthResponse) Validate() error {
  51. if len(r.Questions) == 0 {
  52. err := errors.New("interactive auth error: response does not contain questions")
  53. return err
  54. }
  55. if len(r.Questions) != len(r.Echos) {
  56. err := fmt.Errorf("interactive auth error: response questions don't match echos: %v %v",
  57. len(r.Questions), len(r.Echos))
  58. return err
  59. }
  60. return nil
  61. }
  62. // AuthConfig defines configuration parameters for auth plugins
  63. type AuthConfig struct {
  64. // Scope defines the scope for the authentication plugin.
  65. // - 1 means passwords only
  66. // - 2 means public keys only
  67. // - 4 means keyboard interactive only
  68. // - 8 means TLS certificates only
  69. // you can combine the scopes, for example 3 means password and public key, 5 password and keyboard
  70. // interactive and so on
  71. Scope int `json:"scope" mapstructure:"scope"`
  72. }
  73. func (c *AuthConfig) validate() error {
  74. authScopeMax := AuthScopePassword + AuthScopePublicKey + AuthScopeKeyboardInteractive + AuthScopeTLSCertificate
  75. if c.Scope == 0 || c.Scope > authScopeMax {
  76. return fmt.Errorf("invalid auth scope: %v", c.Scope)
  77. }
  78. return nil
  79. }
  80. type authPlugin struct {
  81. config Config
  82. service auth.Authenticator
  83. client *plugin.Client
  84. }
  85. func newAuthPlugin(config Config) (*authPlugin, error) {
  86. p := &authPlugin{
  87. config: config,
  88. }
  89. if err := p.initialize(); err != nil {
  90. logger.Warn(logSender, "", "unable to create auth plugin: %v, config %+v", err, config)
  91. return nil, err
  92. }
  93. return p, nil
  94. }
  95. func (p *authPlugin) initialize() error {
  96. killProcess(p.config.Cmd)
  97. logger.Debug(logSender, "", "create new auth plugin %q", p.config.Cmd)
  98. if err := p.config.AuthOptions.validate(); err != nil {
  99. return fmt.Errorf("invalid options for auth plugin %q: %v", p.config.Cmd, err)
  100. }
  101. secureConfig, err := p.config.getSecureConfig()
  102. if err != nil {
  103. return err
  104. }
  105. client := plugin.NewClient(&plugin.ClientConfig{
  106. HandshakeConfig: auth.Handshake,
  107. Plugins: auth.PluginMap,
  108. Cmd: exec.Command(p.config.Cmd, p.config.Args...),
  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 kms 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. }