script-server icon indicating copy to clipboard operation
script-server copied to clipboard

LDAP - Option for not Anonymous / Not Simple

Open charles-nulud opened this issue 2 years ago • 22 comments

Is there an option to use bind_dn and bind_password for LDAP authentication?

As our LDAP server does not allow anonymous login.

charles-nulud avatar Apr 14 '23 03:04 charles-nulud

Hi @charles-nulud script server doesn't use anonymous authentication (that's why a user has to provide username and password). It uses SIMPLE protocol, with bind_dn and password.

Unfortunately, other protocols are not supported. Which one is used by your server?

bugy avatar Apr 14 '23 07:04 bugy

I have modified the auth_ldap.py and it works to assign username with bind_dn and password with bind_pw value. and it works.

But I think it is a good option, if you can include this as option for LDAP, as not all LDAP server just accept username and password, like for us, it requires to connect as admin user/password to query, thus I need to use bind_dn and bind_pw.

charles-nulud avatar Apr 14 '23 08:04 charles-nulud

Could you tell me please, what did you modify exactly?

bugy avatar Apr 14 '23 08:04 bugy

from:

def _connect(self, full_username, password):
    connection = Connection(
        self.url,
        user=full_username,
        password=password,
        authentication=SIMPLE,
        read_only=True,
        version=self.version
    )
    connection.bind()
    return connection

to:

def _connect(self, full_username, password): connection = Connection( self.url, user='uid=admin,ou=People,dc=xx,dc=xx,dc=xx', password='xxxxxxx', authentication=SIMPLE, read_only=True, version=self.version, ) connection.bind() return connection

and I suggest modify this so that in conf.json, you can provide bind_dn and bind_pw for optional admin user/pass and use this instead of the $username / password to authenticate.

charles-nulud avatar Apr 14 '23 08:04 charles-nulud

But with such changes, a user can specify any credentials and still login (with admin rights)

bugy avatar Apr 14 '23 08:04 bugy

oh yes, my mistake. it bypasses the username/password check enter in the ui.

need to modify that the search still need to function and only connect should use admin user/pass..

charles-nulud avatar Apr 14 '23 09:04 charles-nulud

The implementation should be like this on my testldap.py:

LDAP connection and bind

server = ldap3.Server(LDAP_SERVER, port=LDAP_PORT) conn = ldap3.Connection(server, user=LDAP_BIND_DN, password=LDAP_BIND_PW, auto_bind=True)

LDAP search and authentication

conn.search(LDAP_BASE_DN, '(uid={})'.format(username)) if conn.entries: user_dn = conn.entries[0].entry_dn try: conn = ldap3.Connection(server, user=user_dn, password=password, auto_bind=True) print('Login successful!') except ldap3.core.exceptions.LDAPBindError: print('Login failed!') else: print('User not found!')

charles-nulud avatar Apr 14 '23 09:04 charles-nulud

But this is effectively the same as what script server is doing. You are using SIMPLE protocol as well. The only difference is that you are using user_dn instead of username. But for this purpose, there is username_pattern setting

Or am I missing something?

bugy avatar Apr 14 '23 09:04 bugy

our LDAP server only accepts connection from admin user. How can I implement this?

login using admin, but search the user/pass using the $username/password of the gui

charles-nulud avatar Apr 14 '23 09:04 charles-nulud

conn = ldap3.Connection(server, user=user_dn, password=password, auto_bind=True)

Isn't it what you are doing here? Logging in (connecting) as a normal user.

bugy avatar Apr 14 '23 09:04 bugy

yes but it needs to connect bind first as admin:

LDAP connection and bind

server = ldap3.Server(LDAP_SERVER, port=LDAP_PORT) conn = ldap3.Connection(server, user=LDAP_BIND_DN, password=LDAP_BIND_PW, auto_bind=True)

charles-nulud avatar Apr 14 '23 09:04 charles-nulud

Sorry, I'm not a LDAP expert, and don't fully understand how it works. From my understanding, by doing conn = ldap3.Connection(server, user=user_dn, password=password, auto_bind=True) you are creating a completely new connection.

What error do you get, if you use default script server implementation, with username_pattern specified? (or by entering user_dn in a login form)

bugy avatar Apr 14 '23 09:04 bugy

I am getting "Invalid Credentials" using default. Tried different username_pattern already.

my sample testldap.py connects using bind_dn and bind_pw first for initial connection and then connect again using the username to verify the username / password is correct.

charles-nulud avatar Apr 14 '23 09:04 charles-nulud

Just to double-check, will it also work, if you remove this part:

conn.search(LDAP_BASE_DN, '(uid={})'.format(username))
if conn.entries:
user_dn = conn.entries[0].entry_dn

And just use username set by script server?

bugy avatar Apr 14 '23 09:04 bugy

Modified` into :

` username = input('Enter your LDAP username: ') password = input('Enter your LDAP password: ')

