package main import ( "errors" "fmt" "net/http" "github.com/go-ldap/ldap/v3" ) /* Check credentials against LDAP */ type LdapPreAuth struct { WithConfig *Config OnWrongPassword ErrorHandler OnFailure ErrorHandler OnCreds CredsHandler } func (l LdapPreAuth) WithCreds(username, password string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var e *LdapWrongPasswordError access_key, secret_key, err := LdapGetS3(l.WithConfig, username, password) if err == nil { l.OnCreds.WithCreds(access_key, secret_key).ServeHTTP(w, r) } else if errors.As(err, &e) { l.OnWrongPassword.WithError(err).ServeHTTP(w, r) } else { l.OnFailure.WithError(e).ServeHTTP(w, r) } }) } /** * Private logic */ type ldapConnector struct { conn *ldap.Conn config *Config userDn string } type LdapError struct { Username string Err error } func (e *LdapError) Error() string { return "ldap error for " + e.Username + ": " + e.Err.Error() } type LdapWrongPasswordError struct{ LdapError } func LdapGetS3(c *Config, username, password string) (access_key, secret_key string, werr error) { // 1. Connect to the server conn, err := ldapConnect(c) if err != nil { werr = &LdapError{username, err} return } defer conn.Close() // 2. Authenticate with provided credentials // @FIXME we should better check the error, it could also be due to an LDAP error err = conn.auth(username, password) if err != nil { werr = &LdapWrongPasswordError{LdapError{username, err}} return } // 3. Fetch user's profile profile, err := conn.profile() if err != nil { werr = &LdapError{username, err} return } // 4. Basic checks upon users' attributes access_key = profile.GetAttributeValue("garage_s3_access_key") secret_key = profile.GetAttributeValue("garage_s3_secret_key") if access_key == "" || secret_key == "" { err = errors.New(fmt.Sprintf("Either access key or secret key is missing in LDAP for %s", conn.userDn)) werr = &LdapError{username, err} return } // 5. Send fetched credentials to the next middleware return } func ldapConnect(c *Config) (ldapConnector, error) { ldapSock, err := ldap.Dial("tcp", c.LdapServer) if err != nil { return ldapConnector{}, err } return ldapConnector{ conn: ldapSock, config: c, }, nil } func (l *ldapConnector) auth(username, password string) error { l.userDn = fmt.Sprintf("%s=%s,%s", l.config.UserNameAttr, username, l.config.UserBaseDN) return l.conn.Bind(l.userDn, password) } func (l *ldapConnector) profile() (*ldap.Entry, error) { searchRequest := ldap.NewSearchRequest( l.userDn, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{"garage_s3_access_key", "garage_s3_secret_key"}, nil) sr, err := l.conn.Search(searchRequest) if err != nil { return nil, err } if len(sr.Entries) != 1 { return nil, errors.New(fmt.Sprintf("Wrong number of LDAP entries, expected 1, got %d", len(sr.Entries))) } return sr.Entries[0], nil } func (l *ldapConnector) Close() { if l.conn != nil { l.conn.Close() l.conn = nil } }