171
pkg/simple/client/ldap/channel.go
Normal file
171
pkg/simple/client/ldap/channel.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/go-ldap/ldap"
|
||||
)
|
||||
|
||||
// channelPool implements the Pool interface based on buffered channels.
|
||||
type channelPool struct {
|
||||
// storage for our net.Conn connections
|
||||
mu sync.Mutex
|
||||
conns chan ldap.Client
|
||||
|
||||
name string
|
||||
aliveChecks bool
|
||||
|
||||
// net.Conn generator
|
||||
factory PoolFactory
|
||||
closeAt []uint16
|
||||
}
|
||||
|
||||
// PoolFactory is a function to create new connections.
|
||||
type PoolFactory func(string) (ldap.Client, error)
|
||||
|
||||
// NewChannelPool returns a new pool based on buffered channels with an initial
|
||||
// capacity and maximum capacity. Factory is used when initial capacity is
|
||||
// greater than zero to fill the pool. A zero initialCap doesn't fill the Pool
|
||||
// until a new Get() is called. During a Get(), If there is no new connection
|
||||
// available in the pool, a new connection will be created via the Factory()
|
||||
// method.
|
||||
//
|
||||
// closeAt will automagically mark the connection as unusable if the return code
|
||||
// of the call is one of those passed, most likely you want to set this to something
|
||||
// like
|
||||
// []uint8{ldap.LDAPResultTimeLimitExceeded, ldap.ErrorNetwork}
|
||||
func NewChannelPool(initialCap, maxCap int, name string, factory PoolFactory, closeAt []uint16) (Pool, error) {
|
||||
if initialCap < 0 || maxCap <= 0 || initialCap > maxCap {
|
||||
return nil, errors.New("invalid capacity settings")
|
||||
}
|
||||
|
||||
c := &channelPool{
|
||||
conns: make(chan ldap.Client, maxCap),
|
||||
name: name,
|
||||
factory: factory,
|
||||
closeAt: closeAt,
|
||||
aliveChecks: true,
|
||||
}
|
||||
|
||||
// create initial connections, if something goes wrong,
|
||||
// just close the pool error out.
|
||||
for i := 0; i < initialCap; i++ {
|
||||
conn, err := factory(c.name)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, errors.New("factory is not able to fill the pool: " + err.Error())
|
||||
}
|
||||
c.conns <- conn
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *channelPool) AliveChecks(on bool) {
|
||||
c.mu.Lock()
|
||||
c.aliveChecks = on
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *channelPool) getConns() chan ldap.Client {
|
||||
c.mu.Lock()
|
||||
conns := c.conns
|
||||
c.mu.Unlock()
|
||||
return conns
|
||||
}
|
||||
|
||||
// Get implements the Pool interfaces Get() method. If there is no new
|
||||
// connection available in the pool, a new connection will be created via the
|
||||
// Factory() method.
|
||||
func (c *channelPool) Get() (*PoolConn, error) {
|
||||
conns := c.getConns()
|
||||
if conns == nil {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
// wrap our connections with our ldap.Client implementation (wrapConn
|
||||
// method) that puts the connection back to the pool if it's closed.
|
||||
select {
|
||||
case conn := <-conns:
|
||||
if conn == nil {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if !c.aliveChecks || isAlive(conn) {
|
||||
return c.wrapConn(conn, c.closeAt), nil
|
||||
}
|
||||
conn.Close()
|
||||
return c.NewConn()
|
||||
default:
|
||||
return c.NewConn()
|
||||
}
|
||||
}
|
||||
|
||||
func isAlive(conn ldap.Client) bool {
|
||||
_, err := conn.Search(&ldap.SearchRequest{BaseDN: "", Scope: ldap.ScopeBaseObject, Filter: "(&)", Attributes: []string{"1.1"}})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *channelPool) NewConn() (*PoolConn, error) {
|
||||
conn, err := c.factory(c.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.wrapConn(conn, c.closeAt), nil
|
||||
}
|
||||
|
||||
// put puts the connection back to the pool. If the pool is full or closed,
|
||||
// conn is simply closed. A nil conn will be rejected.
|
||||
func (c *channelPool) put(conn ldap.Client) {
|
||||
if conn == nil {
|
||||
log.Printf("connection is nil. rejecting")
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.conns == nil {
|
||||
// pool is closed, close passed connection
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// put the resource back into the pool. If the pool is full, this will
|
||||
// block and the default case will be executed.
|
||||
select {
|
||||
case c.conns <- conn:
|
||||
return
|
||||
default:
|
||||
// pool is full, close passed connection
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *channelPool) Close() {
|
||||
c.mu.Lock()
|
||||
conns := c.conns
|
||||
c.conns = nil
|
||||
c.factory = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if conns == nil {
|
||||
return
|
||||
}
|
||||
|
||||
close(conns)
|
||||
for conn := range conns {
|
||||
conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *channelPool) Len() int { return len(c.getConns()) }
|
||||
|
||||
func (c *channelPool) wrapConn(conn ldap.Client, closeAt []uint16) *PoolConn {
|
||||
p := &PoolConn{c: c, closeAt: closeAt}
|
||||
p.Conn = conn
|
||||
return p
|
||||
}
|
||||
96
pkg/simple/client/ldap/conn.go
Normal file
96
pkg/simple/client/ldap/conn.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/go-ldap/ldap"
|
||||
)
|
||||
|
||||
// PoolConn implements Client to override the Close() method
|
||||
type PoolConn struct {
|
||||
Conn ldap.Client
|
||||
c *channelPool
|
||||
unusable bool
|
||||
closeAt []uint16
|
||||
}
|
||||
|
||||
func (p *PoolConn) Start() {
|
||||
p.Conn.Start()
|
||||
}
|
||||
|
||||
func (p *PoolConn) StartTLS(config *tls.Config) error {
|
||||
// FIXME - check if already TLS and then ignore?
|
||||
return p.Conn.StartTLS(config)
|
||||
}
|
||||
|
||||
// Close() puts the given connects back to the pool instead of closing it.
|
||||
func (p *PoolConn) Close() {
|
||||
if p.unusable {
|
||||
log.Printf("Closing unusable connection")
|
||||
if p.Conn != nil {
|
||||
p.Conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
p.c.put(p.Conn)
|
||||
}
|
||||
|
||||
func (p *PoolConn) SimpleBind(simpleBindRequest *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) {
|
||||
return p.Conn.SimpleBind(simpleBindRequest)
|
||||
}
|
||||
|
||||
func (p *PoolConn) Bind(username, password string) error {
|
||||
return p.Conn.Bind(username, password)
|
||||
}
|
||||
|
||||
func (p *PoolConn) ModifyDN(modifyDNRequest *ldap.ModifyDNRequest) error {
|
||||
return p.Conn.ModifyDN(modifyDNRequest)
|
||||
}
|
||||
|
||||
// MarkUnusable() marks the connection not usable any more, to let the pool close it
|
||||
// instead of returning it to pool.
|
||||
func (p *PoolConn) MarkUnusable() {
|
||||
p.unusable = true
|
||||
}
|
||||
|
||||
func (p *PoolConn) autoClose(err error) {
|
||||
for _, code := range p.closeAt {
|
||||
if ldap.IsErrorWithCode(err, code) {
|
||||
p.MarkUnusable()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PoolConn) SetTimeout(t time.Duration) {
|
||||
p.Conn.SetTimeout(t)
|
||||
}
|
||||
|
||||
func (p *PoolConn) Add(addRequest *ldap.AddRequest) error {
|
||||
return p.Conn.Add(addRequest)
|
||||
}
|
||||
|
||||
func (p *PoolConn) Del(delRequest *ldap.DelRequest) error {
|
||||
return p.Conn.Del(delRequest)
|
||||
}
|
||||
|
||||
func (p *PoolConn) Modify(modifyRequest *ldap.ModifyRequest) error {
|
||||
return p.Conn.Modify(modifyRequest)
|
||||
}
|
||||
|
||||
func (p *PoolConn) Compare(dn, attribute, value string) (bool, error) {
|
||||
return p.Conn.Compare(dn, attribute, value)
|
||||
}
|
||||
|
||||
func (p *PoolConn) PasswordModify(passwordModifyRequest *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) {
|
||||
return p.Conn.PasswordModify(passwordModifyRequest)
|
||||
}
|
||||
|
||||
func (p *PoolConn) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||
return p.Conn.Search(searchRequest)
|
||||
}
|
||||
func (p *PoolConn) SearchWithPaging(searchRequest *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error) {
|
||||
return p.Conn.SearchWithPaging(searchRequest, pagingSize)
|
||||
}
|
||||
79
pkg/simple/client/ldap/ldapclient.go
Normal file
79
pkg/simple/client/ldap/ldapclient.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 The KubeSphere Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/go-ldap/ldap"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
pool Pool
|
||||
ldapHost string
|
||||
ManagerDN string
|
||||
ManagerPassword string
|
||||
UserSearchBase string
|
||||
GroupSearchBase string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&ldapHost, "ldap-server", "localhost:389", "ldap server host")
|
||||
flag.StringVar(&ManagerDN, "ldap-manager-dn", "cn=admin,dc=example,dc=org", "ldap manager dn")
|
||||
flag.StringVar(&ManagerPassword, "ldap-manager-password", "admin", "ldap manager password")
|
||||
flag.StringVar(&UserSearchBase, "ldap-user-search-base", "ou=Users,dc=example,dc=org", "ldap user search base")
|
||||
flag.StringVar(&GroupSearchBase, "ldap-group-search-base", "ou=Groups,dc=example,dc=org", "ldap group search base")
|
||||
}
|
||||
|
||||
func ldapClientPool() Pool {
|
||||
|
||||
once.Do(func() {
|
||||
var err error
|
||||
pool, err = NewChannelPool(8, 96, "kubesphere", func(s string) (ldap.Client, error) {
|
||||
conn, err := ldap.Dial("tcp", ldapHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}, []uint16{ldap.LDAPResultTimeLimitExceeded, ldap.ErrorNetwork})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
})
|
||||
return pool
|
||||
}
|
||||
|
||||
func Client() (ldap.Client, error) {
|
||||
conn, err := ldapClientPool().Get()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(ManagerDN, ManagerPassword)
|
||||
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
26
pkg/simple/client/ldap/pool.go
Normal file
26
pkg/simple/client/ldap/pool.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrClosed is the error resulting if the pool is closed via pool.Close().
|
||||
ErrClosed = errors.New("pool is closed")
|
||||
)
|
||||
|
||||
// Pool interface describes a pool implementation. A pool should have maximum
|
||||
// capacity. An ideal pool is threadsafe and easy to use.
|
||||
type Pool interface {
|
||||
// Get returns a new connection from the pool. Closing the connections puts
|
||||
// it back to the Pool. Closing it when the pool is destroyed or full will
|
||||
// be counted as an error.
|
||||
Get() (*PoolConn, error)
|
||||
|
||||
// Close closes the pool and all its connections. After Close() the pool is
|
||||
// no longer usable.
|
||||
Close()
|
||||
|
||||
// Len returns the current number of connections of the pool.
|
||||
Len() int
|
||||
}
|
||||
Reference in New Issue
Block a user