rustdesk / rustdesk-server-pro

Some scripts for RustDesk Server Pro are hosted here.
149 stars 75 forks source link

Issue with LDAP/Active Directory #74

Closed ArnaudL75 closed 1 year ago

ArnaudL75 commented 1 year ago

Bug Description

Hello,

I have an issue when I try to connect my Server Pro to my Active Directory.

I provide the following informations:

I get the error message: Failed to update Ldap: LDAP operation result: rc=49 (invalidCredentials), dn: "", text: "80090308: LdapErr: DSID-0C090434, comment: AcceptSecurityContext error, data 52e, v4f7c"

I am completely sure of the bind information.

When I try a tail -f /var/log/rustdesk-server/hbbs.log, I get : [2023-09-13 18:46:33.591068 +02:00] INFO [src/ldap.rs:133] New LDAP connection: ldap://ad1.mycompany.intra:389

And I see the TCP connection on my FW.

Have you got an idea ?

Thanks a lot for your product !

How to Reproduce

Bottom

Expected Behavior

Expected : LDAP connection

Operating system(s) on local side and remote side

Debian 11

RustDesk Version(s) on local side and remote side

1.1.9

Screenshots

Capture d’écran 2023-09-13 184925

Capture d’écran 2023-09-13 185023

Additional Context

No response

rustdesk commented 1 year ago

how about change to

Base DN = CN=Users,DC=mycompany,DC=INTRA
Bind DN = CN=Admin_mycompany,CN=Users,DC=mycompany,DC=INTRA

or

Base DN = CN=Users,DC=mycompany,DC=INTRA
Bind DN = Admin_mycompany
rustdesk commented 1 year ago

@dinger1986

ArnaudL75 commented 1 year ago

I just tried it now and unfortunately neither of them works.

Thanks for your support !

dinger1986 commented 1 year ago

I'll test it out again, what version of AD are you using?

dinger1986 commented 1 year ago

I cant get it to work, with TLS enabled I get an error on the AD server in event viewer without I get nothing just errors on The Web Console of RustDesk

To check DN etc I use dsquery user -name adminname also LDAP server setup as LDAP://fqdn I get the same errors as @ArnaudL75

Also in hbbs logs

image

Would it be helpful if I spin up a Windows DC for testing? I can do that tomorrow if needed, I have also disabled the firewall etc incase that was getting in the way

rustdesk commented 1 year ago

We will test it ourself on dev side.

rustdesk commented 1 year ago

@fufesou

ArnaudL75 commented 1 year ago

I'll test it out again, what version of AD are you using?

Hi,

I'm using AD on Windows Server 2022 :)

fufesou commented 1 year ago

@ArnaudL75 Hi, can you please try the lower case DC and CN.

LDAP Host = ad1.mycompany.intra
LDAP Port = 389
Base DN = dc=mycompany,dc=INTRA
Bind DN = cn=Admin_mycompany,cn=Users,dc=mycompany,dc=INTRA
ArnaudL75 commented 1 year ago

Yeeahhhhh it works !

Actually, it's work also with 👍

LDAP Host = ad1.mycompany.intra LDAP Port = 389 Base DN = DC=MYCOMPANY,DC=INTRA Bind DN = cn=Admin_mycompany,CN=Users,DC=MYCOMPANY,DC=INTRA

Just the first "CN" has to be lowercase :)

Thank you !

rustdesk commented 1 year ago
Base DN = CN=Users,DC=mycompany,DC=INTRA
Bind DN = Admin_mycompany

This does not work?

dinger1986 commented 1 year ago

Does that work in testing? @fufesou

dinger1986 commented 1 year ago

Base DN = CN=Users,DC=mycompany,DC=INTRA

Bind DN = Admin_mycompany

This does not work?

No some are also Organisation units as well

ArnaudL75 commented 1 year ago
Base DN = CN=Users,DC=mycompany,DC=INTRA
Bind DN = Admin_mycompany

