auth.go 6.1 KB

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