func (c *LDAPConnector) Sync() chan struct{} { stop := make(chan struct{}) go func() { for { select { case <-time.After(c.idp.ldapPool.PoolCheckTimer): alive, killed := c.idp.ldapPool.CheckConnections() if alive > 0 { log.Infof("Connector ID=%v idle_conns=%v", c.id, alive) } if killed > 0 { log.Warningf("Connector ID=%v closed %v dead connections.", c.id, killed) } case <-stop: return } } }() return stop }
// Put makes a connection ready for re-use and puts it back into the pool. If the connection // cannot be reused it is discarded. If there already are MaxIdleConn connections in the pool // the connection is discarded. func (p *LDAPPool) Put(c *ldap.Conn) { p.m.Lock() if p.conns == nil { // First call to Put, initialize map p.conns = make(map[*ldap.Conn]struct{}) } if len(p.conns)+1 > p.MaxIdleConn { p.m.Unlock() c.Close() return } p.m.Unlock() // drop to anonymous bind err := c.Bind("", "") if err != nil { // unsupported or disallowed, throw away connection log.Warningf("Unable to re-use LDAP Connection after failure to bind anonymously: %v", err) c.Close() return } p.m.Lock() p.conns[c] = struct{}{} p.m.Unlock() }
func (cfg *LDAPConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) { ns.Path = path.Join(ns.Path, httpPathCallback) tpl := tpls.Lookup(LDAPLoginPageTemplateName) if tpl == nil { return nil, fmt.Errorf("unable to find necessary HTML template") } // defaults const defaultNameAttribute = "cn" const defaultEmailAttribute = "mail" const defaultBindTemplate = "uid=%u,%b" const defaultSearchScope = ldap.ScopeWholeSubtree if cfg.UseTLS && cfg.UseSSL { return nil, fmt.Errorf("Invalid configuration. useTLS and useSSL are mutual exclusive.") } if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 { return nil, fmt.Errorf("Invalid configuration. Both certFile and keyFile must be specified.") } nameAttribute := defaultNameAttribute if len(cfg.NameAttribute) > 0 { nameAttribute = cfg.NameAttribute } emailAttribute := defaultEmailAttribute if len(cfg.EmailAttribute) > 0 { emailAttribute = cfg.EmailAttribute } bindTemplate := defaultBindTemplate if len(cfg.BindTemplate) > 0 { if cfg.SearchBeforeAuth { log.Warningf("bindTemplate not used when searchBeforeAuth specified.") } bindTemplate = cfg.BindTemplate } searchScope := defaultSearchScope if len(cfg.SearchScope) > 0 { switch { case strings.EqualFold(cfg.SearchScope, "BASE"): searchScope = ldap.ScopeBaseObject case strings.EqualFold(cfg.SearchScope, "ONE"): searchScope = ldap.ScopeSingleLevel case strings.EqualFold(cfg.SearchScope, "SUB"): searchScope = ldap.ScopeWholeSubtree default: return nil, fmt.Errorf("Invalid value for searchScope: '%v'. Must be one of 'base', 'one' or 'sub'.", cfg.SearchScope) } } if cfg.Timeout != 0 { ldap.DefaultTimeout = cfg.Timeout * time.Millisecond } tlsConfig := &tls.Config{ ServerName: cfg.ServerHost, InsecureSkipVerify: cfg.SkipCertVerification, } if (cfg.UseTLS || cfg.UseSSL) && len(cfg.CaFile) > 0 { buf, err := ioutil.ReadFile(cfg.CaFile) if err != nil { return nil, err } rootCertPool := x509.NewCertPool() ok := rootCertPool.AppendCertsFromPEM(buf) if ok { tlsConfig.RootCAs = rootCertPool } else { return nil, fmt.Errorf("%v: Unable to parse certificate data.", cfg.CaFile) } } if (cfg.UseTLS || cfg.UseSSL) && len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 { cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) if err != nil { return nil, err } tlsConfig.Certificates = []tls.Certificate{cert} } idp := &LDAPIdentityProvider{ serverHost: cfg.ServerHost, serverPort: cfg.ServerPort, useTLS: cfg.UseTLS, useSSL: cfg.UseSSL, baseDN: cfg.BaseDN, nameAttribute: nameAttribute, emailAttribute: emailAttribute, searchBeforeAuth: cfg.SearchBeforeAuth, searchFilter: cfg.SearchFilter, searchScope: searchScope, searchBindDN: cfg.SearchBindDN, searchBindPw: cfg.SearchBindPw, bindTemplate: bindTemplate, tlsConfig: tlsConfig, } idpc := &LDAPConnector{ id: cfg.ID, idp: idp, namespace: ns, trustedEmailProvider: cfg.TrustedEmailProvider, loginFunc: lf, loginTpl: tpl, } return idpc, nil }
func (m *LDAPIdentityProvider) Identity(username, password string) (*oidc.Identity, error) { var err error var bindDN, ldapUid, ldapName, ldapEmail string var ldapConn *ldap.Conn ldapConn, err = m.LDAPConnect() if err != nil { return nil, err } defer ldapConn.Close() if m.searchBeforeAuth { err = ldapConn.Bind(m.searchBindDN, m.searchBindPw) if err != nil { return nil, err } filter := m.ParseString(m.searchFilter, username) attributes := []string{ m.nameAttribute, m.emailAttribute, } s := ldap.NewSearchRequest(m.baseDN, m.searchScope, ldap.NeverDerefAliases, 0, 0, false, filter, attributes, nil) sr, err := ldapConn.Search(s) if err != nil { return nil, err } if len(sr.Entries) == 0 { err = fmt.Errorf("Search returned no match. filter='%v' base='%v'", filter, m.baseDN) return nil, err } bindDN = sr.Entries[0].DN ldapName = sr.Entries[0].GetAttributeValue(m.nameAttribute) ldapEmail = sr.Entries[0].GetAttributeValue(m.emailAttribute) // drop to anonymous bind, prepare for bind as user err = ldapConn.Bind("", "") if err != nil { // unsupported or disallowed, reconnect log.Warningf("Re-connecting to LDAP Server after failure to bind anonymously: %v", err) ldapConn.Close() ldapConn, err = m.LDAPConnect() if err != nil { return nil, err } } } else { bindDN = m.ParseString(m.bindTemplate, username) } // authenticate user err = ldapConn.Bind(bindDN, password) if err != nil { return nil, err } ldapUid = bindDN return &oidc.Identity{ ID: ldapUid, Name: ldapName, Email: ldapEmail, }, nil }
func (cfg *LDAPConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) { ns.Path = path.Join(ns.Path, httpPathCallback) tpl := tpls.Lookup(LDAPLoginPageTemplateName) if tpl == nil { return nil, fmt.Errorf("unable to find necessary HTML template") } if cfg.UseTLS && cfg.UseSSL { return nil, fmt.Errorf("Invalid configuration. useTLS and useSSL are mutual exclusive.") } if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 { return nil, fmt.Errorf("Invalid configuration. Both certFile and keyFile must be specified.") } // Set default values if cfg.NameAttribute == "" { cfg.NameAttribute = "cn" } if cfg.EmailAttribute == "" { cfg.EmailAttribute = "mail" } if cfg.MaxIdleConn > 0 { cfg.MaxIdleConn = 5 } if cfg.BindTemplate == "" { cfg.BindTemplate = "uid=%u,%b" } else if cfg.SearchBeforeAuth { log.Warningf("bindTemplate not used when searchBeforeAuth specified.") } searchScope := ldap.ScopeWholeSubtree if cfg.SearchScope != "" { switch { case strings.EqualFold(cfg.SearchScope, "BASE"): searchScope = ldap.ScopeBaseObject case strings.EqualFold(cfg.SearchScope, "ONE"): searchScope = ldap.ScopeSingleLevel case strings.EqualFold(cfg.SearchScope, "SUB"): searchScope = ldap.ScopeWholeSubtree default: return nil, fmt.Errorf("Invalid value for searchScope: '%v'. Must be one of 'base', 'one' or 'sub'.", cfg.SearchScope) } } if cfg.Host == "" { if cfg.ServerHost == "" { return nil, errors.New("no host provided") } // For backward compatibility construct host form old fields. cfg.Host = fmt.Sprintf("%s:%d", cfg.ServerHost, cfg.ServerPort) } host, _, err := net.SplitHostPort(cfg.Host) if err != nil { return nil, fmt.Errorf("host is not of form 'host:port': %v", err) } tlsConfig := &tls.Config{ServerName: host} if (cfg.UseTLS || cfg.UseSSL) && len(cfg.CaFile) > 0 { buf, err := ioutil.ReadFile(cfg.CaFile) if err != nil { return nil, err } rootCertPool := x509.NewCertPool() ok := rootCertPool.AppendCertsFromPEM(buf) if ok { tlsConfig.RootCAs = rootCertPool } else { return nil, fmt.Errorf("%v: Unable to parse certificate data.", cfg.CaFile) } } if (cfg.UseTLS || cfg.UseSSL) && len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 { cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) if err != nil { return nil, err } tlsConfig.Certificates = []tls.Certificate{cert} } idpc := &LDAPConnector{ id: cfg.ID, namespace: ns, loginFunc: lf, loginTpl: tpl, baseDN: cfg.BaseDN, nameAttribute: cfg.NameAttribute, emailAttribute: cfg.EmailAttribute, searchBeforeAuth: cfg.SearchBeforeAuth, searchFilter: cfg.SearchFilter, searchScope: searchScope, searchBindDN: cfg.SearchBindDN, searchBindPw: cfg.SearchBindPw, bindTemplate: cfg.BindTemplate, ldapPool: &LDAPPool{ MaxIdleConn: cfg.MaxIdleConn, PoolCheckTimer: defaultPoolCheckTimer, Host: cfg.Host, UseTLS: cfg.UseTLS, UseSSL: cfg.UseSSL, TLSConfig: tlsConfig, }, } return idpc, nil }