2015-11-11 01:08:40 +05:30
|
|
|
package functional
|
|
|
|
|
|
|
|
import (
|
|
|
|
"html/template"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2016-05-27 16:55:31 +05:30
|
|
|
"sync"
|
2015-11-11 01:08:40 +05:30
|
|
|
"testing"
|
2016-05-27 16:55:31 +05:30
|
|
|
"time"
|
2015-11-11 01:08:40 +05:30
|
|
|
|
|
|
|
"github.com/coreos/dex/connector"
|
2016-05-27 16:55:31 +05:30
|
|
|
"golang.org/x/net/context"
|
2015-11-11 01:08:40 +05:30
|
|
|
"gopkg.in/ldap.v2"
|
|
|
|
)
|
|
|
|
|
2016-02-26 01:27:26 +05:30
|
|
|
type LDAPServer struct {
|
2016-06-22 05:11:04 +05:30
|
|
|
Host string // Address (host:port) of LDAP service.
|
|
|
|
LDAPSHost string // Address (host:port) of LDAPS service (TLS port).
|
|
|
|
BindDN string
|
|
|
|
BindPw string
|
2016-02-26 01:27:26 +05:30
|
|
|
}
|
2015-11-11 01:08:40 +05:30
|
|
|
|
2016-02-26 01:27:26 +05:30
|
|
|
const (
|
2016-06-22 05:11:04 +05:30
|
|
|
ldapEnvHost = "DEX_TEST_LDAP_HOST"
|
|
|
|
ldapsEnvHost = "DEX_TEST_LDAPS_HOST"
|
|
|
|
|
2016-02-26 01:27:26 +05:30
|
|
|
ldapEnvBindName = "DEX_TEST_LDAP_BINDNAME"
|
|
|
|
ldapEnvBindPass = "DEX_TEST_LDAP_BINDPASS"
|
|
|
|
)
|
2015-11-11 01:08:40 +05:30
|
|
|
|
2016-02-26 01:27:26 +05:30
|
|
|
func ldapServer(t *testing.T) LDAPServer {
|
2016-06-22 05:11:04 +05:30
|
|
|
getenv := func(key string) string {
|
|
|
|
val := os.Getenv(key)
|
|
|
|
if val == "" {
|
|
|
|
t.Fatalf("%s not set", key)
|
2015-11-11 01:08:40 +05:30
|
|
|
}
|
2016-06-22 05:11:04 +05:30
|
|
|
t.Logf("%s=%v", key, val)
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
return LDAPServer{
|
|
|
|
Host: getenv(ldapEnvHost),
|
|
|
|
LDAPSHost: getenv(ldapsEnvHost),
|
|
|
|
BindDN: os.Getenv(ldapEnvBindName),
|
|
|
|
BindPw: os.Getenv(ldapEnvBindPass),
|
2015-11-11 01:08:40 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLDAPConnect(t *testing.T) {
|
2016-02-26 01:27:26 +05:30
|
|
|
server := ldapServer(t)
|
2016-06-22 05:11:04 +05:30
|
|
|
l, err := ldap.Dial("tcp", server.Host)
|
2015-11-11 01:08:40 +05:30
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-26 01:27:26 +05:30
|
|
|
err = l.Bind(server.BindDN, server.BindPw)
|
2015-11-11 01:08:40 +05:30
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
l.Close()
|
|
|
|
}
|
|
|
|
|
2016-02-26 01:27:26 +05:30
|
|
|
func TestConnectorLDAPHealthy(t *testing.T) {
|
|
|
|
server := ldapServer(t)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
config connector.LDAPConnectorConfig
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
config: connector.LDAPConnectorConfig{
|
2016-06-22 05:11:04 +05:30
|
|
|
ID: "ldap",
|
|
|
|
Host: "localhost:0",
|
2016-02-26 01:27:26 +05:30
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
config: connector.LDAPConnectorConfig{
|
2016-06-22 05:11:04 +05:30
|
|
|
ID: "ldap",
|
|
|
|
Host: server.Host,
|
2016-02-26 01:27:26 +05:30
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
config: connector.LDAPConnectorConfig{
|
2016-06-22 05:11:04 +05:30
|
|
|
ID: "ldap",
|
|
|
|
Host: server.Host,
|
|
|
|
UseTLS: true,
|
|
|
|
CertFile: "/tmp/ldap.crt",
|
|
|
|
KeyFile: "/tmp/ldap.key",
|
|
|
|
CaFile: "/tmp/openldap-ca.pem",
|
2016-02-26 01:27:26 +05:30
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
config: connector.LDAPConnectorConfig{
|
2016-06-22 05:11:04 +05:30
|
|
|
ID: "ldap",
|
|
|
|
Host: server.LDAPSHost,
|
|
|
|
UseSSL: true,
|
|
|
|
CertFile: "/tmp/ldap.crt",
|
|
|
|
KeyFile: "/tmp/ldap.key",
|
|
|
|
CaFile: "/tmp/openldap-ca.pem",
|
2016-02-26 01:27:26 +05:30
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
templates := template.New(connector.LDAPLoginPageTemplateName)
|
|
|
|
c, err := tt.config.Connector(url.URL{}, nil, templates)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("case %d: failed to create connector: %v", i, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := c.Healthy(); err != nil {
|
|
|
|
if !tt.wantErr {
|
|
|
|
t.Errorf("case %d: Healthy() returned error: %v", i, err)
|
|
|
|
}
|
|
|
|
} else if tt.wantErr {
|
|
|
|
t.Errorf("case %d: expected Healthy() to fail", i)
|
|
|
|
}
|
2015-11-11 01:08:40 +05:30
|
|
|
}
|
|
|
|
}
|
2016-05-27 16:55:31 +05:30
|
|
|
|
|
|
|
func TestLDAPPoolHighWatermarkAndLockContention(t *testing.T) {
|
|
|
|
server := ldapServer(t)
|
|
|
|
ldapPool := &connector.LDAPPool{
|
|
|
|
MaxIdleConn: 30,
|
2016-06-22 05:11:04 +05:30
|
|
|
Host: server.Host,
|
2016-05-27 16:55:31 +05:30
|
|
|
UseTLS: false,
|
|
|
|
UseSSL: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Excercise pool operations with MaxIdleConn + 10 concurrent goroutines.
|
|
|
|
// We are testing both pool high watermark code and lock contention
|
|
|
|
numRoutines := ldapPool.MaxIdleConn + 10
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(numRoutines)
|
|
|
|
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
for i := 0; i < numRoutines; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
default:
|
2016-06-22 05:11:04 +05:30
|
|
|
err := ldapPool.Do(func(conn *ldap.Conn) error {
|
|
|
|
s := &ldap.SearchRequest{
|
|
|
|
Scope: ldap.ScopeBaseObject,
|
|
|
|
Filter: "(objectClass=*)",
|
|
|
|
}
|
|
|
|
_, err := conn.Search(s)
|
|
|
|
return err
|
|
|
|
})
|
2016-05-27 16:55:31 +05:30
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Search request failed. Dead/invalid LDAP connection from pool?: %v", err)
|
|
|
|
}
|
|
|
|
_, _ = ldapPool.CheckConnections()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for all operations to complete and check status.
|
|
|
|
// There should be MaxIdleConn connections in the pool. This confirms:
|
|
|
|
// 1. The tests was indeed executed concurrently
|
|
|
|
// 2. Even though we ran more routines than the configured MaxIdleConn the high
|
|
|
|
// watermark code did its job and closed surplus connections
|
|
|
|
wg.Wait()
|
|
|
|
alive, killed := ldapPool.CheckConnections()
|
|
|
|
if alive < ldapPool.MaxIdleConn {
|
|
|
|
t.Errorf("expected %v connections, got alive=%v killed=%v", ldapPool.MaxIdleConn, alive, killed)
|
|
|
|
}
|
|
|
|
}
|