wiltonsr / ldapAuth

An open source Traefik Middleware that enables authentication via LDAP in a similar way to Traefik Enterprise
https://plugins.traefik.io/plugins/628c9eb7ffc0cd18356a979c/ldap-auth
Apache License 2.0
117 stars 10 forks source link

BindDN and BindPassword are not used for AllowedGroups checking #61

Closed sasjafor closed 7 months ago

sasjafor commented 7 months ago

When using the options BindDN and BindPassword in combination with AllowedGroups then the AllowedGroups are checked using the user credentials instead of the bind credentials.

Is this intentional behaviour?

For me this creates an issue since the individual users cannot access group membership information. Only the bind user has access to those parts of the directory.

If it's unintentional, consider this a bug report. If it's intentional, then a feature request for a toggle to choose the behaviour.

wiltonsr commented 7 months ago

Hi, @sasjafor

Could you provide your confs and debug logs?

When using the options BindDN and BindPassword in combination with AllowedGroups then the AllowedGroups are checked using the user credentials instead of the bind credentials.

Is this intentional behaviour?

Please check Operations Mode docs. All subsequent LDAP operations will be performed based on the Operation Mode defined in confs. You can also check this mode by checking ldapAuth's logs.

You can also check this example. I think you need to set searchFilter option to perform what you need.

sasjafor commented 7 months ago

Hello @wiltonsr,

I checked the source code and I saw that the method ServeHTTP first calls LdapCheckUser and then LdapCheckUserAuthorized. LdapCheckUser performs a bind on the LDAP connection with the credentials of the user trying to sign in. Then there is no further bind until LdapCheckUserAuthorized is called, where the group membership is checked, while still bound using the user credentials instead of the BindDN and BindPassword. This is not mentioned in the docs as far as I could see.

The example you provided does not use AllowedGroups and as such does not apply to my use case.

Here is my config:

http:
  middlewares:
    my-ldapAuth:
      plugin:
        ldapAuth:
          Enabled: true
          LogLevel: "DEBUG"
          Url: "ldap://mydomain.example"
          Port: 389
          BaseDN: "dc=company,dc=local"
          Attribute: "uid"
          BindDN: "cn=read,dc=company,dc=local"
          BindPassword: "<censored>"
          AllowedGroups:
            - "cn=root,ou=groups,dc=company,dc=local"
          SearchFilter: (\{\{.Attribute\}\}=\{\{.Username\}\})

And the debug log:

traefik    | time="2024-03-20T10:08:47Z" level=info msg="Configuration loaded from flags."
traefik    | INFO: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: Starting my-ldapAuth@file Middleware...
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: Enabled => 'true'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: LogLevel => 'DEBUG'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: URL => 'ldap://mydomain.example'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: Port => '389'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: CacheTimeout => '300'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: CacheCookieName => 'ldapAuth_session_token'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: CacheCookiePath => ''
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: CacheCookieSecure => 'false'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: CacheKey => 'super-secret-key'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: StartTLS => 'false'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: InsecureSkipVerify => 'false'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: MinVersionTLS => 'tls.VersionTLS12'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: MaxVersionTLS => 'tls.VersionTLS13'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: CertificateAuthority => ''
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: Attribute => 'uid'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: SearchFilter => '(\{\{.Attribute\}\}=\{\{.Username\}\})'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: BaseDN => 'dc=company,dc=local'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: BindDN => 'cn=read,dc=company,dc=local'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: BindPassword => '<censored>'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: ForwardUsername => 'true'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: ForwardUsernameHeader => 'Username'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: ForwardAuthorization => 'false'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: ForwardExtraLdapHeaders => 'false'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: WWWAuthenticateHeader => 'true'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: WWWAuthenticateHeaderRealm => ''
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: EnableNestedGroupFilter => 'false'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: AllowedGroups => '[cn=root,ou=groups,dc=company,dc=local]'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: AllowedUsers => '[]'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:08:47 restricted.go:51: Username => ''
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Session details: &{ map[] 0xc00166e140 true {0xc002b8b040 {0xc002df56c0 0xc0020c12a8 406}} ldapAuth_session_token}
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:52: No session found! Trying to authenticate in LDAP
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Connect Address: 'ldap://mydomain.example:389'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Running in Search Mode
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Performing User BindDN Search
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Search Filter: '(uid=testuser)'
traefik    | INFO: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Authenticating User: cn=Test User,ou=users,dc=company,dc=local
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Group Filter: '(|(member=cn=Test User,ou=users,dc=company,dc=local)(uniqueMember=cn=Test User,ou=users,dc=company,dc=local)(memberUid=testuser))'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Searching Group: 'cn=root,ou=groups,dc=company,dc=local' with User: 'cn=Test User,ou=users,dc=company,dc=local'
traefik    | INFO: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: LDAP Result Code 32 "No Such Object": 
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: User: 'testuser' not found in Group: 'cn=root,ou=groups,dc=company,dc=local'
traefik    | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:52: [LDAP Result Code 32 "No Such Object": 
traefik    | User 'testuser' does not match any allowed users nor allowed groups.]
traefik    | ERROR: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: LDAP Result Code 32 "No Such Object": 
traefik    | User 'testuser' does not match any allowed users nor allowed groups.
wiltonsr commented 7 months ago

