Ylianst / MeshCentral

A complete web-based remote monitoring and management web site. Once setup you can install agents and perform remote desktop session to devices on the local network or over the Internet.
https://meshcentral.com
Apache License 2.0
3.99k stars 537 forks source link

AssertionError – Groups, memberships and OpenLDAP #5426

Open MasinAD opened 11 months ago

MasinAD commented 11 months ago

Describe the bug Setting values for ldapUserRequiredGroupMembership or ldapSiteAdminGroups makes logins fail. The log contains:

---- Log start at 10/13/2023, 12:48:37 PM ----
12:48:37 PM - ldap: LDAP Error: AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:

  assert.ok(len)

12:48:37 PM - ldap: LDAP Error: ConnectionError: 1__ldap://10.0.3.128:389/ closed

To Reproduce Oh, what do I know? :-D

Steps to reproduce the behavior:

  1. Login

Maybe the config.json helps more than those steps.

If I remove both settings attributes I can login.

Expected behavior

Server Software:

Additional context The necessary services for MySQL, OpenLDAP, MeshCentral and Nginx reverse proxy are in different containers in the same local subnet. This should not affect the login process.

I tried to pin down the cause of the problem and wrote a very simple copy of MeshCentral's LDAP login flow. This also errors out with:

root@meshcentral-my:/opt/test# node index.js
string(61) "The expression evaluated to a falsy value:

  assert.ok(len)
"
object AssertionError(5) {
    ["generatedMessage"] => boolean(true)
    ["code"] => string(13) "ERR_ASSERTION"
    ["actual"] => number(0)
    ["expected"] => boolean(true)
    ["operator"] => string(2) "=="
}

Not stopping at ldapauth-fork I went down the ldapjs rabbit hole:

node:assert:399
    throw err;
    ^

AssertionError [ERR_ASSERTION]: input string cannot be empty
    at Object.parse (/opt/test/node_modules/ldap-filter/lib/index.js:246:12)
    at Object.parseString (/opt/test/node_modules/ldapjs/lib/filters/index.js:179:27)
    at Client.search (/opt/test/node_modules/ldapjs/lib/client/client.js:571:30)
    at Object.<anonymous> (/opt/test/test_ldapjs.js:38:8)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47 {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '=='
}

But this style of JavaScript is way beyond my abilities. It looks like the same error and I can trigger it when providing an empty search filter to ldapjs.client.search, not just no search filter but an empty string. I cannot tell if that's the cause of the problem for any of the other two errors.

assert.ok(len) only appears in node_modules/asn1/lib/ber/writer.js but I cannot tell if that's important.

I tried console.log debugging in MeshCentral's webserver.js and found out that there are no group memberships stored in the ldap user object. ldapsearch -H ldap://10.0.3.128:389/ -D uid=system_ro,ou=Users,ou=System,dc=wikimedia,dc=de -W -b ou=People,dc=wikimedia,dc=de "(uid=maal)"does not return group memberships either but querying memberOf directly does: ldapsearch -H ldap://10.0.3.128:389/ -D uid=system_ro,ou=Users,ou=System,dc=wikimedia,dc=de -W -b ou=People,dc=wikimedia,dc=de "(uid=maal)" memberof.

Your config.json file

{
    "$schema": "https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json",
    "settings": {
        "meshErrorLogPath": "/var/log/meshcentral/",
        "log": "ldap",
        "debug": "ldap",
        "cert": "meshcentral.wikimedia.de",
        "mySQL": {
            "host": "10.0.3.58",
            "port": 3306,
            "database": "meshcentral",
            "user": "meshcentral",
            "password": "<sorryexcuseforapassword>"
        },
        "WANonly": true,
        "LANonly": false,
        "sessionTime": 1440,
        "sessionKey": "<strongsessionkey>",
        "sessionSameSite": "strict",
        "redirPort": 1080,
        "port": 1443,
        "agentPort": 19779,
        "relayPort": 19780,
        "redirAliasPort": 80,
        "aliasPort": 443,
        "agentAliasPort": 19779,
        "relayAliasPort": 19780,
        "agentPortTls": true,
        "agentLogDump": true,
        "agentCoreDump": true,
        "allowFraming": false,
        "webRTC": false,
        "selfUpdate": true,
        "amtManager": true,
        "orphanAgentUser": "admin",
        "allowHighQualityDesktop": true,
        "publicPushNotification": true,
        "desktopMultiplex": true,
        "authLog": "/var/log/meshcentral/auth.log",
        "tlsOffload": "10.0.3.30",
        "trustedProxy": "10.0.3.30",
        "mpsPort": 4433,
        "mpsAliasPort": 4433,
        "mpsTlsOffload": true,
        "mpsHighSecurity": true,
        "autoBackup": {
            "mysqlDumpPath": "/usr/bin/mysqldump"
        },
        "rootCertCommonName": "WMDE-CentralRoot-XXXXXX"
    },
    "domains": {
        "": {
            "title": "Wikimedia Deutschland",
            "title2": "MeshCentral",
            "newAccounts": 0,
            "auth": "ldap",
            "_ldapSaveUserToFile": "/tmp/meshcentral_ldap.txt",
            "ldapUserKey": "uid",
            "ldapUserBinaryKey": null,
            "ldapUserName": "uid",
            "ldapUserEmail": "mail",
            "ldapUserRealName": "cn",
            "ldapUserPhoneNumber": "telephoneNumber",
            "ldapUserImage": "jpegPhoto",
            "ldapUserGroups": "memberOf",
            "ldapSyncWithUserGroups": {
                "filter": [
                    "IT",
                    "Mitarbeiter"
                ]
            },
            "ldapUserRequiredGroupMembership": [
                "cn=Mitarbeiter,ou=Groups,dc=wikimedia,dc=de"
            ],
            "ldapSiteAdminGroups": [
                "cn=IT,ou=Groups,dc=wikimedia,dc=de"
            ],
            "ldapOptions": {
                "url": "ldap://10.0.3.128:389/",
                "bindDN": "uid=system_ro,ou=Users,ou=System,dc=wikimedia,dc=de",
                "bindCredentials": "<strongpassword>",
                "searchBase": "ou=People,dc=wikimedia,dc=de",
                "searchFilter": "(|(uid={{username}})(mail={{username}}))",
                "bindProperty": "dn",
                "groupSearchBase": "ou=Groups,dc=wikimedia,dc=de",
                "groupSearchFilter": "(member={{dn}})",
                "groupDnProperty": "{{dn}}"
            },
            "certUrl": "https://wiki.wikimedia.de"
        }
    }
}
MasinAD commented 11 months ago