server = ldap3.Server(LDAP_SERVER, port=LDAP_PORT) conn = ldap3.Connection(server, user=username, password=password, auto_bind=True)

try: conn = ldap3.Connection(server, user=user_dn, password=password, auto_bind=True) print('Login successful!') except ldap3.core.exceptions.LDAPBindError: print('Login failed!')

and get error: Traceback (most recent call last): File "/opt/script-server/conf/runners/scripts/testldapuser.py", line 17, in conn = ldap3.Connection(server, user=username, password=password, auto_bind=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/root/anaconda3/envs/env_python3/lib/python3.11/site-packages/ldap3/core/connection.py", line 363, in init self._do_auto_bind() File "/root/anaconda3/envs/env_python3/lib/python3.11/site-packages/ldap3/core/connection.py", line 412, in _do_auto_bind raise LDAPBindError(error) ldap3.core.exceptions.LDAPBindError: automatic bind not successful - invalidCredentials

charles-nulud avatar Apr 14 '23 10:04 charles-nulud

I think the goal, is first establish a connection and bind that uses bind_dn/bind_pw and then authenticate user using search should use the $username / password of the gui

charles-nulud avatar Apr 14 '23 10:04 charles-nulud

def _connect(self, full_username, password):
    server = ldap3.Server(LDAP_SERVER, port=LDAP_PORT)
    conn = ldap3.Connection(server, user=LDAP_BIND_DN, password=LDAP_BIND_PW, auto_bind=True)

    try:
        conn = ldap3.Connection(server, user=full_username, password=password, auto_bind=True)
        print('Login successful!')
    except ldap3.core.exceptions.LDAPBindError:
        print('Login failed!')
    else:
        print('User not found!')

Could you check, if this code works for you?

bugy avatar Apr 14 '23 11:04 bugy

My point is: conn.search(LDAP_BASE_DN, '(uid={})'.format(username)) this should be 100% redundant

bugy avatar Apr 14 '23 11:04 bugy

On Fri, Apr 14, 2023 at 7:03 AM Iaroslav Shepilov @.***> wrote:

conn = ldap3.Connection(server, user=LDAP_BIND_DN, password=LDAP_BIND_PW, auto_bind=True)

conn = ldap3.Connection(server, user=full_username, password=password, auto_bind=True)

The first will work, the second (sometimes, depending on the LDAP server config) won't.

I'm not an LDAP expert either, but we just went through something like this with LDAP config here at my real job. LDAP supports the concept of the bind connection as a distinct user, separate from the search. You absolutely need the bind user/pass as their own settings, as LDAP administrators often don't grant bind permissions to users defined within the LDAP, so that they can limit who can actually poll the dirtree (and prevent slurping of the directory).

Once you establish the bind connection as the bind-user, you can attempt to auth with the GUI-user/pass.

Message ID: @.***>

MNeill73 avatar Apr 14 '23 15:04 MNeill73

Can you check https://ldap3.readthedocs.io/en/latest/bind.html particularly this part:

"Bind as a different user while the Connection is open"

`# import class and constants from ldap3 import Server, Connection, ALL, LDAPBindError

define the server

s = Server('servername', get_info=ALL) # define an unsecure LDAP server, requesting info on DSE and schema

define the connection

c = Connection(s, user='user_dn', password='user_password')

perform the Bind operation

if not c.bind(): print('error in bind', c.result)

Bind again with another user

if not c.rebind(user='different_user_dn', password='different_user_password') print('error in rebind', c.result)`

maybe we can use this.

charles-nulud avatar Apr 17 '23 03:04 charles-nulud

This seems works:

def _connect(self, full_username, password):

    LDAP_BIND_DN = 'uid=xxxxx,ou=People,dc=xx,dc=xx,dc=xx'
    LDAP_BIND_PW = 'xxxxxxx'
    LDAP_BASE_DN = 'ou=People,dc=xx,dc=xx,dc=xx'

    connection = Connection(self.url, user=LDAP_BIND_DN, password=LDAP_BIND_PW, authentication=SIMPLE, read_only=True, version=self.version)
    connection.bind()
    connection.search(LDAP_BASE_DN, '(uid={})'.format(full_username))
    if connection.entries:
        user_dn = connection.entries[0].entry_dn
        try:
            connection = Connection(self.url, user=user_dn, password=password, auto_bind=True)
            connection.bind()
            return connection

        except ldap3.core.exceptions.LDAPBindError:
            LOGGER.exception('LDAP Error occurred while ldap authentication of user ' + username)

charles-nulud avatar Apr 17 '23 06:04 charles-nulud

Hi @charles-nulud could you use username_template and remove "search" part from the code above? This step seems redundant to me. There is no need to search for user dn, if you can define it via username_pattern Anyways, if your current code works locally for you, it's great :)

bugy avatar Apr 17 '23 06:04 bugy