Skip to content
Snippets Groups Projects
Commit 29e07bac authored by ale's avatar ale
Browse files

Use ai3/go-common for LDAP connections

parent 2746cdca
No related branches found
No related tags found
No related merge requests found
......@@ -6,8 +6,8 @@ import (
"io/ioutil"
"log"
"strings"
"time"
ldaputil "git.autistici.org/ai3/go-common/ldap"
"gopkg.in/ldap.v2"
)
......@@ -90,7 +90,7 @@ func (c *LDAPConfig) Valid() error {
type ldapBackend struct {
config *LDAPConfig
conn *ldap.Conn
pool *ldaputil.ConnectionPool
}
func NewLDAPBackend(config *LDAPConfig) (*ldapBackend, error) {
......@@ -106,28 +106,19 @@ func NewLDAPBackend(config *LDAPConfig) (*ldapBackend, error) {
}
// Connect.
conn, err := ldap.Dial("unix", "/var/lib/ldapi")
pool, err := ldaputil.NewConnectionPool(config.URI, config.BindDN, strings.TrimSpace(string(bindPw)), 5)
if err != nil {
return nil, err
}
if err = conn.Bind(config.BindDN, strings.TrimSpace(string(bindPw))); err != nil {
conn.Close()
return nil, err
}
return &ldapBackend{
config: config,
conn: conn,
pool: pool,
}, nil
}
func (b *ldapBackend) GetKeys(ctx context.Context, username string) [][]byte {
// Try to turn the context deadline into a LDAP connection timeout...
if deadline, ok := ctx.Deadline(); ok {
b.conn.SetTimeout(time.Until(deadline))
}
result, err := b.conn.Search(b.config.Query.searchRequest(username))
result, err := b.pool.Search(ctx, b.config.Query.searchRequest(username))
if err != nil {
log.Printf("LDAP error: %v", err)
return nil
......
package ldaputil
import (
"context"
"errors"
"net"
"net/url"
"time"
"gopkg.in/ldap.v2"
)
// ConnectionPool provides a goroutine-safe pool of long-lived LDAP
// connections that will reconnect on errors.
type ConnectionPool struct {
network string
addr string
bindDN string
bindPw string
c chan *ldap.Conn
}
var defaultConnectTimeout = 5 * time.Second
func (p *ConnectionPool) connect(ctx context.Context) (*ldap.Conn, error) {
// Dial the connection with a timeout, if the context has a
// deadline (as it should). If the context does not have a
// deadline, we set a default timeout.
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultConnectTimeout)
}
c, err := net.DialTimeout(p.network, p.addr, time.Until(deadline))
if err != nil {
return nil, err
}
conn := ldap.NewConn(c, false)
conn.Start()
conn.SetTimeout(time.Until(deadline))
if _, err = conn.SimpleBind(ldap.NewSimpleBindRequest(p.bindDN, p.bindPw, nil)); err != nil {
conn.Close()
return nil, err
}
return conn, err
}
// Get a fresh connection from the pool.
func (p *ConnectionPool) Get(ctx context.Context) (*ldap.Conn, error) {
// Grab a connection from the cache, or create a new one if
// there are no available connections.
select {
case conn := <-p.c:
return conn, nil
default:
return p.connect(ctx)
}
}
// Release a used connection onto the pool.
func (p *ConnectionPool) Release(conn *ldap.Conn, err error) {
// Connections that failed should not be reused.
if err != nil {
conn.Close()
return
}
// Return the connection to the cache, or close it if it's
// full.
select {
case p.c <- conn:
default:
conn.Close()
}
}
// Close all connections. Not implemented yet.
func (p *ConnectionPool) Close() {}
// Parse a LDAP URI into network and address strings suitable for
// ldap.Dial.
func parseLDAPURI(uri string) (string, string, error) {
u, err := url.Parse(uri)
if err != nil {
return "", "", err
}
network := "tcp"
addr := "localhost:389"
switch u.Scheme {
case "ldap":
if u.Host != "" {
addr = u.Host
}
case "ldapi":
network = "unix"
addr = u.Path
default:
return "", "", errors.New("unsupported scheme")
}
return network, addr, nil
}
// NewConnectionPool creates a new pool of LDAP connections to the
// specified server, using the provided bind credentials. The pool
// will cache at most cacheSize connections.
func NewConnectionPool(uri, bindDN, bindPw string, cacheSize int) (*ConnectionPool, error) {
network, addr, err := parseLDAPURI(uri)
if err != nil {
return nil, err
}
return &ConnectionPool{
c: make(chan *ldap.Conn, cacheSize),
network: network,
addr: addr,
bindDN: bindDN,
bindPw: bindPw,
}, nil
}
package ldaputil
import (
"context"
"time"
"github.com/cenkalti/backoff"
"gopkg.in/ldap.v2"
"git.autistici.org/ai3/go-common/clientutil"
)
// Treat all errors as potential network-level issues, except for a
// whitelist of LDAP protocol level errors that we know are benign.
func isTemporaryLDAPError(err error) bool {
ldapErr, ok := err.(*ldap.Error)
if !ok {
return true
}
switch ldapErr.ResultCode {
case ldap.ErrorNetwork:
return true
default:
return false
}
}
// Search performs the given search request. It will retry the request
// on temporary errors.
func (p *ConnectionPool) Search(ctx context.Context, searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
var result *ldap.SearchResult
err := clientutil.Retry(func() error {
conn, err := p.Get(ctx)
if err != nil {
if isTemporaryLDAPError(err) {
return clientutil.TempError(err)
}
return err
}
if deadline, ok := ctx.Deadline(); ok {
conn.SetTimeout(time.Until(deadline))
}
result, err = conn.Search(searchRequest)
if err != nil && isTemporaryLDAPError(err) {
p.Release(conn, nil)
return clientutil.TempError(err)
}
p.Release(conn, err)
return err
}, backoff.WithContext(clientutil.NewExponentialBackOff(), ctx))
return result, err
}
......@@ -14,6 +14,12 @@
"revision": "c2c933578837d28b6f0e9b0b4b183d53ab28785e",
"revisionTime": "2017-12-11T08:01:45Z"
},
{
"checksumSHA1": "mEnXMNziH82HFtGngHU19VHTVHs=",
"path": "git.autistici.org/ai3/go-common/ldap",
"revision": "c2c933578837d28b6f0e9b0b4b183d53ab28785e",
"revisionTime": "2017-12-11T08:01:45Z"
},
{
"checksumSHA1": "3bComZxAfgnoTG4UDlyFgLyeykc=",
"path": "git.autistici.org/ai3/go-common/serverutil",
......@@ -23,8 +29,8 @@
{
"checksumSHA1": "DFjm2ZJpUwioPApa3htGXLEFWl8=",
"path": "git.autistici.org/id/go-sso",
"revision": "8396b6ffcb3731465f1f0d05e2af4f5b139591b1",
"revisionTime": "2017-12-09T17:37:13Z"
"revision": "68704340c9193b1a241dfd28bf691866db0df5f1",
"revisionTime": "2017-12-13T22:16:10Z"
},
{
"checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=",
......@@ -126,15 +132,15 @@
"checksumSHA1": "X6Q8nYb+KXh+64AKHwWOOcyijHQ=",
"origin": "git.autistici.org/id/go-sso/vendor/golang.org/x/crypto/ed25519",
"path": "golang.org/x/crypto/ed25519",
"revision": "8396b6ffcb3731465f1f0d05e2af4f5b139591b1",
"revisionTime": "2017-12-09T17:37:13Z"
"revision": "68704340c9193b1a241dfd28bf691866db0df5f1",
"revisionTime": "2017-12-13T22:16:10Z"
},
{
"checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=",
"origin": "git.autistici.org/id/go-sso/vendor/golang.org/x/crypto/ed25519/internal/edwards25519",
"path": "golang.org/x/crypto/ed25519/internal/edwards25519",
"revision": "8396b6ffcb3731465f1f0d05e2af4f5b139591b1",
"revisionTime": "2017-12-09T17:37:13Z"
"revision": "68704340c9193b1a241dfd28bf691866db0df5f1",
"revisionTime": "2017-12-13T22:16:10Z"
},
{
"checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment