Skip to content

Commit 3377be3

Browse files
authored
Merge pull request #618 from xxpopygaixx/ldap_active_directory_style_objectsid_search
support AD style objectSid search
2 parents bfdbd78 + a165064 commit 3377be3

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Simple diffinition of AD domain user
2+
3+
# Root DSE.
4+
dn:
5+
namingContexts: dc=example_domain_name
6+
subschemaSubentry: cn=schema
7+
8+
# Schema definition
9+
dn: cn=schema
10+
objectClass: top
11+
objectClass: subschema
12+
# 'activeDirectoryObjectSidMatch' is a custom logic which allows filtering by (objectSid=S-1-...).
13+
# Such filtering is AD only feature so we should specify that objectSid can be found by either (objectSid=S-1-...) or (objectSid=\00\01\AB...).
14+
# Custom objectClass 'myUser' make it possible to attach 'activeDirectoryObjectSidMatch' to entry.
15+
attributeTypes: ( 1.2.3.4.5.6.7.8 NAME 'objectSid' DESC 'objectSid' EQUALITY activeDirectoryObjectSidMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
16+
objectClasses: ( 1.2.3.4.5.6.7.9 NAME 'myUser' SUP top STRUCTURAL MUST ( cn ) MAY ( objectSid ) )
17+
18+
# Example domain
19+
dn: dc=example_domain_name
20+
objectClass: top
21+
objectClass: domain
22+
dc: example_domain_name
23+
24+
# Example user
25+
dn: uid=example_user_name,dc=example_domain_name
26+
objectClass: myUser
27+
objectClass: user
28+
cn: example_user_name
29+
30+
# SID in binary form (Base64 encoded).
31+
# S-1-5-21-1234567890-1234567890-1234567890-1001
32+
objectSid:: AQUAAAAAAAUVAAAA0gKWSdIClknSApZJ6QMAAA==

providers/directory/search.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/base64"
7+
"encoding/binary"
78
"fmt"
89
"github.com/brianvoe/gofakeit/v6"
910
log "github.com/sirupsen/logrus"
@@ -373,6 +374,12 @@ func (p *parser) equal(name, value string) (predicate, error) {
373374
s = strings.ReplaceAll(v, "-", "")
374375
return v == s
375376
}
377+
case "activeDirectoryObjectSidMatch", "0.0.0.0":
378+
v, _ := sidToBytes(value)
379+
f = func(s string) bool {
380+
b := []byte(s)
381+
return bytes.Equal(b, v) || value == s
382+
}
376383
default:
377384
return nil, fmt.Errorf("unsupported equality type: %v", t)
378385
}
@@ -536,3 +543,46 @@ func levenshtein(a, b string) int {
536543
// Return the final Levenshtein distance
537544
return matrix[len(a)][len(b)]
538545
}
546+
547+
// Converts sddl Sid to []byte
548+
func sidToBytes(sid string) ([]byte, error) {
549+
sid = strings.ReplaceAll(sid, "S-", "")
550+
parts := strings.Split(sid, "-")
551+
552+
if len(parts) < 3 {
553+
return nil, fmt.Errorf("invalid sid string format %v", sid)
554+
}
555+
556+
var result []byte
557+
558+
// Revision
559+
rev, revErr := strconv.ParseUint(parts[0], 10, 32)
560+
if revErr != nil {
561+
return nil, fmt.Errorf("invalid uint value %v at position: %v", parts[0], 0)
562+
}
563+
result = append(result, byte(rev))
564+
565+
// SubAuthorityCount
566+
result = append(result, byte(len(parts)-2))
567+
568+
// IdentifierAuthority
569+
for range 5 {
570+
result = append(result, 0)
571+
}
572+
authId, authIdErr := strconv.ParseUint(parts[1], 10, 32)
573+
if authIdErr != nil {
574+
return nil, fmt.Errorf("invalid uint value %v at position: %v", parts[1], 1)
575+
}
576+
result = append(result, byte(authId))
577+
for i, part := range parts[2:] {
578+
val, valErr := strconv.ParseUint(part, 10, 32)
579+
if valErr != nil {
580+
return nil, fmt.Errorf("invalid uint value %v at position: %v", part, i)
581+
}
582+
b := make([]byte, 4)
583+
binary.LittleEndian.PutUint32(b, uint32(val))
584+
result = append(result, b...)
585+
}
586+
587+
return result, nil
588+
}

providers/directory/search_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,41 @@ func TestSearch_Schema(t *testing.T) {
243243
require.Len(t, res.Results, 1)
244244
},
245245
},
246+
{
247+
name: "ldap filter objectSid using AD style",
248+
input: `{ "files": [ "./users.ldif" ] }`,
249+
reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{
250+
"file:/users.ldif": {Raw: []byte(`
251+
dn:
252+
namingContexts: dc=example_domain_name
253+
subschemaSubentry: cn=schema
254+
255+
dn: cn=schema
256+
objectClass: top
257+
objectClass: subschema
258+
attributeTypes: ( 1.2.3.4.5.6.7.8 NAME 'objectSid' DESC 'objectSid' EQUALITY activeDirectoryObjectSidMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
259+
260+
dn: cn=user1
261+
objectSid:: AQUAAAAAAAUVAAAA0gKWSdIClknSApZJ6QMAAA==
262+
263+
dn: cn=user2
264+
objectSid:: AQUAAAAAAAUVAAAAF8sUcR3r8QcekDXQw9wAAA==
265+
`)},
266+
}},
267+
test: func(t *testing.T, h ldap.Handler, err error) {
268+
require.NoError(t, err)
269+
270+
rr := ldaptest.NewRecorder()
271+
h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{
272+
Scope: ldap.ScopeWholeSubtree,
273+
Filter: fmt.Sprintf("(objectSid=S-1-5-21-1234567890-1234567890-1234567890-1001)"),
274+
}))
275+
res := rr.Message.(*ldap.SearchResponse)
276+
277+
require.Len(t, res.Results, 1)
278+
require.Equal(t, "cn=user1", res.Results[0].Dn)
279+
},
280+
},
246281
}
247282

248283
t.Parallel()

0 commit comments

Comments
 (0)