inverse-inc / packetfence

PacketFence is a fully supported, trusted, Free and Open Source network access control (NAC) solution. Boasting an impressive feature set including a captive-portal for registration and remediation, centralized wired and wireless management, powerful BYOD management options, 802.1X support, layer-2 isolation of problematic devices; PacketFence can be used to effectively secure networks small to very large heterogeneous networks.
https://packetfence.org
GNU General Public License v2.0
1.38k stars 289 forks source link

LDAP Conditions not working for LDAP authentication Source #8088

Closed lqdmist closed 1 week ago

lqdmist commented 6 months ago

Describe the bug I have two authentication sources. One is LDAP and the other is Active Directory. They are both configured the same:

I can successfully authenticate only using the LDAP auth source. The AD one does not seem to work for authentication for some reason. Whilst the AD auth source is able to setup LDAP conditions, the LDAP source does not. When searching for a LDAP attribute using LDAP auth source, nothing shows up.

Steps to reproduce the behavior:

  1. Create a new authentication source using the type LDAP.

  2. Setup LDAP and make sure to test the authentication.

  3. Add a new authentication rule and try to provide a LDAP condition.

  4. It does not provide a list of attributes: ldap

  5. However, in the Active Directory auth source, the LDAP condition attributes are shown like they should: AD

Expected behavior Being able to use LDAP conditions using the LDAP authentication source.

Tested on

lqdmist commented 6 months ago

No one?

DeGrootePasja commented 6 months ago

Hi, I ran into the same issue. Is this project still maintained since there is no response for over 3 weeks on this issue ?????????? @louismunro @candlerb @ashang

lqdmist commented 6 months ago

I am doubting anyone still works on this project. I have send them a mail in the past (over 3 months ago) asking for support but still have not heard back from them..

candlerb commented 6 months ago

I don't know why you pinged me. I haven't looked at packetfence for many years and I don't know anyone who still uses it; wireless and 802.1x is the primary access method in most places these days.

Inverse Inc provide (or used to provide) paid support; if you have a budget for it, you could try that route.

lqdmist commented 6 months ago

I have asked for paid support but never received a response. @fdurand @extrafu @jrouzierinverse

rexfordnyrk commented 2 months ago

I have faced the same problem in 13.1 and 13.2. on Zen, Debian and RHEL 8 the option are available for authentication rules for AD and even Google LDAP however it is missing in LDAP. I have had discussions via mail and no one to help atm.

I just read 14 is out so I am going to try setting that up within the week and see if it's fixed. I will report back here.

lqdmist commented 2 months ago

Let's hope this issue has been fixed in 14. I will try to test it out as soon as time is on our hands

rexfordnyrk commented 2 months ago

Unfortunately, this is not fixed. I just installed and tried it. It authenticates fine but LDAP auth rules cannot be set using LDAP attributes.

Screenshot at 2024-09-11 19-11-20

I needed this for role/vlan assignment using the memberOf attribute. but now looks like I may have to set up different SSIDs for different departments. This introduces the problem of no connection to staff when they move to other parts of the building where their dept SSID ain't visible or is weak. The next option will be to have one VLAN and SSID and make it general staff, which also comes with other issues.

rexfordnyrk commented 2 months ago

The problem seems to start in v13.0 after the conditions were separated to "Add PacketFence Conditions" and "Add LDAP Conditions". I went back to install and test v11.2 and v12.2. Both have this working fine with all the attributes showing as seen in the image below.

Screenshot at 2024-09-11 21-06-33

fdurand commented 2 months ago

Can you verify with the inspect mode in chrome and in the network tab what is the error message returned by the search ?

Something like that:

image

rexfordnyrk commented 2 months ago

Nope, I get a valid 200 and no errors from network requests or whatsoever. In the preview I only see my domain.

dc=domain,dc=com,dc=gh: {}

Screenshot at 2024-09-12 17-01-45

@fdurand do let me know if you need anything else to aid in troubleshooting it.

fdurand commented 2 months ago

OK so if it's empty at least it's able to connect. Can you try first by disabling "Use pfconnector" and see if it helps. Also do you have any differences when you use memberOf is cn=admini.... instead of memberOf is member of cn=admini... ?

rexfordnyrk commented 2 months ago

So I have tried both with "Use pfconnector" enabled and disabled. I have same results, nothing.

Again nothing shows up at all when I start typing. IT just says No results.

IS there any other config or service if PF that could be affecting this?

fdurand commented 2 months ago

But when you change from "is member of" to is , does it change something ?