This does not work?

It works !

rustdesk commented 1 year ago

I just tried it now and unfortunately neither of them works.

Thanks for your support !

"Neither works" wasted a lot of time. :(

ArnaudL75 commented 1 year ago
Base DN = CN=Users,DC=mycompany,DC=INTRA
Bind DN = Admin_mycompany

This does not work?

Weird, yesterday it seems to not work ... I'll retry

fufesou commented 1 year ago

Does that work in testing? @fufesou

Yes, the key words "cn" should be in lower case. I'll fix this issue.

dinger1986 commented 1 year ago

lower case is fine, can document it, Ill test it lower, didnt even try that tbh as MS stipulates uppercase

dinger1986 commented 1 year ago

works fine with containers but doesnt work with organisational units

ArnaudL75 commented 1 year ago

The connection works, sorry for the wasted time :(

But I believe that the bind user will not look for users anywhere other than the root of the Base DN.

I would test with another config:

Base DN: OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA Bind DN: Admin_mycompany

The connection works.

However, I have lower level OUs, for example:

OU=MMT,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA OU=PON,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA OU=SAI,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA

When I try to login, it works for users in OU=Utilisateurs but not for OU=MMT, or PON, or SAI.

I have this error:

LDAP operation result: rc=49 (invalidCredentials), dn: "", text: "80090308: LdapErr: DSID-0C090434, comment: AcceptSecurityContext error, data 52e, v4f7c"

rustdesk commented 1 year ago

But I believe that the bind user will not look for users anywhere other than the root of the Base DN.

Yes, only search on the root of the Base DN. Actually we do not search in current code, we just use cn=<name>,<basedn>

Is it possible there are duplicate names?

cn=abc,OU=PON,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA
cn=abc,OU=MMT,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA
ArnaudL75 commented 1 year ago

OK I understand the thinking!

But it is not possible to have the same username twice in different OUs. AD forbids it, I just tried it. Usernames are unique.

rustdesk commented 1 year ago

Do you have the requirement to search within the subtree of the base DN?

To perform a search within a subtree in LDAP and set the parameters, you need to specify the following:

  1. Base DN: Specify the starting point of the search. It is an LDAP path that identifies the location from which the search should begin.
  2. Scope: Specify the scope of the search. There are three options:
    • "base": Search only the base DN itself.
    • "one" (or "onelevel"): Search only the immediate children of the base DN.
    • "sub" (or "subtree"): Search the entire subtree, including the base DN and all its descendants.
  3. Filter: Define the filter criteria for the search. It specifies the attributes and conditions that need to be matched.
  4. Attributes: Specify the list of attributes to be returned, limiting the attributes included in the search result.

The specific method to set up a search within a subtree may vary depending on the LDAP library, tool, or programming language you are using. Here's an example Python code snippet using LDAP search operation to search within a subtree:

import ldap

# Set LDAP server connection parameters
ldap_server = 'ldap://ldap.example.com'
bind_dn = 'cn=admin,dc=example,dc=com'
bind_password = 'password'

# Create LDAP connection
conn = ldap.initialize(ldap_server)
conn.simple_bind_s(bind_dn, bind_password)

# Set search parameters
base_dn = 'ou=users,dc=example,dc=com'
scope = ldap.SCOPE_SUBTREE
filter = '(objectClass=person)'
attributes = ['cn', 'mail']

# Perform the search
result = conn.search_s(base_dn, scope, filter, attributes)

# Process the search result
for dn, entry in result:
    print('DN:', dn)
    print('CN:', entry['cn'][0])
    print('Email:', entry['mail'][0])

# Close the LDAP connection
conn.unbind()

Please refer to the documentation of the specific LDAP library or tool you are using to set up a search within a subtree.

dinger1986 commented 1 year ago

So I can use the domain admin to create the connection but it needs to be "administrator", Ill check the groups on that, but cant get any other users to log in

dinger1986 commented 1 year ago

Ill spin up a new server today, I am thinking could be an issues as this domain started as an sbs 2011 domain and has just been upgraded over time

ArnaudL75 commented 1 year ago

And to spice things up, it would be perfect if my Bind DN will not be in my DN base, but outside :D

MYCOMPANY.INTRA (It's my domain)    Users       bind-user    MyCompany (It's my main OU)       Utilisateurs (OU)          MMT (OU)             User1          PON (OU)             User2           SAI (OU)_             User3

rustdesk commented 1 year ago

@fufesou let's add "scope" options as below when you fix case senstive issue, also add attributes, we search cn=<name> and uid=<cname> both.

this https://www.ezeelogin.com/kb/article/error-could-not-bind-to-any-ldap-server-80090308-ldaperr-dsid-0c090453-comment-acceptsecuritycontext-error-data-52e-v3839-396.html is a good example, but we do not need to complex like this (too many attribute fields on below pic). image

Do you have the requirement to search within the subtree of the base DN?

To perform a search within a subtree in LDAP and set the parameters, you need to specify the following:

  1. Base DN: Specify the starting point of the search. It is an LDAP path that identifies the location from which the search should begin.
  2. Scope: Specify the scope of the search. There are three options:

    • "base": Search only the base DN itself.
    • "one" (or "onelevel"): Search only the immediate children of the base DN.
    • "sub" (or "subtree"): Search the entire subtree, including the base DN and all its descendants.
  3. Filter: Define the filter criteria for the search. It specifies the attributes and conditions that need to be matched.
  4. Attributes: Specify the list of attributes to be returned, limiting the attributes included in the search result.

The specific method to set up a search within a subtree may vary depending on the LDAP library, tool, or programming language you are using. Here's an example Python code snippet using LDAP search operation to search within a subtree:

import ldap

# Set LDAP server connection parameters
ldap_server = 'ldap://ldap.example.com'
bind_dn = 'cn=admin,dc=example,dc=com'
bind_password = 'password'

# Create LDAP connection
conn = ldap.initialize(ldap_server)
conn.simple_bind_s(bind_dn, bind_password)

# Set search parameters
base_dn = 'ou=users,dc=example,dc=com'
scope = ldap.SCOPE_SUBTREE
filter = '(objectClass=person)'
attributes = ['cn', 'mail']

# Perform the search
result = conn.search_s(base_dn, scope, filter, attributes)

# Process the search result
for dn, entry in result:
    print('DN:', dn)
    print('CN:', entry['cn'][0])
    print('Email:', entry['mail'][0])

# Close the LDAP connection
conn.unbind()

Please refer to the documentation of the specific LDAP library or tool you are using to set up a search within a subtree.

fufesou commented 1 year ago

OU=MMT,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA

@ArnaudL75 This type of user works for me.

1694698473536

https://github.com/rustdesk/rustdesk-server-pro/assets/13586388/2148c630-0992-42f3-a832-35b348fb8d16

ArnaudL75 commented 1 year ago

Hello,

In this case, it works for me too ;)

I have two problems:

--###### THE FIRST #####-- With these settings:

Base DN: OU=Utilisateurs, OU=MYCOMPANY, DC=LPL, DC=INTRA
Bind DN : Admin_mycompany

Connection is OK if my user "Admin_mycompany" is inside my OU named Utilisateurs.

BUT the system cannot find users in lower OU like OU=MMT,OU=Utilisateurs,OU=MYCOMPANY,DC=LPL,DC=INTRA It cannot search in subtree. But it seems to be soon repaired, with the option "--scope".

--###### THE SECOND PROBLEM #####-- With these settings:

Base DN: OU=Utilisateurs, OU=MYCOMPANY, DC=LPL, DC=INTRA
Bind DN : cn=Admin_mycompany, OU=Users, DC=LPL, DC=INTRA

The connection to LDAP is wrong because the Bind DN does not end with Base DN. My user "Admin_mycompany" is outside the Base DN, not in the Base DN subtree :)

