Skip to content

[Ldap] LDAP authentication should return a meaningful error when the LDAP server is unavailable #44898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 29, 2022

Conversation

Jayfrown
Copy link
Contributor

@Jayfrown Jayfrown commented Jan 3, 2022

Q A
Branch? 6.1
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #44089
License MIT

Return a 500 ISE if the LDAP server is unreachable.

I have tested the following scenarios:

LDAP server unreachable

Adapter/ExtLdap/Connection.php throws a ConnectionException which is not caught, and this results in a 500 ISE shown to the user. In development mode, you see a relevant trace and the correct error hinting to an unreachable LDAP server, and in production mode this wouldn't be shown to the user, and would be logged.

LDAP server reachable, with invalid search bind credentials

The LdapUserProvider tries to search the LDAP directory for the provided user. To search, it tries to authenticate using the (invalid) bind credentials, resulting in Adapter/ExtLdap/Connection.php throwing an InvalidCredentialsException which is not caught. As this is indicative of a misconfiguration within the application (ie. the app's search bind credentials are wrong, and searching for any user will always fail) I believe a 500 ISE is appropriate.

We could opt to catch the InvalidCredentialsException inside the LdapUserProvider and throw something more specific, to indicate it is the applications search bind credentials that are invalid, as opposed to the credentials the user provided during authentication.

LDAP server reachable, with valid search bind credentials, authenticating as an unknown user

As the search bind credentials are valid, the LdapUserProvider searches the LDAP directory, finds no entries, and throws a UserNotFoundException which results in the authenticator returning {"code":401,"message":"Invalid credentials."}. I believe this is good / expected behavior.

LDAP server reachable, with valid search bind credentials, using known user with invalid password

The ldap/Adapter/ExtLdap/Connection.php throws an InvalidCredentialsException. The ldap/Security/CheckLdapCredentialsListener.php now catches the InvalidCredentialsException (instead of the ConnectionException) and throws a BadCredentialsException such that the authenticator returns {"code":401,"message":"Invalid credentials."}

LDAP server reachable, with valid search bind credentials, using known user with valid password

The credentials are validated and the user is logged in, everything works as expected


throw $e;
}
$this->ldap->bind($this->searchDn, $this->searchPassword);
Copy link
Contributor Author

@Jayfrown Jayfrown Jan 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could opt to catch InvalidCredentialsException here in order to throw something more-specific that would indicate it is the search bind credentials (used during enumeration) that are invalid, as opposed to the credentials that the user has provided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same in CheckLdapCredentialsListener.php line 77

Point being that throwing the InvalidCredentialsException can be ambiguous in either meaning "user-provided credentials are invalid" or "configured search credentials are invalid"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7ab0611 introduces an InvalidSearchCredentialsException which is thrown when binding fails when using the configured search credentials.

@Jayfrown
Copy link
Contributor Author

Jayfrown commented Jan 3, 2022

@wouterj FWIW, I have the following docker-compose setup for a local LDAP:

docker-compose.yml:
services:

  openldap:
    image: osixia/openldap:1.5.0
    restart: unless-stopped
    environment:
      LDAP_LOG_LEVEL: "256"
      LDAP_ORGANISATION: ${LDAP_ORGANISATION:-Example Inc.}
      LDAP_DOMAIN: ${LDAP_DOMAIN:-example.org}
      LDAP_BASE_DN: ${LDAP_BASE_DN:-"dc=example,dc=org"}
      LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD:-admin}
      LDAP_CONFIG_PASSWORD: ${LDAP_CONFIG_PASSWORD:-config}
      LDAP_RFC2307BIS_SCHEMA: "false"
      LDAP_BACKEND: "mdb"
      LDAP_TLS: "true"
      LDAP_TLS_CRT_FILENAME: "ldap.crt"
      LDAP_TLS_KEY_FILENAME: "ldap.key"
      LDAP_TLS_DH_PARAM_FILENAME: "dhparam.pem"
      LDAP_TLS_CA_CRT_FILENAME: "ca.crt"
      LDAP_TLS_ENFORCE: "false"
      LDAP_TLS_CIPHER_SUITE: "SECURE256:-VERS-SSL3.0"
      LDAP_TLS_VERIFY_CLIENT: "demand"
      LDAP_REPLICATION: "false"
      KEEP_EXISTING_CONFIG: "false"
      LDAP_REMOVE_CONFIG_AFTER_SETUP: "true"
      LDAP_SSL_HELPER_PREFIX: "ldap"
    tty: true
    stdin_open: true
    volumes:
      - ldap_data:/var/lib/ldap
      - ldap_config:/etc/ldap/slapd.d
      - ldap_certs:/container/service/slapd/assets/certs/
    ports:
      - "389:389"
      - "636:636"

  phpldapadmin:
    image: osixia/phpldapadmin:latest
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "openldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8080:80"
    depends_on:
      - openldap

volumes:
  ldap_data:
  ldap_config:
  ldap_certs:

And the following configuration in Symfony:

services.yaml:
    Symfony\Component\Ldap\Ldap:
        arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
        tags: ['ldap']
    Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
        arguments:
            - host: '%env(resolve:LDAP_HOST)%'
              port: '%env(resolve:LDAP_PORT)%'
              encryption: '%env(resolve:LDAP_ENCRYPTION)%'
              options:
                  protocol_version: 3
                  referrals: false