rexfordnyrk commented 2 months ago

No attribute shows up at all as it does in a list for google ldap

https://github.com/user-attachments/assets/4af4f298-9dbc-415b-a95e-bf1a41f53c94

fdurand commented 2 months ago

Ok so can you verify again with the inspect mode what are the error message returned by all the search requests ?

rexfordnyrk commented 2 months ago

That's the thing, I monitored while making changes. No errors.

fdurand commented 2 months ago

Ok so let see the request then:

image

and show me the search_query

rexfordnyrk commented 2 months ago

I figured the screenshot will not capture everything so I copied the request payload source.

{
  "server": {
    "administration_rules": [],
    "append_to_searchattributes": "",
    "authentication_rules": [
      {
        "actions": [
          {
            "type": "set_role",
            "value": "default"
          },
          {
            "type": "set_access_duration",
            "value": "12h"
          }
        ],
        "conditions": [],
        "description": "All users if not matched with another rule",
        "id": "catchall",
        "match": "all",
        "status": "enabled"
      },
      {
        "actions": [],
        "conditions": [
          {
            "attribute": null,
            "operator": null,
            "value": null,
            "type": "ldap"
          }
        ],
        "description": "a test ",
        "id": "hey",
        "match": "all",
        "status": "enabled"
      }
    ],
    "basedn": "dc=domain,dc=com,dc=gh",
    "binddn": "uid=uaccount,ou=people,dc=domain,dc=com,dc=gh",
    "ca_file": "",
    "ca_file_upload": null,
    "cache_match": "0",
    "class": "internal",
    "client_cert_file": "",
    "client_cert_file_upload": null,
    "client_key_file": "",
    "client_key_file_upload": null,
    "connection_timeout": "2",
    "dead_duration": "60",
    "description": "Authentication sources using LDAP for domain.com.gh",
    "email_attribute": "mail",
    "encryption": "ssl",
    "host": [
      "mail.domain.com.gh"
    ],
    "id": "DOMAIN_LDAP",
    "ldapfilter_operator": null,
    "monitor": "1",
    "not_deletable": false,
    "not_sortable": false,
    "password": "somepassword",
    "port": "636",
    "read_timeout": "10",
    "realms": [
      "default",
      "local",
      "null"that 
    ],
    "scope": "sub",
    "searchattributes": [],
    "set_access_durations_action": [
      ""
    ],
    "set_role_from_source_action": null,
    "shuffle": "0",
    "trigger_portal_mfa_action": null,
    "trigger_radius_mfa_action": null,
    "type": "LDAP",
    "use_connector": "1",
    "usernameattribute": "mail",
    "verify": "none",
    "write_timeout": "5",
    "allowed_domains": [],
    "banned_domains": []
  },
  "search_query": {
    "filter": null,
    "scope": "base",
    "attributes": [
      "subSchemaSubEntry"
    ],
    "base_dn": "dc=domain,dc=com,dc=gh",
    "size_limit": 1000
  }
}

A Few observations I have made.

  1. Network requests for search does not happen when typing the attribute name. You only see the search request when you change an option on the page.

  2. The no matter what is typed, once you click outside the box, everything is erased or removed from the box.

  3. Others like AD and Google LDAP sources show the available attributes to add authentication rules even when you have not configured a valid connection.

  4. It seems that this problem only started when the "add conditions" were split for LDAP. The AD Source version shows up fine with the "memberOf:1.2.840.113556.1.4.1941" option right when you click the arrow or box. The Google Workspaces Source doesn't have two choices but has all the attributes for LDAP and PF. Meanwhile, the code suggests Google LDAP source extends the LDAP Source package. (Just from observations of the .pm files for the auth sources, I don't write in that language) I think the problem lies in the attributes list, the predefined attributes and operators do not show in the list for LDAP.

Is it possible to modify and test those files? I'm thinking mainly to try and revert to what existed in version 12.2 LDAP Auth Source. since it looks to me that some addition after then introduced this problem. However, I do not know what other aspects of the system the auth sources are dependent on and where some changes might have also been made to affect it. But if everything is in the Auth sources .pm files. I'd like to play with them and see if I can help with that testing.

fdurand commented 2 months ago

So what is not working correctly is that request:

  "search_query": {
    "filter": null,
    "scope": "base",
    "attributes": [
      "subSchemaSubEntry"
    ],
    "base_dn": "dc=domain,dc=com,dc=gh",
    "size_limit": 1000
  }

