dnknth / ldap-ui

Minimalistic web frontend for OpenLDAP
MIT License
350 stars 27 forks source link

Internal Server Error #56

Closed abn0mad closed 5 months ago

abn0mad commented 5 months ago

Hi there,

First off, many thanks for your app, a much needed alternative to phpldapadmin etc.

I'm having a bit of trouble with the app...

I have it running in a (rootless) podman container, behind an nginx proxy that provides TLS. I can log in fine and it does display the basic tree, but right of the bat I get the "Internal Server Error" message, which repeats if I click on any entry.

It is connected to an OpenLDAP instance (ldaps://idm.example.com:636)

The console error (edited):

[2024-04-11 08:13:16,437] ERROR in app: Exception on request GET /api/entry/uid=idm_admin,ou=people,dc=example,dc=com
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1396, in handle_request
    return await self.full_dispatch_request(request_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1434, in full_dispatch_request
    result = await self.handle_user_exception(error)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1029, in handle_user_exception
    raise error
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1432, in full_dispatch_request
    result = await self.dispatch_request(request_context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1526, in dispatch_request
    return await self.ensure_async(handler)(**request_.view_args)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 106, in wrapped_view
    resp = await view(**values)
           ^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 120, in wrapped_view
    data = await connected(authenticated(view))(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 46, in wrapped_view
    data = await view(**values)
           ^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 77, in wrapped_view
    return await view(**values)
           ^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 268, in entry
    return _entry(await unique(request.ldap.search(dn, ldap.SCOPE_BASE)))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 206, in _entry
    must_attrs, _may_attrs = app.schema.attribute_types(ocs)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^

AttributeError: 'NoneType' object has no attribute 'attribute_types'

Could you perhaps shed some light on what I should investigate or do next?

Do I need to change settings.py perhaps or update the ldap schema somehow?

I have the LDAP server connected to various apps such as SFTPGo, Forgejo / Gitea, as well as Authelia where it authenticates a planning app called Planka. All of that works fine...

dnknth commented 5 months ago

Hello @abn0mad,

app.schema appears to be None. As this is loaded when the client requests api/schema from the backend, I would assume that the root cause shows up earlier in the log. Could you please check for other exceptions and share the first one if present?

abn0mad commented 5 months ago

Thanks for the reply @dnknth - much appreciated :)

Indeed the ldap-ui instance cannot access cn=schema. It can only be accessed using the internal ldapi://// ... on the container running the openldap instance. The log indeed immediately shows that 'cn=schema' doesn't exist (although it does, it's just not accessible through DN bind).

I'm by no means a seasoned ldap expert, but from what I can find out on the internet; everyone seems to say the same: don't allow DN bind access to cn=config,cn=schema for security reasons. Is it really necessary for ldap-ui to have that level of access? Isn't the ability to read groups, ou, people etc enough for basic day to day tasks?

If there is no way to configure ldap-ui to operate in such a way at present, might it be an option to add different operational modes to ldap-ui - i.e. 'basic' and 'full' ?

Or should I perhaps look into how I should alter my ACL to allow read-only access...? I did see in settings.py that there is a lookup of cn=subschema, which on my deployment does not seem to exist...

As for the initial exception upon starting ldap-ui and attempting to access it via a browser:

[2024-04-14 05:24:46,737] ERROR in app: Exception on request GET /api/schema
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1396, in handle_request
    return await self.full_dispatch_request(request_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1434, in full_dispatch_request
    result = await self.handle_user_exception(error)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1029, in handle_user_exception
    raise error
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1432, in full_dispatch_request
    result = await self.dispatch_request(request_context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/quart/app.py", line 1526, in dispatch_request
    return await self.ensure_async(handler)(**request_.view_args)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 106, in wrapped_view
    resp = await view(**values)
           ^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 120, in wrapped_view
    data = await connected(authenticated(view))(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 46, in wrapped_view
    data = await view(**values)
           ^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 77, in wrapped_view
    return await view(**values)
           ^^^^^^^^^^^^^^^^^^^^
  File "/app/app.py", line 560, in schema
    _dn, subschema_entry = await unique(
                           ^^^^^^^^^^^^^
  File "/app/app.py", line 146, in unique
    raise ValueError("Expected unique result")
ValueError: Expected unique result
dnknth commented 5 months ago

Hello @abn0mad,

There is no way to use ldap-ui without read-only access to the schema. The software is basically an LDAP editor, and everything it knows about the structure of individual entries in the tree is derived from schema information. I am a bit surprised that even the tree was shown. What's clearly missing is an error message when schema retrieval fails.

I cannot advise on the security aspects of read-only bind access to the schema, but until now people have run into all sorts of pitfalls except this one. At least on Debian (my production box) and Alpine (Docker images) schema read access appears to be enabled by default. Is ldaps://idm.example.com running just OpenLdap or some customised / hardened setup?

abn0mad commented 5 months ago

@dnknth - thank you for the prompt reply, again much appreciated.

It is a custom built image based on almalinux running in rootless podman, originally based on the following two guides:

https://tylersguides.com/guides/install-openldap-from-source-on-centos-8/ https://tylersguides.com/guides/configuring-openldap-for-linux-authentication/

So far I've only been using it to get ldap authentication working for a few apps on a vps. There's only one person in the database (me), so I could start over if need be in order to get it to work with ldap-ui.

The deployment is based on this:

dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /opt/openldap/var/run/slapd.args
olcPidFile: /opt/openldap/var/run/slapd.pid
olcTLSCACertificateFile: /etc/ssl/ca.pem
olcTLSCertificateFile: /etc/ssl/fullchain.pem
olcTLSCertificateKeyFile: /etc/ssl/privkey.pem
olcTLSCipherSuite: TLSv1.2:HIGH:!aNULL:!eNULL
olcTLSProtocolMin: 3.3

dn: cn=schema,cn=config
objectClass: olcSchemaConfig
cn: schema

dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulepath: /opt/openldap/libexec/openldap
olcModuleload: back_mdb.la
olcModuleload: argon2.la

include: file:///opt/openldap/etc/openldap/schema/core.ldif
include: file:///opt/openldap/etc/openldap/schema/cosine.ldif
include: file:///opt/openldap/etc/openldap/schema/nis.ldif
include: file:///opt/openldap/etc/openldap/schema/inetorgperson.ldif
include: file:///opt/openldap/etc/openldap/schema/dyngroup.ldif
#include: file:///opt/openldap/etc/openldap/schema/ppolicy.ldif
#include: file:///opt/openldap/etc/openldap/schema/openssh-lpk.ldif

dn: olcDatabase=frontend,cn=config
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: frontend
olcPasswordHash: {ARGON2}
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by * none

dn: olcDatabase=config,cn=config
objectClass: olcDatabaseConfig
olcDatabase: config
olcRootDN: cn=config
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by * none

rootDN:

dn: olcDatabase=mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: mdb
olcSuffix: dc=example,dc=com
olcRootDN: cn=admin,dc=example,dc=com
olcRootPW: REPLACE ME WITH $( slappaswd -o module-load=argon2 -h {ARGON2} )
olcDbDirectory: /var/lib/ldap
olcDbIndex: objectClass,uid,uidNumber,gidNumber eq
olcDbMaxSize: 107374182400
olcAccess: to attrs=userPassword
  by self write
  by anonymous auth
  by dn.subtree="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
  by * none
olcAccess: to attrs=shadowLastChange by self write
  by dn.subtree="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
  by dn.subtree="ou=system,dc=example,dc=com" read
  by * none
olcAccess: to dn.subtree="ou=system,dc=example,dc=com" by dn.subtree="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
  by * none
olcAccess: to dn.subtree="dc=example,dc=com" by dn.subtree="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
  by users read 
  by * none

I have hooked up ldap-ui to it using cn=admin,dc=example,dc=com - which does display the basic tree but won't display any details, along with giving an error message right off the bat. I used the admin login as I thought that would be necessary to make any changes to the entries in the database. I do have cn=readonly,ou=system,dc=example,dc=com as well, but from my limited undertanding of ldap, this wouldn't be able to access cn=config,cn=schema any more than cn=admin would in this particular setup ..? (correct me if I'm wrong, you clearly are far more adept at this than I am).

As for error messages; in my previous post I pasted the first error that ldap-ui generates, all other errors are like the one in my first post.

So I guess I should look up how to make cn=schema available read-only to cn=admin,dc=example,dc=com ..?

dnknth commented 5 months ago

Hello @abn0mad,

The ACL of olcDatabase=frontend,cn=config ends with by * none which prevents read access to the schema by anyone except the local root user via the ldapi:// protocol. The default Debian configuration explicitly allows it by adding

to dn.base="cn=Subschema" by * read

If you prefer to restrict access to a specific user, replace by * read with that user's DN like in by dn.exact="cn=admin,dc=example,dc=com" read by * none, if only authenticated user should be allowed read access, then this is by users read by * none etc.

I wonder where you may have seen advice that exposing the schema read-only may have security implications? After all it contains structural information that is already widely published, and having that information should gain an adversary nothing if the ACLs prevent access to sensitive information.

abn0mad commented 5 months ago

@dnknth - again, many thanks for the thorough and helpful reply. :)

(Deeply embarrassed) You're absolutely right. And changing the ACL as you specified did the job. I can now use ldap-ui as intended.

I believe I read it in some old Red Hat and / or IBM posts when I first got started with OpenLDAP. I can't quite remember where: quick, annoyed browsing, skimming text… (More embarrassment…)

Thank you very much for the help, and I do apologise for wasting your time, although it was educational. My hat is off to you, good sir.

dnknth commented 5 months ago

Hello @abn0mad,

don't worry, OpenLdap is notorious for complexity. Glad that it works now for you as yours truly intended.

dnknth commented 5 months ago

By the way, there should have been a big red "Unknown error" message on page load. Did you see that?

abn0mad commented 5 months ago

Only if I clicked on Admin, for everything else including first load it was a big yellow / orange-ish bar saying internal server error...