security.yaml:
    providers:
        app_ldap:
            ldap:
                service: Symfony\Component\Ldap\Ldap
                base_dn: '%env(resolve:LDAP_BASE_DN)%'
                search_dn: '%env(resolve:LDAP_SEARCH_DN)%'
                search_password: '%env(resolve:LDAP_SEARCH_PASSWORD)%'
                default_roles: ROLE_USER
                uid_key: uid
                
    firewalls:
        main:
            provider: app_ldap
.env:
LDAP_HOST="openldap"
LDAP_PORT="389"
LDAP_ENCRYPTION="none"
LDAP_SEARCH_DN="cn=admin,dc=example,dc=org"
LDAP_SEARCH_PASSWORD="admin"
LDAP_BASE_DN="ou=Users,dc=example,dc=org"

You'd have to create the rest of the schema by hand, if needed I can try to provide an LDIF.

@Nyholm Nyholm added the Ldap label Jan 4, 2022
@carsonbot carsonbot changed the title LDAP authentication should return a meaningful error when the LDAP server is unavailable [Ldap] LDAP authentication should return a meaningful error when the LDAP server is unavailable Jan 4, 2022
@carsonbot
Copy link

Hey!

I think @karlshea has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

@Jayfrown
Copy link
Contributor Author

@wouterj if there's anything I can do to make reviewing easier, let me know

@stof
Copy link
Member

stof commented Mar 22, 2022

CheckLdapCredentialsListener has 2 modes. Your PR description lets me think that you tested only one of them.

@Jayfrown Jayfrown force-pushed the ldap-server-unavailable branch from ec7a0be to 58716bb Compare March 22, 2022 19:07
@Jayfrown
Copy link
Contributor Author

CheckLdapCredentialsListener has 2 modes. Your PR description lets me think that you tested only one of them.

@stof Thanks. It is very possible I might have missed something, as I'm not familiar with the two modes you mention. Could you expand on this or send some documentation my way?

@stof
Copy link
Member

stof commented Mar 22, 2022

I'm not familiar with the ldap part either. But for those 2 modes, see the if/else inside the try/catch you changed

@Jayfrown
Copy link
Contributor Author

@stof Thanks. If I understand correctly the difference is when query_string is set, which, according to the documentation:

makes the user provider search for a user and then use the found DN for the bind process

Basically allowing the LDAP component to search for a given user in multiple OUs instead of simply interpolating the username into the dn_string.

Expand default behavior
  • Interpolate the user-provided username in the dn_string, eg. uid={username},dc=example,dc=com becomes uid=jayfrown,dc=example,dc=com
  • Use the user-provided password to bind as the interpolated dn_string
    • If the bind is successful, the credentials are accepted and log-in continues
    • If the bind is not successful, the credentials are rejected and log-in is prohibited
Expand query_string behavior
  • Interpolate the user-provided username in the query_string, eg. (&(uid={username})) becomes (&(uid=jayfrown))
  • Bind with the configured search credentials
  • Perform an LDAP search inside the dn_string using the query_string, eg. if dn_string is dc=example,dc=com, this can return uid=jayfrown,dc=companyA,dc=example,dc=com or uid=jayfrown,dc=companyB,dc=example,dc=com, etc.
    • If the search does not yield exactly one result, throw a BadCredentialsException indicating that the user-provided username is invalid
    • If the search yields exactly one result, use the user-provided password to bind as the found DN
      • If the bind is successful, the credentials are accepted and log-in continues
      • If the bind is not successful, the credentials are rejected and log-in is prohibited

Having said all that, my changes should not impact either scenario apart from what I've already described. However, when I have some more time I will set up a configuration using query_string and test with that as well.

@Jayfrown Jayfrown force-pushed the ldap-server-unavailable branch from 58716bb to 8452c7a Compare March 22, 2022 21:07
Jayfrown added a commit to Jayfrown/ldap that referenced this pull request Mar 23, 2022
Jayfrown added a commit to Jayfrown/symfony-ldap-example that referenced this pull request Mar 23, 2022
[`Jayfrown/ldap
dev-ldap-server-unavailable`](https://github.com/Jayfrown/ldap/tree/ldap-server-unavailable)
is `symfony/ldap 6.1.*` with symfony/symfony#44898 applied.
Jayfrown added a commit to Jayfrown/symfony-ldap-example that referenced this pull request Mar 23, 2022
[`Jayfrown/ldap
dev-ldap-server-unavailable`](https://github.com/Jayfrown/ldap/tree/ldap-server-unavailable)
is `symfony/ldap 6.1.*` with symfony/symfony#44898 applied.
@Jayfrown
Copy link
Contributor Author

I made an example application to make it easier to test and verify behavior. The repository includes a docker-compose configuration that spins up an OpenLDAP container, seeds it with some users, and exposes the ports such that the local application can communicate with it.

There are a couple of branches to test all permutations (with/without query_string as well as with/without this PR applied)

Branches with this PR applied require Jayfrown/ldap dev-ldap-server-unavailable which is a clone of the symfony/ldap repository which includes the work in this PR.

Note that after switching to/from branches with this PR applied it is necessary to run composer install.


@stof I've tested the same scenarios as mentioned in the OP with the query_string configuration and the behavior remains the same.

@Jayfrown Jayfrown force-pushed the ldap-server-unavailable branch from 65aaf0c to 7ab0611 Compare March 23, 2022 02:31
@fabpot fabpot force-pushed the ldap-server-unavailable branch from 7ab0611 to ad2cc0e Compare March 29, 2022 06:21
@fabpot
Copy link
Member

fabpot commented Mar 29, 2022

Thank you @Jayfrown.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LDAP authentication should return a meaningful error when the LDAP server is unavailable
6 participants