It suppose to return the ldap attributes of the LDAP server. Do you know on your ldap server what is the request to retrieve all attributes ?

rexfordnyrk commented 2 months ago

Hello Durand, Unfortunately I'm not doing not know. However if it helps. I am using an Open LDAP from a Zimbra installation. I use that for a number of services but I haven't had to manually query or do this. So I can't tell.

rexfordnyrk commented 1 month ago

Hello @fdurand Does the above change anything?

Also, I am thinking about using v12.2 for my setup considering I do see the Attributes show up for authentication roles on the LDAP sources page.

I am going to test it and see if it works as expected and report back.

E-ThanG commented 1 month ago

I'm also having the same issue with all of our LDAP servers in PF 13.2.0. It works just fine with AD.

Today I learned that the type of LDAP server makes a big difference. With the Red Hat directory server LDAP, the schema can be downloaded by ldapsearch -x -D "YourLogin" -W -H ldaps://your-ldap-server.tld -b 'cn=schema' + Note the '+' and that the search base isn't null. Is there a PF internal file that I can edit to modify that query?

That doesn't work with MS-LDAP (AD) though, I'm still working with other teams to figure that one out.

As a work-around, you can make the configuration in an AD source, edit conf/authentication.conf, copy the condition/rule to your LDAP source, reload the configs and the server.

wcooley commented 1 month ago

So what is not working correctly is that request:


  "search_query": {
    "filter": null,
    "scope": "base",
    "attributes": [
      "subSchemaSubEntry"
    ],
    "base_dn": "dc=domain,dc=com,dc=gh",

^^^ This is the problem ^^^

The Root DSE should be the an empty base DN (with 'base' scope and null filter), to search for subSchemaSubEntry, which then tells you where to find the schema itself. It's a two step process.

"size_limit": 1000

}



It suppose to return the ldap attributes of the LDAP server. Do you know on your ldap server what is the request to retrieve all attributes ?

On the LDAP server that @E-ThanG is working on, the process (using the ldapsearch command) would look like this:

$ ldapsearch -s base  -b ''  '(objectclass=*)' subSchemaSubEntry
dn: cn=schema
subSchemaSubEntry: cn=schema