I'd say the bug is in ldapjs but I'm no ldapjs user. Maybe we can escalate the problem upstream somehow.

si458 commented 11 months ago

Can u share the output of a user from ldap? I use phpldapadmin for this If I remember when I was testing ldap, I had to set the groups to a number not the whole ad string (if that makes sense?)

MasinAD commented 11 months ago

I haven't installed phpldapadmin. But I can use ldapsearch. If this isn't enough I can try either Apache Directory Studio or JXplorer.

Here the actual LDAP LDIF for my account with some redactions:

root@meshcentral-my:~# ldapsearch -H ldap://10.0.3.128:389/ -D uid=system_ro,ou=Users,ou=System,dc=wikimedia,dc=de -W -b ou=People,dc=wikimedia,dc=de "(uid=maal)"
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <ou=People,dc=wikimedia,dc=de> with scope subtree
# filter: (uid=maal)
# requesting: ALL
#

# maal, People, wikimedia.de
dn: uid=maal,ou=People,dc=wikimedia,dc=de
objectClass: inetOrgPerson
uid: maal
displayName: Masin Al-Dujaili
givenName:: TWFzaW4g
sn: Al-Dujaili
jpegPhoto:: <snip>
mail: <valid e-mail address>
userPassword:: <redacted>
cn: Masin Al-Dujaili

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

And TIL operational attributes require this (observe the + sign at the end of the command):

root@meshcentral-my:~# ldapsearch -H ldap://10.0.3.128:389/ -D uid=system_ro,ou=Users,ou=System,dc=wikimedia,dc=de -W -b ou=People,dc=wikimedia,dc=de "(uid=maal)" +
Enter LDAP Password: 
# extended LDIF
#
# LDAPv3
# base <ou=People,dc=wikimedia,dc=de> with scope subtree
# filter: (uid=maal)
# requesting: + 
#

# maal, People, wikimedia.de
dn: uid=maal,ou=People,dc=wikimedia,dc=de
structuralObjectClass: inetOrgPerson
entryUUID: 0657407a-ee26-103c-857c-f79dcc79eb63
creatorsName: cn=admin,dc=wikimedia,dc=de
createTimestamp: 20221101114236Z
memberOf: cn=administrators,ou=groups,dc=wikimedia,dc=de
memberOf: cn=domain users,ou=groups,dc=wikimedia,dc=de
memberOf: cn=it,ou=groups,dc=wikimedia,dc=de
memberOf: cn=mitarbeiter,ou=groups,dc=wikimedia,dc=de
memberOf: cn=gswiki-bureaucrat,ou=groups,dc=wikimedia,dc=de
memberOf: cn=gswiki-sysop,ou=groups,dc=wikimedia,dc=de
memberOf: cn=civicrm-importer,ou=Groups,dc=wikimedia,dc=de
entryCSN: 20230906115337.308671Z#000000#001#000000
modifyTimestamp: 20230906115337Z
modifiersName: cn=admin,dc=wikimedia,dc=de
entryDN: uid=maal,ou=People,dc=wikimedia,dc=de
subschemaSubentry: cn=Subschema
hasSubordinates: FALSE

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
MasinAD commented 11 months ago

I have to correct myself: I can have the settings in there without the error but I can only login with ldapSiteAdminGroups set though I don't know if it has any effect. With ldapUserRequiredGroupMembership set I get "Access denied" in the login form. But in both cases that's only if I have not set

            "ldapoptions": {
                …
                "groupSearchBase": "ou=Groups,dc=wikimedia,dc=de",
                "groupSearchFilter": "(member={{dn}})",
                "groupDnProperty": "{{dn}}"
            }

So, there's some link to group search filters.

MasinAD commented 11 months ago

image Well, I guess my admin rights got removed :-D, so it does not have any effect.