rustdesk commented 1 year ago

@fufesou take care above two problems, As for second problem, let us have clever check, checking the root dn rather than the whole base dn.

fufesou commented 1 year ago

I don't think we should add "attributes" fields. The "attributes"%2C-,attributes,-Which%20attributes%20to) are used as the returned entries.

4.5.1. Search Request

We can use the "filter" field during login operation.

For "scope"

The only usage is to search whether "uid" and "user password" exist, so that the login operation can be much simpler. But the uniqueness of uid seems not guaranteed.

@rustdesk Can we ignore the situation where uid is not unique?

1694742643774

https://openldap-software.0penldap.narkive.com/rmN2duog/how-to-check-uniqueness-of-uidnumber#:~:text=uniqueness%20of%20uidNumber%20%3F-,LDAP%20doesn%27t%20support%20such%20constraints%20in%20the%20way%20some%20databases%20do.,-Either%20search%20a

1694742723062
rustdesk commented 1 year ago

Maybe some extra custom filter.

rustdesk commented 1 year ago

Maybe some extra custom filter.

Not very important, just making scope work first.

fufesou commented 1 year ago

For "scope"

The only usage is to search whether "uid" and "user password" exist, so that the login operation can be much simpler. But the uniqueness of uid seems not guaranteed.

We need to bind when configuring ldap. So the "scope" should not be used at this time.