$ ldapsearch -s sub  -b 'cn=schema' '(objectclass=*)' \+
dn: cn=schema
modifiersName: cn=Directory Manager
modifyTimestamp: 20240730182603Z
objectClasses: ( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass X-ORIGIN 'RFC 45
 12' )
objectClasses: ( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectNam
 e X-ORIGIN 'RFC 4512' )
objectClasses: ( 2.5.20.1 NAME 'subschema' AUXILIARY MAY ( dITStructureRules $
  nameForms $ dITContentRules $ objectClasses $ attributeTypes $ matchingRules
  $ matchingRuleUse ) X-ORIGIN 'RFC 4512' )
...

With ActiveDirectory, it includes the subSchemaSubEntry with every entry, so you can do it with any supported base and null filter. Fortunately, AD does provide a Root DSE, so if you do the first search with that, it should work.

E-ThanG commented 1 month ago

Update for MS-LDAP and Red Hat directory server LDAP:

It looks to me that the LDAP source is performing the lookup for subSchemaSubEntry. The base_dn should be empty there, but it works for MS-LDAP and is able to obtain the info it needs, (CN=Aggregate,CN=Schema,CN=Configuration,... for MS-LDAP) It doesn't use that as the base_dn for the next request, that's a problem. Also, the scope of the second request should be sub, not base. RH-LDAP seems to give results with either scope, but MS-LDAP requires sub scope. I'm setting size to 1000 to match the existing, Setting size to 1 works as well..

RH-LDAP won't give the info that we need for the first query with something in the base_dn.

For MS-LDAP ldapsearch ... -z 1 -s base -b '' '(objectClass=*)' subSchemaSubEntry gives "CN=Aggregate,CN=Schema,CN=Configuration,..."

ldapsearch ... -z 1000 -s sub -b 'CN=Aggregate,CN=Schema,CN=Configuration,...' '(objectClass=subSchema)' attributeTypes gives the attributeTypes

For RH LDAP: ldapsearch ... -z 1 -s base -b '' '(objectClass=*)' subSchemaSubEntry gives "subSchemaSubEntry: cn=schema"

ldapsearch ... -z 1000 -s sub -b 'cn=schema.' '(objectClass=subSchema)' attributeTypes gives the attributeTypes

The AD source does the first lookup with base_dn empty and uses the new base_dn for the second request. That works fine. It also leaves the scope at base. I guess that's OK for AD, just not MS-LDAP?

And a question, it always performs the initial lookup twice, is there a reason for that?

E-ThanG commented 1 month ago

This function from useOpenLdap.js returns an empty string for our LDAP servers. The case of S and E in subSchemaSubEntry is inconsistent within the function. It may work on some LDAP servers though. The LDAP lookup itself is case insensitive, but the extraction from the attribute array is case sensitive. I'm told that there is no guarantee that the case of the attribute will be consistent between different server implementations or administrators. This should probably be rewritten to be case insensitive, but at the very least should use consistent casing within the function:

  const getSubSchemaDN = () => {
    return sendLdapSearchRequest({...form.value}, null, 'base', ['subSchemaSubEntry'], form.value.basedn)
      .then((response) => {
        let firstAttribute = response[Object.keys(response)[0]]
        return firstAttribute['subschemaSubentry']
      })
  }

These changes work for me in AD, MS-LDAP and RH-LDAP. I fixed the case of subSchemaSubEntry and made a few small tweaks as mentioned above. I haven't altered the case sensitivity of the attribute.

diff --git a/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useAdLdap.js b/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useAdLdap.js
index 4bf2e7e..28004cc 100644
--- a/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useAdLdap.js
+++ b/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useAdLdap.js
@@ -12,7 +12,7 @@ function useAdLdap(form) {

   const performSearch = (filter, scope, attributes, base_dn) => {
     let server = { ...form.value }
-    return sendLdapSearchRequest(server, filter, scope, attributes, base_dn)
+    return sendLdapSearchRequest(server, filter, scope, attributes, base_dn, 1000)
       .then((result) => {
           if (_.isEmpty(result)) {
             return isAttributeDn(server, filter, scope, attributes, base_dn).then((isDn) => {
@@ -36,7 +36,7 @@ function useAdLdap(form) {
   }

   const getSubSchemaDN = () => {
-    return sendLdapSearchRequest({...form.value}, null, 'base', ['subschemaSubentry'], '')
+    return sendLdapSearchRequest({...form.value}, null, 'base', ['subschemaSubentry'], '', 1)
       .then((result) => {
         let firstAttribute = result[Object.keys(result)[0]]
         return firstAttribute['subSchemaSubEntry']
@@ -45,9 +45,10 @@ function useAdLdap(form) {

   const fetchAttributeTypes = (subSchemaDN) => {
     return sendLdapSearchRequest({...form.value}, '(objectclass=subschema)',
-      'base',
+      'sub',
       ['attributetypes'],
-      subSchemaDN)
+      subSchemaDN,
+      1000)
       .then((response) => {
         const keys = Object.keys(response)
         if (keys.length) {
diff --git a/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useOpenLdap.js b/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useOpenLdap.js
index faae791..b2490c1 100644
--- a/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useOpenLdap.js
+++ b/html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useOpenLdap.js
@@ -10,7 +10,7 @@ import {
 function useOpenLdap(form) {

   const performSearch = (filter, scope, attributes, base_dn) => {
-    return sendLdapSearchRequest({...form.value}, filter, scope, attributes, base_dn)
+    return sendLdapSearchRequest({...form.value}, filter, scope, attributes, base_dn, 1000)
       .then((result) => {
           return {results: parseLdapResponseToAttributeArray(result, extractAttributeFromFilter(filter)), success: true}
         }
@@ -18,18 +18,19 @@ function useOpenLdap(form) {
   }

   const getSubSchemaDN = () => {
-    return sendLdapSearchRequest({...form.value}, null, 'base', ['subSchemaSubEntry'], form.value.basedn)
+    return sendLdapSearchRequest({...form.value}, null, 'base', ['subSchemaSubEntry'], '', 1)
       .then((response) => {
         let firstAttribute = response[Object.keys(response)[0]]
-        return firstAttribute['subschemaSubentry']
+        return firstAttribute['subSchemaSubEntry']
       })
   }

   const fetchAttributeTypes = (subSchemaDN) => {
     return sendLdapSearchRequest({...form.value}, '(objectclass=subschema)',
-      'base',
+      'sub',
       ['attributeTypes'],
-      subSchemaDN)
+      subSchemaDN,
+      1000)
       .then((response) => {
         const keys = Object.keys(response)
         if (keys.length) {
rexfordnyrk commented 4 weeks ago

hello @E-ThanG , Thank you for the work done. I have tried your solution and modified the said files at the respective lines to match what you have. However, this still does not work for me. Authentication works just fine for me but I cant set auth rules for LDAP.

I don't know if this changes anything but I am using an OpenLDAP installation that comes along with Zimbra 8.15.x on a RHEL8 server. I am no expert with LDAP. I wanted to know if there is something else I need to do to get this to work. my aim is simply to use the "memberOf" attribute to determine roles per department. Thank you in advance.

E-ThanG commented 3 weeks ago

Did you rebuild the docker container once you made the code change? /usr/local/pf/addons/dev-helpers/build-local-container.sh httpd.admin_dispatcher Then restart that service.

You can also try confirming with ldapsearch that your schema is in subSchemaSubEntry

I should add that I've only had success with populating the attributes, not the values of them. So I have a drop down that contains memberOf, but I can't select "is" and get a list of group choices. If you use "contains" you can type the value you expect. For my use case I am obtaining roles from eduPersonPrimaryAffiliation, which only has a few options that are all short text strings

rexfordnyrk commented 3 weeks ago

Oh okay thanks for the pointers. I just went through the documentation and learned I've got to use the command below to rebuild the service of choose and then restart the service.

/usr/local/pf/addons/dev-helpers/build-local-container.sh <service>

Just to be sure... I'm doing this for the admin portal service right?

I'll try this as soon as I get behind my machine.

E-ThanG commented 3 weeks ago

I updated my comment to include that just before you replied :D It's "httpd.admin_dispatcher". It's been a few weeks since I last did this, I'm making changes to another VM now to confirm.

E-ThanG commented 3 weeks ago

Glad I did, it failed to compile. You have to have the docs folder. Copy that from the relevant github release

rexfordnyrk commented 3 weeks ago

Okay. When you say I have to have the docs folder what do you mean. Do you mean I must use the instructions in the docs of the specific release? Eg 13.2.0 In this case. Sorry I'm new to modifying PF's code.

E-ThanG commented 3 weeks ago

I am as well, it's been quite a learning process.

There are probably better ways, like "git clone". I downloaded 14.0.0 zip from GitHub on my Windows computer, extract that and copy the entire docs directory into the PacketFence server in /usr/local/pf/. I used WinSCP to transfer the files.

rexfordnyrk commented 3 weeks ago

Oh okay thank you. I use Linux. So scp command should be fine for me. If I get you right, in your experience so far, without this docs directory in /usr/local/pf/ Running the command to rebuild will not succeed?

E-ThanG commented 3 weeks ago

Yes, it won't be able to complete the docker. I think they need the docs folder because you can view the documentation on the PF server help page, they need to include it for that process.

I rebuilt it and the changes didn't seem to have applied after restarting the service. Stopping and starting it seems to have worked though. I'm not sure why that would be.


root@centurion:/usr/local/pf# /usr/local/pf/addons/dev-helpers/build-local-container.sh httpd.admin_dispatcher
=================================================================================
Building image packetfence/httpd.admin_dispatcher:maintenance-14-0
---------------------------------------------------------------------------------
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            Install the buildx component to build images with BuildKit:
            https://docs.docker.com/go/buildx/

ERRO[0008] Can't add file /usr/local/pf/var/run/api-frontend-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/collectd-unixsock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/haproxy-admin-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/haproxy-admin.stats to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/haproxy-portal-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/httpd.aaa-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/httpd.admin_dispatcher-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/httpd.dispatcher-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/httpd.portal-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/httpd.webservices-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/ntlm-auth-api-OITAD-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfacct-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfconfig-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfcron-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pffilter.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfldapexplorer-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfperl-api-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfpki-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfqueue-backend.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/pfsso-systemd-notify.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/radiusd.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/redis_cache.sock to tar: archive/tar: sockets not supported
ERRO[0008] Can't add file /usr/local/pf/var/run/redis_queue.sock to tar: archive/tar: sockets not supported
Sending build context to Docker daemon  837.2MB
Step 1/28 : ARG KNK_REGISTRY_URL
Step 2/28 : ARG IMAGE_TAG
Step 3/28 : FROM ${KNK_REGISTRY_URL}/pfbuild-debian-bookworm:${IMAGE_TAG}
maintenance-14-0: Pulling from inverse-inc/packetfence/pfbuild-debian-bookworm
90e5e7d8b87a: Pull complete
c44988c0b21b: Pull complete
5039f65d71d6: Pull complete
792c730b072e: Pull complete
482ca41033c6: Pull complete
Digest: sha256:55959e537fba54c7815c1bbdf386938ac9038f03acbb8b48d0957ffbdb120a9d
Status: Downloaded newer image for ghcr.io/inverse-inc/packetfence/pfbuild-debian-bookworm:maintenance-14-0
 ---> c9df3f0aa339
Step 4/28 : SHELL ["/bin/bash", "-c"]
 ---> Running in 28ed2ab056ae
 ---> Removed intermediate container 28ed2ab056ae
 ---> 9b1ca6e3bd0c
Step 5/28 : RUN mkdir -p /usr/local/pf/ /html
 ---> Running in 1aaf3687271b
 ---> Removed intermediate container 1aaf3687271b
 ---> 33a4675ea6b9
Step 6/28 : WORKDIR /usr/local/pf/
 ---> Running in b888b1e9cdd6
 ---> Removed intermediate container b888b1e9cdd6
 ---> c8725e46fb8f
Step 7/28 : COPY go/go.mod /usr/local/pf/go/
 ---> 89097ca24aac
Step 8/28 : COPY go/go.sum /usr/local/pf/go/
 ---> 59629d945de6
Step 9/28 : RUN cd /usr/local/pf/go/ && go mod download
 ---> Running in d164947a2e05
 ---> Removed intermediate container d164947a2e05
 ---> ed2c61ddf712
Step 10/28 : COPY ./go /usr/local/pf/go
 ---> 9c97ed94bd24
Step 11/28 : COPY ./lib /usr/local/pf/lib
 ---> e48faa3a91a2
Step 12/28 : COPY ./html/swagger-ui /usr/local/pf/html/swagger-ui
 ---> cf41b3eec7a7
Step 13/28 : COPY ./html /html
 ---> c0161968d128
Step 14/28 : COPY ./docs /usr/local/pf/docs
COPY failed: file not found in build context or excluded by .dockerignore: stat docs: file does not exist
rexfordnyrk commented 3 weeks ago

Thank you a lot. I'll get on this in 30 mins time and get back.

rexfordnyrk commented 3 weeks ago

Hello @E-ThanG it's been quite the learning curve for the most part of the night.

  1. Yes I was able to build the container successfully. It turns out you need the src and the html/swagger-ui directories too. Once I placed those in, It built successfully.

  2. I did the test and noticed the ldap request goes through alright however the response seems to break the request. with Go API complaining of not being able to unmarshall the attributes. which (with my knowledge in Golang) tells me my LDAP server is not providing the needed info. { "message": "json: cannot unmarshal string into Go struct field SearchQuery.search_query.attributes of type []string" }

  3. I did some check using the ldapsearch queries you have up there and got the following results

    
    ldapsearch -x -D "uid=user,ou=people,dc=domain,dc=com,dc=gh" -W -H ldaps://domain.com.gh -b '' -s base '(objectclass=*)' subSchemaSubEntry
    Enter LDAP Password: 

extended LDIF

#

LDAPv3

base <> with scope baseObject

filter: (objectclass=*)

requesting: subSchemaSubEntry

#

# dn: subschemaSubentry: cn=Subschema

search result

search: 2 result: 0 Success

numResponses: 2

numEntries: 1


4. However after the above, when I run `ldapsearch ...   -z 1000 -s sub -b 'cn=Subschema.' '(objectClass=subSchema)' attributeTypes`   I only get 

extended LDIF

#

LDAPv3

base with scope subtree

filter: (objectClass=subSchema)

requesting: +

#

search result

search: 2 result: 32 No such object

numResponses: 1


So I decided to change the `-s sub` to `-s base`  and I got the following

ldapsearch ... -z 1000 -s base -b 'cn=Subschema' '(objectClass=subSchema)' attributeTypes

extended LDIF

#

LDAPv3

base with scope baseObject

filter: (objectClass=subSchema)

requesting: attributeTypes

#

Subschema

dn: cn=Subschema attributeTypes: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1. 38 ) attributeTypes: ( 2.5.21.9 NAME 'structuralObjectClass' DESC 'RFC4512: structu ral object class of entry' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1. 1466.115.121.1.38 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) attributeTypes: ( 2.5.18.1 NAME 'createTimestamp' DESC 'RFC4512: time which ob ject was created' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrder ingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATI ON USAGE directoryOperation ) attributeTypes: ( 2.5.18.2 NAME 'modifyTimestamp' DESC 'RFC4512: time which ob ject was last modified' EQUALITY generalizedTimeMatch ORDERING generalizedTim eOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODI FICATION USAGE directoryOperation )



Is this what is needed? 

If so then I guess I'll have to change the second query requests in the js files from sub to base?

@fdurand  
> It suppose to return the ldap attributes of the LDAP server. Do you know on your ldap server what is the request to retrieve all attributes ?

Is the query above what you were asking?

Thanks for your help so far. 
E-ThanG commented 3 weeks ago

That's interesting. I'll have to try base scope on that second query. Edit: reads my comment above, already done. :D

I get results with sub scope though. They may have to do server type detection to determine the needed scope. Or try base, then try sub scope.

Since it differs, I'd bet that there's an additional attribute that indicates the scope to use in order to access the schema.

rexfordnyrk commented 3 weeks ago

Considering how different the presentation of my response with the attributes from the my LDAP Server is to what @wcooley showed, it looks to me like I am not getting a fix for my scenario soon. I'm thinking parsing the response fro the actual attributes is also a different thing altogether and that's why the API is complaining of not being able to convert the strings into json for the front end.

E-ThanG commented 3 weeks ago

I don't see it that way, its just a matter of fixing the case of the first search's attribute and determining scope of 2nd query. Along with the other small tweaks, all minor issues

I only get the conversion error if there is no content, it's trying to iterate an empty array. Are you seeing that with a full second query response or only before you changed scope to base? Did you change the scope in the code and rebuild the docker?

rexfordnyrk commented 3 weeks ago

Oh okay,Kindly help me with this I am trying to figure out what to correct. Coz the container building process takes a while I don't want to be making little changes.

  1. When you get on the auth source page, without doing anything. what does the first search request look like for you? I need it to match -b '' -z 1 -s base '(objectclass=*)' subSchemaSubEntry I have search_query: {filter: null, scope: "base", attributes: ["subSchemaSubEntry"], base_dn: "", size_limit: 1} with response: {dc=domain,dc=com,dc=gh: {}}

  2. My second query, the one to retrieve the attributes (failing) has the following search query. I am going to try to get it to match -z 1000 -s base -b 'cn=Subschema' '(objectClass=subSchema)' attributeTypes

    search_query:{
    "filter": "(objectclass=subschema)",
    "scope": "base",
    "attributes": "sub",
    "base_dn": [
        "attributeTypes"
    ],
    "size_limit": 1000
    }
E-ThanG commented 3 weeks ago

Nothing changes in the code except the scope of the second LDAP search is "base" instead of "sub". Just that one word in one function call.

rexfordnyrk commented 3 weeks ago

Yes, I thought so too but that did not work. It turns out that because the first request was not returning the subschemaSubentry: cn=Subschema inside the subSchemaDN variable. So the "base_dn":"" field in the request was empty instead of having the value cn=Subschema So I copied the request as cURL into my terminal and changed the empty value to "base_dn":"cn=Sudschema" and that gave me all the attributes. So I ended up replacing the subSchemaDN variable and hardcoding the value into the request for the useOpenLdap.js, then built it and voila.

Screenshot at 2024-10-19 14-56-49

Now going to continue my mission. Once I get this working. I will do a fresh install of 14. and run it again

E-ThanG commented 3 weeks ago

@rexfordnyrk Yep, there's that case-sensitivity I was talking about. How about this? Works for me in both MS-LDAP and RedHat LDAP with base scope for all queries. The idea here is that all of the attribute extraction is case-insensitive.

There may be some LDAP implementations that have case-sensitive queries. Apparently it's a configuration option and also some schema attributes can be case-sensitive. But as long as we have a solution that works for most cases, it's probably good enough.

html/pfappserver/root/src/views/Configuration/sources/_components/ldapCondition/useOpenLdap.js
import _ from 'lodash';
import {
  extractAttributeFromFilter,
  parseLdapResponseToAttributeArray,
  parseLdapStringToArray,
  sendLdapSearchRequest
} from '@/views/Configuration/sources/_components/ldapCondition/common';

function useOpenLdap(form) {

  const performSearch = (filter, scope, attributes, base_dn) => {
    return sendLdapSearchRequest({...form.value}, filter, scope, attributes, base_dn, 1000)
      .then((result) => {
          return {results: parseLdapResponseToAttributeArray(result, extractAttributeFromFilter(filter)), success: true}
        }
      )
  }

  const getSubSchemaDN = () => {
    return sendLdapSearchRequest({...form.value}, null, 'base', ['subSchemaSubEntry'], '', 1)
      .then((response) => {
        const keys = Object.keys(response)
        if (keys.length) {
          const firstAttribute = response[keys[0]]
          const lowerCaseKeys = Object.keys(firstAttribute).map(key => key.toLowerCase())
          const subSchemaSubEntryIndex = lowerCaseKeys.indexOf('subschemasubentry')
          if (subSchemaSubEntryIndex !== -1) {
            const subSchemaSubEntryKey = Object.keys(firstAttribute)[subSchemaSubEntryIndex]
            return firstAttribute[subSchemaSubEntryKey]
          }
        }
        return []
      })
  }

  const fetchAttributeTypes = (subSchemaDN) => {
    return sendLdapSearchRequest({...form.value},'(objectClass=subSchema)','base',['attributeTypes'],subSchemaDN,1000)
      .then((response) => {
      const keys = Object.keys(response)
        if (keys.length) {
          const firstAttribute = response[keys[0]]
          const lowerCaseKeys = Object.keys(firstAttribute).map(key => key.toLowerCase())
          const attributeTypesIndex = lowerCaseKeys.indexOf('attributetypes')
          if (attributeTypesIndex !== -1) {
            const attributeTypesKey = Object.keys(firstAttribute)[attributeTypesIndex]
            return firstAttribute[attributeTypesKey]
          }
        }
        return []
      })
  }

  const getAttributes = () => {
    return getSubSchemaDN()
      .then((subSchemaDN) => {
        return fetchAttributeTypes(subSchemaDN)
      })
      .then((attributeTypes) => {
        return extractAttributeNames(attributeTypes)
      })
  }

  const checkConnection = () => {
    return getSubSchemaDN().then(() => true).catch(() => false)
  }

  return {
    getAttributes: getAttributes,
    checkConnection: checkConnection,
    performSearch: performSearch
  }
}

function extractAttributeNames(attributes) {
  let attributeNames = []
  attributes.forEach((attribute) => {
    const properties = attribute.split(' ')
    const attributeName = properties[properties.indexOf('NAME') + 1]
    if (attributeName === '(') {
      attributeNames.push(...extractAttributeNameAliases(properties))
    } else {
      attributeNames.push(_.trim(attributeName, '\''))
    }
  })
  return attributeNames
}

function extractAttributeNameAliases(attributeProperties) {
  const attributeStartIndex = attributeProperties.indexOf('NAME') + 1
  attributeProperties = attributeProperties.slice(attributeStartIndex)
  attributeProperties = attributeProperties.slice(0, attributeProperties.indexOf(')') + 1)
  const attributeString = attributeProperties.join(' ')

  return parseLdapStringToArray(attributeString).map((item) => _.trim(item, '\''))
}

export default useOpenLdap
rexfordnyrk commented 3 weeks ago

Yes This should work for most. but IT does not seem to work for mine because subSchemaDN doesn't get populated so is empty. At the moment I am trying to figure out the auth rules using ldap but I'm stuck because I am no LDAP expert.

I have the query alright and it works fine but I need to be able to translate it to into PF auth rules. The query below provides me with the list of distribution list a user belongs to. Usually a general staff one and another for the department.

ldapsearch -x -H $ldap_master_url -D "uid=zimbra,cn=admins,cn=zimbra" -w $zimbra_ldap_password -Lb 'dc=domain,dc=com,dc=gh' zimbramailforwardingaddress=rexford.nyarko@domain.com.gh mail

Below is the output as explained earlier.


version: 1

#
# LDAPv3
# base <dc=domain,dc=com,dc=gh> with scope subtree
# filter: zimbramailforwardingaddress=rexford.nyarko@domain.com.gh
# requesting: mail 
#

# systems, people, domain.com.gh
dn: uid=systems,ou=people,dc=domain,dc=com,dc=gh
mail: systems@domain.com.gh

# allstaff, people, domain.com.gh
dn: uid=allstaff,ou=people,dc=domain,dc=com,dc=gh
mail: allstaff@domain.com.gh

I need to make PF get this information and see if it contains a specific one then assign a role based on that. I initially expected memberOf to get this done but it doesn't not.

E-ThanG commented 3 weeks ago

@fdurand I'm fairly confident that my code above is a fix for at least one of the causes of this issue.

However, I've identified another reason why the schema isn't able to be downloaded as well. If the base_dn is configured with OU=People,DC=Domain,DC=com it works fine. But if the base_dn is only DC=Domain,DC=com it doesn't. It's like it needs to have something on the left to chop off in order to find the root DSE. With DC=Domain,DC=com it looks for the root DSE at DC=com, and of course finds nothing. I haven't researched this extensively, I may be mistaken. But it's repeatable for my configuration and LDAP servers.