-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
authentication_ldap_test.go
258 lines (245 loc) · 13.9 KB
/
authentication_ldap_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Copyright 2024 The Cockroach Authors.
//
// Licensed as a CockroachDB Enterprise file under the Cockroach Community
// License (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
package ldapccl
import (
"context"
"crypto/tls"
"fmt"
"strings"
"testing"
"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/security/username"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/hba"
"github.com/cockroachdb/cockroach/pkg/testutils"
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/errors"
"github.com/go-ldap/ldap/v3"
"github.com/stretchr/testify/require"
)
const (
emptyParam = "empty"
invalidParam = "invalid"
)
type mockLDAPUtil struct {
conn *ldap.Conn
tlsConfig *tls.Config
}
func (lu *mockLDAPUtil) LDAPSConn(ctx context.Context, conf ldapAuthenticatorConf) error {
if strings.Contains(conf.ldapServer, invalidParam) {
return errors.Newf(ldapsFailureMessage + ": invalid ldap server provided")
} else if strings.Contains(conf.ldapPort, invalidParam) {
return errors.Newf(ldapsFailureMessage + ": invalid ldap port provided")
}
lu.conn = &ldap.Conn{}
return nil
}
func (lu *mockLDAPUtil) Bind(ctx context.Context, username string, password string) error {
if strings.Contains(username, invalidParam) {
return errors.Newf(bindFailureMessage + ": invalid username provided")
} else if strings.Contains(password, invalidParam) {
return errors.Newf(bindFailureMessage + ": invalid password provided")
}
return nil
}
func (lu *mockLDAPUtil) Search(
ctx context.Context, conf ldapAuthenticatorConf, username string,
) (distinguishedName string, err error) {
if err := lu.Bind(ctx, conf.ldapBindDN, conf.ldapBindPassword); err != nil {
return "", errors.Wrap(err, searchFailureMessage)
}
if strings.Contains(conf.ldapBaseDN, invalidParam) {
return "", errors.Newf(searchFailureMessage+": invalid base DN %q provided", conf.ldapBaseDN)
}
if strings.Contains(conf.ldapSearchFilter, invalidParam) {
return "", errors.Newf(searchFailureMessage+": invalid search filter %q provided", conf.ldapSearchFilter)
}
if strings.Contains(conf.ldapSearchAttribute, invalidParam) {
return "", errors.Newf(searchFailureMessage+": invalid search attribute %q provided", conf.ldapSearchAttribute)
}
if strings.Contains(username, invalidParam) {
return "", errors.Newf(searchFailureMessage+": invalid search value %q provided", username)
}
distinguishedNames := strings.Split(username, ",")
switch {
case len(username) == 0:
return "", errors.Newf(searchFailureMessage+": user %q does not exist", username)
case len(distinguishedNames) > 1:
return "", errors.Newf(searchFailureMessage+": too many matching entries returned for user %q", username)
}
return distinguishedNames[0], nil
}
var _ ILDAPUtil = &mockLDAPUtil{}
func constructHBAEntry(
t *testing.T,
hbaEntryBase string,
hbaConfLDAPDefaultOpts map[string]string,
hbaConfLDAPOpts map[string]string,
) hba.Entry {
hbaEntryLDAP := hbaEntryBase
// add options from default and override default options when provided with one
for opt, value := range hbaConfLDAPDefaultOpts {
setValue := value
if hbaConfLDAPOpts[opt] == emptyParam {
continue
} else if hbaConfLDAPOpts[opt] != "" {
setValue = hbaConfLDAPOpts[opt]
}
hbaEntryLDAP += fmt.Sprintf("\"%s=%s\" ", opt, setValue)
}
// add non default options
for additionalOpt, additionalOptValue := range hbaConfLDAPOpts {
if _, ok := hbaConfLDAPDefaultOpts[additionalOpt]; !ok {
hbaEntryLDAP += fmt.Sprintf("\"%s=%s\" ", additionalOpt, additionalOptValue)
}
}
hbaConf, err := hba.ParseAndNormalize(hbaEntryLDAP)
if err != nil {
t.Fatalf("error parsing hba conf: %v", err)
}
if len(hbaConf.Entries) != 1 {
t.Fatalf("hba conf value invalid: should contain only 1 entry")
}
return hbaConf.Entries[0]
}
func TestLDAPAuthentication(t *testing.T) {
defer leaktest.AfterTest(t)()
defer log.Scope(t).Close(t)
// Intercept the call to NewLDAPUtil and return the mocked NewLDAPUtil function
defer testutils.TestingHook(
&NewLDAPUtil,
func(ctx context.Context, conf ldapAuthenticatorConf) (ILDAPUtil, error) {
return &mockLDAPUtil{tlsConfig: &tls.Config{}}, nil
})()
ctx := context.Background()
s := serverutils.StartServerOnly(t, base.TestServerArgs{})
defer s.Stopper().Stop(ctx)
verifier := ConfigureLDAPAuth(ctx, s.AmbientCtx(), s.ClusterSettings(), s.StorageClusterID())
hbaEntryBase := "host all all all ldap "
hbaConfLDAPDefaultOpts := map[string]string{
"ldapserver": "localhost", "ldapport": "636", "ldapbasedn": "dc=localhost", "ldapbinddn": "cn=readonly,dc=localhost",
"ldapbindpasswd": "readonly_pwd", "ldapsearchattribute": "uid", "ldapsearchfilter": "(memberOf=cn=users,ou=groups,dc=localhost)",
}
testCases := []struct {
testName string
hbaConfLDAPOpts map[string]string
user string
pwd string
ldapAuthSuccess bool
expectedErr string
expectedErrDetails string
expectedDetailedErrMsg string
}{
{testName: "proper hba conf and valid user cred",
user: "foo", pwd: "bar", ldapAuthSuccess: true},
{testName: "proper hba conf and root user cred",
user: "root", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: invalid identity",
expectedErrDetails: "cannot use LDAP auth to login to a reserved user root"},
{testName: "proper hba conf and node user cred",
user: "node", pwd: "bar", ldapAuthSuccess: false, expectedErr: "LDAP authentication: invalid identity",
expectedErrDetails: "cannot use LDAP auth to login to a reserved user node"},
{testName: "invalid ldap option",
hbaConfLDAPOpts: map[string]string{"invalidOpt": "invalidVal"}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to fetch hba conf options",
expectedDetailedErrMsg: "error when fetching hba conf options for LDAP: invalid LDAP option provided in hba conf: invalidOpt"},
{testName: "empty server",
hbaConfLDAPOpts: map[string]string{"ldapserver": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing ldap server"},
{testName: "invalid server",
hbaConfLDAPOpts: map[string]string{"ldapserver": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to establish LDAP connection",
expectedDetailedErrMsg: "error when trying to create LDAP connection: LDAPs connection failed: invalid ldap server provided"},
{testName: "empty port",
hbaConfLDAPOpts: map[string]string{"ldapport": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing ldap port"},
{testName: "invalid port",
hbaConfLDAPOpts: map[string]string{"ldapport": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to establish LDAP connection",
expectedDetailedErrMsg: "error when trying to create LDAP connection: LDAPs connection failed: invalid ldap port provided"},
{testName: "empty base dn",
hbaConfLDAPOpts: map[string]string{"ldapbasedn": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing base DN"},
{testName: "invalid base dn",
hbaConfLDAPOpts: map[string]string{"ldapbasedn": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user foo on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: invalid base DN \"invalid\" provided"},
{testName: "empty bind dn",
hbaConfLDAPOpts: map[string]string{"ldapbinddn": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing bind DN"},
{testName: "invalid bind dn",
hbaConfLDAPOpts: map[string]string{"ldapbinddn": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user foo on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: LDAP bind failed: invalid username provided"},
{testName: "empty bind pwd",
hbaConfLDAPOpts: map[string]string{"ldapbindpasswd": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing bind password"},
{testName: "invalid bind pwd",
hbaConfLDAPOpts: map[string]string{"ldapbindpasswd": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user foo on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: LDAP bind failed: invalid password provided"},
{testName: "empty search attribute",
hbaConfLDAPOpts: map[string]string{"ldapsearchattribute": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing search attribute"},
{testName: "invalid search attribute",
hbaConfLDAPOpts: map[string]string{"ldapsearchattribute": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user foo on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: invalid search attribute \"invalid\" provided"},
{testName: "empty search filter",
hbaConfLDAPOpts: map[string]string{"ldapsearchfilter": emptyParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to validate authenticator options",
expectedDetailedErrMsg: "error validation authenticator options for LDAP: ldap params in HBA conf missing search filter"},
{testName: "invalid search filter",
hbaConfLDAPOpts: map[string]string{"ldapsearchfilter": invalidParam}, user: "foo", pwd: "bar", ldapAuthSuccess: false,
expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user foo on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: invalid search filter \"invalid\" provided"},
{testName: "invalid ldap user",
user: invalidParam, pwd: "bar", ldapAuthSuccess: false, expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user invalid on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: invalid search value \"invalid\" provided"},
{testName: "no such ldap user",
user: "", pwd: "bar", ldapAuthSuccess: false, expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: user \"\" does not exist"},
{testName: "too many matching ldap users",
user: "foo,foo2,foo3", pwd: "bar", ldapAuthSuccess: false, expectedErr: "LDAP authentication: unable to find LDAP user distinguished name",
expectedErrDetails: "cannot find provided user foo,foo2,foo3 on LDAP server",
expectedDetailedErrMsg: "error when searching for user in LDAP server: LDAP search failed: too many matching entries returned for user \"foo,foo2,foo3\""},
{testName: "invalid ldap password",
user: "foo", pwd: invalidParam, ldapAuthSuccess: false, expectedErr: "LDAP authentication: unable to bind as LDAP user",
expectedErrDetails: "credentials invalid for LDAP server user foo",
expectedDetailedErrMsg: "error when biding as user foo with DN(foo) in LDAP server: LDAP bind failed: invalid password provided"},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d: testName:%v hbConfOpts:%v user:%v password:%v", i, tc.testName, tc.hbaConfLDAPOpts, tc.user, tc.pwd), func(t *testing.T) {
hbaEntry := constructHBAEntry(t, hbaEntryBase, hbaConfLDAPDefaultOpts, tc.hbaConfLDAPOpts)
detailedErrorMsg, err := verifier.ValidateLDAPLogin(
ctx, s.ClusterSettings(), username.MakeSQLUsernameFromPreNormalizedString(tc.user), tc.pwd, &hbaEntry, nil)
if (err == nil) != tc.ldapAuthSuccess {
t.Fatalf("expected success=%t, got err=%v", tc.ldapAuthSuccess, err)
}
if err != nil {
require.Equal(t, tc.expectedErr, err.Error())
require.Equal(t, tc.expectedErrDetails, errors.FlattenDetails(err))
require.Equal(t, tc.expectedDetailedErrMsg, detailedErrorMsg)
}
})
}
}