https://github.com/rustdesk/rustdesk-server-pro/issues/74#issuecomment-1719542044

The first problem should not be considered on configuring.

We can extract the uid when login, and then search the "uid&userPassword".

rustdesk commented 1 year ago

do not use search the "uid&userPassword". use old bind way. Just as what said, just adding search bindn, the oher use old way. Always follow our "mimimum modification" principle, do not modify old logic.

fufesou commented 1 year ago

https://github.com/rustdesk/rustdesk-server-pro/issues/74#issuecomment-1719542044

The second problem.

I've added a merge_bind_base_dn method to handle this case. But I'm not very sure about it.

In the following code, the base dn and bind dn are first normalized to ensure the keywords match and the blank chars are removed.

    #[test]
    fn test_merge_bind_base_dn() {
        let normalized_base_dn = "";
        let normalized_bind_dn = "cn=aBc,ou=ou2";
        let merged_bind_dn = "cn=aBc,ou=ou2";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );

        let normalized_base_dn = "dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2,dc=example,dc=com";
        let merged_bind_dn = "cn=aBc,ou=ou2,dc=example,dc=com";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );

        let normalized_base_dn = "dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2";
        let merged_bind_dn = "cn=aBc,ou=ou2,dc=example,dc=com";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );

        let normalized_base_dn = "ou=ou1,dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2";
        let merged_bind_dn = "cn=aBc,ou=ou2,ou=ou1,dc=example,dc=com";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );

        let normalized_base_dn = "ou=ou1,dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2";
        let merged_bind_dn = "cn=aBc,ou=ou2,ou=ou1,dc=example,dc=com";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );

        let normalized_base_dn = "ou=ou1,dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2,dc=example,dc=com";
        let merged_bind_dn = "cn=aBc,ou=ou2,dc=example,dc=com";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );

        let normalized_base_dn = "ou=ou1,dc=ss,dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2,dc=example,dc=com";
        assert!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .is_err()
        );

        let normalized_base_dn = "ou=ou1,dc=example,dc=com";
        let normalized_bind_dn = "cn=aBc,ou=ou2,dc=ss,dc=example,dc=com";
        let merged_bind_dn = "cn=aBc,ou=ou2,dc=ss,dc=example,dc=com";
        assert_eq!(
            LdapConfig::merge_bind_base_dn(normalized_base_dn, normalized_bind_dn.to_string())
                .unwrap(),
            merged_bind_dn
        );
    }
rustdesk commented 1 year ago

1.1.10