ldapauth.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package httpd
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "fmt"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "github.com/drakkan/sftpgo/ldapauthserver/logger"
  10. "github.com/go-chi/chi/middleware"
  11. "github.com/go-chi/render"
  12. "github.com/go-ldap/ldap/v3"
  13. "golang.org/x/crypto/ssh"
  14. )
  15. func getSFTPGoUser(entry *ldap.Entry, username string) (SFTPGoUser, error) {
  16. var err error
  17. var user SFTPGoUser
  18. uid := ldapConfig.DefaultUID
  19. gid := ldapConfig.DefaultGID
  20. status := 1
  21. if !ldapConfig.ForceDefaultUID {
  22. uid, err = strconv.Atoi(entry.GetAttributeValue(ldapConfig.GetUIDNumber()))
  23. if err != nil {
  24. return user, err
  25. }
  26. }
  27. if !ldapConfig.ForceDefaultGID {
  28. uid, err = strconv.Atoi(entry.GetAttributeValue(ldapConfig.GetGIDNumber()))
  29. if err != nil {
  30. return user, err
  31. }
  32. }
  33. sftpgoUser := SFTPGoUser{
  34. Username: username,
  35. HomeDir: entry.GetAttributeValue(ldapConfig.GetHomeDirectory()),
  36. UID: uid,
  37. GID: gid,
  38. Status: status,
  39. }
  40. sftpgoUser.Permissions = make(map[string][]string)
  41. sftpgoUser.Permissions["/"] = []string{"*"}
  42. return sftpgoUser, nil
  43. }
  44. func checkSFTPGoUserAuth(w http.ResponseWriter, r *http.Request) {
  45. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  46. var authReq externalAuthRequest
  47. err := render.DecodeJSON(r.Body, &authReq)
  48. if err != nil {
  49. logger.Warn(logSender, middleware.GetReqID(r.Context()), "error decoding auth request: %v", err)
  50. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  51. return
  52. }
  53. l, err := ldap.DialURL(ldapConfig.BindURL, ldap.DialWithTLSConfig(&tls.Config{
  54. InsecureSkipVerify: ldapConfig.InsecureSkipVerify,
  55. RootCAs: rootCAs,
  56. }))
  57. if err != nil {
  58. logger.Warn(logSender, middleware.GetReqID(r.Context()), "error connecting to the LDAP server: %v", err)
  59. sendAPIResponse(w, r, err, "Error connecting to the LDAP server", http.StatusInternalServerError)
  60. return
  61. }
  62. defer l.Close()
  63. err = l.Bind(ldapConfig.BindUsername, ldapConfig.BindPassword)
  64. if err != nil {
  65. logger.Warn(logSender, middleware.GetReqID(r.Context()), "error binding to the LDAP server: %v", err)
  66. sendAPIResponse(w, r, err, "Error binding to the LDAP server", http.StatusInternalServerError)
  67. return
  68. }
  69. searchRequest := ldap.NewSearchRequest(
  70. ldapConfig.BaseDN,
  71. ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
  72. strings.Replace(ldapConfig.SearchFilter, "%s", authReq.Username, 1),
  73. ldapConfig.SearchBaseAttrs,
  74. nil,
  75. )
  76. sr, err := l.Search(searchRequest)
  77. if err != nil {
  78. logger.Warn(logSender, middleware.GetReqID(r.Context()), "error searching LDAP user %#v: %v", authReq.Username, err)
  79. sendAPIResponse(w, r, err, "Error searching LDAP user", http.StatusInternalServerError)
  80. return
  81. }
  82. if len(sr.Entries) != 1 {
  83. logger.Warn(logSender, middleware.GetReqID(r.Context()), "expected one user, found: %v", len(sr.Entries))
  84. sendAPIResponse(w, r, nil, fmt.Sprintf("Expected one user, found: %v", len(sr.Entries)), http.StatusNotFound)
  85. return
  86. }
  87. if len(authReq.PublicKey) > 0 {
  88. userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(authReq.PublicKey))
  89. if err != nil {
  90. logger.Warn(logSender, middleware.GetReqID(r.Context()), "invalid public key for user %#v: %v", authReq.Username, err)
  91. sendAPIResponse(w, r, err, "Invalid public key", http.StatusBadRequest)
  92. return
  93. }
  94. authOk := false
  95. for _, k := range sr.Entries[0].GetAttributeValues(ldapConfig.GetPublicKey()) {
  96. key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  97. // we skip an invalid public key stored inside the LDAP server
  98. if err != nil {
  99. continue
  100. }
  101. if bytes.Equal(key.Marshal(), userKey.Marshal()) {
  102. authOk = true
  103. break
  104. }
  105. }
  106. if !authOk {
  107. logger.Warn(logSender, middleware.GetReqID(r.Context()), "public key authentication failed for user: %#v", authReq.Username)
  108. sendAPIResponse(w, r, nil, "public key authentication failed", http.StatusForbidden)
  109. return
  110. }
  111. } else {
  112. // bind to the LDAP server with the user dn and the given password to check the password
  113. userdn := sr.Entries[0].DN
  114. err = l.Bind(userdn, authReq.Password)
  115. if err != nil {
  116. logger.Warn(logSender, middleware.GetReqID(r.Context()), "password authentication failed for user: %#v", authReq.Username)
  117. sendAPIResponse(w, r, nil, "password authentication failed", http.StatusForbidden)
  118. return
  119. }
  120. }
  121. user, err := getSFTPGoUser(sr.Entries[0], authReq.Username)
  122. if err != nil {
  123. logger.Warn(logSender, middleware.GetReqID(r.Context()), "get user from LDAP entry failed for username %#v: %v",
  124. authReq.Username, err)
  125. sendAPIResponse(w, r, err, "mapping LDAP user failed", http.StatusInternalServerError)
  126. return
  127. }
  128. render.JSON(w, r, user)
  129. }