LdapCheckUser performs a bind on the LDAP connection with the credentials of the user trying to sign in.

This only happens if your searchFilter is empty.

https://github.com/wiltonsr/ldapAuth/blob/590144334bba55a99727907cd6f9a52ecd236a19/ldapauth.go#L251-L264

Otherwise, the function SearchMode will be called

https://github.com/wiltonsr/ldapAuth/blob/590144334bba55a99727907cd6f9a52ecd236a19/ldapauth.go#L463-L474

And the bind will be made authenticated or anonymous based on bindDN and bindPassword options.

The example you provided does not use AllowedGroups and as such does not apply to my use case.

There is an example using AllowedGroups. But your confs looks OK and you are running in SearchMode

traefik | DEBUG: ldapAuth: 2024/03/20 10:09:24 restricted.go:51: Running in Search Mode

Could you provide a ldapsearch in your "cn=root,ou=groups,dc=company,dc=local" group?

An example using ldap.forumsys.com will be

ldapsearch  -x \
  -b "ou=mathematicians,dc=example,dc=com" \
  -H ldap://ldap.forumsys.com \
  -D "uid=tesla,dc=example,dc=com" \
  -w password

In this case we got

# extended LDIF
#
# LDAPv3
# base <ou=mathematicians,dc=example,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# mathematicians, example.com
dn: ou=mathematicians,dc=example,dc=com
uniqueMember: uid=euclid,dc=example,dc=com
uniqueMember: uid=riemann,dc=example,dc=com
uniqueMember: uid=euler,dc=example,dc=com
uniqueMember: uid=gauss,dc=example,dc=com
uniqueMember: uid=test,dc=example,dc=com
ou: mathematicians
cn: Mathematicians
objectClass: groupOfUniqueNames
objectClass: top

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

That matches with the Group Filter with the attribute uniqueMember

https://github.com/wiltonsr/ldapAuth/blob/590144334bba55a99727907cd6f9a52ecd236a19/ldapauth.go#L338-L345

sasjafor commented 7 months ago

Yes, SearchMode is called, but later in LdapCheckUser a bind is performed again also using the user credentials:

https://github.com/wiltonsr/ldapAuth/blob/5de938dfa1b7d8c5746e8a11cdc4f2f33155b27b/ldapauth.go#L274

sasjafor commented 7 months ago

I added

conn.Bind(la.config.BindDN, la.config.BindPassword)

before LdapCheckUserAuthorized is called and that fixed my problem.

https://github.com/wiltonsr/ldapAuth/blob/5de938dfa1b7d8c5746e8a11cdc4f2f33155b27b/ldapauth.go#L194-L202

wiltonsr commented 7 months ago

Yes, SearchMode is called, but later in LdapCheckUser a bind is performed again also using the user credentials:

You are right, thanks for pointing that out.

EDIT: I will work on this in this branch. I would like to ping you to test the fix as soon as possible.

wiltonsr commented 7 months ago

Available in v0.1.8.

sasjafor commented 7 months ago

I tested and now it works as expected. Thanks for the quick fix!