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) { // 1. Connect to the server conn, err := ldapConnect(l.WithConfig) if err != nil { l.OnFailure.WithError(err).ServeHTTP(w, r) 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 { l.OnWrongPassword.WithError(err).ServeHTTP(w, r) return } // 3. Fetch user's profile profile, err := conn.profile() if err != nil { l.OnFailure.WithError(err).ServeHTTP(w, r) 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)) l.OnFailure.WithError(err).ServeHTTP(w, r) return } // 5. Send fetched credentials to the next middleware l.OnCreds.WithCreds(access_key, secret_key).ServeHTTP(w, r) }) } /** * Private logic */ type ldapConnector struct { conn *ldap.Conn config *Config userDn string } 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 } }