Rewrite LDAP tests to use Docker
This commit is contained in:
@@ -1,20 +1,18 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
)
|
||||
@@ -50,12 +48,6 @@ type subtest struct {
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -125,12 +117,6 @@ userpassword: bar
|
||||
|
||||
func TestQueryWithEmailSuffix(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -187,12 +173,6 @@ userpassword: bar
|
||||
|
||||
func TestUserFilter(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=Seattle,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: Seattle
|
||||
@@ -283,12 +263,6 @@ userpassword: bar
|
||||
|
||||
func TestGroupQuery(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -371,12 +345,6 @@ member: cn=jane,ou=People,dc=example,dc=org
|
||||
|
||||
func TestGroupsOnUserEntity(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -468,12 +436,6 @@ gidNumber: 1002
|
||||
|
||||
func TestGroupFilter(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -574,12 +536,6 @@ member: cn=jane,ou=People,dc=example,dc=org
|
||||
|
||||
func TestStartTLS(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -617,12 +573,6 @@ userpassword: foo
|
||||
|
||||
func TestInsecureSkipVerify(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -660,12 +610,6 @@ userpassword: foo
|
||||
|
||||
func TestLDAPS(t *testing.T) {
|
||||
schema := `
|
||||
dn: dc=example,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: Example Company
|
||||
dc: example
|
||||
|
||||
dn: ou=People,dc=example,dc=org
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
@@ -729,8 +673,7 @@ func TestUsernamePrompt(t *testing.T) {
|
||||
// runTests runs a set of tests against an LDAP schema. It does this by
|
||||
// setting up an OpenLDAP server and injecting the provided scheme.
|
||||
//
|
||||
// The tests require the slapd and ldapadd binaries available in the host
|
||||
// machine's PATH.
|
||||
// The tests require Docker.
|
||||
//
|
||||
// The DEX_LDAP_TESTS must be set to "1"
|
||||
func runTests(t *testing.T, schema string, connMethod connectionMethod, config *Config, tests []subtest) {
|
||||
@@ -738,12 +681,6 @@ func runTests(t *testing.T, schema string, connMethod connectionMethod, config *
|
||||
t.Skipf("%s not set. Skipping test (run 'export %s=1' to run tests)", envVar, envVar)
|
||||
}
|
||||
|
||||
for _, cmd := range []string{"slapd", "ldapadd"} {
|
||||
if _, err := exec.LookPath(cmd); err != nil {
|
||||
t.Errorf("%s not available", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -755,100 +692,65 @@ func runTests(t *testing.T, schema string, connMethod connectionMethod, config *
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
configBytes := new(bytes.Buffer)
|
||||
|
||||
data := tmplData{
|
||||
TempDir: tempDir,
|
||||
Includes: includes(t, wd),
|
||||
}
|
||||
data.TLSCertPath, data.TLSKeyPath = tlsAssets(t, wd)
|
||||
|
||||
if err := slapdConfigTmpl.Execute(configBytes, data); err != nil {
|
||||
schemaPath := filepath.Join(tempDir, "schema.ldif")
|
||||
if err := ioutil.WriteFile(schemaPath, []byte(schema), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configPath := filepath.Join(tempDir, "ldap.conf")
|
||||
if err := ioutil.WriteFile(configPath, configBytes.Bytes(), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
schemaPath := filepath.Join(tempDir, "schema.ldap")
|
||||
if err := ioutil.WriteFile(schemaPath, []byte(schema), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: "osixia/openldap:1.3.0",
|
||||
ExposedPorts: []string{"389/tcp", "636/tcp"},
|
||||
Cmd: []string{"--copy-service"},
|
||||
Env: map[string]string{
|
||||
"LDAP_BASE_DN": "dc=example,dc=org",
|
||||
"LDAP_TLS": "true",
|
||||
"LDAP_TLS_VERIFY_CLIENT": "try",
|
||||
},
|
||||
BindMounts: map[string]string{
|
||||
filepath.Join(wd, "testdata", "certs"): "/container/service/slapd/assets/certs",
|
||||
schemaPath: "/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif",
|
||||
},
|
||||
WaitingFor: wait.ForAll(
|
||||
wait.ForLog("slapd starting").WithOccurrence(3).WithStartupTimeout(time.Minute),
|
||||
wait.ForListeningPort("389/tcp"),
|
||||
wait.ForListeningPort("636/tcp"),
|
||||
),
|
||||
}
|
||||
|
||||
socketPath := url.QueryEscape(filepath.Join(tempDir, "ldap.unix"))
|
||||
ctx := context.Background()
|
||||
|
||||
slapdOut := new(bytes.Buffer)
|
||||
slapd, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
if err != nil {
|
||||
if slapd != nil {
|
||||
logs, err := slapd.Logs(ctx)
|
||||
if err == nil {
|
||||
defer logs.Close()
|
||||
|
||||
cmd := exec.Command(
|
||||
"slapd",
|
||||
"-d", "any",
|
||||
"-h", "ldap://localhost:10389/ ldaps://localhost:10636/ ldapi://"+socketPath,
|
||||
"-f", configPath,
|
||||
)
|
||||
cmd.Stdout = slapdOut
|
||||
cmd.Stderr = slapdOut
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
// Wait group finishes once slapd has exited.
|
||||
//
|
||||
// Use a wait group because multiple goroutines can't listen on
|
||||
// cmd.Wait(). It triggers the race detector.
|
||||
wg = new(sync.WaitGroup)
|
||||
// Ensure only one condition can set the slapdFailed boolean.
|
||||
once = new(sync.Once)
|
||||
slapdFailed bool
|
||||
)
|
||||
|
||||
wg.Add(1)
|
||||
go func() { cmd.Wait(); wg.Done() }()
|
||||
|
||||
defer func() {
|
||||
if slapdFailed {
|
||||
// If slapd exited before it was killed, print its logs.
|
||||
t.Logf("%s\n", slapdOut)
|
||||
logLines, err := ioutil.ReadAll(logs)
|
||||
if err != nil {
|
||||
t.Log(string(logLines))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
once.Do(func() { slapdFailed = true })
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
once.Do(func() { slapdFailed = false })
|
||||
cmd.Process.Kill()
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
// Try a few times to connect to the LDAP server. On slower machines
|
||||
// it can take a while for it to come up.
|
||||
connected := false
|
||||
wait := 100 * time.Millisecond
|
||||
for i := 0; i < 5; i++ {
|
||||
time.Sleep(wait)
|
||||
|
||||
ldapadd := exec.Command(
|
||||
"ldapadd", "-x",
|
||||
"-D", "cn=admin,dc=example,dc=org",
|
||||
"-w", "admin",
|
||||
"-f", schemaPath,
|
||||
"-H", "ldap://localhost:10389/",
|
||||
)
|
||||
if out, err := ldapadd.CombinedOutput(); err != nil {
|
||||
t.Logf("ldapadd: %s", out)
|
||||
wait = wait * 2 // backoff
|
||||
continue
|
||||
}
|
||||
connected = true
|
||||
break
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !connected {
|
||||
t.Errorf("ldapadd command failed")
|
||||
return
|
||||
defer slapd.Terminate(ctx)
|
||||
|
||||
ip, err := slapd.Host(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err := slapd.MappedPort(ctx, "389")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlsPort, err := slapd.MappedPort(ctx, "636")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Shallow copy.
|
||||
@@ -858,17 +760,17 @@ func runTests(t *testing.T, schema string, connMethod connectionMethod, config *
|
||||
// group search configuration.
|
||||
switch connMethod {
|
||||
case connectStartTLS:
|
||||
c.Host = "localhost:10389"
|
||||
c.RootCA = "testdata/ca.crt"
|
||||
c.Host = fmt.Sprintf("%s:%s", ip, port.Port())
|
||||
c.RootCA = "testdata/certs/ca.crt"
|
||||
c.StartTLS = true
|
||||
case connectLDAPS:
|
||||
c.Host = "localhost:10636"
|
||||
c.RootCA = "testdata/ca.crt"
|
||||
c.Host = fmt.Sprintf("%s:%s", ip, tlsPort.Port())
|
||||
c.RootCA = "testdata/certs/ca.crt"
|
||||
case connectInsecureSkipVerify:
|
||||
c.Host = "localhost:10636"
|
||||
c.Host = fmt.Sprintf("%s:%s", ip, tlsPort.Port())
|
||||
c.InsecureSkipVerify = true
|
||||
case connectLDAP:
|
||||
c.Host = "localhost:10389"
|
||||
c.Host = fmt.Sprintf("%s:%s", ip, port.Port())
|
||||
c.InsecureNoSSL = true
|
||||
}
|
||||
|
||||
@@ -934,98 +836,3 @@ func runTests(t *testing.T, schema string, connMethod connectionMethod, config *
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Standard OpenLDAP schema files to include.
|
||||
//
|
||||
// These are copied from the /etc/openldap/schema directory.
|
||||
var includeFiles = []string{
|
||||
"core.schema",
|
||||
"cosine.schema",
|
||||
"inetorgperson.schema",
|
||||
"misc.schema",
|
||||
"nis.schema",
|
||||
"openldap.schema",
|
||||
}
|
||||
|
||||
// tmplData is the struct used to execute the SLAPD config template.
|
||||
type tmplData struct {
|
||||
// Directory for database to be writen to.
|
||||
TempDir string
|
||||
// List of schema files to include.
|
||||
Includes []string
|
||||
// TLS assets for LDAPS.
|
||||
TLSKeyPath string
|
||||
TLSCertPath string
|
||||
}
|
||||
|
||||
// Config template copied from:
|
||||
// http://www.zytrax.com/books/ldap/ch5/index.html#step1-slapd
|
||||
//
|
||||
// TLS instructions found here:
|
||||
// http://www.openldap.org/doc/admin24/tls.html
|
||||
var slapdConfigTmpl = template.Must(template.New("").Parse(`
|
||||
{{ range $i, $include := .Includes }}
|
||||
include {{ $include }}
|
||||
{{ end }}
|
||||
|
||||
# MODULELOAD definitions
|
||||
# not required (comment out) before version 2.3
|
||||
moduleload back_bdb.la
|
||||
|
||||
database bdb
|
||||
suffix "dc=example,dc=org"
|
||||
|
||||
# root or superuser
|
||||
rootdn "cn=admin,dc=example,dc=org"
|
||||
rootpw admin
|
||||
# The database directory MUST exist prior to running slapd AND
|
||||
# change path as necessary
|
||||
directory {{ .TempDir }}
|
||||
|
||||
TLSCertificateFile {{ .TLSCertPath }}
|
||||
TLSCertificateKeyFile {{ .TLSKeyPath }}
|
||||
|
||||
# Indices to maintain for this directory
|
||||
# unique id so equality match only
|
||||
index uid eq
|
||||
# allows general searching on commonname, givenname and email
|
||||
index cn,gn,mail eq,sub
|
||||
# allows multiple variants on surname searching
|
||||
index sn eq,sub
|
||||
# sub above includes subintial,subany,subfinal
|
||||
# optimise department searches
|
||||
index ou eq
|
||||
# if searches will include objectClass uncomment following
|
||||
# index objectClass eq
|
||||
# shows use of default index parameter
|
||||
index default eq,sub
|
||||
# indices missing - uses default eq,sub
|
||||
index telephonenumber
|
||||
|
||||
# other database parameters
|
||||
# read more in slapd.conf reference section
|
||||
cachesize 10000
|
||||
checkpoint 128 15
|
||||
`))
|
||||
|
||||
func tlsAssets(t *testing.T, wd string) (certPath, keyPath string) {
|
||||
certPath = filepath.Join(wd, "testdata", "server.crt")
|
||||
keyPath = filepath.Join(wd, "testdata", "server.key")
|
||||
for _, p := range []string{certPath, keyPath} {
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
t.Fatalf("failed to find TLS asset file: %s %v", p, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func includes(t *testing.T, wd string) (paths []string) {
|
||||
for _, f := range includeFiles {
|
||||
p := filepath.Join(wd, "testdata", f)
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
t.Fatalf("failed to find schema file: %s %v", p, err)
|
||||
}
|
||||
paths = append(paths, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
Reference in New Issue
Block a user