From 29e07bacfdbc03685eb11f7b5c7d087d3621e17f Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Wed, 13 Dec 2017 22:42:41 +0000 Subject: [PATCH] Use ai3/go-common for LDAP connections --- backend/ldap.go | 19 +-- .../ai3/go-common/ldap/pool.go | 125 ++++++++++++++++++ .../ai3/go-common/ldap/search.go | 54 ++++++++ vendor/vendor.json | 18 ++- 4 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 vendor/git.autistici.org/ai3/go-common/ldap/pool.go create mode 100644 vendor/git.autistici.org/ai3/go-common/ldap/search.go diff --git a/backend/ldap.go b/backend/ldap.go index b24729cc..03c2b755 100644 --- a/backend/ldap.go +++ b/backend/ldap.go @@ -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 diff --git a/vendor/git.autistici.org/ai3/go-common/ldap/pool.go b/vendor/git.autistici.org/ai3/go-common/ldap/pool.go new file mode 100644 index 00000000..6d8093e9 --- /dev/null +++ b/vendor/git.autistici.org/ai3/go-common/ldap/pool.go @@ -0,0 +1,125 @@ +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 +} diff --git a/vendor/git.autistici.org/ai3/go-common/ldap/search.go b/vendor/git.autistici.org/ai3/go-common/ldap/search.go new file mode 100644 index 00000000..872f6fec --- /dev/null +++ b/vendor/git.autistici.org/ai3/go-common/ldap/search.go @@ -0,0 +1,54 @@ +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 +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 15bcb981..8026fa82 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -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=", -- GitLab